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.

203 lines
5.9 KiB

  1. // Copyright (c) 2015-2021 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. package cmd
  18. import (
  19. "errors"
  20. "fmt"
  21. "strconv"
  22. "strings"
  23. )
  24. const (
  25. byteRangePrefix = "bytes="
  26. )
  27. // HTTPRangeSpec represents a range specification as supported by S3 GET
  28. // object request.
  29. //
  30. // Case 1: Not present -> represented by a nil RangeSpec
  31. // Case 2: bytes=1-10 (absolute start and end offsets) -> RangeSpec{false, 1, 10}
  32. // Case 3: bytes=10- (absolute start offset with end offset unspecified) -> RangeSpec{false, 10, -1}
  33. // Case 4: bytes=-30 (suffix length specification) -> RangeSpec{true, -30, -1}
  34. type HTTPRangeSpec struct {
  35. // Does the range spec refer to a suffix of the object?
  36. IsSuffixLength bool
  37. // Start and end offset specified in range spec
  38. Start, End int64
  39. }
  40. // GetLength - get length of range
  41. func (h *HTTPRangeSpec) GetLength(resourceSize int64) (rangeLength int64, err error) {
  42. switch {
  43. case resourceSize < 0:
  44. return 0, errors.New("Resource size cannot be negative")
  45. case h == nil:
  46. rangeLength = resourceSize
  47. case h.IsSuffixLength:
  48. specifiedLen := -h.Start
  49. rangeLength = specifiedLen
  50. if specifiedLen > resourceSize {
  51. rangeLength = resourceSize
  52. }
  53. case h.Start >= resourceSize:
  54. return 0, InvalidRange{
  55. OffsetBegin: h.Start,
  56. OffsetEnd: h.End,
  57. ResourceSize: resourceSize,
  58. }
  59. case h.End > -1:
  60. end := h.End
  61. if resourceSize <= end {
  62. end = resourceSize - 1
  63. }
  64. rangeLength = end - h.Start + 1
  65. case h.End == -1:
  66. rangeLength = resourceSize - h.Start
  67. default:
  68. return 0, errors.New("Unexpected range specification case")
  69. }
  70. return rangeLength, nil
  71. }
  72. // GetOffsetLength computes the start offset and length of the range
  73. // given the size of the resource
  74. func (h *HTTPRangeSpec) GetOffsetLength(resourceSize int64) (start, length int64, err error) {
  75. if h == nil {
  76. // No range specified, implies whole object.
  77. return 0, resourceSize, nil
  78. }
  79. length, err = h.GetLength(resourceSize)
  80. if err != nil {
  81. return 0, 0, err
  82. }
  83. start = h.Start
  84. if h.IsSuffixLength {
  85. start = resourceSize + h.Start
  86. if start < 0 {
  87. start = 0
  88. }
  89. }
  90. return start, length, nil
  91. }
  92. // Parse a HTTP range header value into a HTTPRangeSpec
  93. func parseRequestRangeSpec(rangeString string) (hrange *HTTPRangeSpec, err error) {
  94. // Return error if given range string doesn't start with byte range prefix.
  95. if !strings.HasPrefix(rangeString, byteRangePrefix) {
  96. return nil, fmt.Errorf("'%s' does not start with '%s'", rangeString, byteRangePrefix)
  97. }
  98. // Trim byte range prefix.
  99. byteRangeString := strings.TrimPrefix(rangeString, byteRangePrefix)
  100. // Check if range string contains delimiter '-', else return error. eg. "bytes=8"
  101. sepIndex := strings.Index(byteRangeString, "-")
  102. if sepIndex == -1 {
  103. return nil, fmt.Errorf("'%s' does not have a valid range value", rangeString)
  104. }
  105. offsetBeginString := byteRangeString[:sepIndex]
  106. offsetBegin := int64(-1)
  107. // Convert offsetBeginString only if its not empty.
  108. if len(offsetBeginString) > 0 {
  109. if offsetBeginString[0] == '+' {
  110. return nil, fmt.Errorf("Byte position ('%s') must not have a sign", offsetBeginString)
  111. } else if offsetBegin, err = strconv.ParseInt(offsetBeginString, 10, 64); err != nil {
  112. return nil, fmt.Errorf("'%s' does not have a valid first byte position value", rangeString)
  113. } else if offsetBegin < 0 {
  114. return nil, fmt.Errorf("First byte position is negative ('%d')", offsetBegin)
  115. }
  116. }
  117. offsetEndString := byteRangeString[sepIndex+1:]
  118. offsetEnd := int64(-1)
  119. // Convert offsetEndString only if its not empty.
  120. if len(offsetEndString) > 0 {
  121. if offsetEndString[0] == '+' {
  122. return nil, fmt.Errorf("Byte position ('%s') must not have a sign", offsetEndString)
  123. } else if offsetEnd, err = strconv.ParseInt(offsetEndString, 10, 64); err != nil {
  124. return nil, fmt.Errorf("'%s' does not have a valid last byte position value", rangeString)
  125. } else if offsetEnd < 0 {
  126. return nil, fmt.Errorf("Last byte position is negative ('%d')", offsetEnd)
  127. }
  128. }
  129. switch {
  130. case offsetBegin > -1 && offsetEnd > -1:
  131. if offsetBegin > offsetEnd {
  132. return nil, errInvalidRange
  133. }
  134. return &HTTPRangeSpec{false, offsetBegin, offsetEnd}, nil
  135. case offsetBegin > -1:
  136. return &HTTPRangeSpec{false, offsetBegin, -1}, nil
  137. case offsetEnd > -1:
  138. if offsetEnd == 0 {
  139. return nil, errInvalidRange
  140. }
  141. return &HTTPRangeSpec{true, -offsetEnd, -1}, nil
  142. default:
  143. // rangeString contains first and last byte positions missing. eg. "bytes=-"
  144. return nil, fmt.Errorf("'%s' does not have valid range value", rangeString)
  145. }
  146. }
  147. // String returns stringified representation of range for a particular resource size.
  148. func (h *HTTPRangeSpec) String(resourceSize int64) string {
  149. if h == nil {
  150. return ""
  151. }
  152. off, length, err := h.GetOffsetLength(resourceSize)
  153. if err != nil {
  154. return ""
  155. }
  156. return fmt.Sprintf("%d-%d", off, off+length-1)
  157. }
  158. // ToHeader returns the Range header value.
  159. func (h *HTTPRangeSpec) ToHeader() (string, error) {
  160. if h == nil {
  161. return "", nil
  162. }
  163. start := strconv.Itoa(int(h.Start))
  164. end := strconv.Itoa(int(h.End))
  165. switch {
  166. case h.Start >= 0 && h.End >= 0:
  167. if h.Start > h.End {
  168. return "", errInvalidRange
  169. }
  170. case h.IsSuffixLength:
  171. end = strconv.Itoa(int(h.Start * -1))
  172. start = ""
  173. case h.Start > -1:
  174. end = ""
  175. default:
  176. return "", errors.New("does not have valid range value")
  177. }
  178. return fmt.Sprintf("bytes=%s-%s", start, end), nil
  179. }