Browse Source

Copy chunked.go from the standard library, so that changes there won't break things here any more.

Andy Balholm 6 years ago
parent
commit
e64c637219
2 changed files with 184 additions and 4 deletions
  1. 181 0
      chunked.go
  2. 3 4
      request.go

+ 181 - 0
chunked.go

@@ -0,0 +1,181 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// The wire protocol for HTTP's "chunked" Transfer-Encoding.
+// This code is derived from the standard library's http/httputil/chunked.go,
+
+package icap
+
+import (
+	"bufio"
+	"errors"
+	"fmt"
+	"io"
+)
+
+const maxLineLength = 4096 // assumed <= bufio.defaultBufSize
+
+var errLineTooLong = errors.New("header line too long")
+
+// NewChunkedReader returns a new chunkedReader that translates the data read from r
+// out of HTTP "chunked" format before returning it.
+// The chunkedReader returns io.EOF when the final 0-length chunk is read.
+//
+// NewChunkedReader is not needed by normal applications. The http package
+// automatically decodes chunking when reading response bodies.
+func newChunkedReader(r io.Reader) io.Reader {
+	br, ok := r.(*bufio.Reader)
+	if !ok {
+		br = bufio.NewReader(r)
+	}
+	return &chunkedReader{r: br}
+}
+
+type chunkedReader struct {
+	r   *bufio.Reader
+	n   uint64 // unread bytes in chunk
+	err error
+	buf [2]byte
+}
+
+func (cr *chunkedReader) beginChunk() {
+	// chunk-size CRLF
+	var line []byte
+	line, cr.err = readLine(cr.r)
+	if cr.err != nil {
+		return
+	}
+	cr.n, cr.err = parseHexUint(line)
+	if cr.err != nil {
+		return
+	}
+	if cr.n == 0 {
+		cr.err = io.EOF
+	}
+}
+
+func (cr *chunkedReader) Read(b []uint8) (n int, err error) {
+	if cr.err != nil {
+		return 0, cr.err
+	}
+	if cr.n == 0 {
+		cr.beginChunk()
+		if cr.err != nil {
+			return 0, cr.err
+		}
+	}
+	if uint64(len(b)) > cr.n {
+		b = b[0:cr.n]
+	}
+	n, cr.err = cr.r.Read(b)
+	cr.n -= uint64(n)
+	if cr.n == 0 && cr.err == nil {
+		// end of chunk (CRLF)
+		if _, cr.err = io.ReadFull(cr.r, cr.buf[:]); cr.err == nil {
+			if cr.buf[0] != '\r' || cr.buf[1] != '\n' {
+				cr.err = errors.New("malformed chunked encoding")
+			}
+		}
+	}
+	return n, cr.err
+}
+
+// Read a line of bytes (up to \n) from b.
+// Give up if the line exceeds maxLineLength.
+// The returned bytes are a pointer into storage in
+// the bufio, so they are only valid until the next bufio read.
+func readLine(b *bufio.Reader) (p []byte, err error) {
+	if p, err = b.ReadSlice('\n'); err != nil {
+		// We always know when EOF is coming.
+		// If the caller asked for a line, there should be a line.
+		if err == io.EOF {
+			err = io.ErrUnexpectedEOF
+		} else if err == bufio.ErrBufferFull {
+			err = errLineTooLong
+		}
+		return nil, err
+	}
+	if len(p) >= maxLineLength {
+		return nil, errLineTooLong
+	}
+	return trimTrailingWhitespace(p), nil
+}
+
+func trimTrailingWhitespace(b []byte) []byte {
+	for len(b) > 0 && isASCIISpace(b[len(b)-1]) {
+		b = b[:len(b)-1]
+	}
+	return b
+}
+
+func isASCIISpace(b byte) bool {
+	return b == ' ' || b == '\t' || b == '\n' || b == '\r'
+}
+
+// NewChunkedWriter returns a new chunkedWriter that translates writes into HTTP
+// "chunked" format before writing them to w. Closing the returned chunkedWriter
+// sends the final 0-length chunk that marks the end of the stream.
+//
+// NewChunkedWriter is not needed by normal applications. The http
+// package adds chunking automatically if handlers don't set a
+// Content-Length header. Using NewChunkedWriter inside a handler
+// would result in double chunking or chunking with a Content-Length
+// length, both of which are wrong.
+func NewChunkedWriter(w io.Writer) io.WriteCloser {
+	return &chunkedWriter{w}
+}
+
+// Writing to chunkedWriter translates to writing in HTTP chunked Transfer
+// Encoding wire format to the underlying Wire chunkedWriter.
+type chunkedWriter struct {
+	Wire io.Writer
+}
+
+// Write the contents of data as one chunk to Wire.
+// NOTE: Note that the corresponding chunk-writing procedure in Conn.Write has
+// a bug since it does not check for success of io.WriteString
+func (cw *chunkedWriter) Write(data []byte) (n int, err error) {
+
+	// Don't send 0-length data. It looks like EOF for chunked encoding.
+	if len(data) == 0 {
+		return 0, nil
+	}
+
+	if _, err = fmt.Fprintf(cw.Wire, "%x\r\n", len(data)); err != nil {
+		return 0, err
+	}
+	if n, err = cw.Wire.Write(data); err != nil {
+		return
+	}
+	if n != len(data) {
+		err = io.ErrShortWrite
+		return
+	}
+	_, err = io.WriteString(cw.Wire, "\r\n")
+
+	return
+}
+
+func (cw *chunkedWriter) Close() error {
+	_, err := io.WriteString(cw.Wire, "0\r\n")
+	return err
+}
+
+func parseHexUint(v []byte) (n uint64, err error) {
+	for _, b := range v {
+		n <<= 4
+		switch {
+		case '0' <= b && b <= '9':
+			b = b - '0'
+		case 'a' <= b && b <= 'f':
+			b = b - 'a' + 10
+		case 'A' <= b && b <= 'F':
+			b = b - 'A' + 10
+		default:
+			return 0, fmt.Errorf("invalid chunk length: '%s'", v)
+		}
+		n |= uint64(b)
+	}
+	return
+}

+ 3 - 4
request.go

@@ -14,7 +14,6 @@ import (
 	"io"
 	"io/ioutil"
 	"net/http"
-	"net/http/httputil"
 	"net/textproto"
 	"net/url"
 	"strconv"
@@ -146,7 +145,7 @@ func ReadRequest(b *bufio.ReadWriter) (req *Request, err error) {
 	if hasBody {
 		if p := req.Header.Get("Preview"); p != "" {
 			moreBody := true
-			req.Preview, err = ioutil.ReadAll(httputil.NewChunkedReader(b))
+			req.Preview, err = ioutil.ReadAll(newChunkedReader(b))
 			if err != nil {
 				if strings.Contains(err.Error(), "ieof") {
 					// The data ended with "0; ieof", which the HTTP chunked reader doesn't understand.
@@ -162,7 +161,7 @@ func ReadRequest(b *bufio.ReadWriter) (req *Request, err error) {
 			}
 			bodyReader = ioutil.NopCloser(r)
 		} else {
-			bodyReader = ioutil.NopCloser(httputil.NewChunkedReader(b))
+			bodyReader = ioutil.NopCloser(newChunkedReader(b))
 		}
 	}
 
@@ -229,7 +228,7 @@ func (c *continueReader) Read(p []byte) (n int, err error) {
 		if err != nil {
 			return 0, err
 		}
-		c.cr = httputil.NewChunkedReader(c.buf)
+		c.cr = newChunkedReader(c.buf)
 	}
 
 	return c.cr.Read(p)