textproto

Imports

Imports #

"bufio"
"fmt"
"io"
"net"
"bufio"
"fmt"
"io"
"sync"
"bufio"
"bytes"
"errors"
"fmt"
"io"
"math"
"strconv"
"strings"
"sync"
_ "unsafe"

Constants & Variables

colon var #

var colon = *ast.CallExpr

commonHeader var #

commonHeader interns common header strings.

var commonHeader map[string]string

commonHeaderOnce var #

var commonHeaderOnce sync.Once

crnl var #

var crnl = []byte{...}

dotcrnl var #

var dotcrnl = []byte{...}

errMessageTooLarge var #

TODO: This should be a distinguishable error (ErrMessageTooLarge) to allow mime/multipart to detect it.

var errMessageTooLarge = *ast.CallExpr

nl var #

var nl = *ast.CallExpr

toLower const #

const toLower = *ast.BinaryExpr

wstateBegin const #

const wstateBegin = iota

wstateBeginLine const #

const wstateBeginLine

wstateCR const #

const wstateCR

wstateData const #

const wstateData

Type Aliases

MIMEHeader type #

A MIMEHeader represents a MIME-style header mapping keys to sets of values.

type MIMEHeader map[string][]string

ProtocolError type #

A ProtocolError describes a protocol violation such as an invalid response or a hung-up connection.

type ProtocolError string

Structs

Conn struct #

A Conn represents a textual network protocol connection. It consists of a [Reader] and [Writer] to manage I/O and a [Pipeline] to sequence concurrent requests on the connection. These embedded types carry methods with them; see the documentation of those types for details.

type Conn struct {
Reader
Writer
Pipeline
conn io.ReadWriteCloser
}

Error struct #

An Error represents a numeric error response from a server.

type Error struct {
Code int
Msg string
}

Pipeline struct #

A Pipeline manages a pipelined in-order request/response sequence. To use a Pipeline p to manage multiple clients on a connection, each client should run: id := p.Next() // take a number p.StartRequest(id) // wait for turn to send request «send request» p.EndRequest(id) // notify Pipeline that request is sent p.StartResponse(id) // wait for turn to read response «read response» p.EndResponse(id) // notify Pipeline that response is read A pipelined server can use the same calls to ensure that responses computed in parallel are written in the correct order.

type Pipeline struct {
mu sync.Mutex
id uint
request sequencer
response sequencer
}

Reader struct #

A Reader implements convenience methods for reading requests or responses from a text protocol network connection.

type Reader struct {
R *bufio.Reader
dot *dotReader
buf []byte
}

Writer struct #

A Writer implements convenience methods for writing requests or responses to a text protocol network connection.

type Writer struct {
W *bufio.Writer
dot *dotWriter
}

dotReader struct #

type dotReader struct {
r *Reader
state int
}

dotWriter struct #

type dotWriter struct {
w *Writer
state int
}

sequencer struct #

A sequencer schedules a sequence of numbered events that must happen in order, one after the other. The event numbering must start at 0 and increment without skipping. The event number wraps around safely as long as there are not 2^32 simultaneous events pending.

type sequencer struct {
mu sync.Mutex
id uint
wait map[uint]chan struct{...}
}

Functions

Add method #

Add adds the key, value pair to the header. It appends to any existing values associated with key.

func (h MIMEHeader) Add(key string, value string)

CanonicalMIMEHeaderKey function #

CanonicalMIMEHeaderKey returns the canonical format of the MIME header key s. The canonicalization converts the first letter and any letter following a hyphen to upper case; the rest are converted to lowercase. For example, the canonical key for "accept-encoding" is "Accept-Encoding". MIME header keys are assumed to be ASCII only. If s contains a space or invalid header field bytes, it is returned without modifications.

func CanonicalMIMEHeaderKey(s string) string

Close method #

func (d *dotWriter) Close() error

Close method #

Close closes the connection.

func (c *Conn) Close() error

Cmd method #

Cmd is a convenience method that sends a command after waiting its turn in the pipeline. The command text is the result of formatting format with args and appending \r\n. Cmd returns the id of the command, for use with StartResponse and EndResponse. For example, a client might run a HELP command that returns a dot-body by using: id, err := c.Cmd("HELP") if err != nil { return nil, err } c.StartResponse(id) defer c.EndResponse(id) if _, _, err = c.ReadCodeLine(110); err != nil { return nil, err } text, err := c.ReadDotBytes() if err != nil { return nil, err } return c.ReadCodeLine(250)

func (c *Conn) Cmd(format string, args ...any) (id uint, err error)

Del method #

Del deletes the values associated with key.

func (h MIMEHeader) Del(key string)

Dial function #

Dial connects to the given address on the given network using [net.Dial] and then returns a new [Conn] for the connection.

func Dial(network string, addr string) (*Conn, error)

DotReader method #

DotReader returns a new [Reader] that satisfies Reads using the decoded text of a dot-encoded block read from r. The returned Reader is only valid until the next call to a method on r. Dot encoding is a common framing used for data blocks in text protocols such as SMTP. The data consists of a sequence of lines, each of which ends in "\r\n". The sequence itself ends at a line containing just a dot: ".\r\n". Lines beginning with a dot are escaped with an additional dot to avoid looking like the end of the sequence. The decoded form returned by the Reader's Read method rewrites the "\r\n" line endings into the simpler "\n", removes leading dot escapes if present, and stops with error [io.EOF] after consuming (and discarding) the end-of-sequence line.

func (r *Reader) DotReader() io.Reader

DotWriter method #

DotWriter returns a writer that can be used to write a dot-encoding to w. It takes care of inserting leading dots when necessary, translating line-ending \n into \r\n, and adding the final .\r\n line when the DotWriter is closed. The caller should close the DotWriter before the next call to a method on w. See the documentation for the [Reader.DotReader] method for details about dot-encoding.

func (w *Writer) DotWriter() io.WriteCloser

End method #

End notifies the sequencer that the event numbered id has completed, allowing it to schedule the event numbered id+1. It is a run-time error to call End with an id that is not the number of the active event.

func (s *sequencer) End(id uint)

EndRequest method #

EndRequest notifies p that the request with the given id has been sent (or, if this is a server, received).

func (p *Pipeline) EndRequest(id uint)

EndResponse method #

EndResponse notifies p that the response with the given id has been received (or, if this is a server, sent).

func (p *Pipeline) EndResponse(id uint)

Error method #

func (p ProtocolError) Error() string

Error method #

func (e *Error) Error() string

Get method #

Get gets the first value associated with the given key. It is case insensitive; [CanonicalMIMEHeaderKey] is used to canonicalize the provided key. If there are no values associated with the key, Get returns "". To use non-canonical keys, access the map directly.

func (h MIMEHeader) Get(key string) string

NewConn function #

NewConn returns a new [Conn] using conn for I/O.

func NewConn(conn io.ReadWriteCloser) *Conn

NewReader function #

NewReader returns a new [Reader] reading from r. To avoid denial of service attacks, the provided [bufio.Reader] should be reading from an [io.LimitReader] or similar Reader to bound the size of responses.

func NewReader(r *bufio.Reader) *Reader

NewWriter function #

NewWriter returns a new [Writer] writing to w.

func NewWriter(w *bufio.Writer) *Writer

Next method #

Next returns the next id for a request/response pair.

func (p *Pipeline) Next() uint

PrintfLine method #

PrintfLine writes the formatted output followed by \r\n.

func (w *Writer) PrintfLine(format string, args ...any) error

Read method #

Read satisfies reads by decoding dot-encoded data read from d.r.

func (d *dotReader) Read(b []byte) (n int, err error)

ReadCodeLine method #

ReadCodeLine reads a response code line of the form code message where code is a three-digit status code and the message extends to the rest of the line. An example of such a line is: 220 plan9.bell-labs.com ESMTP If the prefix of the status does not match the digits in expectCode, ReadCodeLine returns with err set to &Error{code, message}. For example, if expectCode is 31, an error will be returned if the status is not in the range [310,319]. If the response is multi-line, ReadCodeLine returns an error. An expectCode <= 0 disables the check of the status code.

func (r *Reader) ReadCodeLine(expectCode int) (code int, message string, err error)

ReadContinuedLine method #

ReadContinuedLine reads a possibly continued line from r, eliding the final trailing ASCII white space. Lines after the first are considered continuations if they begin with a space or tab character. In the returned data, continuation lines are separated from the previous line only by a single space: the newline and leading white space are removed. For example, consider this input: Line 1 continued... Line 2 The first call to ReadContinuedLine will return "Line 1 continued..." and the second will return "Line 2". Empty lines are never continued.

func (r *Reader) ReadContinuedLine() (string, error)

ReadContinuedLineBytes method #

ReadContinuedLineBytes is like [Reader.ReadContinuedLine] but returns a []byte instead of a string.

func (r *Reader) ReadContinuedLineBytes() ([]byte, error)

ReadDotBytes method #

ReadDotBytes reads a dot-encoding and returns the decoded data. See the documentation for the [Reader.DotReader] method for details about dot-encoding.

func (r *Reader) ReadDotBytes() ([]byte, error)

ReadDotLines method #

ReadDotLines reads a dot-encoding and returns a slice containing the decoded lines, with the final \r\n or \n elided from each. See the documentation for the [Reader.DotReader] method for details about dot-encoding.

func (r *Reader) ReadDotLines() ([]string, error)

ReadLine method #

ReadLine reads a single line from r, eliding the final \n or \r\n from the returned string.

func (r *Reader) ReadLine() (string, error)

ReadLineBytes method #

ReadLineBytes is like [Reader.ReadLine] but returns a []byte instead of a string.

func (r *Reader) ReadLineBytes() ([]byte, error)

ReadMIMEHeader method #

ReadMIMEHeader reads a MIME-style header from r. The header is a sequence of possibly continued Key: Value lines ending in a blank line. The returned map m maps [CanonicalMIMEHeaderKey](key) to a sequence of values in the same order encountered in the input. For example, consider this input: My-Key: Value 1 Long-Key: Even Longer Value My-Key: Value 2 Given that input, ReadMIMEHeader returns the map: map[string][]string{ "My-Key": {"Value 1", "Value 2"}, "Long-Key": {"Even Longer Value"}, }

func (r *Reader) ReadMIMEHeader() (MIMEHeader, error)

ReadResponse method #

ReadResponse reads a multi-line response of the form: code-message line 1 code-message line 2 ... code message line n where code is a three-digit status code. The first line starts with the code and a hyphen. The response is terminated by a line that starts with the same code followed by a space. Each line in message is separated by a newline (\n). See page 36 of RFC 959 (https://www.ietf.org/rfc/rfc959.txt) for details of another form of response accepted: code-message line 1 message line 2 ... code message line n If the prefix of the status does not match the digits in expectCode, ReadResponse returns with err set to &Error{code, message}. For example, if expectCode is 31, an error will be returned if the status is not in the range [310,319]. An expectCode <= 0 disables the check of the status code.

func (r *Reader) ReadResponse(expectCode int) (code int, message string, err error)

Set method #

Set sets the header entries associated with key to the single element value. It replaces any existing values associated with key.

func (h MIMEHeader) Set(key string, value string)

Start method #

Start waits until it is time for the event numbered id to begin. That is, except for the first event, it waits until End(id-1) has been called.

func (s *sequencer) Start(id uint)

StartRequest method #

StartRequest blocks until it is time to send (or, if this is a server, receive) the request with the given id.

func (p *Pipeline) StartRequest(id uint)

StartResponse method #

StartResponse blocks until it is time to receive (or, if this is a server, send) the request with the given id.

func (p *Pipeline) StartResponse(id uint)

TrimBytes function #

TrimBytes returns b without leading and trailing ASCII space.

func TrimBytes(b []byte) []byte

TrimString function #

TrimString returns s without leading and trailing ASCII space.

func TrimString(s string) string

Values method #

Values returns all values associated with the given key. It is case insensitive; [CanonicalMIMEHeaderKey] is used to canonicalize the provided key. To use non-canonical keys, access the map directly. The returned slice is not a copy.

func (h MIMEHeader) Values(key string) []string

Write method #

func (d *dotWriter) Write(b []byte) (n int, err error)

canonicalMIMEHeaderKey function #

canonicalMIMEHeaderKey is like CanonicalMIMEHeaderKey but is allowed to mutate the provided byte slice before returning the string. For invalid inputs (if a contains spaces or non-token bytes), a is unchanged and a string copy is returned. ok is true if the header key contains only valid characters and spaces. ReadMIMEHeader accepts header keys containing spaces, but does not canonicalize them.

func canonicalMIMEHeaderKey(a []byte) (_ string, ok bool)

closeDot method #

closeDot drains the current DotReader if any, making sure that it reads until the ending dot line.

func (r *Reader) closeDot()

closeDot method #

func (w *Writer) closeDot()

initCommonHeader function #

func initCommonHeader()

isASCIILetter function #

func isASCIILetter(b byte) bool

isASCIISpace function #

func isASCIISpace(b byte) bool

mustHaveFieldNameColon function #

mustHaveFieldNameColon ensures that, per RFC 7230, the field-name is on a single line, so the first line must contain a colon.

func mustHaveFieldNameColon(line []byte) error

noValidation function #

noValidation is a no-op validation func for readContinuedLineSlice that permits any lines.

func noValidation(_ []byte) error

parseCodeLine function #

func parseCodeLine(line string, expectCode int) (code int, continued bool, message string, err error)

readCodeLine method #

func (r *Reader) readCodeLine(expectCode int) (code int, continued bool, message string, err error)

readContinuedLineSlice method #

readContinuedLineSlice reads continued lines from the reader buffer, returning a byte slice with all lines. The validateFirstLine function is run on the first read line, and if it returns an error then this error is returned from readContinuedLineSlice. It reads up to lim bytes of data (or unlimited if lim is less than 0).

func (r *Reader) readContinuedLineSlice(lim int64, validateFirstLine func([]byte) error) ([]byte, error)

readLineSlice method #

readLineSlice reads a single line from r, up to lim bytes long (or unlimited if lim is less than 0), eliding the final \r or \r\n from the returned string.

func (r *Reader) readLineSlice(lim int64) ([]byte, error)

readMIMEHeader function #

readMIMEHeader is a version of ReadMIMEHeader which takes a limit on the header size. It is called by the mime/multipart package.

func readMIMEHeader(r *Reader, maxMemory int64, maxHeaders int64) (MIMEHeader, error)

skipSpace method #

skipSpace skips R over all spaces and returns the number of bytes skipped.

func (r *Reader) skipSpace() int

trim function #

trim returns s with leading and trailing spaces and tabs removed. It does not assume Unicode or UTF-8.

func trim(s []byte) []byte

upcomingHeaderKeys method #

upcomingHeaderKeys returns an approximation of the number of keys that will be in this header. If it gets confused, it returns 0.

func (r *Reader) upcomingHeaderKeys() (n int)

validHeaderFieldByte function #

validHeaderFieldByte reports whether c is a valid byte in a header field name. RFC 7230 says: header-field = field-name ":" OWS field-value OWS field-name = token tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA token = 1*tchar

func validHeaderFieldByte(c byte) bool

validHeaderValueByte function #

validHeaderValueByte reports whether c is a valid byte in a header field value. RFC 7230 says: field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] field-vchar = VCHAR / obs-text obs-text = %x80-FF RFC 5234 says: HTAB = %x09 SP = %x20 VCHAR = %x21-7E

func validHeaderValueByte(c byte) bool

Generated with Arrow