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.
5
// The wire protocol for HTTP's "chunked" Transfer-Encoding.
7
// This code is duplicated in net/http and net/http/httputil.
8
// Please make any changes in both files.
20
const maxLineLength = 4096 // assumed <= bufio.defaultBufSize
22
var ErrLineTooLong = errors.New("header line too long")
24
// newChunkedReader returns a new chunkedReader that translates the data read from r
25
// out of HTTP "chunked" format before returning it.
26
// The chunkedReader returns io.EOF when the final 0-length chunk is read.
28
// newChunkedReader is not needed by normal applications. The http package
29
// automatically decodes chunking when reading response bodies.
30
func newChunkedReader(r io.Reader) io.Reader {
31
br, ok := r.(*bufio.Reader)
33
br = bufio.NewReader(r)
35
return &chunkedReader{r: br}
38
type chunkedReader struct {
40
n uint64 // unread bytes in chunk
45
func (cr *chunkedReader) beginChunk() {
48
line, cr.err = readLine(cr.r)
52
cr.n, cr.err = parseHexUint(line)
61
func (cr *chunkedReader) chunkHeaderAvailable() bool {
64
peek, _ := cr.r.Peek(n)
65
return bytes.IndexByte(peek, '\n') >= 0
70
func (cr *chunkedReader) Read(b []uint8) (n int, err error) {
73
if n > 0 && !cr.chunkHeaderAvailable() {
74
// We've read enough. Don't potentially block
75
// reading a new chunk header.
85
if uint64(len(rbuf)) > cr.n {
89
n0, cr.err = cr.r.Read(rbuf)
93
// If we're at the end of a chunk, read the next two
94
// bytes to verify they are "\r\n".
95
if cr.n == 0 && cr.err == nil {
96
if _, cr.err = io.ReadFull(cr.r, cr.buf[:2]); cr.err == nil {
97
if cr.buf[0] != '\r' || cr.buf[1] != '\n' {
98
cr.err = errors.New("malformed chunked encoding")
106
// Read a line of bytes (up to \n) from b.
107
// Give up if the line exceeds maxLineLength.
108
// The returned bytes are a pointer into storage in
109
// the bufio, so they are only valid until the next bufio read.
110
func readLine(b *bufio.Reader) (p []byte, err error) {
111
if p, err = b.ReadSlice('\n'); err != nil {
112
// We always know when EOF is coming.
113
// If the caller asked for a line, there should be a line.
115
err = io.ErrUnexpectedEOF
116
} else if err == bufio.ErrBufferFull {
121
if len(p) >= maxLineLength {
122
return nil, ErrLineTooLong
124
return trimTrailingWhitespace(p), nil
127
func trimTrailingWhitespace(b []byte) []byte {
128
for len(b) > 0 && isASCIISpace(b[len(b)-1]) {
134
func isASCIISpace(b byte) bool {
135
return b == ' ' || b == '\t' || b == '\n' || b == '\r'
138
// newChunkedWriter returns a new chunkedWriter that translates writes into HTTP
139
// "chunked" format before writing them to w. Closing the returned chunkedWriter
140
// sends the final 0-length chunk that marks the end of the stream.
142
// newChunkedWriter is not needed by normal applications. The http
143
// package adds chunking automatically if handlers don't set a
144
// Content-Length header. Using newChunkedWriter inside a handler
145
// would result in double chunking or chunking with a Content-Length
146
// length, both of which are wrong.
147
func newChunkedWriter(w io.Writer) io.WriteCloser {
148
return &chunkedWriter{w}
151
// Writing to chunkedWriter translates to writing in HTTP chunked Transfer
152
// Encoding wire format to the underlying Wire chunkedWriter.
153
type chunkedWriter struct {
157
// Write the contents of data as one chunk to Wire.
158
// NOTE: Note that the corresponding chunk-writing procedure in Conn.Write has
159
// a bug since it does not check for success of io.WriteString
160
func (cw *chunkedWriter) Write(data []byte) (n int, err error) {
162
// Don't send 0-length data. It looks like EOF for chunked encoding.
167
if _, err = fmt.Fprintf(cw.Wire, "%x\r\n", len(data)); err != nil {
170
if n, err = cw.Wire.Write(data); err != nil {
174
err = io.ErrShortWrite
177
_, err = io.WriteString(cw.Wire, "\r\n")
182
func (cw *chunkedWriter) Close() error {
183
_, err := io.WriteString(cw.Wire, "0\r\n")
187
func parseHexUint(v []byte) (n uint64, err error) {
188
for _, b := range v {
191
case '0' <= b && b <= '9':
193
case 'a' <= b && b <= 'f':
195
case 'A' <= b && b <= 'F':
198
return 0, errors.New("invalid byte in chunk length")