~emersion/soju-dev

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
1

[PATCH] Hand-made word splitter for BouncerServ

Details
Message ID
<20210620203025.7564-1-hubert@hirtz.pm>
DKIM signature
pass
Download raw message
Patch: +106 -7
Remove the (direct) dependency on shlex (go-scfg still depends on it)
---
 go.mod          |  1 -
 service.go      | 64 ++++++++++++++++++++++++++++++++++++++++++++-----
 service_test.go | 48 +++++++++++++++++++++++++++++++++++++
 3 files changed, 106 insertions(+), 7 deletions(-)
 create mode 100644 service_test.go

diff --git a/go.mod b/go.mod
index be33bb7..18fa1c7 100644
--- a/go.mod
+++ b/go.mod
@@ -6,7 +6,6 @@ require (
	git.sr.ht/~emersion/go-scfg v0.0.0-20201019143924-142a8aa629fc
	git.sr.ht/~sircmpwn/go-bare v0.0.0-20210331145808-46f9b5e5bcf9
	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/mattn/go-sqlite3 v1.14.6
	github.com/pires/go-proxyproto v0.5.0
diff --git a/service.go b/service.go
index fbbb91f..487b663 100644
--- a/service.go
+++ b/service.go
@@ -22,7 +22,6 @@ import (
	"strings"
	"time"

	"github.com/google/shlex"
	"golang.org/x/crypto/bcrypt"
	"gopkg.in/irc.v3"
)
@@ -63,13 +62,66 @@ func sendServicePRIVMSG(dc *downstreamConn, text string) {
	})
}

func handleServicePRIVMSG(dc *downstreamConn, text string) {
	words, err := shlex.Split(text)
	if err != nil {
		sendServicePRIVMSG(dc, fmt.Sprintf("error: failed to parse command: %v", err))
		return
func splitWords(s string) (words []string) {
	var lastWord strings.Builder
	var escape bool
	var lastByteWasSpace bool = true
	var wordDelim byte = ' '

	for i := 0; i < len(s); i++ {
		b := s[i]

		if escape {
			// last char was a backslash, write the byte as-is.
			lastWord.WriteByte(b)
			escape = false
			continue
		}
		if b == '\\' {
			escape = true
			continue
		}

		if b == wordDelim && wordDelim == ' ' {
			// end of last word
			if !lastByteWasSpace {
				words = append(words, lastWord.String())
				lastWord.Reset()
			}
			lastByteWasSpace = true
			continue
		}
		lastByteWasSpace = false
		if b == wordDelim {
			// wordDelim is either " or ', switch back to
			// space-delimited words.
			wordDelim = ' '
			continue
		}

		if b == '"' || b == '\'' {
			if wordDelim == ' ' {
				// start of (double-)quoted word
				wordDelim = b
			} else {
				// either wordDelim is " and b is ' or vice-versa
				lastWord.WriteByte(b)
			}
			continue
		}

		lastWord.WriteByte(b)
	}

	if !lastByteWasSpace {
		words = append(words, lastWord.String())
	}

	return
}

func handleServicePRIVMSG(dc *downstreamConn, text string) {
	words := splitWords(text)
	cmd, params, err := serviceCommands.Get(words)
	if err != nil {
		sendServicePRIVMSG(dc, fmt.Sprintf(`error: %v (type "help" for a list of commands)`, err))
diff --git a/service_test.go b/service_test.go
new file mode 100644
index 0000000..357c2ae
--- /dev/null
+++ b/service_test.go
@@ -0,0 +1,48 @@
package soju

import (
	"testing"
)

func assertSplit(t *testing.T, input string, expected []string) {
	actual := splitWords(input)
	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!",
		"",
	})
	assertSplit(t, "end of 'file", []string{
		"end",
		"of",
		"file",
	})
}
-- 
2.32.0
Details
Message ID
<JSf1u6cOXUG3W9lT3ftspjQ8GF-NbpDWmtcD2U39PL4YT6J0mZRz4ZY8mGW4DKfJbM_RdukdaR-XDtcAOcB2xJ3TzpVqYYC0DBufbMSscjU=@emersion.fr>
In-Reply-To
<20210620203025.7564-1-hubert@hirtz.pm> (view parent)
DKIM signature
pass
Download raw message
Thanks for working on this! I originally wanted to put finishing touches
on this patch, but ended up reworking it:

- Operate on runes instead of bytes, and use unicode.IsSpace to detect
  white-space characters
- Error out if a quoted string or backslash sequence isn't terminated
- Style: reword loop body structure to make it (IMHO) more readable
- Style: don't use naked returns

Thanks!
Reply to thread Export thread (mbox)