Hand-made word splitter for BouncerServ
Remove the (direct) dependency on shlex (go-scfg still depends on it). Co-authored-by: Simon Ser <contact@emersion.fr>
This commit is contained in:
parent
f3f864dddc
commit
a21585ac41
1
go.mod
1
go.mod
@ -6,7 +6,6 @@ require (
|
|||||||
git.sr.ht/~emersion/go-scfg v0.0.0-20201019143924-142a8aa629fc
|
git.sr.ht/~emersion/go-scfg v0.0.0-20201019143924-142a8aa629fc
|
||||||
git.sr.ht/~sircmpwn/go-bare v0.0.0-20210331145808-46f9b5e5bcf9
|
git.sr.ht/~sircmpwn/go-bare v0.0.0-20210331145808-46f9b5e5bcf9
|
||||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21
|
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
|
||||||
github.com/klauspost/compress v1.11.13 // indirect
|
github.com/klauspost/compress v1.11.13 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.6
|
github.com/mattn/go-sqlite3 v1.14.6
|
||||||
github.com/pires/go-proxyproto v0.5.0
|
github.com/pires/go-proxyproto v0.5.0
|
||||||
|
59
service.go
59
service.go
@ -21,8 +21,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
"github.com/google/shlex"
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
"gopkg.in/irc.v3"
|
"gopkg.in/irc.v3"
|
||||||
)
|
)
|
||||||
@ -63,10 +63,63 @@ func sendServicePRIVMSG(dc *downstreamConn, text string) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func splitWords(s string) ([]string, error) {
|
||||||
|
var words []string
|
||||||
|
var lastWord strings.Builder
|
||||||
|
escape := false
|
||||||
|
prev := ' '
|
||||||
|
wordDelim := ' '
|
||||||
|
|
||||||
|
for _, r := range s {
|
||||||
|
if escape {
|
||||||
|
// last char was a backslash, write the byte as-is.
|
||||||
|
lastWord.WriteRune(r)
|
||||||
|
escape = false
|
||||||
|
} else if r == '\\' {
|
||||||
|
escape = true
|
||||||
|
} else if wordDelim == ' ' && unicode.IsSpace(r) {
|
||||||
|
// end of last word
|
||||||
|
if !unicode.IsSpace(prev) {
|
||||||
|
words = append(words, lastWord.String())
|
||||||
|
lastWord.Reset()
|
||||||
|
}
|
||||||
|
} else if r == wordDelim {
|
||||||
|
// wordDelim is either " or ', switch back to
|
||||||
|
// space-delimited words.
|
||||||
|
wordDelim = ' '
|
||||||
|
} else if r == '"' || r == '\'' {
|
||||||
|
if wordDelim == ' ' {
|
||||||
|
// start of (double-)quoted word
|
||||||
|
wordDelim = r
|
||||||
|
} else {
|
||||||
|
// either wordDelim is " and r is ' or vice-versa
|
||||||
|
lastWord.WriteRune(r)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lastWord.WriteRune(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
prev = r
|
||||||
|
}
|
||||||
|
|
||||||
|
if !unicode.IsSpace(prev) {
|
||||||
|
words = append(words, lastWord.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if wordDelim != ' ' {
|
||||||
|
return nil, fmt.Errorf("unterminated quoted string")
|
||||||
|
}
|
||||||
|
if escape {
|
||||||
|
return nil, fmt.Errorf("unterminated backslash sequence")
|
||||||
|
}
|
||||||
|
|
||||||
|
return words, nil
|
||||||
|
}
|
||||||
|
|
||||||
func handleServicePRIVMSG(dc *downstreamConn, text string) {
|
func handleServicePRIVMSG(dc *downstreamConn, text string) {
|
||||||
words, err := shlex.Split(text)
|
words, err := splitWords(text)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sendServicePRIVMSG(dc, fmt.Sprintf("error: failed to parse command: %v", err))
|
sendServicePRIVMSG(dc, fmt.Sprintf(`error: failed to parse command: %v`, err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
54
service_test.go
Normal file
54
service_test.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package soju
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func assertSplit(t *testing.T, input string, expected []string) {
|
||||||
|
actual, err := splitWords(input)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%q: %v", input, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(actual) != len(expected) {
|
||||||
|
t.Errorf("%q: expected %d words, got %d\nexpected: %v\ngot: %v", input, len(expected), len(actual), expected, actual)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i := 0; i < len(actual); i++ {
|
||||||
|
if actual[i] != expected[i] {
|
||||||
|
t.Errorf("%q: expected word #%d to be %q, got %q\nexpected: %v\ngot: %v", input, i, expected[i], actual[i], expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSplit(t *testing.T) {
|
||||||
|
assertSplit(t, " ch 'up' #soju 'relay'-det\"ache\"d message ", []string{
|
||||||
|
"ch",
|
||||||
|
"up",
|
||||||
|
"#soju",
|
||||||
|
"relay-detached",
|
||||||
|
"message",
|
||||||
|
})
|
||||||
|
assertSplit(t, "net update \\\"free\\\"node -pass 'political \"stance\" desu!' -realname '' -nick lee", []string{
|
||||||
|
"net",
|
||||||
|
"update",
|
||||||
|
"\"free\"node",
|
||||||
|
"-pass",
|
||||||
|
"political \"stance\" desu!",
|
||||||
|
"-realname",
|
||||||
|
"",
|
||||||
|
"-nick",
|
||||||
|
"lee",
|
||||||
|
})
|
||||||
|
assertSplit(t, "Omedeto,\\ Yui! ''", []string{
|
||||||
|
"Omedeto, Yui!",
|
||||||
|
"",
|
||||||
|
})
|
||||||
|
|
||||||
|
if _, err := splitWords("end of 'file"); err == nil {
|
||||||
|
t.Errorf("expected error on unterminated single quote")
|
||||||
|
}
|
||||||
|
if _, err := splitWords("end of backquote \\"); err == nil {
|
||||||
|
t.Errorf("expected error on unterminated backquote sequence")
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user