3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-11-29 15:40:02 +01:00
ergo/irc/ircconn.go

145 lines
3.4 KiB
Go
Raw Normal View History

2020-05-05 04:29:10 +02:00
package irc
import (
"bufio"
"bytes"
2020-08-04 05:44:44 +02:00
"errors"
2020-05-05 04:29:10 +02:00
"net"
"unicode/utf8"
"github.com/gorilla/websocket"
"github.com/goshuirc/irc-go/ircmsg"
"github.com/oragono/oragono/irc/utils"
)
const (
maxReadQBytes = ircmsg.MaxlenTagsFromClient + MaxLineLen + 1024
2020-05-05 04:29:10 +02:00
)
var (
2020-08-04 05:44:44 +02:00
crlf = []byte{'\r', '\n'}
errReadQ = errors.New("ReadQ Exceeded")
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
2020-05-05 04:29:10 +02:00
reader *bufio.Reader
}
func NewIRCStreamConn(conn *utils.WrappedConn) *IRCStreamConn {
2020-05-05 04:29:10 +02:00
return &IRCStreamConn{
conn: conn,
}
}
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() (line []byte, err error) {
// lazy initialize the reader in case the IP is banned
if cc.reader == nil {
cc.reader = bufio.NewReaderSize(cc.conn, maxReadQBytes)
}
var isPrefix bool
line, isPrefix, err = cc.reader.ReadLine()
if isPrefix {
return nil, errReadQ
}
2020-06-22 20:54:43 +02:00
if globalUtf8EnforcementSetting && !utf8.Valid(line) {
err = errInvalidUtf8
}
2020-05-05 04:29:10 +02:00
return
}
func (cc *IRCStreamConn) Close() (err error) {
return cc.conn.Close()
}
// IRCWSConn is an IRCConn over a websocket.
type IRCWSConn struct {
conn *websocket.Conn
}
func NewIRCWSConn(conn *websocket.Conn) IRCWSConn {
return IRCWSConn{conn: conn}
}
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)
2020-06-22 20:54:43 +02:00
if !globalUtf8EnforcementSetting && !utf8.Valid(buf) {
// there's not much we can do about this;
// silently drop the message
2020-05-05 04:29:10 +02:00
return nil
}
return wc.conn.WriteMessage(websocket.TextMessage, buf)
}
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) {
messageType, line, err := wc.conn.ReadMessage()
if err == nil {
if messageType == websocket.TextMessage {
return line, nil
} else {
// for purposes of fakelag, treat non-text message as an empty line
return nil, nil
2020-05-05 04:29:10 +02:00
}
} else if err == websocket.ErrReadLimit {
return line, errReadQ
} else {
return line, err
2020-05-05 04:29:10 +02:00
}
}
func (wc IRCWSConn) Close() (err error) {
return wc.conn.Close()
}