protocol-http 0.53.0 → 0.55.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.
data/releases.md CHANGED
@@ -1,5 +1,63 @@
1
1
  # Releases
2
2
 
3
+ ## v0.55.0
4
+
5
+ - **Breaking**: Move `Protocol::HTTP::Header::QuotedString` to `Protocol::HTTP::QuotedString` for better reusability.
6
+ - **Breaking**: Handle cookie key/value pairs using `QuotedString` as per RFC 6265.
7
+ - Don't use URL encoding for cookie key/value.
8
+ - **Breaking**: Remove `Protocol::HTTP::URL` and `Protocol::HTTP::Reference` – replaced by `Protocol::URL` gem.
9
+ - `Protocol::HTTP::URL` -\> `Protocol::URL::Encoding`.
10
+ - `Protocol::HTTP::Reference` -\> `Protocol::URL::Reference`.
11
+
12
+ ## v0.54.0
13
+
14
+ - Introduce rich support for `Header::Digest`, `Header::ServerTiming`, `Header::TE`, `Header::Trailer` and `Header::TransferEncoding`.
15
+
16
+ ### Improved HTTP Trailer Security
17
+
18
+ This release introduces significant security improvements for HTTP trailer handling, addressing potential HTTP request smuggling vulnerabilities by implementing a restrictive-by-default policy for trailer headers.
19
+
20
+ - **Security-by-default**: HTTP trailers are now validated and restricted by default to prevent HTTP request smuggling attacks.
21
+ - Only safe headers are permitted in trailers:
22
+ - `date` - Response generation timestamps (safe metadata)
23
+ - `digest` - Content integrity verification (safe metadata)
24
+ - `etag` - Cache validation tags (safe metadata)
25
+ - `server-timing` - Performance metrics (safe metadata)
26
+ - All other trailers are ignored by default.
27
+
28
+ If you are using this library for gRPC, you will need to use a custom policy to allow the `grpc-status` and `grpc-message` trailers:
29
+
30
+ ``` ruby
31
+ module GRPCStatus
32
+ def self.new(value)
33
+ Integer(value)
34
+ end
35
+
36
+ def self.trailer?
37
+ true
38
+ end
39
+ end
40
+
41
+ module GRPCMessage
42
+ def self.new(value)
43
+ value
44
+ end
45
+
46
+ def self.trailer?
47
+ true
48
+ end
49
+ end
50
+
51
+ GRPC_POLICY = Protocol::HTTP::Headers::POLICY.dup
52
+ GRPC_POLICY['grpc-status'] = GRPCStatus
53
+ GRPC_POLICY['grpc-message'] = GRPCMessage
54
+
55
+ # Reinterpret the headers using the new policy:
56
+ response.headers.policy = GRPC_POLICY
57
+ response.headers['grpc-status'] # => 0
58
+ response.headers['grpc-message'] # => "OK"
59
+ ```
60
+
3
61
  ## v0.53.0
4
62
 
5
63
  - Improve consistency of Body `#inspect`.
@@ -76,3 +134,160 @@ def call(request)
76
134
  # ...
77
135
  end
78
136
  ```
137
+
138
+ ## v0.29.0
139
+
140
+ - Introduce `rewind` and `rewindable?` methods for body rewinding capabilities.
141
+ - Add support for output buffer in `read_partial`/`readpartial` methods.
142
+ - `Reader#buffered!` now returns `self` for method chaining.
143
+
144
+ ## v0.28.0
145
+
146
+ - Add convenient `Reader#buffered!` method to buffer the body.
147
+ - Modernize gem infrastructure with RuboCop integration.
148
+
149
+ ## v0.27.0
150
+
151
+ - Expand stream interface to support `gets`/`puts` operations.
152
+ - Skip empty key/value pairs in header processing.
153
+ - Prefer lowercase method names for consistency.
154
+ - Add `as_json` support to avoid default Rails implementation.
155
+ - Use `@callback` to track invocation state.
156
+ - Drop `base64` gem dependency.
157
+
158
+ ## v0.26.0
159
+
160
+ - Prefer connection `close` over `keep-alive` when both are present.
161
+ - Add support for `#readpartial` method.
162
+ - Add `base64` dependency.
163
+
164
+ ## v0.25.0
165
+
166
+ - Introduce explicit support for informational responses (1xx status codes).
167
+ - Add `cache-control` support for `must-revalidate`, `proxy-revalidate`, and `s-maxage` directives.
168
+ - Add `#strong_match?` and `#weak_match?` methods to `ETags` header.
169
+ - Fix `last-modified`, `if-modified-since` and `if-unmodified-since` headers to use proper `Date` parsing.
170
+ - Improve date/expires header parsing.
171
+ - Add tests for `Stream#close_read`.
172
+ - Check if input is closed before raising `IOError`.
173
+ - Ensure saved files truncate existing file by default.
174
+
175
+ ## v0.24.0
176
+
177
+ - Add output stream `#<<` as alias for `#write`.
178
+ - Add support for `Headers#include?` and `#key?` methods.
179
+ - Fix URL unescape functionality.
180
+ - Fix cookie parsing issues.
181
+ - Fix superclass mismatch in `Protocol::HTTP::Middleware::Builder`.
182
+ - Allow trailers without explicit `trailer` header.
183
+ - Fix cookie handling and Ruby 2 keyword arguments.
184
+
185
+ ## v0.23.0
186
+
187
+ - Improve argument handling.
188
+ - Rename `path` parameter to `target` to better match RFCs.
189
+
190
+ ## v0.22.0
191
+
192
+ - Rename `trailers` to `trailer` for consistency.
193
+
194
+ ## v0.21.0
195
+
196
+ - Streaming interface improvements.
197
+ - Rename `Streamable` to `Completable`.
198
+
199
+ ## v0.20.0
200
+
201
+ - Improve `Authorization` header implementation.
202
+
203
+ ## v0.19.0
204
+
205
+ - Expose `Body#ready?` for more efficient response handling.
206
+
207
+ ## v0.18.0
208
+
209
+ - Add `#trailers` method which enumerates trailers without marking tail.
210
+ - Don't clear trailers in `#dup`, move functionality to `flatten!`.
211
+ - All requests and responses must have mutable headers instance.
212
+
213
+ ## v0.17.0
214
+
215
+ - Remove deferred headers due to complexity.
216
+ - Remove deprecated `Headers#slice!`.
217
+ - Add support for static, dynamic and streaming content to `cache-control` model.
218
+ - Initial support for trailers.
219
+ - Add support for `Response#not_modified?`.
220
+
221
+ ## v0.16.0
222
+
223
+ - Add support for `if-match` and `if-none-match` headers.
224
+ - Revert `Request#target` change for HTTP/2 compatibility.
225
+
226
+ ## v0.15.0
227
+
228
+ - Prefer `Request#target` over `Request#path`.
229
+ - Add body implementation to support HEAD requests.
230
+ - Add support for computing digest on buffered body.
231
+ - Add `Headers#set(key, value)` to replace existing values.
232
+ - Add support for `vary` header.
233
+ - Add support for `no-cache` & `no-store` cache directives.
234
+
235
+ ## v0.14.0
236
+
237
+ - Add `Cacheable` body for buffering and caching responses.
238
+ - Add support for `cache-control` header.
239
+
240
+ ## v0.13.0
241
+
242
+ - Add support for `connection` header.
243
+ - Fix handling of keyword arguments.
244
+
245
+ ## v0.12.0
246
+
247
+ - Improved handling of `cookie` header.
248
+ - Add `Headers#clear` method.
249
+
250
+ ## v0.11.0
251
+
252
+ - Ensure `Body#call` invokes `stream.close` when done.
253
+
254
+ ## v0.10.0
255
+
256
+ - Allow user to specify size for character devices.
257
+
258
+ ## v0.9.1
259
+
260
+ - Add support for `authorization` header.
261
+
262
+ ## v0.8.0
263
+
264
+ - Remove `reason` from `Response`.
265
+
266
+ ## v0.7.0
267
+
268
+ - Explicit path handling in `Reference#with`.
269
+
270
+ ## v0.6.0
271
+
272
+ - Initial version with basic HTTP protocol support.
273
+
274
+ ## v0.5.1
275
+
276
+ - Fix path splitting behavior when path is empty.
277
+ - Add `connect` method.
278
+ - Support protocol in `[]` constructor.
279
+ - Incorporate middleware functionality.
280
+
281
+ ## v0.4.0
282
+
283
+ - Add `Request`, `Response` and `Body` classes from `async-http`.
284
+ - Allow deletion of non-existent header fields.
285
+
286
+ ## v0.3.0
287
+
288
+ - **Initial release** of `protocol-http` gem.
289
+ - Initial implementation of HTTP/2 flow control.
290
+ - Support for connection preface and settings frames.
291
+ - Initial headers support.
292
+ - Implementation of `Connection`, `Client` & `Server` classes.
293
+ - HTTP/2 protocol framing and headers.
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,20 +1,20 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: protocol-http
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.53.0
4
+ version: 0.55.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
8
  - Thomas Morgan
9
9
  - Bruno Sutic
10
10
  - Herrick Fang
11
+ - William T. Nelson
11
12
  - Bryan Powell
12
13
  - Dan Olson
13
14
  - Earlopain
14
15
  - Genki Takiuchi
15
16
  - Marcelo Junior
16
17
  - Olle Jonsson
17
- - William T. Nelson
18
18
  - Yuta Iwama
19
19
  bindir: bin
20
20
  cert_chain:
@@ -55,6 +55,7 @@ extra_rdoc_files: []
55
55
  files:
56
56
  - context/design-overview.md
57
57
  - context/getting-started.md
58
+ - context/headers.md
58
59
  - context/hypertext-references.md
59
60
  - context/index.yaml
60
61
  - context/message-body.md
@@ -90,22 +91,25 @@ files:
90
91
  - lib/protocol/http/header/connection.rb
91
92
  - lib/protocol/http/header/cookie.rb
92
93
  - lib/protocol/http/header/date.rb
94
+ - lib/protocol/http/header/digest.rb
93
95
  - lib/protocol/http/header/etag.rb
94
96
  - lib/protocol/http/header/etags.rb
95
97
  - lib/protocol/http/header/multiple.rb
96
98
  - lib/protocol/http/header/priority.rb
97
- - lib/protocol/http/header/quoted_string.rb
99
+ - lib/protocol/http/header/server_timing.rb
98
100
  - lib/protocol/http/header/split.rb
101
+ - lib/protocol/http/header/te.rb
102
+ - lib/protocol/http/header/trailer.rb
103
+ - lib/protocol/http/header/transfer_encoding.rb
99
104
  - lib/protocol/http/header/vary.rb
100
105
  - lib/protocol/http/headers.rb
101
106
  - lib/protocol/http/methods.rb
102
107
  - lib/protocol/http/middleware.rb
103
108
  - lib/protocol/http/middleware/builder.rb
104
109
  - lib/protocol/http/peer.rb
105
- - lib/protocol/http/reference.rb
110
+ - lib/protocol/http/quoted_string.rb
106
111
  - lib/protocol/http/request.rb
107
112
  - lib/protocol/http/response.rb
108
- - lib/protocol/http/url.rb
109
113
  - lib/protocol/http/version.rb
110
114
  - license.md
111
115
  - readme.md
@@ -130,7 +134,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
130
134
  - !ruby/object:Gem::Version
131
135
  version: '0'
132
136
  requirements: []
133
- rubygems_version: 3.6.9
137
+ rubygems_version: 3.7.2
134
138
  specification_version: 4
135
139
  summary: Provides abstractions to handle HTTP protocols.
136
140
  test_files: []
metadata.gz.sig CHANGED
@@ -1,3 +1,3 @@
1
- ��<�!/3A�鶶R�����W��{�Q��9 ++VH�3HDɖ\>9�\�k�� $�Ͳ>0z0�'&�XW7؆��EhJ����TiO��sI����J֥�:18}O ;�ý�s�k��ڂ|���������;���+ɤ��JuaY�0�
2
- }�?������ ��r9h��py˨��p���Y��O���-�ZT�%8���O��b@�eQ�Ku[1��� `��-��L��̄-�Jq��S*���2mՅ�6�x�"0 ��'���h 1��e�lrՆ�iSh,��
3
- �鸛 *G����^�>8?VZ3L����msP�����.D�5U=u�aEڕچ�
1
+ ����&1>[�.�]�k��iտ �GZ^��a��Ak��8�-�$\t�:&�7Ն���=���@r~ܔ�ʌ3b͐�) ��:�5���—�c��kgV�~z����2$cog���O�
2
+ K��\�үm�������� ���J��{B��Y��������w�n����c@�>O1י\�4[�"��p�wlX�0�b��iT�u�2���Fݴ��Y�L:ȼIs�C��{ҙKK-'��x"�����@3eͦa1x�EЋ##�m΀���p�� F@E,Yu;�J0�n�P�ц�?�S��mh�c=�:�Z��%��:����Cus�uK�
3
+ 0��ç�4�\�a� {��8o�G�Z
@@ -1,49 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Released under the MIT License.
4
- # Copyright, 2025, by Samuel Williams.
5
-
6
- module Protocol
7
- module HTTP
8
- module Header
9
- # According to https://tools.ietf.org/html/rfc7231#appendix-C
10
- TOKEN = /[!#$%&'*+\-.^_`|~0-9A-Z]+/i
11
-
12
- QUOTED_STRING = /"(?:.(?!(?<!\\)"))*.?"/
13
-
14
- # https://tools.ietf.org/html/rfc7231#section-5.3.1
15
- QVALUE = /0(\.[0-9]{0,3})?|1(\.[0]{0,3})?/
16
-
17
- # Handling of HTTP quoted strings.
18
- module QuotedString
19
- # Unquote a "quoted-string" value according to <https://tools.ietf.org/html/rfc7230#section-3.2.6>. It should already match the QUOTED_STRING pattern above by the parser.
20
- def self.unquote(value, normalize_whitespace = true)
21
- value = value[1...-1]
22
-
23
- value.gsub!(/\\(.)/, '\1')
24
-
25
- if normalize_whitespace
26
- # LWS = [CRLF] 1*( SP | HT )
27
- value.gsub!(/[\r\n]+\s+/, " ")
28
- end
29
-
30
- return value
31
- end
32
-
33
- QUOTES_REQUIRED = /[()<>@,;:\\"\/\[\]?={} \t]/
34
-
35
- # Quote a string for HTTP header values if required.
36
- #
37
- # @raises [ArgumentError] if the value contains invalid characters like control characters or newlines.
38
- def self.quote(value, force = false)
39
- # Check if quoting is required:
40
- if value =~ QUOTES_REQUIRED or force
41
- "\"#{value.gsub(/["\\]/, '\\\\\0')}\""
42
- else
43
- value
44
- end
45
- end
46
- end
47
- end
48
- end
49
- end
@@ -1,253 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Released under the MIT License.
4
- # Copyright, 2018-2025, by Samuel Williams.
5
-
6
- require_relative "url"
7
-
8
- module Protocol
9
- module HTTP
10
- # A relative reference, excluding any authority. The path part of an HTTP request.
11
- class Reference
12
- include Comparable
13
-
14
- # Generate a reference from a path and user parameters. The path may contain a `#fragment` or `?query=parameters`.
15
- def self.parse(path = "/", parameters = nil)
16
- base, fragment = path.split("#", 2)
17
- path, query = base.split("?", 2)
18
-
19
- self.new(path, query, fragment, parameters)
20
- end
21
-
22
- # Initialize the reference.
23
- #
24
- # @parameter path [String] The path component, e.g. `/foo/bar/index.html`.
25
- # @parameter query [String | Nil] The un-parsed query string, e.g. 'x=10&y=20'.
26
- # @parameter fragment [String | Nil] The fragment, the part after the '#'.
27
- # @parameter parameters [Hash | Nil] User supplied parameters that will be appended to the query part.
28
- def initialize(path = "/", query = nil, fragment = nil, parameters = nil)
29
- @path = path
30
- @query = query
31
- @fragment = fragment
32
- @parameters = parameters
33
- end
34
-
35
- # @attribute [String] The path component, e.g. `/foo/bar/index.html`.
36
- attr_accessor :path
37
-
38
- # @attribute [String] The un-parsed query string, e.g. 'x=10&y=20'.
39
- attr_accessor :query
40
-
41
- # @attribute [String] The fragment, the part after the '#'.
42
- attr_accessor :fragment
43
-
44
- # @attribute [Hash] User supplied parameters that will be appended to the query part.
45
- attr_accessor :parameters
46
-
47
- # Freeze the reference.
48
- #
49
- # @returns [Reference] The frozen reference.
50
- def freeze
51
- return self if frozen?
52
-
53
- @path.freeze
54
- @query.freeze
55
- @fragment.freeze
56
- @parameters.freeze
57
-
58
- super
59
- end
60
-
61
- # Implicit conversion to an array.
62
- #
63
- # @returns [Array] The reference as an array, `[path, query, fragment, parameters]`.
64
- def to_ary
65
- [@path, @query, @fragment, @parameters]
66
- end
67
-
68
- # Compare two references.
69
- #
70
- # @parameter other [Reference] The other reference to compare.
71
- # @returns [Integer] -1, 0, 1 if the reference is less than, equal to, or greater than the other reference.
72
- def <=> other
73
- to_ary <=> other.to_ary
74
- end
75
-
76
- # Type-cast a reference.
77
- #
78
- # @parameter reference [Reference | String] The reference to type-cast.
79
- # @returns [Reference] The type-casted reference.
80
- def self.[] reference
81
- if reference.is_a? self
82
- return reference
83
- else
84
- return self.parse(reference)
85
- end
86
- end
87
-
88
- # @returns [Boolean] Whether the reference has parameters.
89
- def parameters?
90
- @parameters and !@parameters.empty?
91
- end
92
-
93
- # @returns [Boolean] Whether the reference has a query string.
94
- def query?
95
- @query and !@query.empty?
96
- end
97
-
98
- # @returns [Boolean] Whether the reference has a fragment.
99
- def fragment?
100
- @fragment and !@fragment.empty?
101
- end
102
-
103
- # Append the reference to the given buffer.
104
- def append(buffer = String.new)
105
- if query?
106
- buffer << URL.escape_path(@path) << "?" << @query
107
- buffer << "&" << URL.encode(@parameters) if parameters?
108
- else
109
- buffer << URL.escape_path(@path)
110
- buffer << "?" << URL.encode(@parameters) if parameters?
111
- end
112
-
113
- if fragment?
114
- buffer << "#" << URL.escape(@fragment)
115
- end
116
-
117
- return buffer
118
- end
119
-
120
- # Convert the reference to a string, e.g. `/foo/bar/index.html?x=10&y=20#section`
121
- #
122
- # @returns [String] The reference as a string.
123
- def to_s
124
- append
125
- end
126
-
127
- # Merges two references as specified by RFC2396, similar to `URI.join`.
128
- def + other
129
- other = self.class[other]
130
-
131
- self.class.new(
132
- expand_path(self.path, other.path, true),
133
- other.query,
134
- other.fragment,
135
- other.parameters,
136
- )
137
- end
138
-
139
- # Just the base path, without any query string, parameters or fragment.
140
- def base
141
- self.class.new(@path, nil, nil, nil)
142
- end
143
-
144
- # Update the reference with the given path, parameters and fragment.
145
- #
146
- # @parameter path [String] Append the string to this reference similar to `File.join`.
147
- # @parameter parameters [Hash] Append the parameters to this reference.
148
- # @parameter fragment [String] Set the fragment to this value.
149
- # @parameter pop [Boolean] If the path contains a trailing filename, pop the last component of the path before appending the new path.
150
- # @parameter merge [Boolean] If the parameters are specified, merge them with the existing parameters, otherwise replace them (including query string).
151
- def with(path: nil, parameters: false, fragment: @fragment, pop: false, merge: true)
152
- if merge
153
- # Merge mode: combine new parameters with existing, keep query:
154
- # parameters = (@parameters || {}).merge(parameters || {})
155
- if @parameters
156
- if parameters
157
- parameters = @parameters.merge(parameters)
158
- else
159
- parameters = @parameters
160
- end
161
- elsif !parameters
162
- parameters = @parameters
163
- end
164
-
165
- query = @query
166
- else
167
- # Replace mode: use new parameters if provided, clear query when replacing:
168
- if parameters == false
169
- # No new parameters provided, keep existing:
170
- parameters = @parameters
171
- query = @query
172
- else
173
- # New parameters provided, replace and clear query:
174
- # parameters = parameters
175
- query = nil
176
- end
177
- end
178
-
179
- if path
180
- path = expand_path(@path, path, pop)
181
- else
182
- path = @path
183
- end
184
-
185
- self.class.new(path, query, fragment, parameters)
186
- end
187
-
188
- private
189
-
190
- def split(path)
191
- if path.empty?
192
- [path]
193
- else
194
- path.split("/", -1)
195
- end
196
- end
197
-
198
- def expand_absolute_path(path, parts)
199
- parts.each do |part|
200
- if part == ".."
201
- path.pop
202
- elsif part == "."
203
- # Do nothing.
204
- else
205
- path << part
206
- end
207
- end
208
-
209
- if path.first != ""
210
- path.unshift("")
211
- end
212
- end
213
-
214
- def expand_relative_path(path, parts)
215
- parts.each do |part|
216
- if part == ".." and path.any?
217
- path.pop
218
- elsif part == "."
219
- # Do nothing.
220
- else
221
- path << part
222
- end
223
- end
224
- end
225
-
226
- # @param pop [Boolean] whether to remove the last path component of the base path, to conform to URI merging behaviour, as defined by RFC2396.
227
- def expand_path(base, relative, pop = true)
228
- if relative.start_with? "/"
229
- return relative
230
- end
231
-
232
- path = split(base)
233
-
234
- # RFC2396 Section 5.2:
235
- # 6) a) All but the last segment of the base URI's path component is
236
- # copied to the buffer. In other words, any characters after the
237
- # last (right-most) slash character, if any, are excluded.
238
- path.pop if pop or path.last == ""
239
-
240
- parts = split(relative)
241
-
242
- # Absolute path:
243
- if path.first == ""
244
- expand_absolute_path(path, parts)
245
- else
246
- expand_relative_path(path, parts)
247
- end
248
-
249
- return path.join("/")
250
- end
251
- end
252
- end
253
- end