123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146 |
- // Copyright (c) 2020 Shivaram Lingamneni
- // released under the MIT license
-
- package irc
-
- import (
- "bytes"
- "net"
- "unicode/utf8"
-
- "github.com/ergochat/irc-go/ircmsg"
- "github.com/ergochat/irc-go/ircreader"
- "github.com/gorilla/websocket"
-
- "github.com/ergochat/ergo/irc/utils"
- )
-
- const (
- initialBufferSize = 1024
- )
-
- var (
- crlf = []byte{'\r', '\n'}
- )
-
- // maximum total length, in bytes, of a single IRC message:
- func maxReadQBytes() int {
- return ircmsg.MaxlenTagsFromClient + MaxLineLen + 1024
- }
-
- // IRCConn abstracts away the distinction between a regular
- // net.Conn (which includes both raw TCP and TLS) and a websocket.
- // it doesn't expose the net.Conn, io.Reader, or io.Writer interfaces
- // because websockets are message-oriented, not stream-oriented, and
- // therefore this abstraction is message-oriented as well.
- type IRCConn interface {
- UnderlyingConn() *utils.WrappedConn
-
- // these take an IRC line or lines, correctly terminated with CRLF:
- WriteLine([]byte) error
- WriteLines([][]byte) error
- // this returns an IRC line, possibly terminated with CRLF, LF, or nothing:
- ReadLine() (line []byte, err error)
-
- Close() error
- }
-
- // IRCStreamConn is an IRCConn over a regular stream connection.
- type IRCStreamConn struct {
- conn *utils.WrappedConn
-
- reader ircreader.Reader
- }
-
- func NewIRCStreamConn(conn *utils.WrappedConn) *IRCStreamConn {
- var c IRCStreamConn
- c.conn = conn
- c.reader.Initialize(conn.Conn, initialBufferSize, maxReadQBytes())
- return &c
- }
-
- func (cc *IRCStreamConn) UnderlyingConn() *utils.WrappedConn {
- return cc.conn
- }
-
- func (cc *IRCStreamConn) WriteLine(buf []byte) (err error) {
- _, err = cc.conn.Write(buf)
- return
- }
-
- func (cc *IRCStreamConn) WriteLines(buffers [][]byte) (err error) {
- // on Linux, with a plaintext TCP or Unix domain socket,
- // the Go runtime will optimize this into a single writev(2) call:
- _, err = (*net.Buffers)(&buffers).WriteTo(cc.conn)
- return
- }
-
- func (cc *IRCStreamConn) ReadLine() ([]byte, error) {
- line, err := cc.reader.ReadLine()
- if err != nil {
- return nil, err
- } else if globalUtf8EnforcementSetting && !utf8.Valid(line) {
- return line, errInvalidUtf8
- } else {
- return line, nil
- }
- }
-
- func (cc *IRCStreamConn) Close() (err error) {
- return cc.conn.Close()
- }
-
- // IRCWSConn is an IRCConn over a websocket.
- type IRCWSConn struct {
- conn *websocket.Conn
- binary bool
- }
-
- func NewIRCWSConn(conn *websocket.Conn) IRCWSConn {
- binary := conn.Subprotocol() == "binary.ircv3.net"
- return IRCWSConn{conn: conn, binary: binary}
- }
-
- func (wc IRCWSConn) UnderlyingConn() *utils.WrappedConn {
- // just assume that the type is OK
- wConn, _ := wc.conn.UnderlyingConn().(*utils.WrappedConn)
- return wConn
- }
-
- func (wc IRCWSConn) WriteLine(buf []byte) (err error) {
- buf = bytes.TrimSuffix(buf, crlf)
- // #1483: if we have websockets at all, then we're enforcing utf8
- messageType := websocket.TextMessage
- if wc.binary {
- messageType = websocket.BinaryMessage
- }
- return wc.conn.WriteMessage(messageType, buf)
- }
-
- func (wc IRCWSConn) WriteLines(buffers [][]byte) (err error) {
- for _, buf := range buffers {
- err = wc.WriteLine(buf)
- if err != nil {
- return
- }
- }
- return
- }
-
- func (wc IRCWSConn) ReadLine() (line []byte, err error) {
- messageType, line, err := wc.conn.ReadMessage()
- if err == nil {
- if messageType == websocket.BinaryMessage && !utf8.Valid(line) {
- return line, errInvalidUtf8
- }
- return line, nil
- } else if err == websocket.ErrReadLimit {
- return line, ircreader.ErrReadQ
- } else {
- return line, err
- }
- }
-
- func (wc IRCWSConn) Close() (err error) {
- return wc.conn.Close()
- }
|