|
@@ -1,9 +1,9 @@
|
1
|
1
|
package irc
|
2
|
2
|
|
3
|
3
|
import (
|
4
|
|
- "bufio"
|
5
|
4
|
"bytes"
|
6
|
5
|
"errors"
|
|
6
|
+ "io"
|
7
|
7
|
"net"
|
8
|
8
|
"unicode/utf8"
|
9
|
9
|
|
|
@@ -14,7 +14,8 @@ import (
|
14
|
14
|
)
|
15
|
15
|
|
16
|
16
|
const (
|
17
|
|
- maxReadQBytes = ircmsg.MaxlenTagsFromClient + MaxLineLen + 1024
|
|
17
|
+ maxReadQBytes = ircmsg.MaxlenTagsFromClient + MaxLineLen + 1024
|
|
18
|
+ initialBufferSize = 1024
|
18
|
19
|
)
|
19
|
20
|
|
20
|
21
|
var (
|
|
@@ -41,8 +42,13 @@ type IRCConn interface {
|
41
|
42
|
|
42
|
43
|
// IRCStreamConn is an IRCConn over a regular stream connection.
|
43
|
44
|
type IRCStreamConn struct {
|
44
|
|
- conn *utils.WrappedConn
|
45
|
|
- reader *bufio.Reader
|
|
45
|
+ conn *utils.WrappedConn
|
|
46
|
+
|
|
47
|
+ buf []byte
|
|
48
|
+ start int // start of valid (i.e., read but not yet consumed) data in the buffer
|
|
49
|
+ end int // end of valid data in the buffer
|
|
50
|
+ searchFrom int // start of valid data in the buffer not yet searched for \n
|
|
51
|
+ eof bool
|
46
|
52
|
}
|
47
|
53
|
|
48
|
54
|
func NewIRCStreamConn(conn *utils.WrappedConn) *IRCStreamConn {
|
|
@@ -67,21 +73,58 @@ func (cc *IRCStreamConn) WriteLines(buffers [][]byte) (err error) {
|
67
|
73
|
return
|
68
|
74
|
}
|
69
|
75
|
|
70
|
|
-func (cc *IRCStreamConn) ReadLine() (line []byte, err error) {
|
71
|
|
- // lazy initialize the reader in case the IP is banned
|
72
|
|
- if cc.reader == nil {
|
73
|
|
- cc.reader = bufio.NewReaderSize(cc.conn, maxReadQBytes)
|
74
|
|
- }
|
|
76
|
+func (cc *IRCStreamConn) ReadLine() ([]byte, error) {
|
|
77
|
+ for {
|
|
78
|
+ // try to find a terminated line in the buffered data already read
|
|
79
|
+ nlidx := bytes.IndexByte(cc.buf[cc.searchFrom:cc.end], '\n')
|
|
80
|
+ if nlidx != -1 {
|
|
81
|
+ // got a complete line
|
|
82
|
+ line := cc.buf[cc.start : cc.searchFrom+nlidx]
|
|
83
|
+ cc.start = cc.searchFrom + nlidx + 1
|
|
84
|
+ cc.searchFrom = cc.start
|
|
85
|
+ if globalUtf8EnforcementSetting && !utf8.Valid(line) {
|
|
86
|
+ return line, errInvalidUtf8
|
|
87
|
+ } else {
|
|
88
|
+ return line, nil
|
|
89
|
+ }
|
|
90
|
+ }
|
75
|
91
|
|
76
|
|
- var isPrefix bool
|
77
|
|
- line, isPrefix, err = cc.reader.ReadLine()
|
78
|
|
- if isPrefix {
|
79
|
|
- return nil, errReadQ
|
80
|
|
- }
|
81
|
|
- if globalUtf8EnforcementSetting && !utf8.Valid(line) {
|
82
|
|
- err = errInvalidUtf8
|
|
92
|
+ if cc.start == 0 && len(cc.buf) == maxReadQBytes {
|
|
93
|
+ return nil, errReadQ // out of space, can't expand or slide
|
|
94
|
+ }
|
|
95
|
+
|
|
96
|
+ if cc.eof {
|
|
97
|
+ return nil, io.EOF
|
|
98
|
+ }
|
|
99
|
+
|
|
100
|
+ if len(cc.buf) < maxReadQBytes && (len(cc.buf)-(cc.end-cc.start) < initialBufferSize/2) {
|
|
101
|
+ // allocate a new buffer, copy any remaining data
|
|
102
|
+ newLen := utils.RoundUpToPowerOfTwo(len(cc.buf) + 1)
|
|
103
|
+ if newLen > maxReadQBytes {
|
|
104
|
+ newLen = maxReadQBytes
|
|
105
|
+ } else if newLen < initialBufferSize {
|
|
106
|
+ newLen = initialBufferSize
|
|
107
|
+ }
|
|
108
|
+ newBuf := make([]byte, newLen)
|
|
109
|
+ copy(newBuf, cc.buf[cc.start:cc.end])
|
|
110
|
+ cc.buf = newBuf
|
|
111
|
+ } else if cc.start != 0 {
|
|
112
|
+ // slide remaining data back to the front of the buffer
|
|
113
|
+ copy(cc.buf, cc.buf[cc.start:cc.end])
|
|
114
|
+ }
|
|
115
|
+ cc.end = cc.end - cc.start
|
|
116
|
+ cc.start = 0
|
|
117
|
+
|
|
118
|
+ cc.searchFrom = cc.end
|
|
119
|
+ n, err := cc.conn.Read(cc.buf[cc.end:])
|
|
120
|
+ cc.end += n
|
|
121
|
+ if n != 0 && err == io.EOF {
|
|
122
|
+ // we may have received new \n-terminated lines, try to parse them
|
|
123
|
+ cc.eof = true
|
|
124
|
+ } else if err != nil {
|
|
125
|
+ return nil, err
|
|
126
|
+ }
|
83
|
127
|
}
|
84
|
|
- return
|
85
|
128
|
}
|
86
|
129
|
|
87
|
130
|
func (cc *IRCStreamConn) Close() (err error) {
|