You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

271 lines
7.0 KiB

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