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  }