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