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.

ircconn.go 4.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. // Copyright (c) 2020 Shivaram Lingamneni
  2. // released under the MIT license
  3. package irc
  4. import (
  5. "bytes"
  6. "errors"
  7. "io"
  8. "net"
  9. "unicode/utf8"
  10. "github.com/gorilla/websocket"
  11. "github.com/goshuirc/irc-go/ircmsg"
  12. "github.com/oragono/oragono/irc/utils"
  13. )
  14. const (
  15. maxReadQBytes = ircmsg.MaxlenTagsFromClient + MaxLineLen + 1024
  16. initialBufferSize = 1024
  17. )
  18. var (
  19. crlf = []byte{'\r', '\n'}
  20. errReadQ = errors.New("ReadQ Exceeded")
  21. errWSBinaryMessage = errors.New("WebSocket binary messages are unsupported")
  22. )
  23. // IRCConn abstracts away the distinction between a regular
  24. // net.Conn (which includes both raw TCP and TLS) and a websocket.
  25. // it doesn't expose the net.Conn, io.Reader, or io.Writer interfaces
  26. // because websockets are message-oriented, not stream-oriented, and
  27. // therefore this abstraction is message-oriented as well.
  28. type IRCConn interface {
  29. UnderlyingConn() *utils.WrappedConn
  30. // these take an IRC line or lines, correctly terminated with CRLF:
  31. WriteLine([]byte) error
  32. WriteLines([][]byte) error
  33. // this returns an IRC line, possibly terminated with CRLF, LF, or nothing:
  34. ReadLine() (line []byte, err error)
  35. Close() error
  36. }
  37. // IRCStreamConn is an IRCConn over a regular stream connection.
  38. type IRCStreamConn struct {
  39. conn *utils.WrappedConn
  40. buf []byte
  41. start int // start of valid (i.e., read but not yet consumed) data in the buffer
  42. end int // end of valid data in the buffer
  43. searchFrom int // start of valid data in the buffer not yet searched for \n
  44. eof bool
  45. }
  46. func NewIRCStreamConn(conn *utils.WrappedConn) *IRCStreamConn {
  47. return &IRCStreamConn{
  48. conn: conn,
  49. }
  50. }
  51. func (cc *IRCStreamConn) UnderlyingConn() *utils.WrappedConn {
  52. return cc.conn
  53. }
  54. func (cc *IRCStreamConn) WriteLine(buf []byte) (err error) {
  55. _, err = cc.conn.Write(buf)
  56. return
  57. }
  58. func (cc *IRCStreamConn) WriteLines(buffers [][]byte) (err error) {
  59. // on Linux, with a plaintext TCP or Unix domain socket,
  60. // the Go runtime will optimize this into a single writev(2) call:
  61. _, err = (*net.Buffers)(&buffers).WriteTo(cc.conn)
  62. return
  63. }
  64. func (cc *IRCStreamConn) ReadLine() ([]byte, error) {
  65. for {
  66. // try to find a terminated line in the buffered data already read
  67. nlidx := bytes.IndexByte(cc.buf[cc.searchFrom:cc.end], '\n')
  68. if nlidx != -1 {
  69. // got a complete line
  70. line := cc.buf[cc.start : cc.searchFrom+nlidx]
  71. cc.start = cc.searchFrom + nlidx + 1
  72. cc.searchFrom = cc.start
  73. if globalUtf8EnforcementSetting && !utf8.Valid(line) {
  74. return line, errInvalidUtf8
  75. } else {
  76. return line, nil
  77. }
  78. }
  79. if cc.start == 0 && len(cc.buf) == maxReadQBytes {
  80. return nil, errReadQ // out of space, can't expand or slide
  81. }
  82. if cc.eof {
  83. return nil, io.EOF
  84. }
  85. if len(cc.buf) < maxReadQBytes && (len(cc.buf)-(cc.end-cc.start) < initialBufferSize/2) {
  86. // allocate a new buffer, copy any remaining data
  87. newLen := utils.RoundUpToPowerOfTwo(len(cc.buf) + 1)
  88. if newLen > maxReadQBytes {
  89. newLen = maxReadQBytes
  90. } else if newLen < initialBufferSize {
  91. newLen = initialBufferSize
  92. }
  93. newBuf := make([]byte, newLen)
  94. copy(newBuf, cc.buf[cc.start:cc.end])
  95. cc.buf = newBuf
  96. } else if cc.start != 0 {
  97. // slide remaining data back to the front of the buffer
  98. copy(cc.buf, cc.buf[cc.start:cc.end])
  99. }
  100. cc.end = cc.end - cc.start
  101. cc.start = 0
  102. cc.searchFrom = cc.end
  103. n, err := cc.conn.Read(cc.buf[cc.end:])
  104. cc.end += n
  105. if n != 0 && err == io.EOF {
  106. // we may have received new \n-terminated lines, try to parse them
  107. cc.eof = true
  108. } else if err != nil {
  109. return nil, err
  110. }
  111. }
  112. }
  113. func (cc *IRCStreamConn) Close() (err error) {
  114. return cc.conn.Close()
  115. }
  116. // IRCWSConn is an IRCConn over a websocket.
  117. type IRCWSConn struct {
  118. conn *websocket.Conn
  119. }
  120. func NewIRCWSConn(conn *websocket.Conn) IRCWSConn {
  121. return IRCWSConn{conn: conn}
  122. }
  123. func (wc IRCWSConn) UnderlyingConn() *utils.WrappedConn {
  124. // just assume that the type is OK
  125. wConn, _ := wc.conn.UnderlyingConn().(*utils.WrappedConn)
  126. return wConn
  127. }
  128. func (wc IRCWSConn) WriteLine(buf []byte) (err error) {
  129. buf = bytes.TrimSuffix(buf, crlf)
  130. // #1483: if we have websockets at all, then we're enforcing utf8
  131. return wc.conn.WriteMessage(websocket.TextMessage, buf)
  132. }
  133. func (wc IRCWSConn) WriteLines(buffers [][]byte) (err error) {
  134. for _, buf := range buffers {
  135. err = wc.WriteLine(buf)
  136. if err != nil {
  137. return
  138. }
  139. }
  140. return
  141. }
  142. func (wc IRCWSConn) ReadLine() (line []byte, err error) {
  143. messageType, line, err := wc.conn.ReadMessage()
  144. if err == nil {
  145. if messageType == websocket.TextMessage {
  146. return line, nil
  147. } else {
  148. return nil, errWSBinaryMessage
  149. }
  150. } else if err == websocket.ErrReadLimit {
  151. return line, errReadQ
  152. } else {
  153. return line, err
  154. }
  155. }
  156. func (wc IRCWSConn) Close() (err error) {
  157. return wc.conn.Close()
  158. }