aboutsummaryrefslogtreecommitdiff
path: root/internal/net/conn.go
blob: 6d78d8f245c29aedc215fdecbe26010b75abe880 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package net

import (
	"bufio"
	"bytes"
	"io"
	"net"
	"strings"
	"time"
)

type Conn interface {
	ReadMessage() (string, error)
	io.Writer
	io.Closer
	// SetWriteDeadline is optional for transports: sessions that support it
	// enforce per-write deadlines so a stalled peer surfaces as a timeout
	// instead of a hung writer goroutine. Implementations without deadline
	// support return nil (deadlines are best-effort).
	SetWriteDeadline(t time.Time) error
	SetEcho(bool) error
}

type tcpConn struct {
	conn    net.Conn
	reader  *bufio.Reader
	echoOff bool
}

func newTCPConn(conn net.Conn) *tcpConn {
	return &tcpConn{
		conn:   conn,
		reader: bufio.NewReaderSize(conn, maxLineLength*4),
	}
}

// maxLineLength bounds how much of an incoming line is accepted. Longer
// lines are drained from the socket and truncated so a client cannot grow
// the read buffer without limit.
const maxLineLength = 1024

func (t *tcpConn) ReadMessage() (string, error) {
	line, err := t.reader.ReadSlice('\n')
	if err == bufio.ErrBufferFull {
		// Copy the bounded prefix out of the reusable read buffer BEFORE
		// draining the rest of the oversized line; further reads overwrite the
		// buffer the slice points into.
		prefixLen := len(line)
		if prefixLen > maxLineLength {
			prefixLen = maxLineLength
		}
		prefix := make([]byte, prefixLen)
		copy(prefix, line[:prefixLen])
		for {
			_, err = t.reader.ReadSlice('\n')
			if err == nil {
				break
			}
			if err != bufio.ErrBufferFull {
				return "", err
			}
		}
		line = prefix
	} else if err != nil {
		return "", err
	}
	text := strings.TrimRight(string(line), "\r\n")
	text = stripIAC(text)
	if t.echoOff {
		t.conn.Write([]byte("\r\n"))
	}
	return text, nil
}

func (t *tcpConn) Write(b []byte) (int, error) {
	return t.conn.Write(b)
}

func (t *tcpConn) SetWriteDeadline(ts time.Time) error {
	return t.conn.SetWriteDeadline(ts)
}

func (t *tcpConn) Close() error {
	return t.conn.Close()
}

func (t *tcpConn) SetEcho(on bool) error {
	t.echoOff = !on
	if on {
		return t.writeTelnetCmd(wont, echo)
	}
	return t.writeTelnetCmd(will, echo)
}

const (
	iac  = 255
	will = 251
	wont = 252
	do   = 253
	dont = 254
	sb   = 250
	se   = 240
	echo = 1
)

func (t *tcpConn) writeTelnetCmd(cmd, opt byte) error {
	_, err := t.conn.Write([]byte{iac, cmd, opt})
	return err
}

func stripIAC(s string) string {
	b := []byte(s)
	var out []byte
	i := 0
	iacSE := []byte{iac, se}
	for i < len(b) {
		if b[i] == iac && i+2 < len(b) {
			if b[i+1] == sb {
				end := bytes.Index(b[i+2:], iacSE)
				if end >= 0 {
					i += end + 4
					continue
				}
			}
			i += 3
			continue
		}
		if b[i] == iac {
			i++
			continue
		}
		out = append(out, b[i])
		i++
	}
	return string(out)
}