mirror of
				https://github.com/ergochat/ergo.git
				synced 2025-11-03 23:37:22 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			179 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			179 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright (c) 2020 Shivaram Lingamneni
 | 
						|
// released under the MIT license
 | 
						|
 | 
						|
package irc
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"io"
 | 
						|
	"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
 | 
						|
	buf    []byte
 | 
						|
	binary bool
 | 
						|
}
 | 
						|
 | 
						|
func NewIRCWSConn(conn *websocket.Conn) *IRCWSConn {
 | 
						|
	return &IRCWSConn{
 | 
						|
		conn:   conn,
 | 
						|
		binary: conn.Subprotocol() == "binary.ircv3.net",
 | 
						|
		buf:    make([]byte, initialBufferSize),
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
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) {
 | 
						|
	_, reader, err := wc.conn.NextReader()
 | 
						|
	switch err {
 | 
						|
	case nil:
 | 
						|
		// OK
 | 
						|
	case websocket.ErrReadLimit:
 | 
						|
		return line, ircreader.ErrReadQ
 | 
						|
	default:
 | 
						|
		return line, err
 | 
						|
	}
 | 
						|
 | 
						|
	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
 | 
						|
		if !utf8.Valid(line) {
 | 
						|
			return line, errInvalidUtf8
 | 
						|
		}
 | 
						|
		return line, nil
 | 
						|
	case nil, websocket.ErrReadLimit:
 | 
						|
		// nil means we filled wc.buf without exhausting the reader:
 | 
						|
		return line, ircreader.ErrReadQ
 | 
						|
	default:
 | 
						|
		return line, err
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
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) {
 | 
						|
	return wc.conn.Close()
 | 
						|
}
 |