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