Source file src/pkg/net/http/httputil/chunked.go
1 // Copyright 2009 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 // The wire protocol for HTTP's "chunked" Transfer-Encoding. 6 7 // This code is a duplicate of ../chunked.go with these edits: 8 // s/newChunked/NewChunked/g 9 // s/package http/package httputil/ 10 // Please make any changes in both files. 11 12 package httputil 13 14 import ( 15 "bufio" 16 "bytes" 17 "errors" 18 "io" 19 "strconv" 20 ) 21 22 const maxLineLength = 4096 // assumed <= bufio.defaultBufSize 23 24 var ErrLineTooLong = errors.New("header line too long") 25 26 // NewChunkedReader returns a new chunkedReader that translates the data read from r 27 // out of HTTP "chunked" format before returning it. 28 // The chunkedReader returns io.EOF when the final 0-length chunk is read. 29 // 30 // NewChunkedReader is not needed by normal applications. The http package 31 // automatically decodes chunking when reading response bodies. 32 func NewChunkedReader(r io.Reader) io.Reader { 33 br, ok := r.(*bufio.Reader) 34 if !ok { 35 br = bufio.NewReader(r) 36 } 37 return &chunkedReader{r: br} 38 } 39 40 type chunkedReader struct { 41 r *bufio.Reader 42 n uint64 // unread bytes in chunk 43 err error 44 } 45 46 func (cr *chunkedReader) beginChunk() { 47 // chunk-size CRLF 48 var line string 49 line, cr.err = readLine(cr.r) 50 if cr.err != nil { 51 return 52 } 53 cr.n, cr.err = strconv.ParseUint(line, 16, 64) 54 if cr.err != nil { 55 return 56 } 57 if cr.n == 0 { 58 cr.err = io.EOF 59 } 60 } 61 62 func (cr *chunkedReader) Read(b []uint8) (n int, err error) { 63 if cr.err != nil { 64 return 0, cr.err 65 } 66 if cr.n == 0 { 67 cr.beginChunk() 68 if cr.err != nil { 69 return 0, cr.err 70 } 71 } 72 if uint64(len(b)) > cr.n { 73 b = b[0:cr.n] 74 } 75 n, cr.err = cr.r.Read(b) 76 cr.n -= uint64(n) 77 if cr.n == 0 && cr.err == nil { 78 // end of chunk (CRLF) 79 b := make([]byte, 2) 80 if _, cr.err = io.ReadFull(cr.r, b); cr.err == nil { 81 if b[0] != '\r' || b[1] != '\n' { 82 cr.err = errors.New("malformed chunked encoding") 83 } 84 } 85 } 86 return n, cr.err 87 } 88 89 // Read a line of bytes (up to \n) from b. 90 // Give up if the line exceeds maxLineLength. 91 // The returned bytes are a pointer into storage in 92 // the bufio, so they are only valid until the next bufio read. 93 func readLineBytes(b *bufio.Reader) (p []byte, err error) { 94 if p, err = b.ReadSlice('\n'); err != nil { 95 // We always know when EOF is coming. 96 // If the caller asked for a line, there should be a line. 97 if err == io.EOF { 98 err = io.ErrUnexpectedEOF 99 } else if err == bufio.ErrBufferFull { 100 err = ErrLineTooLong 101 } 102 return nil, err 103 } 104 if len(p) >= maxLineLength { 105 return nil, ErrLineTooLong 106 } 107 108 // Chop off trailing white space. 109 p = bytes.TrimRight(p, " \r\t\n") 110 111 return p, nil 112 } 113 114 // readLineBytes, but convert the bytes into a string. 115 func readLine(b *bufio.Reader) (s string, err error) { 116 p, e := readLineBytes(b) 117 if e != nil { 118 return "", e 119 } 120 return string(p), nil 121 } 122 123 // NewChunkedWriter returns a new chunkedWriter that translates writes into HTTP 124 // "chunked" format before writing them to w. Closing the returned chunkedWriter 125 // sends the final 0-length chunk that marks the end of the stream. 126 // 127 // NewChunkedWriter is not needed by normal applications. The http 128 // package adds chunking automatically if handlers don't set a 129 // Content-Length header. Using NewChunkedWriter inside a handler 130 // would result in double chunking or chunking with a Content-Length 131 // length, both of which are wrong. 132 func NewChunkedWriter(w io.Writer) io.WriteCloser { 133 return &chunkedWriter{w} 134 } 135 136 // Writing to chunkedWriter translates to writing in HTTP chunked Transfer 137 // Encoding wire format to the underlying Wire chunkedWriter. 138 type chunkedWriter struct { 139 Wire io.Writer 140 } 141 142 // Write the contents of data as one chunk to Wire. 143 // NOTE: Note that the corresponding chunk-writing procedure in Conn.Write has 144 // a bug since it does not check for success of io.WriteString 145 func (cw *chunkedWriter) Write(data []byte) (n int, err error) { 146 147 // Don't send 0-length data. It looks like EOF for chunked encoding. 148 if len(data) == 0 { 149 return 0, nil 150 } 151 152 head := strconv.FormatInt(int64(len(data)), 16) + "\r\n" 153 154 if _, err = io.WriteString(cw.Wire, head); err != nil { 155 return 0, err 156 } 157 if n, err = cw.Wire.Write(data); err != nil { 158 return 159 } 160 if n != len(data) { 161 err = io.ErrShortWrite 162 return 163 } 164 _, err = io.WriteString(cw.Wire, "\r\n") 165 166 return 167 } 168 169 func (cw *chunkedWriter) Close() error { 170 _, err := io.WriteString(cw.Wire, "0\r\n") 171 return err 172 }