You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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