1// Copyright (c) 2017 Daniel Oaks <daniel@danieloaks.net> 2// released under the MIT license 3 4package utils 5 6import ( 7 "strings" 8 "time" 9) 10 11func IsRestrictedCTCPMessage(message string) bool { 12 // block all CTCP privmsgs to Tor clients except for ACTION 13 // DCC can potentially be used for deanonymization, the others for fingerprinting 14 return strings.HasPrefix(message, "\x01") && !strings.HasPrefix(message, "\x01ACTION") 15} 16 17type MessagePair struct { 18 Message string 19 Concat bool // should be relayed with the multiline-concat tag 20} 21 22// SplitMessage represents a message that's been split for sending. 23// Two possibilities: 24// (a) Standard message that can be relayed on a single 512-byte line 25// (MessagePair contains the message, Split == nil) 26// (b) multiline message that was split on the client side 27// (Message == "", Split contains the split lines) 28type SplitMessage struct { 29 Message string 30 Msgid string 31 Split []MessagePair 32 Time time.Time 33} 34 35func MakeMessage(original string) (result SplitMessage) { 36 result.Message = original 37 result.Msgid = GenerateSecretToken() 38 result.SetTime() 39 40 return 41} 42 43func (sm *SplitMessage) Append(message string, concat bool) { 44 if sm.Msgid == "" { 45 sm.Msgid = GenerateSecretToken() 46 } 47 sm.Split = append(sm.Split, MessagePair{ 48 Message: message, 49 Concat: concat, 50 }) 51} 52 53func (sm *SplitMessage) SetTime() { 54 // strip the monotonic time, it's a potential source of problems: 55 sm.Time = time.Now().UTC().Round(0) 56} 57 58func (sm *SplitMessage) LenLines() int { 59 if sm.Split == nil { 60 if sm.Message == "" { 61 return 0 62 } else { 63 return 1 64 } 65 } 66 return len(sm.Split) 67} 68 69func (sm *SplitMessage) ValidMultiline() bool { 70 // must contain at least one nonblank line 71 for i := 0; i < len(sm.Split); i++ { 72 if len(sm.Split[i].Message) != 0 { 73 return true 74 } 75 } 76 return false 77} 78 79func (sm *SplitMessage) IsRestrictedCTCPMessage() bool { 80 if IsRestrictedCTCPMessage(sm.Message) { 81 return true 82 } 83 for i := 0; i < len(sm.Split); i++ { 84 if IsRestrictedCTCPMessage(sm.Split[i].Message) { 85 return true 86 } 87 } 88 return false 89} 90 91func (sm *SplitMessage) Is512() bool { 92 return sm.Split == nil 93} 94 95// TokenLineBuilder is a helper for building IRC lines composed of delimited tokens, 96// with a maximum line length. 97type TokenLineBuilder struct { 98 lineLen int 99 delim string 100 buf strings.Builder 101 result []string 102} 103 104func (t *TokenLineBuilder) Initialize(lineLen int, delim string) { 105 t.lineLen = lineLen 106 t.delim = delim 107} 108 109// Add adds a token to the line, creating a new line if necessary. 110func (t *TokenLineBuilder) Add(token string) { 111 tokenLen := len(token) 112 if t.buf.Len() != 0 { 113 tokenLen += len(t.delim) 114 } 115 if t.lineLen < t.buf.Len()+tokenLen { 116 t.result = append(t.result, t.buf.String()) 117 t.buf.Reset() 118 } 119 if t.buf.Len() != 0 { 120 t.buf.WriteString(t.delim) 121 } 122 t.buf.WriteString(token) 123} 124 125// Lines terminates the line-building and returns all the lines. 126func (t *TokenLineBuilder) Lines() (result []string) { 127 result = t.result 128 t.result = nil 129 if t.buf.Len() != 0 { 130 result = append(result, t.buf.String()) 131 t.buf.Reset() 132 } 133 return 134} 135 136// BuildTokenLines is a convenience to apply TokenLineBuilder to a predetermined 137// slice of tokens. 138func BuildTokenLines(lineLen int, tokens []string, delim string) []string { 139 var tl TokenLineBuilder 140 tl.Initialize(lineLen, delim) 141 for _, arg := range tokens { 142 tl.Add(arg) 143 } 144 return tl.Lines() 145} 146