1// Copyright 2010 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5// Package textproto implements generic support for text-based request/response 6// protocols in the style of HTTP, NNTP, and SMTP. 7// 8// The package provides: 9// 10// [Error], which represents a numeric error response from 11// a server. 12// 13// [Pipeline], to manage pipelined requests and responses 14// in a client. 15// 16// [Reader], to read numeric response code lines, 17// key: value headers, lines wrapped with leading spaces 18// on continuation lines, and whole text blocks ending 19// with a dot on a line by itself. 20// 21// [Writer], to write dot-encoded text blocks. 22// 23// [Conn], a convenient packaging of [Reader], [Writer], and [Pipeline] for use 24// with a single network connection. 25package textproto 26 27import ( 28 "bufio" 29 "fmt" 30 "io" 31 "net" 32) 33 34// An Error represents a numeric error response from a server. 35type Error struct { 36 Code int 37 Msg string 38} 39 40func (e *Error) Error() string { 41 return fmt.Sprintf("%03d %s", e.Code, e.Msg) 42} 43 44// A ProtocolError describes a protocol violation such 45// as an invalid response or a hung-up connection. 46type ProtocolError string 47 48func (p ProtocolError) Error() string { 49 return string(p) 50} 51 52// A Conn represents a textual network protocol connection. 53// It consists of a [Reader] and [Writer] to manage I/O 54// and a [Pipeline] to sequence concurrent requests on the connection. 55// These embedded types carry methods with them; 56// see the documentation of those types for details. 57type Conn struct { 58 Reader 59 Writer 60 Pipeline 61 conn io.ReadWriteCloser 62} 63 64// NewConn returns a new [Conn] using conn for I/O. 65func NewConn(conn io.ReadWriteCloser) *Conn { 66 return &Conn{ 67 Reader: Reader{R: bufio.NewReader(conn)}, 68 Writer: Writer{W: bufio.NewWriter(conn)}, 69 conn: conn, 70 } 71} 72 73// Close closes the connection. 74func (c *Conn) Close() error { 75 return c.conn.Close() 76} 77 78// Dial connects to the given address on the given network using [net.Dial] 79// and then returns a new [Conn] for the connection. 80func Dial(network, addr string) (*Conn, error) { 81 c, err := net.Dial(network, addr) 82 if err != nil { 83 return nil, err 84 } 85 return NewConn(c), nil 86} 87 88// Cmd is a convenience method that sends a command after 89// waiting its turn in the pipeline. The command text is the 90// result of formatting format with args and appending \r\n. 91// Cmd returns the id of the command, for use with StartResponse and EndResponse. 92// 93// For example, a client might run a HELP command that returns a dot-body 94// by using: 95// 96// id, err := c.Cmd("HELP") 97// if err != nil { 98// return nil, err 99// } 100// 101// c.StartResponse(id) 102// defer c.EndResponse(id) 103// 104// if _, _, err = c.ReadCodeLine(110); err != nil { 105// return nil, err 106// } 107// text, err := c.ReadDotBytes() 108// if err != nil { 109// return nil, err 110// } 111// return c.ReadCodeLine(250) 112func (c *Conn) Cmd(format string, args ...any) (id uint, err error) { 113 id = c.Next() 114 c.StartRequest(id) 115 err = c.PrintfLine(format, args...) 116 c.EndRequest(id) 117 if err != nil { 118 return 0, err 119 } 120 return id, nil 121} 122 123// TrimString returns s without leading and trailing ASCII space. 124func TrimString(s string) string { 125 for len(s) > 0 && isASCIISpace(s[0]) { 126 s = s[1:] 127 } 128 for len(s) > 0 && isASCIISpace(s[len(s)-1]) { 129 s = s[:len(s)-1] 130 } 131 return s 132} 133 134// TrimBytes returns b without leading and trailing ASCII space. 135func TrimBytes(b []byte) []byte { 136 for len(b) > 0 && isASCIISpace(b[0]) { 137 b = b[1:] 138 } 139 for len(b) > 0 && isASCIISpace(b[len(b)-1]) { 140 b = b[:len(b)-1] 141 } 142 return b 143} 144 145func isASCIISpace(b byte) bool { 146 return b == ' ' || b == '\t' || b == '\n' || b == '\r' 147} 148 149func isASCIILetter(b byte) bool { 150 b |= 0x20 // make lower case 151 return 'a' <= b && b <= 'z' 152} 153