diff --git a/ircevent/irc.go b/ircevent/irc.go index 1b4ac5c..ac3a34c 100644 --- a/ircevent/irc.go +++ b/ircevent/irc.go @@ -351,7 +351,7 @@ func (irc *Connection) sendInternal(b []byte) (err error) { // Send a built ircmsg.Message. func (irc *Connection) SendIRCMessage(msg ircmsg.Message) error { b, err := msg.LineBytesStrict(true, irc.MaxLineLen) - if err != nil { + if err != nil && !(irc.AllowTruncation && err == ircmsg.ErrorBodyTooLong) { if irc.Debug { irc.Log.Printf("couldn't assemble message: %v\n", err) } diff --git a/ircevent/irc_struct.go b/ircevent/irc_struct.go index 5c0a238..c0ecdfe 100644 --- a/ircevent/irc_struct.go +++ b/ircevent/irc_struct.go @@ -41,28 +41,29 @@ type capResult struct { type Connection struct { // config data, user-settable - Server string - TLSConfig *tls.Config - Nick string - User string - RealName string // IRC realname/gecos - WebIRC []string // parameters for the WEBIRC command - Password string // server password (PASS command) - RequestCaps []string // IRCv3 capabilities to request (failure is non-fatal) - SASLLogin string // SASL credentials to log in with (failure is fatal) - SASLPassword string - SASLMech string - QuitMessage string - Version string - Timeout time.Duration - KeepAlive time.Duration - ReconnectFreq time.Duration - MaxLineLen int // maximum line length, not including tags - UseTLS bool - UseSASL bool - EnableCTCP bool - Debug bool - AllowPanic bool // if set, don't recover() from panics in callbacks + Server string + TLSConfig *tls.Config + Nick string + User string + RealName string // IRC realname/gecos + WebIRC []string // parameters for the WEBIRC command + Password string // server password (PASS command) + RequestCaps []string // IRCv3 capabilities to request (failure is non-fatal) + SASLLogin string // SASL credentials to log in with (failure is fatal) + SASLPassword string + SASLMech string + QuitMessage string + Version string + Timeout time.Duration + KeepAlive time.Duration + ReconnectFreq time.Duration + MaxLineLen int // maximum line length, not including tags + UseTLS bool + UseSASL bool + EnableCTCP bool + Debug bool + AllowPanic bool // if set, don't recover() from panics in callbacks + AllowTruncation bool // if set, truncate lines exceeding MaxLineLen and send them // networking and synchronization stateMutex sync.Mutex // innermost mutex: don't block while holding this diff --git a/ircutils/unicode.go b/ircutils/unicode.go index 14f628e..6e1cf44 100644 --- a/ircutils/unicode.go +++ b/ircutils/unicode.go @@ -4,6 +4,8 @@ package ircutils import ( + "strings" + "unicode" "unicode/utf8" ) @@ -23,3 +25,38 @@ func TruncateUTF8Safe(message string, byteLimit int) (result string) { } return message } + +// Sanitizes human-readable text to make it safe for IRC; +// assumes UTF-8 and uses the replacement character where +// applicable. +func SanitizeText(message string, byteLimit int) (result string) { + var buf strings.Builder + + for _, r := range message { + if r == '\x00' || r == '\r' { + continue + } else if r == '\n' { + if buf.Len()+2 <= byteLimit { + buf.WriteString(" ") + continue + } else { + break + } + } else if unicode.IsSpace(r) { + if buf.Len()+1 <= byteLimit { + buf.WriteString(" ") + } else { + break + } + } else { + rLen := utf8.RuneLen(r) + if buf.Len()+rLen <= byteLimit { + buf.WriteRune(r) + } else { + break + } + } + } + + return buf.String() +} diff --git a/ircutils/unicode_test.go b/ircutils/unicode_test.go index 3e0c096..5d6fc76 100644 --- a/ircutils/unicode_test.go +++ b/ircutils/unicode_test.go @@ -31,3 +31,16 @@ func TestTruncateUTF8(t *testing.T) { // shouldn't truncate the whole string assertEqual(TruncateUTF8Safe("\xff\xff\xff\xff\xff\xff", 5), "\xff\xff") } + +func TestSanitize(t *testing.T) { + assertEqual(SanitizeText("abc", 10), "abc") + assertEqual(SanitizeText("abcdef", 5), "abcde") + + assertEqual(SanitizeText("shivaram\x00shivaram\x00shivarampassphrase", 400), "shivaramshivaramshivarampassphrase") + + assertEqual(SanitizeText("the quick brown fox\xffjumps over the lazy dog", 400), "the quick brown fox\xef\xbf\xbdjumps over the lazy dog") + + // \r ignored, \n is two spaces + assertEqual(SanitizeText("the quick brown fox\r\njumps over the lazy dog", 400), "the quick brown fox jumps over the lazy dog") + assertEqual(SanitizeText("the quick brown fox\njumps over the lazy dog", 400), "the quick brown fox jumps over the lazy dog") +}