3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-12-23 19:22:41 +01:00
ergo/irc/ircconn.go

179 lines
4.3 KiB
Go
Raw Normal View History

2021-01-19 14:49:45 +01:00
// Copyright (c) 2020 Shivaram Lingamneni
// released under the MIT license
2020-05-05 04:29:10 +02:00
package irc
import (
"bytes"
"io"
2020-05-05 04:29:10 +02:00
"net"
"unicode/utf8"
"github.com/ergochat/irc-go/ircmsg"
"github.com/ergochat/irc-go/ircreader"
2020-05-05 04:29:10 +02:00
"github.com/gorilla/websocket"
2021-05-25 06:34:38 +02:00
"github.com/ergochat/ergo/irc/utils"
2020-05-05 04:29:10 +02:00
)
const (
initialBufferSize = 1024
2020-05-05 04:29:10 +02:00
)
var (
crlf = []byte{'\r', '\n'}
2020-05-05 04:29:10 +02:00
)
2021-05-24 06:38:47 +02:00
// maximum total length, in bytes, of a single IRC message:
func maxReadQBytes() int {
return ircmsg.MaxlenTagsFromClient + MaxLineLen + 1024
}
2020-05-05 04:29:10 +02:00
// 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.
2020-05-05 04:29:10 +02:00
type IRCConn interface {
UnderlyingConn() *utils.WrappedConn
2020-05-05 04:29:10 +02:00
// these take an IRC line or lines, correctly terminated with CRLF:
WriteLine([]byte) error
WriteLines([][]byte) error
2020-08-04 05:44:44 +02:00
// this returns an IRC line, possibly terminated with CRLF, LF, or nothing:
2020-05-05 04:29:10 +02:00
ReadLine() (line []byte, err error)
Close() error
}
// IRCStreamConn is an IRCConn over a regular stream connection.
type IRCStreamConn struct {
conn *utils.WrappedConn
2021-03-11 02:07:43 +01:00
reader ircreader.Reader
2020-05-05 04:29:10 +02:00
}
func NewIRCStreamConn(conn *utils.WrappedConn) *IRCStreamConn {
2021-02-14 02:58:19 +01:00
var c IRCStreamConn
c.conn = conn
2021-05-24 06:38:47 +02:00
c.reader.Initialize(conn.Conn, initialBufferSize, maxReadQBytes())
2021-02-14 02:58:19 +01:00
return &c
2020-05-05 04:29:10 +02:00
}
func (cc *IRCStreamConn) UnderlyingConn() *utils.WrappedConn {
2020-05-05 04:29:10 +02:00
return cc.conn
}
func (cc *IRCStreamConn) WriteLine(buf []byte) (err error) {
2020-05-05 04:29:10 +02:00
_, err = cc.conn.Write(buf)
return
}
func (cc *IRCStreamConn) WriteLines(buffers [][]byte) (err error) {
2020-05-05 04:29:10 +02:00
// 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) {
2021-02-14 02:58:19 +01:00
line, err := cc.reader.ReadLine()
if err != nil {
return nil, err
} else if globalUtf8EnforcementSetting && !utf8.Valid(line) {
return line, errInvalidUtf8
} else {
return line, nil
2020-05-05 04:29:10 +02:00
}
}
func (cc *IRCStreamConn) Close() (err error) {
return cc.conn.Close()
}
// IRCWSConn is an IRCConn over a websocket.
type IRCWSConn struct {
conn *websocket.Conn
buf []byte
binary bool
2020-05-05 04:29:10 +02:00
}
func NewIRCWSConn(conn *websocket.Conn) *IRCWSConn {
return &IRCWSConn{
conn: conn,
binary: conn.Subprotocol() == "binary.ircv3.net",
2023-01-22 01:10:25 +01:00
buf: make([]byte, initialBufferSize),
}
2020-05-05 04:29:10 +02:00
}
func (wc *IRCWSConn) UnderlyingConn() *utils.WrappedConn {
// just assume that the type is OK
wConn, _ := wc.conn.UnderlyingConn().(*utils.WrappedConn)
return wConn
2020-05-05 04:29:10 +02:00
}
func (wc *IRCWSConn) WriteLine(buf []byte) (err error) {
2020-05-05 04:29:10 +02:00
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)
2020-05-05 04:29:10 +02:00
}
func (wc *IRCWSConn) WriteLines(buffers [][]byte) (err error) {
2020-05-05 04:29:10 +02:00
for _, buf := range buffers {
err = wc.WriteLine(buf)
2020-05-05 04:29:10 +02:00
if err != nil {
return
}
}
return
}
func (wc *IRCWSConn) ReadLine() (line []byte, err error) {
2023-01-22 20:42:58 +01:00
_, reader, err := wc.conn.NextReader()
switch err {
case nil:
// OK
case websocket.ErrReadLimit:
return line, ircreader.ErrReadQ
default:
return line, err
}
2023-01-22 01:10:25 +01:00
line, err = wc.readFull(reader)
switch err {
case io.ErrUnexpectedEOF, io.EOF:
// these are OK. io.ErrUnexpectedEOF is the good case:
// it means we read the full message and it consumed less than the full wc.buf
2023-01-22 20:42:58 +01:00
if !utf8.Valid(line) {
return line, errInvalidUtf8
2020-05-05 04:29:10 +02:00
}
return line, nil
case nil, websocket.ErrReadLimit:
// nil means we filled wc.buf without exhausting the reader:
2021-02-14 02:58:19 +01:00
return line, ircreader.ErrReadQ
default:
return line, err
2020-05-05 04:29:10 +02:00
}
}
2023-01-22 01:10:25 +01:00
func (wc *IRCWSConn) readFull(reader io.Reader) (line []byte, err error) {
// XXX this is io.ReadFull with a single attempt to resize upwards
n, err := io.ReadFull(reader, wc.buf)
if err == nil && len(wc.buf) < maxReadQBytes() {
newBuf := make([]byte, maxReadQBytes())
copy(newBuf, wc.buf[:n])
wc.buf = newBuf
n2, err := io.ReadFull(reader, wc.buf[n:])
return wc.buf[:n+n2], err
}
return wc.buf[:n], err
}
func (wc *IRCWSConn) Close() (err error) {
2020-05-05 04:29:10 +02:00
return wc.conn.Close()
}