protocol-http 0.50.1 → 0.51.1

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: 38991af57b4ea197e5eebf1650490f4d52d52f6bc8e17d3a101d97b0a9fd5bda
4
- data.tar.gz: d00e1f54aa78732dbd390cb3b6b59fe4dee4604acfc9fdbb2c7ec93aea56327d
3
+ metadata.gz: 7152574d762fbdc22baa9dcd2f9d22931a2da4a9b3e0727af9580b7bebe31a1e
4
+ data.tar.gz: 8a704017b5e0897741233497d779a40eba812357238105bef7910090c08cf7a3
5
5
  SHA512:
6
- metadata.gz: 4a6d9ce75c94602ee50ae9666a9e20ce55385681651ae09d50b0b7485b67d682e91916939595ec78d2920d1bd51713225937f03879d7cdac3ebfeed47df6d140
7
- data.tar.gz: c921f21ca8c833280e9eb70d6ef7c6ffe3b61b9e772205e06b5e8ecb3bf60722cda228887924826a4b045fc6a54bbf7bf1c27d96066fe8316eba4f4c01eb59d5
6
+ metadata.gz: 7bd538f9a03da7137f2f58c77777859b5a2b6b14bef08f055104427d9c029d4bc75912dcd10f8e385680c127439c78e6980436ee9200656f0a41b8d2342de0e8
7
+ data.tar.gz: 8a00a43b390046bba90ce8c523aa99cf5aff1a2fd1b3c6b3f480538ef9a201c2c9a6ef5646558feb649625498c95a224ef9ec1c1bfa78caa29f2b1a6128a7e55
checksums.yaml.gz.sig CHANGED
Binary file
@@ -21,6 +21,7 @@ module Protocol
21
21
  # The default wrappers to use for decoding content.
22
22
  DEFAULT_WRAPPERS = {
23
23
  "gzip" => Body::Inflate.method(:for),
24
+ "identity" => ->(body) { body }, # Identity means no encoding
24
25
 
25
26
  # There is no point including this:
26
27
  # 'identity' => ->(body){body},
@@ -46,15 +47,31 @@ module Protocol
46
47
 
47
48
  response = super
48
49
 
49
- if body = response.body and !body.empty? and content_encoding = response.headers.delete(CONTENT_ENCODING)
50
- # We want to unwrap all encodings
51
- content_encoding.reverse_each do |name|
52
- if wrapper = @wrappers[name]
53
- body = wrapper.call(body)
50
+ if body = response.body and !body.empty?
51
+ if content_encoding = response.headers[CONTENT_ENCODING]
52
+ # Process encodings in reverse order and remove them when they are decoded:
53
+ while name = content_encoding.last
54
+ # Look up wrapper with case-insensitive matching:
55
+ wrapper = @wrappers[name.downcase]
56
+
57
+ if wrapper
58
+ body = wrapper.call(body)
59
+ # Remove the encoding we just processed:
60
+ content_encoding.pop
61
+ else
62
+ # Unknown encoding - stop processing here:
63
+ break
64
+ end
65
+ end
66
+
67
+ # Update the response body:
68
+ response.body = body
69
+
70
+ # Remove the content-encoding header if we decoded all encodings:
71
+ if content_encoding.empty?
72
+ response.headers.delete(CONTENT_ENCODING)
54
73
  end
55
74
  end
56
-
57
- response.body = body
58
75
  end
59
76
 
60
77
  return response
@@ -12,12 +12,24 @@ module Protocol
12
12
  # Represents a body suitable for HEAD requests, in other words, a body that is empty and has a known length.
13
13
  class Head < Readable
14
14
  # Create a head body for the given body, capturing its length and then closing it.
15
- def self.for(body)
16
- head = self.new(body.length)
17
-
18
- body.close
15
+ #
16
+ # If a body is provided, the length is determined from the body, and the body is closed.
17
+ # If no body is provided, and the content length is provided, a head body is created with that length.
18
+ # This is useful for creating a head body when you only know the content length but not the actual body, which may happen in adapters for HTTP applications where the application may not provide a body for HEAD requests, but the content length is known.
19
+ #
20
+ # @parameter body [Readable | Nil] the body to create a head for.
21
+ # @parameter length [Integer | Nil] the content length of the body, if known.
22
+ # @returns [Head | Nil] the head body, or nil if the body is nil.
23
+ def self.for(body, length = nil)
24
+ if body
25
+ head = self.new(body.length)
26
+ body.close
27
+ return head
28
+ elsif length
29
+ return self.new(length)
30
+ end
19
31
 
20
- return head
32
+ return nil
21
33
  end
22
34
 
23
35
  # Initialize the head body with the given length.
@@ -47,7 +47,7 @@ module Protocol
47
47
  #
48
48
  # If buffer is given, then the read data will be placed into buffer instead of a newly created String object.
49
49
  #
50
- # @parameterlength [Integer] the amount of data to read
50
+ # @parameter length [Integer] the amount of data to read
51
51
  # @parameter buffer [String] the buffer which will receive the data
52
52
  # @returns [String] a buffer containing the data
53
53
  def read(length = nil, buffer = nil)
@@ -8,5 +8,23 @@ module Protocol
8
8
  # A generic, HTTP protocol error.
9
9
  class Error < StandardError
10
10
  end
11
+
12
+ # Represents a bad request error (as opposed to a server error).
13
+ # This is used to indicate that the request was malformed or invalid.
14
+ module BadRequest
15
+ end
16
+
17
+ # Raised when a singleton (e.g. `content-length`) header is duplicated in a request or response.
18
+ class DuplicateHeaderError < Error
19
+ include BadRequest
20
+
21
+ # @parameter key [String] The header key that was duplicated.
22
+ def initialize(key)
23
+ super("Duplicate singleton header key: #{key.inspect}")
24
+ end
25
+
26
+ # @attribute [String] key The header key that was duplicated.
27
+ attr :key
28
+ end
11
29
  end
12
30
  end
@@ -3,6 +3,8 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2018-2025, by Samuel Williams.
5
5
 
6
+ require_relative "error"
7
+
6
8
  require_relative "header/split"
7
9
  require_relative "header/multiple"
8
10
 
@@ -196,9 +198,18 @@ module Protocol
196
198
  # @parameter key [String] the header key.
197
199
  # @parameter value [String] the header value to assign.
198
200
  def add(key, value)
199
- self[key] = value
201
+ # The value MUST be a string, so we convert it to a string to prevent errors later on.
202
+ value = value.to_s
203
+
204
+ if @indexed
205
+ merge_into(@indexed, key.downcase, value)
206
+ end
207
+
208
+ @fields << [key, value]
200
209
  end
201
210
 
211
+ alias []= add
212
+
202
213
  # Set the specified header key to the specified value, replacing any existing header keys with the same name.
203
214
  #
204
215
  # @parameter key [String] the header key to replace.
@@ -212,7 +223,7 @@ module Protocol
212
223
  # Merge the headers into this instance.
213
224
  def merge!(headers)
214
225
  headers.each do |key, value|
215
- self[key] = value
226
+ self.add(key, value)
216
227
  end
217
228
 
218
229
  return self
@@ -223,31 +234,19 @@ module Protocol
223
234
  self.dup.merge!(headers)
224
235
  end
225
236
 
226
- # Append the value to the given key. Some values can be appended multiple times, others can only be set once.
227
- #
228
- # @parameter key [String] The header key.
229
- # @parameter value [String] The header value.
230
- def []= key, value
231
- if @indexed
232
- merge_into(@indexed, key.downcase, value)
233
- end
234
-
235
- @fields << [key, value]
236
- end
237
-
238
237
  # The policy for various headers, including how they are merged and normalized.
239
238
  POLICY = {
240
239
  # Headers which may only be specified once:
241
- "content-type" => false,
242
240
  "content-disposition" => false,
243
241
  "content-length" => false,
244
- "user-agent" => false,
245
- "referer" => false,
246
- "host" => false,
242
+ "content-type" => false,
247
243
  "from" => false,
244
+ "host" => false,
248
245
  "location" => false,
249
246
  "max-forwards" => false,
247
+ "referer" => false,
250
248
  "retry-after" => false,
249
+ "user-agent" => false,
251
250
 
252
251
  # Custom headers:
253
252
  "connection" => Header::Connection,
@@ -267,6 +266,7 @@ module Protocol
267
266
  "etag" => Header::ETag,
268
267
  "if-match" => Header::ETags,
269
268
  "if-none-match" => Header::ETags,
269
+ "if-range" => false,
270
270
 
271
271
  # Headers which may be specified multiple times, but which can't be concatenated:
272
272
  "www-authenticate" => Multiple,
@@ -332,7 +332,10 @@ module Protocol
332
332
  hash[key] = policy.new(value)
333
333
  end
334
334
  else
335
- # We can't merge these, we only expose the last one set.
335
+ if hash.key?(key)
336
+ raise DuplicateHeaderError, key
337
+ end
338
+
336
339
  hash[key] = value
337
340
  end
338
341
  end
@@ -5,6 +5,6 @@
5
5
 
6
6
  module Protocol
7
7
  module HTTP
8
- VERSION = "0.50.1"
8
+ VERSION = "0.51.1"
9
9
  end
10
10
  end
data/readme.md CHANGED
@@ -24,6 +24,16 @@ Please see the [project documentation](https://socketry.github.io/protocol-http/
24
24
 
25
25
  Please see the [project releases](https://socketry.github.io/protocol-http/releases/index) for all releases.
26
26
 
27
+ ### v0.51.0
28
+
29
+ - `Protocol::HTTP::Headers` now raise a `DuplicateHeaderError` when a duplicate singleton header (e.g. `content-length`) is added.
30
+ - `Protocol::HTTP::Headers#add` now coerces the value to a string when adding a header, ensuring consistent behaviour.
31
+ - `Protocol::HTTP::Body::Head.for` now accepts an optional `length` parameter, allowing it to create a head body even when the body is not provided, based on the known content length.
32
+
33
+ ### v0.50.0
34
+
35
+ - Drop support for Ruby v3.1.
36
+
27
37
  ### v0.48.0
28
38
 
29
39
  - Add support for parsing `accept`, `accept-charset`, `accept-encoding` and `accept-language` headers into structured values.
data/releases.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Releases
2
2
 
3
+ ## v0.51.0
4
+
5
+ - `Protocol::HTTP::Headers` now raise a `DuplicateHeaderError` when a duplicate singleton header (e.g. `content-length`) is added.
6
+ - `Protocol::HTTP::Headers#add` now coerces the value to a string when adding a header, ensuring consistent behaviour.
7
+ - `Protocol::HTTP::Body::Head.for` now accepts an optional `length` parameter, allowing it to create a head body even when the body is not provided, based on the known content length.
8
+
9
+ ## v0.50.0
10
+
11
+ - Drop support for Ruby v3.1.
12
+
3
13
  ## v0.48.0
4
14
 
5
15
  - Add support for parsing `accept`, `accept-charset`, `accept-encoding` and `accept-language` headers into structured values.
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.50.1
4
+ version: 0.51.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -47,7 +47,7 @@ cert_chain:
47
47
  Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
48
48
  voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
49
49
  -----END CERTIFICATE-----
50
- date: 2025-04-29 00:00:00.000000000 Z
50
+ date: 1980-01-02 00:00:00.000000000 Z
51
51
  dependencies: []
52
52
  executables: []
53
53
  extensions: []
@@ -122,7 +122,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
122
122
  - !ruby/object:Gem::Version
123
123
  version: '0'
124
124
  requirements: []
125
- rubygems_version: 3.6.2
125
+ rubygems_version: 3.6.9
126
126
  specification_version: 4
127
127
  summary: Provides abstractions to handle HTTP protocols.
128
128
  test_files: []
metadata.gz.sig CHANGED
Binary file