github.com/rohankumardubey/syslog-redirector-golang@v0.0.0-20140320174030-4859f03d829a/src/pkg/net/http/chunked.go (about) 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 "errors" 15 "fmt" 16 "io" 17 ) 18 19 const maxLineLength = 4096 // assumed <= bufio.defaultBufSize 20 21 var ErrLineTooLong = errors.New("header line too long") 22 23 // newChunkedReader returns a new chunkedReader that translates the data read from r 24 // out of HTTP "chunked" format before returning it. 25 // The chunkedReader returns io.EOF when the final 0-length chunk is read. 26 // 27 // newChunkedReader is not needed by normal applications. The http package 28 // automatically decodes chunking when reading response bodies. 29 func newChunkedReader(r io.Reader) io.Reader { 30 br, ok := r.(*bufio.Reader) 31 if !ok { 32 br = bufio.NewReader(r) 33 } 34 return &chunkedReader{r: br} 35 } 36 37 type chunkedReader struct { 38 r *bufio.Reader 39 n uint64 // unread bytes in chunk 40 err error 41 buf [2]byte 42 } 43 44 func (cr *chunkedReader) beginChunk() { 45 // chunk-size CRLF 46 var line []byte 47 line, cr.err = readLine(cr.r) 48 if cr.err != nil { 49 return 50 } 51 cr.n, cr.err = parseHexUint(line) 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 if _, cr.err = io.ReadFull(cr.r, cr.buf[:]); cr.err == nil { 78 if cr.buf[0] != '\r' || cr.buf[1] != '\n' { 79 cr.err = errors.New("malformed chunked encoding") 80 } 81 } 82 } 83 return n, cr.err 84 } 85 86 // Read a line of bytes (up to \n) from b. 87 // Give up if the line exceeds maxLineLength. 88 // The returned bytes are a pointer into storage in 89 // the bufio, so they are only valid until the next bufio read. 90 func readLine(b *bufio.Reader) (p []byte, err error) { 91 if p, err = b.ReadSlice('\n'); err != nil { 92 // We always know when EOF is coming. 93 // If the caller asked for a line, there should be a line. 94 if err == io.EOF { 95 err = io.ErrUnexpectedEOF 96 } else if err == bufio.ErrBufferFull { 97 err = ErrLineTooLong 98 } 99 return nil, err 100 } 101 if len(p) >= maxLineLength { 102 return nil, ErrLineTooLong 103 } 104 return trimTrailingWhitespace(p), nil 105 } 106 107 func trimTrailingWhitespace(b []byte) []byte { 108 for len(b) > 0 && isASCIISpace(b[len(b)-1]) { 109 b = b[:len(b)-1] 110 } 111 return b 112 } 113 114 func isASCIISpace(b byte) bool { 115 return b == ' ' || b == '\t' || b == '\n' || b == '\r' 116 } 117 118 // newChunkedWriter returns a new chunkedWriter that translates writes into HTTP 119 // "chunked" format before writing them to w. Closing the returned chunkedWriter 120 // sends the final 0-length chunk that marks the end of the stream. 121 // 122 // newChunkedWriter is not needed by normal applications. The http 123 // package adds chunking automatically if handlers don't set a 124 // Content-Length header. Using newChunkedWriter inside a handler 125 // would result in double chunking or chunking with a Content-Length 126 // length, both of which are wrong. 127 func newChunkedWriter(w io.Writer) io.WriteCloser { 128 return &chunkedWriter{w} 129 } 130 131 // Writing to chunkedWriter translates to writing in HTTP chunked Transfer 132 // Encoding wire format to the underlying Wire chunkedWriter. 133 type chunkedWriter struct { 134 Wire io.Writer 135 } 136 137 // Write the contents of data as one chunk to Wire. 138 // NOTE: Note that the corresponding chunk-writing procedure in Conn.Write has 139 // a bug since it does not check for success of io.WriteString 140 func (cw *chunkedWriter) Write(data []byte) (n int, err error) { 141 142 // Don't send 0-length data. It looks like EOF for chunked encoding. 143 if len(data) == 0 { 144 return 0, nil 145 } 146 147 if _, err = fmt.Fprintf(cw.Wire, "%x\r\n", len(data)); err != nil { 148 return 0, err 149 } 150 if n, err = cw.Wire.Write(data); err != nil { 151 return 152 } 153 if n != len(data) { 154 err = io.ErrShortWrite 155 return 156 } 157 _, err = io.WriteString(cw.Wire, "\r\n") 158 159 return 160 } 161 162 func (cw *chunkedWriter) Close() error { 163 _, err := io.WriteString(cw.Wire, "0\r\n") 164 return err 165 } 166 167 func parseHexUint(v []byte) (n uint64, err error) { 168 for _, b := range v { 169 n <<= 4 170 switch { 171 case '0' <= b && b <= '9': 172 b = b - '0' 173 case 'a' <= b && b <= 'f': 174 b = b - 'a' + 10 175 case 'A' <= b && b <= 'F': 176 b = b - 'A' + 10 177 default: 178 return 0, errors.New("invalid byte in chunk length") 179 } 180 n |= uint64(b) 181 } 182 return 183 }