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 // 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. 25 package textproto 26 27 import ( 28 "bufio" 29 "fmt" 30 "io" 31 "net" 32 ) 33 34 // An Error represents a numeric error response from a server. 35 type Error struct { 36 Code int 37 Msg string 38 } 39 40 func (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. 46 type ProtocolError string 47 48 func (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. 57 type 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. 65 func 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. 74 func (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. 80 func 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) 112 func (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. 124 func 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. 135 func 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 145 func isASCIISpace(b byte) bool { 146 return b == ' ' || b == '\t' || b == '\n' || b == '\r' 147 } 148 149 func isASCIILetter(b byte) bool { 150 b |= 0x20 // make lower case 151 return 'a' <= b && b <= 'z' 152 } 153