github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/streaming-v4-unsigned.go (about) 1 // Copyright (c) 2015-2023 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package cmd 19 20 import ( 21 "bufio" 22 "bytes" 23 "fmt" 24 "io" 25 "net/http" 26 "strings" 27 ) 28 29 // newUnsignedV4ChunkedReader returns a new s3UnsignedChunkedReader that translates the data read from r 30 // out of HTTP "chunked" format before returning it. 31 // The s3ChunkedReader returns io.EOF when the final 0-length chunk is read. 32 func newUnsignedV4ChunkedReader(req *http.Request, trailer bool) (io.ReadCloser, APIErrorCode) { 33 if trailer { 34 // Discard anything unsigned. 35 req.Trailer = make(http.Header) 36 trailers := req.Header.Values(awsTrailerHeader) 37 for _, key := range trailers { 38 req.Trailer.Add(key, "") 39 } 40 } else { 41 req.Trailer = nil 42 } 43 return &s3UnsignedChunkedReader{ 44 trailers: req.Trailer, 45 reader: bufio.NewReader(req.Body), 46 buffer: make([]byte, 64*1024), 47 }, ErrNone 48 } 49 50 // Represents the overall state that is required for decoding a 51 // AWS Signature V4 chunked reader. 52 type s3UnsignedChunkedReader struct { 53 reader *bufio.Reader 54 trailers http.Header 55 56 buffer []byte 57 offset int 58 err error 59 debug bool 60 } 61 62 func (cr *s3UnsignedChunkedReader) Close() (err error) { 63 return cr.err 64 } 65 66 // Read - implements `io.Reader`, which transparently decodes 67 // the incoming AWS Signature V4 streaming signature. 68 func (cr *s3UnsignedChunkedReader) Read(buf []byte) (n int, err error) { 69 // First, if there is any unread data, copy it to the client 70 // provided buffer. 71 if cr.offset > 0 { 72 n = copy(buf, cr.buffer[cr.offset:]) 73 if n == len(buf) { 74 cr.offset += n 75 return n, nil 76 } 77 cr.offset = 0 78 buf = buf[n:] 79 } 80 // mustRead reads from input and compares against provided slice. 81 mustRead := func(b ...byte) error { 82 for _, want := range b { 83 got, err := cr.reader.ReadByte() 84 if err == io.EOF { 85 return io.ErrUnexpectedEOF 86 } 87 if got != want { 88 if cr.debug { 89 fmt.Printf("mustread: want: %q got: %q\n", string(want), string(got)) 90 } 91 return errMalformedEncoding 92 } 93 if err != nil { 94 return err 95 } 96 } 97 return nil 98 } 99 var size int 100 for { 101 b, err := cr.reader.ReadByte() 102 if err == io.EOF { 103 err = io.ErrUnexpectedEOF 104 } 105 if err != nil { 106 cr.err = err 107 return n, cr.err 108 } 109 if b == '\r' { // \r\n denotes end of size. 110 err := mustRead('\n') 111 if err != nil { 112 cr.err = err 113 return n, cr.err 114 } 115 break 116 } 117 118 // Manually deserialize the size since AWS specified 119 // the chunk size to be of variable width. In particular, 120 // a size of 16 is encoded as `10` while a size of 64 KB 121 // is `10000`. 122 switch { 123 case b >= '0' && b <= '9': 124 size = size<<4 | int(b-'0') 125 case b >= 'a' && b <= 'f': 126 size = size<<4 | int(b-('a'-10)) 127 case b >= 'A' && b <= 'F': 128 size = size<<4 | int(b-('A'-10)) 129 default: 130 if cr.debug { 131 fmt.Printf("err size: %v\n", string(b)) 132 } 133 cr.err = errMalformedEncoding 134 return n, cr.err 135 } 136 if size > maxChunkSize { 137 cr.err = errChunkTooBig 138 return n, cr.err 139 } 140 } 141 142 if cap(cr.buffer) < size { 143 cr.buffer = make([]byte, size) 144 } else { 145 cr.buffer = cr.buffer[:size] 146 } 147 148 // Now, we read the payload. 149 _, err = io.ReadFull(cr.reader, cr.buffer) 150 if err == io.EOF && size != 0 { 151 err = io.ErrUnexpectedEOF 152 } 153 if err != nil && err != io.EOF { 154 cr.err = err 155 return n, cr.err 156 } 157 158 // If the chunk size is zero we return io.EOF. As specified by AWS, 159 // only the last chunk is zero-sized. 160 if len(cr.buffer) == 0 { 161 if cr.debug { 162 fmt.Println("EOF") 163 } 164 if cr.trailers != nil { 165 err = cr.readTrailers() 166 if cr.debug { 167 fmt.Println("trailer returned:", err) 168 } 169 if err != nil { 170 cr.err = err 171 return 0, err 172 } 173 } 174 cr.err = io.EOF 175 return n, cr.err 176 } 177 // read final terminator. 178 err = mustRead('\r', '\n') 179 if err != nil && err != io.EOF { 180 cr.err = err 181 return n, cr.err 182 } 183 184 cr.offset = copy(buf, cr.buffer) 185 n += cr.offset 186 return n, err 187 } 188 189 // readTrailers will read all trailers and populate cr.trailers with actual values. 190 func (cr *s3UnsignedChunkedReader) readTrailers() error { 191 var valueBuffer bytes.Buffer 192 // Read value 193 for { 194 v, err := cr.reader.ReadByte() 195 if err != nil { 196 if err == io.EOF { 197 return io.ErrUnexpectedEOF 198 } 199 } 200 if v != '\r' { 201 valueBuffer.WriteByte(v) 202 continue 203 } 204 // Must end with \r\n\r\n 205 var tmp [3]byte 206 _, err = io.ReadFull(cr.reader, tmp[:]) 207 if err != nil { 208 if err == io.EOF { 209 return io.ErrUnexpectedEOF 210 } 211 } 212 if !bytes.Equal(tmp[:], []byte{'\n', '\r', '\n'}) { 213 if cr.debug { 214 fmt.Printf("got %q, want %q\n", string(tmp[:]), "\n\r\n") 215 } 216 return errMalformedEncoding 217 } 218 break 219 } 220 221 // Parse trailers. 222 wantTrailers := make(map[string]struct{}, len(cr.trailers)) 223 for k := range cr.trailers { 224 wantTrailers[strings.ToLower(k)] = struct{}{} 225 } 226 input := bufio.NewScanner(bytes.NewReader(valueBuffer.Bytes())) 227 for input.Scan() { 228 line := strings.TrimSpace(input.Text()) 229 if line == "" { 230 continue 231 } 232 // Find first separator. 233 idx := strings.IndexByte(line, trailerKVSeparator[0]) 234 if idx <= 0 || idx >= len(line) { 235 if cr.debug { 236 fmt.Printf("Could not find separator, got %q\n", line) 237 } 238 return errMalformedEncoding 239 } 240 key := strings.ToLower(line[:idx]) 241 value := line[idx+1:] 242 if _, ok := wantTrailers[key]; !ok { 243 if cr.debug { 244 fmt.Printf("Unknown key %q - expected on of %v\n", key, cr.trailers) 245 } 246 return errMalformedEncoding 247 } 248 cr.trailers.Set(key, value) 249 delete(wantTrailers, key) 250 } 251 252 // Check if we got all we want. 253 if len(wantTrailers) > 0 { 254 return io.ErrUnexpectedEOF 255 } 256 return nil 257 }