| 
									
										
										
										
											2021-04-19 03:41:13 +08:00
										 |  |  | // Copyright (c) 2015-2021 MinIO, Inc.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // This file is part of MinIO Object Storage stack
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // This program is free software: you can redistribute it and/or modify
 | 
					
						
							|  |  |  | // it under the terms of the GNU Affero General Public License as published by
 | 
					
						
							|  |  |  | // the Free Software Foundation, either version 3 of the License, or
 | 
					
						
							|  |  |  | // (at your option) any later version.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // This program is distributed in the hope that it will be useful
 | 
					
						
							|  |  |  | // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
					
						
							|  |  |  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
					
						
							|  |  |  | // GNU Affero General Public License for more details.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // You should have received a copy of the GNU Affero General Public License
 | 
					
						
							|  |  |  | // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
					
						
							| 
									
										
										
										
											2015-03-11 16:01:49 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-19 07:23:42 +08:00
										 |  |  | package cmd | 
					
						
							| 
									
										
										
										
											2015-03-11 16:01:49 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2015-03-11 16:01:49 +08:00
										 |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"strconv" | 
					
						
							|  |  |  | 	"strings" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const ( | 
					
						
							| 
									
										
										
										
											2016-07-07 03:50:24 +08:00
										 |  |  | 	byteRangePrefix = "bytes=" | 
					
						
							| 
									
										
										
										
											2015-03-11 16:01:49 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  | // HTTPRangeSpec represents a range specification as supported by S3 GET
 | 
					
						
							|  |  |  | // object request.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // Case 1: Not present -> represented by a nil RangeSpec
 | 
					
						
							|  |  |  | // Case 2: bytes=1-10 (absolute start and end offsets) -> RangeSpec{false, 1, 10}
 | 
					
						
							|  |  |  | // Case 3: bytes=10- (absolute start offset with end offset unspecified) -> RangeSpec{false, 10, -1}
 | 
					
						
							|  |  |  | // Case 4: bytes=-30 (suffix length specification) -> RangeSpec{true, -30, -1}
 | 
					
						
							|  |  |  | type HTTPRangeSpec struct { | 
					
						
							|  |  |  | 	// Does the range spec refer to a suffix of the object?
 | 
					
						
							|  |  |  | 	IsSuffixLength bool | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Start and end offset specified in range spec
 | 
					
						
							|  |  |  | 	Start, End int64 | 
					
						
							| 
									
										
										
										
											2015-03-11 16:01:49 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  | // GetLength - get length of range
 | 
					
						
							|  |  |  | func (h *HTTPRangeSpec) GetLength(resourceSize int64) (rangeLength int64, err error) { | 
					
						
							|  |  |  | 	switch { | 
					
						
							|  |  |  | 	case resourceSize < 0: | 
					
						
							|  |  |  | 		return 0, errors.New("Resource size cannot be negative") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case h == nil: | 
					
						
							|  |  |  | 		rangeLength = resourceSize | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case h.IsSuffixLength: | 
					
						
							|  |  |  | 		specifiedLen := -h.Start | 
					
						
							|  |  |  | 		rangeLength = specifiedLen | 
					
						
							|  |  |  | 		if specifiedLen > resourceSize { | 
					
						
							|  |  |  | 			rangeLength = resourceSize | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case h.Start >= resourceSize: | 
					
						
							|  |  |  | 		return 0, errInvalidRange | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case h.End > -1: | 
					
						
							|  |  |  | 		end := h.End | 
					
						
							|  |  |  | 		if resourceSize <= end { | 
					
						
							|  |  |  | 			end = resourceSize - 1 | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		rangeLength = end - h.Start + 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case h.End == -1: | 
					
						
							|  |  |  | 		rangeLength = resourceSize - h.Start | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		return 0, errors.New("Unexpected range specification case") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return rangeLength, nil | 
					
						
							| 
									
										
										
										
											2015-03-11 16:01:49 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  | // GetOffsetLength computes the start offset and length of the range
 | 
					
						
							|  |  |  | // given the size of the resource
 | 
					
						
							|  |  |  | func (h *HTTPRangeSpec) GetOffsetLength(resourceSize int64) (start, length int64, err error) { | 
					
						
							|  |  |  | 	if h == nil { | 
					
						
							|  |  |  | 		// No range specified, implies whole object.
 | 
					
						
							|  |  |  | 		return 0, resourceSize, nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	length, err = h.GetLength(resourceSize) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return 0, 0, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	start = h.Start | 
					
						
							|  |  |  | 	if h.IsSuffixLength { | 
					
						
							|  |  |  | 		start = resourceSize + h.Start | 
					
						
							|  |  |  | 		if start < 0 { | 
					
						
							|  |  |  | 			start = 0 | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return start, length, nil | 
					
						
							| 
									
										
										
										
											2015-03-11 16:01:49 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  | // Parse a HTTP range header value into a HTTPRangeSpec
 | 
					
						
							|  |  |  | func parseRequestRangeSpec(rangeString string) (hrange *HTTPRangeSpec, err error) { | 
					
						
							| 
									
										
										
										
											2016-07-07 03:50:24 +08:00
										 |  |  | 	// Return error if given range string doesn't start with byte range prefix.
 | 
					
						
							|  |  |  | 	if !strings.HasPrefix(rangeString, byteRangePrefix) { | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("'%s' does not start with '%s'", rangeString, byteRangePrefix) | 
					
						
							| 
									
										
										
										
											2015-03-11 16:01:49 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2015-03-26 06:49:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-07 03:50:24 +08:00
										 |  |  | 	// Trim byte range prefix.
 | 
					
						
							|  |  |  | 	byteRangeString := strings.TrimPrefix(rangeString, byteRangePrefix) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-08 06:05:18 +08:00
										 |  |  | 	// Check if range string contains delimiter '-', else return error. eg. "bytes=8"
 | 
					
						
							| 
									
										
										
										
											2016-07-07 03:50:24 +08:00
										 |  |  | 	sepIndex := strings.Index(byteRangeString, "-") | 
					
						
							|  |  |  | 	if sepIndex == -1 { | 
					
						
							| 
									
										
										
										
											2016-07-08 06:05:18 +08:00
										 |  |  | 		return nil, fmt.Errorf("'%s' does not have a valid range value", rangeString) | 
					
						
							| 
									
										
										
										
											2015-03-26 06:49:42 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-07-07 03:50:24 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-08 22:46:49 +08:00
										 |  |  | 	offsetBeginString := byteRangeString[:sepIndex] | 
					
						
							|  |  |  | 	offsetBegin := int64(-1) | 
					
						
							|  |  |  | 	// Convert offsetBeginString only if its not empty.
 | 
					
						
							|  |  |  | 	if len(offsetBeginString) > 0 { | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  | 		if offsetBeginString[0] == '+' { | 
					
						
							|  |  |  | 			return nil, fmt.Errorf("Byte position ('%s') must not have a sign", offsetBeginString) | 
					
						
							|  |  |  | 		} else if offsetBegin, err = strconv.ParseInt(offsetBeginString, 10, 64); err != nil { | 
					
						
							| 
									
										
										
										
											2016-07-08 06:05:18 +08:00
										 |  |  | 			return nil, fmt.Errorf("'%s' does not have a valid first byte position value", rangeString) | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  | 		} else if offsetBegin < 0 { | 
					
						
							|  |  |  | 			return nil, fmt.Errorf("First byte position is negative ('%d')", offsetBegin) | 
					
						
							| 
									
										
										
										
											2016-07-07 03:50:24 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2015-03-26 06:49:42 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-08 22:46:49 +08:00
										 |  |  | 	offsetEndString := byteRangeString[sepIndex+1:] | 
					
						
							|  |  |  | 	offsetEnd := int64(-1) | 
					
						
							|  |  |  | 	// Convert offsetEndString only if its not empty.
 | 
					
						
							|  |  |  | 	if len(offsetEndString) > 0 { | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  | 		if offsetEndString[0] == '+' { | 
					
						
							|  |  |  | 			return nil, fmt.Errorf("Byte position ('%s') must not have a sign", offsetEndString) | 
					
						
							|  |  |  | 		} else if offsetEnd, err = strconv.ParseInt(offsetEndString, 10, 64); err != nil { | 
					
						
							| 
									
										
										
										
											2016-07-08 06:05:18 +08:00
										 |  |  | 			return nil, fmt.Errorf("'%s' does not have a valid last byte position value", rangeString) | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  | 		} else if offsetEnd < 0 { | 
					
						
							|  |  |  | 			return nil, fmt.Errorf("Last byte position is negative ('%d')", offsetEnd) | 
					
						
							| 
									
										
										
										
											2016-07-07 03:50:24 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2015-03-26 06:49:42 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-07-07 03:50:24 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-07 01:26:40 +08:00
										 |  |  | 	switch { | 
					
						
							|  |  |  | 	case offsetBegin > -1 && offsetEnd > -1: | 
					
						
							| 
									
										
										
										
											2016-07-08 22:46:49 +08:00
										 |  |  | 		if offsetBegin > offsetEnd { | 
					
						
							| 
									
										
										
										
											2016-07-07 03:50:24 +08:00
										 |  |  | 			return nil, errInvalidRange | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  | 		return &HTTPRangeSpec{false, offsetBegin, offsetEnd}, nil | 
					
						
							| 
									
										
										
										
											2018-08-07 01:26:40 +08:00
										 |  |  | 	case offsetBegin > -1: | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  | 		return &HTTPRangeSpec{false, offsetBegin, -1}, nil | 
					
						
							| 
									
										
										
										
											2018-08-07 01:26:40 +08:00
										 |  |  | 	case offsetEnd > -1: | 
					
						
							| 
									
										
										
										
											2016-07-08 22:46:49 +08:00
										 |  |  | 		if offsetEnd == 0 { | 
					
						
							| 
									
										
										
										
											2016-07-08 06:05:18 +08:00
										 |  |  | 			return nil, errInvalidRange | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2018-09-21 10:22:09 +08:00
										 |  |  | 		return &HTTPRangeSpec{true, -offsetEnd, -1}, nil | 
					
						
							| 
									
										
										
										
											2018-08-07 01:26:40 +08:00
										 |  |  | 	default: | 
					
						
							| 
									
										
										
										
											2016-07-08 06:05:18 +08:00
										 |  |  | 		// rangeString contains first and last byte positions missing. eg. "bytes=-"
 | 
					
						
							| 
									
										
										
										
											2016-07-07 03:50:24 +08:00
										 |  |  | 		return nil, fmt.Errorf("'%s' does not have valid range value", rangeString) | 
					
						
							| 
									
										
										
										
											2015-03-26 06:49:42 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2019-12-09 05:58:04 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | // String returns stringified representation of range for a particular resource size.
 | 
					
						
							|  |  |  | func (h *HTTPRangeSpec) String(resourceSize int64) string { | 
					
						
							|  |  |  | 	if h == nil { | 
					
						
							|  |  |  | 		return "" | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	off, length, err := h.GetOffsetLength(resourceSize) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return "" | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return fmt.Sprintf("%d-%d", off, off+length-1) | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-03-09 05:58:55 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | // ToHeader returns the Range header value.
 | 
					
						
							|  |  |  | func (h *HTTPRangeSpec) ToHeader() (string, error) { | 
					
						
							|  |  |  | 	if h == nil { | 
					
						
							|  |  |  | 		return "", nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	start := strconv.Itoa(int(h.Start)) | 
					
						
							|  |  |  | 	end := strconv.Itoa(int(h.End)) | 
					
						
							|  |  |  | 	switch { | 
					
						
							|  |  |  | 	case h.Start >= 0 && h.End >= 0: | 
					
						
							|  |  |  | 		if h.Start > h.End { | 
					
						
							|  |  |  | 			return "", errInvalidRange | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case h.IsSuffixLength: | 
					
						
							|  |  |  | 		end = strconv.Itoa(int(h.Start * -1)) | 
					
						
							|  |  |  | 		start = "" | 
					
						
							|  |  |  | 	case h.Start > -1: | 
					
						
							|  |  |  | 		end = "" | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		return "", fmt.Errorf("does not have valid range value") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return fmt.Sprintf("bytes=%s-%s", start, end), nil | 
					
						
							|  |  |  | } |