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.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. // Copyright (c) 2020 Shivaram Lingamneni
  2. // released under the MIT license
  3. package irc
  4. import (
  5. "bytes"
  6. "io"
  7. "net"
  8. "unicode/utf8"
  9. "github.com/ergochat/irc-go/ircmsg"
  10. "github.com/ergochat/irc-go/ircreader"
  11. "github.com/gorilla/websocket"
  12. "github.com/ergochat/ergo/irc/utils"
  13. )
  14. const (
  15. initialBufferSize = 1024
  16. )
  17. var (
  18. crlf = []byte{'\r', '\n'}
  19. )
  20. // maximum total length, in bytes, of a single IRC message:
  21. func maxReadQBytes() int {
  22. return ircmsg.MaxlenTagsFromClient + MaxLineLen + 1024
  23. }
  24. // IRCConn abstracts away the distinction between a regular
  25. // net.Conn (which includes both raw TCP and TLS) and a websocket.
  26. // it doesn't expose the net.Conn, io.Reader, or io.Writer interfaces
  27. // because websockets are message-oriented, not stream-oriented, and
  28. // therefore this abstraction is message-oriented as well.
  29. type IRCConn interface {
  30. UnderlyingConn() *utils.WrappedConn
  31. // these take an IRC line or lines, correctly terminated with CRLF:
  32. WriteLine([]byte) error
  33. WriteLines([][]byte) error
  34. // this returns an IRC line, possibly terminated with CRLF, LF, or nothing:
  35. ReadLine() (line []byte, err error)
  36. Close() error
  37. }
  38. // IRCStreamConn is an IRCConn over a regular stream connection.
  39. type IRCStreamConn struct {
  40. conn *utils.WrappedConn
  41. reader ircreader.Reader
  42. }
  43. func NewIRCStreamConn(conn *utils.WrappedConn) *IRCStreamConn {
  44. var c IRCStreamConn
  45. c.conn = conn
  46. c.reader.Initialize(conn.Conn, initialBufferSize, maxReadQBytes())
  47. return &c
  48. }
  49. func (cc *IRCStreamConn) UnderlyingConn() *utils.WrappedConn {
  50. return cc.conn
  51. }
  52. func (cc *IRCStreamConn) WriteLine(buf []byte) (err error) {
  53. _, err = cc.conn.Write(buf)
  54. return
  55. }
  56. func (cc *IRCStreamConn) WriteLines(buffers [][]byte) (err error) {
  57. // on Linux, with a plaintext TCP or Unix domain socket,
  58. // the Go runtime will optimize this into a single writev(2) call:
  59. _, err = (*net.Buffers)(&buffers).WriteTo(cc.conn)
  60. return
  61. }
  62. func (cc *IRCStreamConn) ReadLine() ([]byte, error) {
  63. line, err := cc.reader.ReadLine()
  64. if err != nil {
  65. return nil, err
  66. } else if globalUtf8EnforcementSetting && !utf8.Valid(line) {
  67. return line, errInvalidUtf8
  68. } else {
  69. return line, nil
  70. }
  71. }
  72. func (cc *IRCStreamConn) Close() (err error) {
  73. return cc.conn.Close()
  74. }
  75. // IRCWSConn is an IRCConn over a websocket.
  76. type IRCWSConn struct {
  77. conn *websocket.Conn
  78. buf []byte
  79. binary bool
  80. }
  81. func NewIRCWSConn(conn *websocket.Conn) *IRCWSConn {
  82. return &IRCWSConn{
  83. conn: conn,
  84. binary: conn.Subprotocol() == "binary.ircv3.net",
  85. buf: make([]byte, initialBufferSize),
  86. }
  87. }
  88. func (wc *IRCWSConn) UnderlyingConn() *utils.WrappedConn {
  89. // just assume that the type is OK
  90. wConn, _ := wc.conn.UnderlyingConn().(*utils.WrappedConn)
  91. return wConn
  92. }
  93. func (wc *IRCWSConn) WriteLine(buf []byte) (err error) {
  94. buf = bytes.TrimSuffix(buf, crlf)
  95. // #1483: if we have websockets at all, then we're enforcing utf8
  96. messageType := websocket.TextMessage
  97. if wc.binary {
  98. messageType = websocket.BinaryMessage
  99. }
  100. return wc.conn.WriteMessage(messageType, buf)
  101. }
  102. func (wc *IRCWSConn) WriteLines(buffers [][]byte) (err error) {
  103. for _, buf := range buffers {
  104. err = wc.WriteLine(buf)
  105. if err != nil {
  106. return
  107. }
  108. }
  109. return
  110. }
  111. func (wc *IRCWSConn) ReadLine() (line []byte, err error) {
  112. _, reader, err := wc.conn.NextReader()
  113. switch err {
  114. case nil:
  115. // OK
  116. case websocket.ErrReadLimit:
  117. return line, ircreader.ErrReadQ
  118. default:
  119. return line, err
  120. }
  121. line, err = wc.readFull(reader)
  122. switch err {
  123. case io.ErrUnexpectedEOF, io.EOF:
  124. // these are OK. io.ErrUnexpectedEOF is the good case:
  125. // it means we read the full message and it consumed less than the full wc.buf
  126. if !utf8.Valid(line) {
  127. return line, errInvalidUtf8
  128. }
  129. return line, nil
  130. case nil, websocket.ErrReadLimit:
  131. // nil means we filled wc.buf without exhausting the reader:
  132. return line, ircreader.ErrReadQ
  133. default:
  134. return line, err
  135. }
  136. }
  137. func (wc *IRCWSConn) readFull(reader io.Reader) (line []byte, err error) {
  138. // XXX this is io.ReadFull with a single attempt to resize upwards
  139. n, err := io.ReadFull(reader, wc.buf)
  140. if err == nil && len(wc.buf) < maxReadQBytes() {
  141. newBuf := make([]byte, maxReadQBytes())
  142. copy(newBuf, wc.buf[:n])
  143. wc.buf = newBuf
  144. n2, err := io.ReadFull(reader, wc.buf[n:])
  145. return wc.buf[:n+n2], err
  146. }
  147. return wc.buf[:n], err
  148. }
  149. func (wc *IRCWSConn) Close() (err error) {
  150. return wc.conn.Close()
  151. }