chunked_reader_v4.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. package s3api
  2. // the related code is copied and modified from minio source code
  3. /*
  4. * Minio Cloud Storage, (C) 2016 Minio, Inc.
  5. *
  6. * Licensed under the Apache License, Version 2.0 (the "License");
  7. * you may not use this file except in compliance with the License.
  8. * You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. import (
  19. "bufio"
  20. "bytes"
  21. "errors"
  22. "github.com/dustin/go-humanize"
  23. "io"
  24. "net/http"
  25. )
  26. // Streaming AWS Signature Version '4' constants.
  27. const (
  28. streamingContentSHA256 = "STREAMING-AWS4-HMAC-SHA256-PAYLOAD"
  29. )
  30. const maxLineLength = 4 * humanize.KiByte // assumed <= bufio.defaultBufSize 4KiB
  31. // lineTooLong is generated as chunk header is bigger than 4KiB.
  32. var errLineTooLong = errors.New("header line too long")
  33. // Malformed encoding is generated when chunk header is wrongly formed.
  34. var errMalformedEncoding = errors.New("malformed chunked encoding")
  35. // newSignV4ChunkedReader returns a new s3ChunkedReader that translates the data read from r
  36. // out of HTTP "chunked" format before returning it.
  37. // The s3ChunkedReader returns io.EOF when the final 0-length chunk is read.
  38. func newSignV4ChunkedReader(req *http.Request) io.ReadCloser {
  39. return &s3ChunkedReader{
  40. reader: bufio.NewReader(req.Body),
  41. state: readChunkHeader,
  42. }
  43. }
  44. // Represents the overall state that is required for decoding a
  45. // AWS Signature V4 chunked reader.
  46. type s3ChunkedReader struct {
  47. reader *bufio.Reader
  48. state chunkState
  49. lastChunk bool
  50. chunkSignature string
  51. n uint64 // Unread bytes in chunk
  52. err error
  53. }
  54. // Read chunk reads the chunk token signature portion.
  55. func (cr *s3ChunkedReader) readS3ChunkHeader() {
  56. // Read the first chunk line until CRLF.
  57. var hexChunkSize, hexChunkSignature []byte
  58. hexChunkSize, hexChunkSignature, cr.err = readChunkLine(cr.reader)
  59. if cr.err != nil {
  60. return
  61. }
  62. // <hex>;token=value - converts the hex into its uint64 form.
  63. cr.n, cr.err = parseHexUint(hexChunkSize)
  64. if cr.err != nil {
  65. return
  66. }
  67. if cr.n == 0 {
  68. cr.err = io.EOF
  69. }
  70. // Save the incoming chunk signature.
  71. cr.chunkSignature = string(hexChunkSignature)
  72. }
  73. type chunkState int
  74. const (
  75. readChunkHeader chunkState = iota
  76. readChunkTrailer
  77. readChunk
  78. verifyChunk
  79. eofChunk
  80. )
  81. func (cs chunkState) String() string {
  82. stateString := ""
  83. switch cs {
  84. case readChunkHeader:
  85. stateString = "readChunkHeader"
  86. case readChunkTrailer:
  87. stateString = "readChunkTrailer"
  88. case readChunk:
  89. stateString = "readChunk"
  90. case verifyChunk:
  91. stateString = "verifyChunk"
  92. case eofChunk:
  93. stateString = "eofChunk"
  94. }
  95. return stateString
  96. }
  97. func (cr *s3ChunkedReader) Close() (err error) {
  98. return nil
  99. }
  100. // Read - implements `io.Reader`, which transparently decodes
  101. // the incoming AWS Signature V4 streaming signature.
  102. func (cr *s3ChunkedReader) Read(buf []byte) (n int, err error) {
  103. for {
  104. switch cr.state {
  105. case readChunkHeader:
  106. cr.readS3ChunkHeader()
  107. // If we're at the end of a chunk.
  108. if cr.n == 0 && cr.err == io.EOF {
  109. cr.state = readChunkTrailer
  110. cr.lastChunk = true
  111. continue
  112. }
  113. if cr.err != nil {
  114. return 0, cr.err
  115. }
  116. cr.state = readChunk
  117. case readChunkTrailer:
  118. cr.err = readCRLF(cr.reader)
  119. if cr.err != nil {
  120. return 0, errMalformedEncoding
  121. }
  122. cr.state = verifyChunk
  123. case readChunk:
  124. // There is no more space left in the request buffer.
  125. if len(buf) == 0 {
  126. return n, nil
  127. }
  128. rbuf := buf
  129. // The request buffer is larger than the current chunk size.
  130. // Read only the current chunk from the underlying reader.
  131. if uint64(len(rbuf)) > cr.n {
  132. rbuf = rbuf[:cr.n]
  133. }
  134. var n0 int
  135. n0, cr.err = cr.reader.Read(rbuf)
  136. if cr.err != nil {
  137. // We have lesser than chunk size advertised in chunkHeader, this is 'unexpected'.
  138. if cr.err == io.EOF {
  139. cr.err = io.ErrUnexpectedEOF
  140. }
  141. return 0, cr.err
  142. }
  143. // Update the bytes read into request buffer so far.
  144. n += n0
  145. buf = buf[n0:]
  146. // Update bytes to be read of the current chunk before verifying chunk's signature.
  147. cr.n -= uint64(n0)
  148. // If we're at the end of a chunk.
  149. if cr.n == 0 {
  150. cr.state = readChunkTrailer
  151. continue
  152. }
  153. case verifyChunk:
  154. if cr.lastChunk {
  155. cr.state = eofChunk
  156. } else {
  157. cr.state = readChunkHeader
  158. }
  159. case eofChunk:
  160. return n, io.EOF
  161. }
  162. }
  163. }
  164. // readCRLF - check if reader only has '\r\n' CRLF character.
  165. // returns malformed encoding if it doesn't.
  166. func readCRLF(reader io.Reader) error {
  167. buf := make([]byte, 2)
  168. _, err := io.ReadFull(reader, buf[:2])
  169. if err != nil {
  170. return err
  171. }
  172. if buf[0] != '\r' || buf[1] != '\n' {
  173. return errMalformedEncoding
  174. }
  175. return nil
  176. }
  177. // Read a line of bytes (up to \n) from b.
  178. // Give up if the line exceeds maxLineLength.
  179. // The returned bytes are owned by the bufio.Reader
  180. // so they are only valid until the next bufio read.
  181. func readChunkLine(b *bufio.Reader) ([]byte, []byte, error) {
  182. buf, err := b.ReadSlice('\n')
  183. if err != nil {
  184. // We always know when EOF is coming.
  185. // If the caller asked for a line, there should be a line.
  186. if err == io.EOF {
  187. err = io.ErrUnexpectedEOF
  188. } else if err == bufio.ErrBufferFull {
  189. err = errLineTooLong
  190. }
  191. return nil, nil, err
  192. }
  193. if len(buf) >= maxLineLength {
  194. return nil, nil, errLineTooLong
  195. }
  196. // Parse s3 specific chunk extension and fetch the values.
  197. hexChunkSize, hexChunkSignature := parseS3ChunkExtension(buf)
  198. return hexChunkSize, hexChunkSignature, nil
  199. }
  200. // trimTrailingWhitespace - trim trailing white space.
  201. func trimTrailingWhitespace(b []byte) []byte {
  202. for len(b) > 0 && isASCIISpace(b[len(b)-1]) {
  203. b = b[:len(b)-1]
  204. }
  205. return b
  206. }
  207. // isASCIISpace - is ascii space?
  208. func isASCIISpace(b byte) bool {
  209. return b == ' ' || b == '\t' || b == '\n' || b == '\r'
  210. }
  211. // Constant s3 chunk encoding signature.
  212. const s3ChunkSignatureStr = ";chunk-signature="
  213. // parses3ChunkExtension removes any s3 specific chunk-extension from buf.
  214. // For example,
  215. // "10000;chunk-signature=..." => "10000", "chunk-signature=..."
  216. func parseS3ChunkExtension(buf []byte) ([]byte, []byte) {
  217. buf = trimTrailingWhitespace(buf)
  218. semi := bytes.Index(buf, []byte(s3ChunkSignatureStr))
  219. // Chunk signature not found, return the whole buffer.
  220. if semi == -1 {
  221. return buf, nil
  222. }
  223. return buf[:semi], parseChunkSignature(buf[semi:])
  224. }
  225. // parseChunkSignature - parse chunk signature.
  226. func parseChunkSignature(chunk []byte) []byte {
  227. chunkSplits := bytes.SplitN(chunk, []byte(s3ChunkSignatureStr), 2)
  228. return chunkSplits[1]
  229. }
  230. // parse hex to uint64.
  231. func parseHexUint(v []byte) (n uint64, err error) {
  232. for i, b := range v {
  233. switch {
  234. case '0' <= b && b <= '9':
  235. b = b - '0'
  236. case 'a' <= b && b <= 'f':
  237. b = b - 'a' + 10
  238. case 'A' <= b && b <= 'F':
  239. b = b - 'A' + 10
  240. default:
  241. return 0, errors.New("invalid byte in chunk length")
  242. }
  243. if i == 16 {
  244. return 0, errors.New("http chunk length too large")
  245. }
  246. n <<= 4
  247. n |= uint64(b)
  248. }
  249. return
  250. }