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.

text.go 3.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  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. // (MessagePair contains the message, Split == nil)
  21. // (b) multiline message that was split on the client side
  22. // (Message == "", Split contains the split lines)
  23. type SplitMessage struct {
  24. Message string
  25. Msgid string
  26. Split []MessagePair
  27. Time time.Time
  28. }
  29. func MakeMessage(original string) (result SplitMessage) {
  30. result.Message = original
  31. result.Msgid = GenerateSecretToken()
  32. result.SetTime()
  33. return
  34. }
  35. func (sm *SplitMessage) Append(message string, concat bool) {
  36. if sm.Msgid == "" {
  37. sm.Msgid = GenerateSecretToken()
  38. }
  39. sm.Split = append(sm.Split, MessagePair{
  40. Message: message,
  41. Concat: concat,
  42. })
  43. }
  44. func (sm *SplitMessage) SetTime() {
  45. // strip the monotonic time, it's a potential source of problems:
  46. sm.Time = time.Now().UTC().Round(0)
  47. }
  48. func (sm *SplitMessage) LenLines() int {
  49. if sm.Split == nil {
  50. if sm.Message == "" {
  51. return 0
  52. } else {
  53. return 1
  54. }
  55. }
  56. return len(sm.Split)
  57. }
  58. func (sm *SplitMessage) ValidMultiline() bool {
  59. // must contain at least one nonblank line
  60. for i := 0; i < len(sm.Split); i++ {
  61. if len(sm.Split[i].Message) != 0 {
  62. return true
  63. }
  64. }
  65. return false
  66. }
  67. func (sm *SplitMessage) IsRestrictedCTCPMessage() bool {
  68. if IsRestrictedCTCPMessage(sm.Message) {
  69. return true
  70. }
  71. for i := 0; i < len(sm.Split); i++ {
  72. if IsRestrictedCTCPMessage(sm.Split[i].Message) {
  73. return true
  74. }
  75. }
  76. return false
  77. }
  78. func (sm *SplitMessage) Is512() bool {
  79. return sm.Split == nil
  80. }
  81. // TokenLineBuilder is a helper for building IRC lines composed of delimited tokens,
  82. // with a maximum line length.
  83. type TokenLineBuilder struct {
  84. lineLen int
  85. delim string
  86. buf strings.Builder
  87. result []string
  88. }
  89. func (t *TokenLineBuilder) Initialize(lineLen int, delim string) {
  90. t.lineLen = lineLen
  91. t.delim = delim
  92. }
  93. // Add adds a token to the line, creating a new line if necessary.
  94. func (t *TokenLineBuilder) Add(token string) {
  95. tokenLen := len(token)
  96. if t.buf.Len() != 0 {
  97. tokenLen += len(t.delim)
  98. }
  99. if t.lineLen < t.buf.Len()+tokenLen {
  100. t.result = append(t.result, t.buf.String())
  101. t.buf.Reset()
  102. }
  103. if t.buf.Len() != 0 {
  104. t.buf.WriteString(t.delim)
  105. }
  106. t.buf.WriteString(token)
  107. }
  108. // Lines terminates the line-building and returns all the lines.
  109. func (t *TokenLineBuilder) Lines() (result []string) {
  110. result = t.result
  111. t.result = nil
  112. if t.buf.Len() != 0 {
  113. result = append(result, t.buf.String())
  114. t.buf.Reset()
  115. }
  116. return
  117. }
  118. // BuildTokenLines is a convenience to apply TokenLineBuilder to a predetermined
  119. // slice of tokens.
  120. func BuildTokenLines(lineLen int, tokens []string, delim string) []string {
  121. var tl TokenLineBuilder
  122. tl.Initialize(lineLen, delim)
  123. for _, arg := range tokens {
  124. tl.Add(arg)
  125. }
  126. return tl.Lines()
  127. }