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 3.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. // Copyright (c) 2020 Shivaram Lingamneni
  2. // released under the MIT license
  3. package irc
  4. import (
  5. "bytes"
  6. "net"
  7. "unicode/utf8"
  8. "github.com/gorilla/websocket"
  9. "github.com/goshuirc/irc-go/ircmsg"
  10. "github.com/goshuirc/irc-go/ircreader"
  11. "github.com/ergochat/ergo/irc/utils"
  12. )
  13. const (
  14. initialBufferSize = 1024
  15. )
  16. var (
  17. crlf = []byte{'\r', '\n'}
  18. )
  19. // maximum total length, in bytes, of a single IRC message:
  20. func maxReadQBytes() int {
  21. return ircmsg.MaxlenTagsFromClient + MaxLineLen + 1024
  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. reader ircreader.Reader
  41. }
  42. func NewIRCStreamConn(conn *utils.WrappedConn) *IRCStreamConn {
  43. var c IRCStreamConn
  44. c.conn = conn
  45. c.reader.Initialize(conn.Conn, initialBufferSize, maxReadQBytes())
  46. return &c
  47. }
  48. func (cc *IRCStreamConn) UnderlyingConn() *utils.WrappedConn {
  49. return cc.conn
  50. }
  51. func (cc *IRCStreamConn) WriteLine(buf []byte) (err error) {
  52. _, err = cc.conn.Write(buf)
  53. return
  54. }
  55. func (cc *IRCStreamConn) WriteLines(buffers [][]byte) (err error) {
  56. // on Linux, with a plaintext TCP or Unix domain socket,
  57. // the Go runtime will optimize this into a single writev(2) call:
  58. _, err = (*net.Buffers)(&buffers).WriteTo(cc.conn)
  59. return
  60. }
  61. func (cc *IRCStreamConn) ReadLine() ([]byte, error) {
  62. line, err := cc.reader.ReadLine()
  63. if err != nil {
  64. return nil, err
  65. } else if globalUtf8EnforcementSetting && !utf8.Valid(line) {
  66. return line, errInvalidUtf8
  67. } else {
  68. return line, nil
  69. }
  70. }
  71. func (cc *IRCStreamConn) Close() (err error) {
  72. return cc.conn.Close()
  73. }
  74. // IRCWSConn is an IRCConn over a websocket.
  75. type IRCWSConn struct {
  76. conn *websocket.Conn
  77. binary bool
  78. }
  79. func NewIRCWSConn(conn *websocket.Conn) IRCWSConn {
  80. binary := conn.Subprotocol() == "binary.ircv3.net"
  81. return IRCWSConn{conn: conn, binary: binary}
  82. }
  83. func (wc IRCWSConn) UnderlyingConn() *utils.WrappedConn {
  84. // just assume that the type is OK
  85. wConn, _ := wc.conn.UnderlyingConn().(*utils.WrappedConn)
  86. return wConn
  87. }
  88. func (wc IRCWSConn) WriteLine(buf []byte) (err error) {
  89. buf = bytes.TrimSuffix(buf, crlf)
  90. // #1483: if we have websockets at all, then we're enforcing utf8
  91. messageType := websocket.TextMessage
  92. if wc.binary {
  93. messageType = websocket.BinaryMessage
  94. }
  95. return wc.conn.WriteMessage(messageType, buf)
  96. }
  97. func (wc IRCWSConn) WriteLines(buffers [][]byte) (err error) {
  98. for _, buf := range buffers {
  99. err = wc.WriteLine(buf)
  100. if err != nil {
  101. return
  102. }
  103. }
  104. return
  105. }
  106. func (wc IRCWSConn) ReadLine() (line []byte, err error) {
  107. messageType, line, err := wc.conn.ReadMessage()
  108. if err == nil {
  109. if messageType == websocket.BinaryMessage && !utf8.Valid(line) {
  110. return line, errInvalidUtf8
  111. }
  112. return line, nil
  113. } else if err == websocket.ErrReadLimit {
  114. return line, ircreader.ErrReadQ
  115. } else {
  116. return line, err
  117. }
  118. }
  119. func (wc IRCWSConn) Close() (err error) {
  120. return wc.conn.Close()
  121. }