protocol-http 0.66.0 → 0.68.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 92d72520175c17717320ecbee394b356a159c22d825f2b4e9c5cb5ef93aa2fed
4
- data.tar.gz: d0fb84ab33b22734ef0aa72a0d1c5f3af36011106154e415aa1d9aed8f84c570
3
+ metadata.gz: a3428056fc04e13182008e3f50f457e9b63b688c887c528e4afc8606e447c620
4
+ data.tar.gz: e1c5a2b380ff917013e2cdf1e8c6bda8a4995d3850fa960bf933177ebc518295
5
5
  SHA512:
6
- metadata.gz: d4ef6e02a8dc285cf609e5c5be4b3d0d214a4fa6a13e2a270ffbe49935e4253473cfe189d854bdd7c886fee802f5bba08710afe831ef601e0ea30051292b1987
7
- data.tar.gz: 3986994c21ef7bafc0606f3b07f21678bddff510389bb4edd1111d79e10e702638002f1b5577d20cfac1a1c1a67144fc2ab0808d71ece4866dd3065b847d7a0d
6
+ metadata.gz: 6a0554e8f574add7c898db51ecccce34def388ba266c648487ef6f538c0db0d99966f2ee0c15d1462088587e29676ddf94f07531c257326442320cd2d30ab157
7
+ data.tar.gz: 4acaacb6205bc8dcdd5be3ed7a5993dff2012b7ac61ad3a8a1e6ea607c1f01b481218ef3b60efa6bc34a9a86f982077aabe9ec7bafe43b8659b9272cb1e11d6a
checksums.yaml.gz.sig CHANGED
Binary file
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ require_relative "../error"
7
+
8
+ module Protocol
9
+ module HTTP
10
+ module Header
11
+ # Represents a `range` request header.
12
+ class Range
13
+ ParseError = Class.new(Error)
14
+
15
+ TOKEN = /[!#$%&'*+\-.0-9A-Z^_`a-z|~]+/
16
+ HEADER = /\A(?<unit>#{TOKEN})=(?<ranges>.*)\z/
17
+ BYTE_RANGE = /\A(?:(?<first>\d+)-(?<last>\d*)|-(?<suffix>\d+))\z/
18
+ OTHER_RANGE = /\A[\x21-\x2B\x2D-\x7E]+\z/
19
+ SEPARATOR = /\s*,\s*/
20
+
21
+ # Represents one byte-range-spec or suffix-byte-range-spec.
22
+ ByteRange = Struct.new(:first, :last) do
23
+ # Parse one byte range.
24
+ # @parameter value [String] The byte range to parse.
25
+ # @returns [ByteRange] The parsed byte range.
26
+ def self.parse(value)
27
+ unless match = BYTE_RANGE.match(value)
28
+ raise ParseError, "Invalid byte range: #{value.inspect}"
29
+ end
30
+
31
+ if suffix = match[:suffix]
32
+ return self.new(nil, Integer(suffix))
33
+ else
34
+ first = Integer(match[:first])
35
+ last = match[:last]
36
+ last = last.empty? ? nil : Integer(last)
37
+
38
+ if last && last < first
39
+ raise ParseError, "Invalid byte range: #{value.inspect}"
40
+ end
41
+
42
+ return self.new(first, last)
43
+ end
44
+ end
45
+
46
+ # Resolve this byte range against the selected representation size.
47
+ # @parameter size [Integer] The size of the selected representation.
48
+ # @returns [::Range | Nil] The resolved range, or `nil` when it is unsatisfiable.
49
+ def resolve(size)
50
+ if first
51
+ if first < size
52
+ return first..[last || size - 1, size - 1].min
53
+ end
54
+ elsif last > 0 && size > 0
55
+ return [0, size - last].max..size - 1
56
+ end
57
+
58
+ return nil
59
+ end
60
+
61
+ # Convert this byte range to its wire representation.
62
+ # @returns [String] The serialized byte range.
63
+ def to_s
64
+ if first
65
+ "#{first}-#{last}"
66
+ else
67
+ "-#{last}"
68
+ end
69
+ end
70
+ end
71
+
72
+ # Parse a raw range header value.
73
+ # @parameter value [String] The raw header value.
74
+ # @returns [Range] The parsed range header.
75
+ def self.parse(value)
76
+ unless match = HEADER.match(value)
77
+ raise ParseError, "Invalid range header: #{value.inspect}"
78
+ end
79
+
80
+ unit = match[:unit].downcase
81
+ ranges = match[:ranges].split(SEPARATOR, -1)
82
+
83
+ if ranges.empty? || ranges.any?(&:empty?)
84
+ raise ParseError, "Invalid range set: #{match[:ranges].inspect}"
85
+ end
86
+
87
+ if unit == "bytes"
88
+ ranges.map!{|range| ByteRange.parse(range)}
89
+ elsif ranges.any?{|range| !OTHER_RANGE.match?(range)}
90
+ raise ParseError, "Invalid range set: #{match[:ranges].inspect}"
91
+ end
92
+
93
+ return self.new(unit, ranges)
94
+ end
95
+
96
+ # Coerce a value into a range header.
97
+ # @parameter value [Object] The value to coerce.
98
+ # @returns [Range] The parsed range header.
99
+ def self.coerce(value)
100
+ self.parse(value.to_s)
101
+ end
102
+
103
+ # Initialize a range header.
104
+ # @parameter unit [String] The range unit.
105
+ # @parameter ranges [Array] The range specifiers.
106
+ def initialize(unit, ranges)
107
+ @unit = unit
108
+ @ranges = ranges
109
+ end
110
+
111
+ # @attribute [String] The range unit.
112
+ attr :unit
113
+
114
+ # @attribute [Array] The range specifiers.
115
+ attr :ranges
116
+
117
+ # Whether this header contains byte ranges.
118
+ # @returns [Boolean] Whether the range unit is `bytes`.
119
+ def bytes?
120
+ @unit == "bytes"
121
+ end
122
+
123
+ # Resolve all byte ranges against the selected representation size.
124
+ # @parameter size [Integer] The size of the selected representation.
125
+ # @returns [Array(::Range)] The satisfiable byte ranges.
126
+ def resolve(size)
127
+ unless bytes?
128
+ raise ArgumentError, "Cannot resolve #{@unit.inspect} ranges as byte ranges!"
129
+ end
130
+
131
+ size = Integer(size)
132
+ raise ArgumentError, "Size must not be negative!" if size < 0
133
+
134
+ @ranges.filter_map{|range| range.resolve(size)}
135
+ end
136
+
137
+ # Combine another raw range header value with this one.
138
+ # @parameter value [String] The raw range header value.
139
+ def <<(value)
140
+ other = self.class.parse(value)
141
+
142
+ unless other.unit == @unit
143
+ raise ParseError, "Cannot combine range units: #{@unit.inspect} and #{other.unit.inspect}"
144
+ end
145
+
146
+ @ranges.concat(other.ranges)
147
+
148
+ return self
149
+ end
150
+
151
+ # Convert this header to its wire representation.
152
+ # @returns [String] The serialized range header.
153
+ def to_s
154
+ "#{@unit}=#{@ranges.join(",")}"
155
+ end
156
+
157
+ # Whether this header is acceptable in HTTP trailers.
158
+ # @returns [Boolean] `false`, as range headers apply to a selected representation.
159
+ def self.trailer?
160
+ false
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
@@ -18,6 +18,7 @@ require_relative "header/vary"
18
18
  require_relative "header/authorization"
19
19
  require_relative "header/date"
20
20
  require_relative "header/priority"
21
+ require_relative "header/range"
21
22
  require_relative "header/trailer"
22
23
  require_relative "header/server_timing"
23
24
  require_relative "header/digest"
@@ -347,7 +348,7 @@ module Protocol
347
348
  "host" => false,
348
349
  "location" => false,
349
350
  "max-forwards" => false,
350
- "range" => false,
351
+ "range" => Header::Range,
351
352
  "referer" => false,
352
353
  "retry-after" => false,
353
354
  "server" => false,
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ module Protocol
7
+ module HTTP
8
+ # HTTP status codes and their human-readable descriptions.
9
+ module Status
10
+ # Human-readable descriptions for registered and conventional HTTP status codes.
11
+ DESCRIPTIONS = {
12
+ 100 => "Continue",
13
+ 101 => "Switching Protocols",
14
+ 102 => "Processing",
15
+ 103 => "Early Hints",
16
+
17
+ 200 => "OK",
18
+ 201 => "Created",
19
+ 202 => "Accepted",
20
+ 203 => "Non-Authoritative Information",
21
+ 204 => "No Content",
22
+ 205 => "Reset Content",
23
+ 206 => "Partial Content",
24
+ 207 => "Multi-Status",
25
+ 208 => "Already Reported",
26
+ 226 => "IM Used",
27
+
28
+ 300 => "Multiple Choices",
29
+ 301 => "Moved Permanently",
30
+ 302 => "Found",
31
+ 303 => "See Other",
32
+ 304 => "Not Modified",
33
+ 305 => "Use Proxy",
34
+ 306 => "Switch Proxy",
35
+ 307 => "Temporary Redirect",
36
+ 308 => "Permanent Redirect",
37
+
38
+ 400 => "Bad Request",
39
+ 401 => "Unauthorized",
40
+ 402 => "Payment Required",
41
+ 403 => "Forbidden",
42
+ 404 => "Not Found",
43
+ 405 => "Method Not Allowed",
44
+ 406 => "Not Acceptable",
45
+ 407 => "Proxy Authentication Required",
46
+ 408 => "Request Timeout",
47
+ 409 => "Conflict",
48
+ 410 => "Gone",
49
+ 411 => "Length Required",
50
+ 412 => "Precondition Failed",
51
+ 413 => "Content Too Large",
52
+ 414 => "URI Too Long",
53
+ 415 => "Unsupported Media Type",
54
+ 416 => "Range Not Satisfiable",
55
+ 417 => "Expectation Failed",
56
+ 418 => "I'm a Teapot",
57
+ 421 => "Misdirected Request",
58
+ 422 => "Unprocessable Content",
59
+ 423 => "Locked",
60
+ 424 => "Failed Dependency",
61
+ 425 => "Too Early",
62
+ 426 => "Upgrade Required",
63
+ 428 => "Precondition Required",
64
+ 429 => "Too Many Requests",
65
+ 431 => "Request Header Fields Too Large",
66
+ 451 => "Unavailable For Legal Reasons",
67
+
68
+ 500 => "Internal Server Error",
69
+ 501 => "Not Implemented",
70
+ 502 => "Bad Gateway",
71
+ 503 => "Service Unavailable",
72
+ 504 => "Gateway Timeout",
73
+ 505 => "HTTP Version Not Supported",
74
+ 506 => "Variant Also Negotiates",
75
+ 507 => "Insufficient Storage",
76
+ 508 => "Loop Detected",
77
+ 510 => "Not Extended",
78
+ 511 => "Network Authentication Required",
79
+ }.freeze
80
+
81
+ # Look up the human-readable description for a status code.
82
+ # @parameter code [Integer] The HTTP status code.
83
+ # @returns [String | Nil] The standard description, if known.
84
+ def self.description(code)
85
+ return DESCRIPTIONS[code]
86
+ end
87
+ end
88
+ end
89
+ end
@@ -5,6 +5,6 @@
5
5
 
6
6
  module Protocol
7
7
  module HTTP
8
- VERSION = "0.66.0"
8
+ VERSION = "0.68.0"
9
9
  end
10
10
  end
data/lib/protocol/http.rb CHANGED
@@ -5,6 +5,7 @@
5
5
 
6
6
  require_relative "http/version"
7
7
 
8
+ require_relative "http/status"
8
9
  require_relative "http/headers"
9
10
  require_relative "http/request"
10
11
  require_relative "http/response"
data/readme.md CHANGED
@@ -30,6 +30,14 @@ Please see the [project documentation](https://socketry.github.io/protocol-http/
30
30
 
31
31
  Please see the [project releases](https://socketry.github.io/protocol-http/releases/index) for all releases.
32
32
 
33
+ ### v0.68.0
34
+
35
+ - Add HTTP status descriptions.
36
+
37
+ ### v0.67.0
38
+
39
+ - Parse and resolve HTTP `Range` header values according to the default headers policy.
40
+
33
41
  ### v0.66.0
34
42
 
35
43
  - Introduce `Protocol::HTTP::RemoteError` for remote endpoint failures where application processing may have occurred.
@@ -64,16 +72,6 @@ Please see the [project releases](https://socketry.github.io/protocol-http/relea
64
72
  - Introduce `Protocol::HTTP::Middleware.load` method for loading middleware applications from files.
65
73
  - Prevent `ZLib::BufError` when deflating empty chunks by skipping deflation for empty chunks.
66
74
 
67
- ### v0.58.1
68
-
69
- - `Protocol::HTTP::DuplicateHeaderError` now includes the existing and new values for better debugging.
70
-
71
- ### v0.58.0
72
-
73
- - Move trailer validation to `Headers#add` method to ensure all additions are checked at the time of addition as this is a hard requirement.
74
- - Introduce `Headers#header` method to enumerate only the main headers, excluding trailers. This can be used after invoking `Headers#trailer!` to avoid race conditions.
75
- - Fix `Headers#to_h` so that indexed headers are not left in an inconsistent state if errors occur during processing.
76
-
77
75
  ## See Also
78
76
 
79
77
  - [protocol-http1](https://github.com/socketry/protocol-http1) — HTTP/1 client/server implementation using this
data/releases.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Releases
2
2
 
3
+ ## v0.68.0
4
+
5
+ - Add HTTP status descriptions.
6
+
7
+ ## v0.67.0
8
+
9
+ - Parse and resolve HTTP `Range` header values according to the default headers policy.
10
+
3
11
  ## v0.66.0
4
12
 
5
13
  - Introduce `Protocol::HTTP::RemoteError` for remote endpoint failures where application processing may have occurred.
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: protocol-http
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.66.0
4
+ version: 0.68.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -97,6 +97,7 @@ files:
97
97
  - lib/protocol/http/header/generic.rb
98
98
  - lib/protocol/http/header/multiple.rb
99
99
  - lib/protocol/http/header/priority.rb
100
+ - lib/protocol/http/header/range.rb
100
101
  - lib/protocol/http/header/server_timing.rb
101
102
  - lib/protocol/http/header/set_cookie.rb
102
103
  - lib/protocol/http/header/split.rb
@@ -112,6 +113,7 @@ files:
112
113
  - lib/protocol/http/quoted_string.rb
113
114
  - lib/protocol/http/request.rb
114
115
  - lib/protocol/http/response.rb
116
+ - lib/protocol/http/status.rb
115
117
  - lib/protocol/http/version.rb
116
118
  - license.md
117
119
  - readme.md
metadata.gz.sig CHANGED
Binary file