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