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 }