Merge from mainline (167278:168000).
[official-gcc/graphite-test-results.git] / libgo / go / http / transfer.go
blob75030e87dfbb537738fb7712148217ae03ea3838
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 package http
7 import (
8 "bufio"
9 "io"
10 "os"
11 "strconv"
12 "strings"
15 // transferWriter inspects the fields of a user-supplied Request or Response,
16 // sanitizes them without changing the user object and provides methods for
17 // writing the respective header, body and trailer in wire format.
18 type transferWriter struct {
19 Body io.ReadCloser
20 ResponseToHEAD bool
21 ContentLength int64
22 Close bool
23 TransferEncoding []string
24 Trailer map[string]string
27 func newTransferWriter(r interface{}) (t *transferWriter, err os.Error) {
28 t = &transferWriter{}
30 // Extract relevant fields
31 atLeastHTTP11 := false
32 switch rr := r.(type) {
33 case *Request:
34 t.Body = rr.Body
35 t.ContentLength = rr.ContentLength
36 t.Close = rr.Close
37 t.TransferEncoding = rr.TransferEncoding
38 t.Trailer = rr.Trailer
39 atLeastHTTP11 = rr.ProtoAtLeast(1, 1)
40 case *Response:
41 t.Body = rr.Body
42 t.ContentLength = rr.ContentLength
43 t.Close = rr.Close
44 t.TransferEncoding = rr.TransferEncoding
45 t.Trailer = rr.Trailer
46 atLeastHTTP11 = rr.ProtoAtLeast(1, 1)
47 t.ResponseToHEAD = noBodyExpected(rr.RequestMethod)
50 // Sanitize Body,ContentLength,TransferEncoding
51 if t.ResponseToHEAD {
52 t.Body = nil
53 t.TransferEncoding = nil
54 // ContentLength is expected to hold Content-Length
55 if t.ContentLength < 0 {
56 return nil, ErrMissingContentLength
58 } else {
59 if !atLeastHTTP11 || t.Body == nil {
60 t.TransferEncoding = nil
62 if chunked(t.TransferEncoding) {
63 t.ContentLength = -1
64 } else if t.Body == nil { // no chunking, no body
65 t.ContentLength = 0
69 // Sanitize Trailer
70 if !chunked(t.TransferEncoding) {
71 t.Trailer = nil
74 return t, nil
77 func noBodyExpected(requestMethod string) bool {
78 return requestMethod == "HEAD"
81 func (t *transferWriter) WriteHeader(w io.Writer) (err os.Error) {
82 if t.Close {
83 _, err = io.WriteString(w, "Connection: close\r\n")
84 if err != nil {
85 return
89 // Write Content-Length and/or Transfer-Encoding whose values are a
90 // function of the sanitized field triple (Body, ContentLength,
91 // TransferEncoding)
92 if chunked(t.TransferEncoding) {
93 _, err = io.WriteString(w, "Transfer-Encoding: chunked\r\n")
94 if err != nil {
95 return
97 } else if t.ContentLength > 0 || t.ResponseToHEAD {
98 io.WriteString(w, "Content-Length: ")
99 _, err = io.WriteString(w, strconv.Itoa64(t.ContentLength)+"\r\n")
100 if err != nil {
101 return
105 // Write Trailer header
106 if t.Trailer != nil {
107 // TODO: At some point, there should be a generic mechanism for
108 // writing long headers, using HTTP line splitting
109 io.WriteString(w, "Trailer: ")
110 needComma := false
111 for k, _ := range t.Trailer {
112 k = CanonicalHeaderKey(k)
113 switch k {
114 case "Transfer-Encoding", "Trailer", "Content-Length":
115 return &badStringError{"invalid Trailer key", k}
117 if needComma {
118 io.WriteString(w, ",")
120 io.WriteString(w, k)
121 needComma = true
123 _, err = io.WriteString(w, "\r\n")
126 return
129 func (t *transferWriter) WriteBody(w io.Writer) (err os.Error) {
130 // Write body
131 if t.Body != nil {
132 if chunked(t.TransferEncoding) {
133 cw := NewChunkedWriter(w)
134 _, err = io.Copy(cw, t.Body)
135 if err == nil {
136 err = cw.Close()
138 } else if t.ContentLength == -1 {
139 _, err = io.Copy(w, t.Body)
140 } else {
141 _, err = io.Copy(w, io.LimitReader(t.Body, t.ContentLength))
143 if err != nil {
144 return err
146 if err = t.Body.Close(); err != nil {
147 return err
151 // TODO(petar): Place trailer writer code here.
152 if chunked(t.TransferEncoding) {
153 // Last chunk, empty trailer
154 _, err = io.WriteString(w, "\r\n")
157 return
160 type transferReader struct {
161 // Input
162 Header map[string]string
163 StatusCode int
164 RequestMethod string
165 ProtoMajor int
166 ProtoMinor int
167 // Output
168 Body io.ReadCloser
169 ContentLength int64
170 TransferEncoding []string
171 Close bool
172 Trailer map[string]string
175 // msg is *Request or *Response.
176 func readTransfer(msg interface{}, r *bufio.Reader) (err os.Error) {
177 t := &transferReader{}
179 // Unify input
180 switch rr := msg.(type) {
181 case *Response:
182 t.Header = rr.Header
183 t.StatusCode = rr.StatusCode
184 t.RequestMethod = rr.RequestMethod
185 t.ProtoMajor = rr.ProtoMajor
186 t.ProtoMinor = rr.ProtoMinor
187 t.Close = shouldClose(t.ProtoMajor, t.ProtoMinor, t.Header)
188 case *Request:
189 t.Header = rr.Header
190 t.ProtoMajor = rr.ProtoMajor
191 t.ProtoMinor = rr.ProtoMinor
192 // Transfer semantics for Requests are exactly like those for
193 // Responses with status code 200, responding to a GET method
194 t.StatusCode = 200
195 t.RequestMethod = "GET"
198 // Default to HTTP/1.1
199 if t.ProtoMajor == 0 && t.ProtoMinor == 0 {
200 t.ProtoMajor, t.ProtoMinor = 1, 1
203 // Transfer encoding, content length
204 t.TransferEncoding, err = fixTransferEncoding(t.Header)
205 if err != nil {
206 return err
209 t.ContentLength, err = fixLength(t.StatusCode, t.RequestMethod, t.Header, t.TransferEncoding)
210 if err != nil {
211 return err
214 // Trailer
215 t.Trailer, err = fixTrailer(t.Header, t.TransferEncoding)
216 if err != nil {
217 return err
220 // Prepare body reader. ContentLength < 0 means chunked encoding
221 // or close connection when finished, since multipart is not supported yet
222 switch {
223 case chunked(t.TransferEncoding):
224 t.Body = &body{Reader: newChunkedReader(r), hdr: msg, r: r, closing: t.Close}
225 case t.ContentLength >= 0:
226 // TODO: limit the Content-Length. This is an easy DoS vector.
227 t.Body = &body{Reader: io.LimitReader(r, t.ContentLength), closing: t.Close}
228 default:
229 // t.ContentLength < 0, i.e. "Content-Length" not mentioned in header
230 if t.Close {
231 // Close semantics (i.e. HTTP/1.0)
232 t.Body = &body{Reader: r, closing: t.Close}
233 } else {
234 // Persistent connection (i.e. HTTP/1.1)
235 t.Body = &body{Reader: io.LimitReader(r, 0), closing: t.Close}
237 // TODO(petar): It may be a good idea, for extra robustness, to
238 // assume ContentLength=0 for GET requests (and other special
239 // cases?). This logic should be in fixLength().
242 // Unify output
243 switch rr := msg.(type) {
244 case *Request:
245 rr.Body = t.Body
246 rr.ContentLength = t.ContentLength
247 rr.TransferEncoding = t.TransferEncoding
248 rr.Close = t.Close
249 rr.Trailer = t.Trailer
250 case *Response:
251 rr.Body = t.Body
252 rr.ContentLength = t.ContentLength
253 rr.TransferEncoding = t.TransferEncoding
254 rr.Close = t.Close
255 rr.Trailer = t.Trailer
258 return nil
261 // Checks whether chunked is part of the encodings stack
262 func chunked(te []string) bool { return len(te) > 0 && te[0] == "chunked" }
264 // Sanitize transfer encoding
265 func fixTransferEncoding(header map[string]string) ([]string, os.Error) {
266 raw, present := header["Transfer-Encoding"]
267 if !present {
268 return nil, nil
271 header["Transfer-Encoding"] = "", false
272 encodings := strings.Split(raw, ",", -1)
273 te := make([]string, 0, len(encodings))
274 // TODO: Even though we only support "identity" and "chunked"
275 // encodings, the loop below is designed with foresight. One
276 // invariant that must be maintained is that, if present,
277 // chunked encoding must always come first.
278 for _, encoding := range encodings {
279 encoding = strings.ToLower(strings.TrimSpace(encoding))
280 // "identity" encoding is not recored
281 if encoding == "identity" {
282 break
284 if encoding != "chunked" {
285 return nil, &badStringError{"unsupported transfer encoding", encoding}
287 te = te[0 : len(te)+1]
288 te[len(te)-1] = encoding
290 if len(te) > 1 {
291 return nil, &badStringError{"too many transfer encodings", strings.Join(te, ",")}
293 if len(te) > 0 {
294 // Chunked encoding trumps Content-Length. See RFC 2616
295 // Section 4.4. Currently len(te) > 0 implies chunked
296 // encoding.
297 header["Content-Length"] = "", false
298 return te, nil
301 return nil, nil
304 // Determine the expected body length, using RFC 2616 Section 4.4. This
305 // function is not a method, because ultimately it should be shared by
306 // ReadResponse and ReadRequest.
307 func fixLength(status int, requestMethod string, header map[string]string, te []string) (int64, os.Error) {
309 // Logic based on response type or status
310 if noBodyExpected(requestMethod) {
311 return 0, nil
313 if status/100 == 1 {
314 return 0, nil
316 switch status {
317 case 204, 304:
318 return 0, nil
321 // Logic based on Transfer-Encoding
322 if chunked(te) {
323 return -1, nil
326 // Logic based on Content-Length
327 if cl, present := header["Content-Length"]; present {
328 cl = strings.TrimSpace(cl)
329 if cl != "" {
330 n, err := strconv.Atoi64(cl)
331 if err != nil || n < 0 {
332 return -1, &badStringError{"bad Content-Length", cl}
334 return n, nil
335 } else {
336 header["Content-Length"] = "", false
340 // Logic based on media type. The purpose of the following code is just
341 // to detect whether the unsupported "multipart/byteranges" is being
342 // used. A proper Content-Type parser is needed in the future.
343 if strings.Contains(strings.ToLower(header["Content-Type"]), "multipart/byteranges") {
344 return -1, ErrNotSupported
347 // Body-EOF logic based on other methods (like closing, or chunked coding)
348 return -1, nil
351 // Determine whether to hang up after sending a request and body, or
352 // receiving a response and body
353 // 'header' is the request headers
354 func shouldClose(major, minor int, header map[string]string) bool {
355 if major < 1 {
356 return true
357 } else if major == 1 && minor == 0 {
358 v, present := header["Connection"]
359 if !present {
360 return true
362 v = strings.ToLower(v)
363 if !strings.Contains(v, "keep-alive") {
364 return true
366 return false
367 } else if v, present := header["Connection"]; present {
368 // TODO: Should split on commas, toss surrounding white space,
369 // and check each field.
370 if v == "close" {
371 header["Connection"] = "", false
372 return true
375 return false
378 // Parse the trailer header
379 func fixTrailer(header map[string]string, te []string) (map[string]string, os.Error) {
380 raw, present := header["Trailer"]
381 if !present {
382 return nil, nil
385 header["Trailer"] = "", false
386 trailer := make(map[string]string)
387 keys := strings.Split(raw, ",", -1)
388 for _, key := range keys {
389 key = CanonicalHeaderKey(strings.TrimSpace(key))
390 switch key {
391 case "Transfer-Encoding", "Trailer", "Content-Length":
392 return nil, &badStringError{"bad trailer key", key}
394 trailer[key] = ""
396 if len(trailer) == 0 {
397 return nil, nil
399 if !chunked(te) {
400 // Trailer and no chunking
401 return nil, ErrUnexpectedTrailer
403 return trailer, nil
406 // body turns a Reader into a ReadCloser.
407 // Close ensures that the body has been fully read
408 // and then reads the trailer if necessary.
409 type body struct {
410 io.Reader
411 hdr interface{} // non-nil (Response or Request) value means read trailer
412 r *bufio.Reader // underlying wire-format reader for the trailer
413 closing bool // is the connection to be closed after reading body?
416 func (b *body) Close() os.Error {
417 if b.hdr == nil && b.closing {
418 // no trailer and closing the connection next.
419 // no point in reading to EOF.
420 return nil
423 trashBuf := make([]byte, 1024) // local for thread safety
424 for {
425 _, err := b.Read(trashBuf)
426 if err == nil {
427 continue
429 if err == os.EOF {
430 break
432 return err
434 if b.hdr == nil { // not reading trailer
435 return nil
438 // TODO(petar): Put trailer reader code here
440 return nil