httphdr.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. // Package httphdr implements functionality for parsing and formatting
  4. // standard HTTP headers.
  5. package httphdr
  6. import (
  7. "bytes"
  8. "strconv"
  9. "strings"
  10. )
  11. // Range is a range of bytes within some content.
  12. type Range struct {
  13. // Start is the starting offset.
  14. // It is zero if Length is negative; it must not be negative.
  15. Start int64
  16. // Length is the length of the content.
  17. // It is zero if the length extends to the end of the content.
  18. // It is negative if the length is relative to the end (e.g., last 5 bytes).
  19. Length int64
  20. }
  21. // ows is optional whitespace.
  22. const ows = " \t" // per RFC 7230, section 3.2.3
  23. // ParseRange parses a "Range" header per RFC 7233, section 3.
  24. // It only handles "Range" headers where the units is "bytes".
  25. // The "Range" header is usually only specified in GET requests.
  26. func ParseRange(hdr string) (ranges []Range, ok bool) {
  27. // Grammar per RFC 7233, appendix D:
  28. // Range = byte-ranges-specifier | other-ranges-specifier
  29. // byte-ranges-specifier = bytes-unit "=" byte-range-set
  30. // bytes-unit = "bytes"
  31. // byte-range-set =
  32. // *("," OWS)
  33. // (byte-range-spec | suffix-byte-range-spec)
  34. // *(OWS "," [OWS ( byte-range-spec | suffix-byte-range-spec )])
  35. // byte-range-spec = first-byte-pos "-" [last-byte-pos]
  36. // suffix-byte-range-spec = "-" suffix-length
  37. // We do not support other-ranges-specifier.
  38. // All other identifiers are 1*DIGIT.
  39. hdr = strings.Trim(hdr, ows) // per RFC 7230, section 3.2
  40. units, elems, hasUnits := strings.Cut(hdr, "=")
  41. elems = strings.TrimLeft(elems, ","+ows)
  42. for _, elem := range strings.Split(elems, ",") {
  43. elem = strings.Trim(elem, ows) // per RFC 7230, section 7
  44. switch {
  45. case strings.HasPrefix(elem, "-"): // i.e., "-" suffix-length
  46. n, ok := parseNumber(strings.TrimPrefix(elem, "-"))
  47. if !ok {
  48. return ranges, false
  49. }
  50. ranges = append(ranges, Range{0, -n})
  51. case strings.HasSuffix(elem, "-"): // i.e., first-byte-pos "-"
  52. n, ok := parseNumber(strings.TrimSuffix(elem, "-"))
  53. if !ok {
  54. return ranges, false
  55. }
  56. ranges = append(ranges, Range{n, 0})
  57. default: // i.e., first-byte-pos "-" last-byte-pos
  58. prefix, suffix, hasDash := strings.Cut(elem, "-")
  59. n, ok2 := parseNumber(prefix)
  60. m, ok3 := parseNumber(suffix)
  61. if !hasDash || !ok2 || !ok3 || m < n {
  62. return ranges, false
  63. }
  64. ranges = append(ranges, Range{n, m - n + 1})
  65. }
  66. }
  67. return ranges, units == "bytes" && hasUnits && len(ranges) > 0 // must see at least one element per RFC 7233, section 2.1
  68. }
  69. // FormatRange formats a "Range" header per RFC 7233, section 3.
  70. // It only handles "Range" headers where the units is "bytes".
  71. // The "Range" header is usually only specified in GET requests.
  72. func FormatRange(ranges []Range) (hdr string, ok bool) {
  73. b := []byte("bytes=")
  74. for _, r := range ranges {
  75. switch {
  76. case r.Length > 0: // i.e., first-byte-pos "-" last-byte-pos
  77. if r.Start < 0 {
  78. return string(b), false
  79. }
  80. b = strconv.AppendUint(b, uint64(r.Start), 10)
  81. b = append(b, '-')
  82. b = strconv.AppendUint(b, uint64(r.Start+r.Length-1), 10)
  83. b = append(b, ',')
  84. case r.Length == 0: // i.e., first-byte-pos "-"
  85. if r.Start < 0 {
  86. return string(b), false
  87. }
  88. b = strconv.AppendUint(b, uint64(r.Start), 10)
  89. b = append(b, '-')
  90. b = append(b, ',')
  91. case r.Length < 0: // i.e., "-" suffix-length
  92. if r.Start != 0 {
  93. return string(b), false
  94. }
  95. b = append(b, '-')
  96. b = strconv.AppendUint(b, uint64(-r.Length), 10)
  97. b = append(b, ',')
  98. default:
  99. return string(b), false
  100. }
  101. }
  102. return string(bytes.TrimRight(b, ",")), len(ranges) > 0
  103. }
  104. // ParseContentRange parses a "Content-Range" header per RFC 7233, section 4.2.
  105. // It only handles "Content-Range" headers where the units is "bytes".
  106. // The "Content-Range" header is usually only specified in HTTP responses.
  107. //
  108. // If only the completeLength is specified, then start and length are both zero.
  109. //
  110. // Otherwise, the parses the start and length and the optional completeLength,
  111. // which is -1 if unspecified. The start is non-negative and the length is positive.
  112. func ParseContentRange(hdr string) (start, length, completeLength int64, ok bool) {
  113. // Grammar per RFC 7233, appendix D:
  114. // Content-Range = byte-content-range | other-content-range
  115. // byte-content-range = bytes-unit SP (byte-range-resp | unsatisfied-range)
  116. // bytes-unit = "bytes"
  117. // byte-range-resp = byte-range "/" (complete-length | "*")
  118. // unsatisfied-range = "*/" complete-length
  119. // byte-range = first-byte-pos "-" last-byte-pos
  120. // We do not support other-content-range.
  121. // All other identifiers are 1*DIGIT.
  122. hdr = strings.Trim(hdr, ows) // per RFC 7230, section 3.2
  123. suffix, hasUnits := strings.CutPrefix(hdr, "bytes ")
  124. suffix, unsatisfied := strings.CutPrefix(suffix, "*/")
  125. if unsatisfied { // i.e., unsatisfied-range
  126. n, ok := parseNumber(suffix)
  127. if !ok {
  128. return start, length, completeLength, false
  129. }
  130. completeLength = n
  131. } else { // i.e., byte-range "/" (complete-length | "*")
  132. prefix, suffix, hasDash := strings.Cut(suffix, "-")
  133. middle, suffix, hasSlash := strings.Cut(suffix, "/")
  134. n, ok0 := parseNumber(prefix)
  135. m, ok1 := parseNumber(middle)
  136. o, ok2 := parseNumber(suffix)
  137. if suffix == "*" {
  138. o, ok2 = -1, true
  139. }
  140. if !hasDash || !hasSlash || !ok0 || !ok1 || !ok2 || m < n || (o >= 0 && o <= m) {
  141. return start, length, completeLength, false
  142. }
  143. start = n
  144. length = m - n + 1
  145. completeLength = o
  146. }
  147. return start, length, completeLength, hasUnits
  148. }
  149. // FormatContentRange parses a "Content-Range" header per RFC 7233, section 4.2.
  150. // It only handles "Content-Range" headers where the units is "bytes".
  151. // The "Content-Range" header is usually only specified in HTTP responses.
  152. //
  153. // If start and length are non-positive, then it encodes just the completeLength,
  154. // which must be a non-negative value.
  155. //
  156. // Otherwise, it encodes the start and length as a byte-range,
  157. // and optionally emits the complete length if it is non-negative.
  158. // The length must be positive (as RFC 7233 uses inclusive end offsets).
  159. func FormatContentRange(start, length, completeLength int64) (hdr string, ok bool) {
  160. b := []byte("bytes ")
  161. switch {
  162. case start <= 0 && length <= 0 && completeLength >= 0: // i.e., unsatisfied-range
  163. b = append(b, "*/"...)
  164. b = strconv.AppendUint(b, uint64(completeLength), 10)
  165. ok = true
  166. case start >= 0 && length > 0: // i.e., byte-range "/" (complete-length | "*")
  167. b = strconv.AppendUint(b, uint64(start), 10)
  168. b = append(b, '-')
  169. b = strconv.AppendUint(b, uint64(start+length-1), 10)
  170. b = append(b, '/')
  171. if completeLength >= 0 {
  172. b = strconv.AppendUint(b, uint64(completeLength), 10)
  173. ok = completeLength >= start+length && start+length > 0
  174. } else {
  175. b = append(b, '*')
  176. ok = true
  177. }
  178. }
  179. return string(b), ok
  180. }
  181. // parseNumber parses s as an unsigned decimal integer.
  182. // It parses according to the 1*DIGIT grammar, which allows leading zeros.
  183. func parseNumber(s string) (int64, bool) {
  184. suffix := strings.TrimLeft(s, "0123456789")
  185. prefix := s[:len(s)-len(suffix)]
  186. n, err := strconv.ParseInt(prefix, 10, 64)
  187. return n, suffix == "" && err == nil
  188. }