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"
|
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"
|
2021-02-14 02:58:19 +01:00
|
|
|
"github.com/goshuirc/irc-go/ircreader"
|
2020-05-05 04:29:10 +02:00
|
|
|
|
|
|
|
"github.com/oragono/oragono/irc/utils"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2020-08-07 07:10:46 +02:00
|
|
|
maxReadQBytes = ircmsg.MaxlenTagsFromClient + MaxLineLen + 1024
|
|
|
|
initialBufferSize = 1024
|
2020-05-05 04:29:10 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2021-01-15 12:19:13 +01:00
|
|
|
crlf = []byte{'\r', '\n'}
|
|
|
|
errWSBinaryMessage = errors.New("WebSocket binary messages are unsupported")
|
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.
|
2020-05-05 23:20:50 +02:00
|
|
|
// 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 {
|
2020-05-05 23:20:50 +02:00
|
|
|
UnderlyingConn() *utils.WrappedConn
|
2020-05-05 04:29:10 +02:00
|
|
|
|
2020-05-05 23:20:50 +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 {
|
2020-08-07 07:10:46 +02:00
|
|
|
conn *utils.WrappedConn
|
|
|
|
|
2021-02-14 02:58:19 +01:00
|
|
|
reader ircreader.IRCReader
|
2020-05-05 04:29:10 +02:00
|
|
|
}
|
|
|
|
|
2020-05-05 23:20:50 +02:00
|
|
|
func NewIRCStreamConn(conn *utils.WrappedConn) *IRCStreamConn {
|
2021-02-14 02:58:19 +01:00
|
|
|
var c IRCStreamConn
|
|
|
|
c.conn = conn
|
|
|
|
c.reader.Initialize(conn.Conn, initialBufferSize, maxReadQBytes)
|
|
|
|
return &c
|
2020-05-05 04:29:10 +02:00
|
|
|
}
|
|
|
|
|
2020-05-05 23:20:50 +02:00
|
|
|
func (cc *IRCStreamConn) UnderlyingConn() *utils.WrappedConn {
|
2020-05-05 04:29:10 +02:00
|
|
|
return cc.conn
|
|
|
|
}
|
|
|
|
|
2020-05-05 23:20:50 +02:00
|
|
|
func (cc *IRCStreamConn) WriteLine(buf []byte) (err error) {
|
2020-05-05 04:29:10 +02:00
|
|
|
_, err = cc.conn.Write(buf)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-05-05 23:20:50 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-08-07 07:10:46 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewIRCWSConn(conn *websocket.Conn) IRCWSConn {
|
|
|
|
return IRCWSConn{conn: conn}
|
|
|
|
}
|
|
|
|
|
2020-05-05 23:20:50 +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
|
|
|
}
|
|
|
|
|
2020-05-05 23:20:50 +02:00
|
|
|
func (wc IRCWSConn) WriteLine(buf []byte) (err error) {
|
2020-05-05 04:29:10 +02:00
|
|
|
buf = bytes.TrimSuffix(buf, crlf)
|
2021-01-15 12:19:13 +01:00
|
|
|
// #1483: if we have websockets at all, then we're enforcing utf8
|
2020-05-05 04:29:10 +02:00
|
|
|
return wc.conn.WriteMessage(websocket.TextMessage, buf)
|
|
|
|
}
|
|
|
|
|
2020-05-05 23:20:50 +02:00
|
|
|
func (wc IRCWSConn) WriteLines(buffers [][]byte) (err error) {
|
2020-05-05 04:29:10 +02:00
|
|
|
for _, buf := range buffers {
|
2020-05-05 23:20:50 +02:00
|
|
|
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) {
|
2020-08-05 21:00:39 +02:00
|
|
|
messageType, line, err := wc.conn.ReadMessage()
|
|
|
|
if err == nil {
|
|
|
|
if messageType == websocket.TextMessage {
|
|
|
|
return line, nil
|
|
|
|
} else {
|
2021-01-15 12:19:13 +01:00
|
|
|
return nil, errWSBinaryMessage
|
2020-05-05 04:29:10 +02:00
|
|
|
}
|
2020-08-05 21:00:39 +02:00
|
|
|
} else if err == websocket.ErrReadLimit {
|
2021-02-14 02:58:19 +01:00
|
|
|
return line, ircreader.ErrReadQ
|
2020-08-05 21:00:39 +02:00
|
|
|
} else {
|
|
|
|
return line, err
|
2020-05-05 04:29:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (wc IRCWSConn) Close() (err error) {
|
|
|
|
return wc.conn.Close()
|
|
|
|
}
|