protocol-http 0.66.0 → 0.67.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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/protocol/http/header/range.rb +165 -0
- data/lib/protocol/http/headers.rb +2 -1
- data/lib/protocol/http/version.rb +1 -1
- data/readme.md +4 -6
- data/releases.md +4 -0
- data.tar.gz.sig +1 -1
- metadata +2 -1
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d934169186f9f7d3d55577ad02463aca42c88ba3e00b3273e3b6727f8c1b0e06
|
|
4
|
+
data.tar.gz: b8a13b51e95c6cda67717ad822cdc4b46828feefe6802374de6446c6d1a34f9d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d3bc1abcbef44eef390a2de0d4b17d8ebbec0a20180022f43d7e9aaeff0cf153027fec6d7e6b7968fd546dd9b8a321139f8966008acc2f7502f751af34bc5f1a
|
|
7
|
+
data.tar.gz: 733a70b325bcb1db25f9d21b2fd93be191d9cedf1a521bb386f88e028b47411c692a51f38d25f5381f840a143a9296e1e7a04d444bbf3089296b1e7bd02f9e41
|
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" =>
|
|
351
|
+
"range" => Header::Range,
|
|
351
352
|
"referer" => false,
|
|
352
353
|
"retry-after" => false,
|
|
353
354
|
"server" => false,
|
data/readme.md
CHANGED
|
@@ -30,6 +30,10 @@ 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.67.0
|
|
34
|
+
|
|
35
|
+
- Parse and resolve HTTP `Range` header values according to the default headers policy.
|
|
36
|
+
|
|
33
37
|
### v0.66.0
|
|
34
38
|
|
|
35
39
|
- Introduce `Protocol::HTTP::RemoteError` for remote endpoint failures where application processing may have occurred.
|
|
@@ -68,12 +72,6 @@ Please see the [project releases](https://socketry.github.io/protocol-http/relea
|
|
|
68
72
|
|
|
69
73
|
- `Protocol::HTTP::DuplicateHeaderError` now includes the existing and new values for better debugging.
|
|
70
74
|
|
|
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
data.tar.gz.sig
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
�h��H���R��KS�2�v���~��V��ʜ��N�J>�j�!���h2��11���ax ��'��,O�y�R�M89�&[�A�7��l�N]�/븂,�z^"F�Q�,���]����]���!�^���*��3�߽�>����[�c���y;0X���Ǚ��s^\���,4��ު
|
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.
|
|
4
|
+
version: 0.67.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
|
metadata.gz.sig
CHANGED
|
Binary file
|