diff --git a/command/push.go b/command/push.go index ddcdad4..9e0cafa 100644 --- a/command/push.go +++ b/command/push.go @@ -39,13 +39,8 @@ func Push() cli.Command { func doPush(ctx *cli.Context) { conf, confErr := config.ReadConfig(config.GetLocations()) - msgText := make(chan string) - null := '\x00' - if ctx.Bool("no-split") { - go readMessage(ctx.Args(), os.Stdin, msgText, nil) - } else { - go readMessage(ctx.Args(), os.Stdin, msgText, &null) - } + msgTextChan := make(chan string) + go readMessage(ctx.Args(), os.Stdin, msgTextChan, !ctx.Bool("no-split")) priority := ctx.Int("priority") title := ctx.String("title") @@ -81,8 +76,24 @@ func doPush(ctx *cli.Context) { return } + parsedExtras := make(map[string]interface{}) + + if contentType != "" { + parsedExtras["client::display"] = map[string]interface{}{ + "contentType": contentType, + } + } + + if clickUrl != "" { + parsedExtras["client::notification"] = map[string]interface{}{ + "click": map[string]string{ + "url": clickUrl, + }, + } + } + var sent bool - for msgText := range msgText { + for msgText := range msgTextChan { if !ctx.Bool("disable-unescape-backslash") { msgText = utils.Evaluate(msgText) } @@ -91,22 +102,7 @@ func doPush(ctx *cli.Context) { Message: msgText, Title: title, Priority: priority, - } - - msg.Extras = map[string]interface{}{} - - if contentType != "" { - msg.Extras["client::display"] = map[string]interface{}{ - "contentType": contentType, - } - } - - if clickUrl != "" { - msg.Extras["client::notification"] = map[string]interface{}{ - "click": map[string]string{ - "url": clickUrl, - }, - } + Extras: parsedExtras, } pushMessage(parsedURL, token, msg, quiet) diff --git a/command/read.go b/command/read.go index c5b8ca4..c78091f 100644 --- a/command/read.go +++ b/command/read.go @@ -1,61 +1,49 @@ package command import ( + "bufio" + "errors" "io" "strings" "github.com/gotify/cli/v2/utils" ) -func readMessage(args []string, r io.Reader, output chan<- string, split *rune) { - msgArgs := strings.Join(args, " ") +func readMessage(args []string, r io.Reader, output chan<- string, splitOnNull bool) { + defer close(output) - if msgArgs != "" { + switch { + case len(args) > 0: if utils.ProbeStdin(r) { utils.Exit1With("message is set via arguments and stdin, use only one of them") } - output <- msgArgs - close(output) - return - } - - var buf strings.Builder - for { - var tmp [256]byte - n, err := r.Read(tmp[:]) - if err != nil { - if err.Error() == "EOF" { - break + output <- strings.Join(args, " ") + case splitOnNull: + read := bufio.NewReader(r) + for { + s, err := read.ReadString('\x00') + if err != nil { + if !errors.Is(err, io.EOF) { + utils.Exit1With("read error", err) + } + if len(s) > 0 { + output <- s + } + return + } else { + if len(s) > 1 { + output <- strings.TrimSuffix(s, "\x00") + } } - utils.Exit1With(err) } - tmpStr := string(tmp[:n]) - if split != nil { - // split the message on the null character - parts := strings.Split(tmpStr, string(*split)) - if len(parts) == 1 { - buf.WriteString(parts[0]) - continue - } - - previous := buf.String() - // fuse previous with parts[0], send parts[1] .. parts[n-2] and set parts[n-1] as new previous - firstMsg := previous + parts[0] - output <- firstMsg - for _, part := range parts[1 : len(parts)-1] { - output <- part - } - buf.Reset() - buf.WriteString(parts[len(parts)-1]) - } else { - buf.WriteString(tmpStr) + default: + bytes, err := io.ReadAll(r) + if err != nil { + utils.Exit1With("cannot read", err) } + output <- string(bytes) + return } - if buf.Len() > 0 { - output <- buf.String() - } - - close(output) } diff --git a/command/read_test.go b/command/read_test.go index 7023488..ea2d733 100644 --- a/command/read_test.go +++ b/command/read_test.go @@ -1,6 +1,8 @@ package command import ( + "bufio" + "bytes" "strings" "testing" ) @@ -27,11 +29,16 @@ func readChanAll[T any](c chan T) []T { } func TestReadMessage(t *testing.T) { - var split rune = '\x00' - + if bytes.IndexByte([]byte("Hello\x00World"), '\x00') != len("Hello") { + t.Errorf("Expected %v, but got %v", len("Hello"), bytes.IndexByte([]byte("Hello\x00World"), '\x00')) + } + rdr := bufio.NewReader(strings.NewReader("Hello\x00World")) + if s, _ := rdr.ReadString('\x00'); s != "Hello\x00" { + t.Errorf("Expected %x, but got %x", "Hello\x00", s) + } // Test case 1: message set via arguments output := make(chan string) - go readMessage([]string{"Hello", "World"}, nil, output, nil) + go readMessage([]string{"Hello", "World"}, nil, output, false) if res := readChanAll(output); !(slicesEqual(res, []string{"Hello World"})) { t.Errorf("Expected %v, but got %v", []string{"Hello World"}, res) @@ -39,7 +46,7 @@ func TestReadMessage(t *testing.T) { // Test case 2: message set via arguments should not split on 'split' character output = make(chan string) - go readMessage([]string{"Hello\x00World"}, nil, output, &split) + go readMessage([]string{"Hello\x00World"}, nil, output, true) if res := readChanAll(output); !(slicesEqual(res, []string{"Hello\x00World"})) { t.Errorf("Expected %v, but got %v", []string{"Hello\x00World"}, res) @@ -47,9 +54,34 @@ func TestReadMessage(t *testing.T) { // Test case 3: message set via stdin output = make(chan string) - go readMessage([]string{}, strings.NewReader("Hello\x00World"), output, &split) + go readMessage([]string{}, strings.NewReader("Hello\x00World"), output, true) + + if res := readChanAll(output); !(slicesEqual(res, []string{"Hello", "World"})) { + t.Errorf("Expected %v, but got %v", []string{"Hello", "World"}, res) + } + + // Test case 4: multiple null bytes should be split as one + output = make(chan string) + go readMessage([]string{}, strings.NewReader("Hello\x00\x00World"), output, true) if res := readChanAll(output); !(slicesEqual(res, []string{"Hello", "World"})) { t.Errorf("Expected %v, but got %v", []string{"Hello", "World"}, res) } + + // Test case 5: multiple null bytes at the end should be split as one + output = make(chan string) + go readMessage([]string{}, strings.NewReader("Hello\x00\x00"), output, true) + + if res := readChanAll(output); !(slicesEqual(res, []string{"Hello"})) { + t.Errorf("Expected %v, but got %v", []string{"Hello"}, res) + } + + // Test case 6: multiple null bytes at the start should be split as one + output = make(chan string) + go readMessage([]string{}, strings.NewReader("\x00\x00World"), output, true) + + if res := readChanAll(output); !(slicesEqual(res, []string{"World"})) { + t.Errorf("Expected %v, but got %v", []string{"World"}, res) + } + } diff --git a/command/watch.go b/command/watch.go index e098233..6f5c483 100644 --- a/command/watch.go +++ b/command/watch.go @@ -131,7 +131,7 @@ func doWatch(ctx *cli.Context) { fmt.Fprintln(msgData, output) fmt.Fprintln(msgData, "== END NEW OUTPUT ==") case "short": - fmt.Fprintln(msgData, output) + fmt.Fprint(msgData, output) } msgString := msgData.String()