commit c652dad

Hubert Hirtz  ·  2021-04-27 08:10:56 +0000 UTC
parent cede6d5
Split long messages before sending them

avoids silent truncations.

Also consider the LINELEN ISUPPORT token:
- https://defs.ircdocs.horse/defs/isupport.html
- http://rabbit.dereferenced.org/~nenolod/ircv3-harmony/id-wpitcock-ircv3-core.html#rfc.section.3.8.2.1.17
1 files changed,  +44, -1
+44, -1
 1@@ -11,6 +11,7 @@ import (
 2 	"strings"
 3 	"sync/atomic"
 4 	"time"
 5+	"unicode/utf8"
 6 )
 7 
 8 const writeDeadline = 10 * time.Second
 9@@ -177,6 +178,7 @@ type Session struct {
10 	// ISUPPORT features
11 	casemap   func(string) string
12 	chantypes string
13+	linelen   int
14 
15 	users     map[string]*User        // known users.
16 	channels  map[string]Channel      // joined channels.
17@@ -206,6 +208,7 @@ func NewSession(conn net.Conn, params SessionParams) (*Session, error) {
18 		enabledCaps:   map[string]struct{}{},
19 		casemap:       CasemapRFC1459,
20 		chantypes:     "#&",
21+		linelen:       512,
22 		users:         map[string]*User{},
23 		channels:      map[string]Channel{},
24 		chBatches:     map[string]HistoryEvent{},
25@@ -400,12 +403,47 @@ func (s *Session) quit(act actionQuit) (err error) {
26 	return
27 }
28 
29+func splitChunks(s string, chunkLen int) (chunks []string) {
30+	if chunkLen <= 0 {
31+		return []string{s}
32+	}
33+	for chunkLen < len(s) {
34+		i := chunkLen
35+		min := chunkLen - utf8.UTFMax
36+		for min <= i && !utf8.RuneStart(s[i]) {
37+			i--
38+		}
39+		chunks = append(chunks, s[:i])
40+		s = s[i:]
41+	}
42+	if len(s) != 0 {
43+		chunks = append(chunks, s)
44+	}
45+	return
46+}
47+
48 func (s *Session) PrivMsg(target, content string) {
49 	s.acts <- actionPrivMsg{target, content}
50 }
51 
52 func (s *Session) privMsg(act actionPrivMsg) (err error) {
53-	err = s.send("PRIVMSG %s :%s\r\n", act.Target, act.Content)
54+	hostLen := len(s.host)
55+	if hostLen == 0 {
56+		hostLen = len("255.255.255.255")
57+	}
58+	maxMessageLen := s.linelen -
59+		len(":!@ PRIVMSG  :\r\n") -
60+		len(s.nick) -
61+		len(s.user) -
62+		hostLen -
63+		len(act.Target)
64+	chunks := splitChunks(act.Content, maxMessageLen)
65+	for _, chunk := range chunks {
66+		err = s.send("PRIVMSG %s :%s\r\n", act.Target, chunk)
67+		if err != nil {
68+			return
69+		}
70+	}
71 	target := s.Casemap(act.Target)
72 	delete(s.typingStamps, target)
73 	return
74@@ -1084,6 +1122,11 @@ func (s *Session) updateFeatures(features []string) {
75 			}
76 		case "CHANTYPES":
77 			s.chantypes = value
78+		case "LINELEN":
79+			linelen, err := strconv.Atoi(value)
80+			if err == nil && linelen != 0 {
81+				s.linelen = linelen
82+			}
83 		}
84 	}
85 }