protocol-http1 0.39.0 → 0.40.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: 0e973bb5fe97e0766eccc257775e2549397eb1fa8f308851e653987e90f05d66
4
- data.tar.gz: a688b1894c1eeb0dc5c0dd08eb40259b16e6e4b749d6d35939cd3468b5011393
3
+ metadata.gz: f9a61dc65b4fee0a9cca3509493160ae14b9b3268ae3134fb8e39ee8afe126df
4
+ data.tar.gz: 3e04a81cdf3d40a6a95b2cdda1d44d32521d68885a48e35592b498ec61779551
5
5
  SHA512:
6
- metadata.gz: a2fb5ee2311c5536d3d3e75e609a7a577f69854dc5449a04755ac4a0efea07d042b5d0f13b4c42563ad1d762f1ffed421889ab29f1b3392bada9e0a03e314726
7
- data.tar.gz: d9ad14658632916da00953025fac66e6644be9e1e9504d5ad7e5a2bd74577c7ea21ce2073194ff2e9034e865172b609da3f03323d52ff4176f70957f44a2cb4c
6
+ metadata.gz: bf4886e4fba31c3d001ee1edfbb6ca68390969153441a253e251d8f8ab116d28ff4b089adb82a6ec79a97465a0c190887d64dd025d090879f591ef8c90a73c3b
7
+ data.tar.gz: 37eb313331d6f6af51eab12400118662fa408200456fbaa54cd9908c63b598e95f97304f559e72c4a0459239c06938e2e5261da6ff6a39a379920f844886a3cf
checksums.yaml.gz.sig CHANGED
Binary file
@@ -15,6 +15,9 @@ module Protocol
15
15
  class Chunked < HTTP::Body::Readable
16
16
  CRLF = "\r\n"
17
17
 
18
+ # The maximum amount of body data returned by a single read.
19
+ BLOCK_SIZE = 1024 * 64
20
+
18
21
  # Initialize the chunked body.
19
22
  #
20
23
  # @parameter connection [Protocol::HTTP1::Connection] the connection to read the body from.
@@ -27,6 +30,7 @@ module Protocol
27
30
 
28
31
  @length = 0
29
32
  @count = 0
33
+ @remaining = nil
30
34
  end
31
35
 
32
36
  # @attribute [Integer] the number of chunks read so far.
@@ -69,15 +73,39 @@ module Protocol
69
73
  # @returns [String | Nil] the next chunk of data, or `nil` if the body is finished.
70
74
  # @raises [EOFError] if the connection is closed before the expected length is read.
71
75
  def read
72
- if !@finished
73
- if @connection
76
+ while !@finished
77
+ unless @connection
78
+ raise EOFError, "Connection closed before expected length was read!"
79
+ end
80
+
81
+ if @remaining
82
+ if @remaining > 0
83
+ chunk = @connection.readpartial([@remaining, BLOCK_SIZE].min)
84
+ @remaining -= chunk.bytesize
85
+ @length += chunk.bytesize
86
+
87
+ return chunk
88
+ end
89
+
90
+ terminator = @connection.read(CRLF.bytesize)
91
+
92
+ unless terminator&.bytesize == CRLF.bytesize
93
+ raise EOFError, "Connection closed before expected length was read!"
94
+ end
95
+
96
+ unless terminator == CRLF
97
+ raise BadRequest, "Invalid chunk terminator: #{terminator.inspect}"
98
+ end
99
+
100
+ @remaining = nil
101
+ @count += 1
102
+ else
74
103
  length, _extensions = @connection.read_line.split(";", 2)
75
104
 
76
105
  unless length =~ VALID_CHUNK_LENGTH
77
106
  raise BadRequest, "Invalid chunk length: #{length.inspect}"
78
107
  end
79
108
 
80
- # It is possible this line contains chunk extension, so we use `to_i` to only consider the initial integral part:
81
109
  length = Integer(length, 16)
82
110
 
83
111
  if length == 0
@@ -91,27 +119,16 @@ module Protocol
91
119
  return nil
92
120
  end
93
121
 
94
- # Read trailing CRLF:
95
- chunk = @connection.read(length + 2)
96
-
97
- if chunk.bytesize == length + 2
98
- # ...and chomp it off:
99
- chunk.chomp!(CRLF)
100
-
101
- @length += length
102
- @count += 1
103
-
104
- return chunk
105
- else
106
- # The connection has been closed before we have read the requested length:
107
- @connection.close_read
108
- @connection = nil
109
- end
122
+ @remaining = length
110
123
  end
111
-
112
- # If the connection has been closed before we have read the final chunk, raise an error:
113
- raise EOFError, "connection closed before expected length was read!"
114
124
  end
125
+ rescue EOFError
126
+ if connection = @connection
127
+ @connection = nil
128
+ connection.close_read
129
+ end
130
+
131
+ raise EOFError, "Connection closed before expected length was read!"
115
132
  end
116
133
 
117
134
  # @returns [String] a human-readable representation of the body.
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2019-2025, by Samuel Williams.
4
+ # Copyright, 2019-2026, by Samuel Williams.
5
5
 
6
6
  require "protocol/http/body/readable"
7
7
 
@@ -10,6 +10,9 @@ module Protocol
10
10
  module Body
11
11
  # Represents a fixed length body.
12
12
  class Fixed < HTTP::Body::Readable
13
+ # The maximum amount of body data returned by a single read.
14
+ BLOCK_SIZE = 1024 * 64
15
+
13
16
  # Initialize the body with the given connection and length.
14
17
  #
15
18
  # @parameter connection [Protocol::HTTP1::Connection] the connection to read the body from.
@@ -55,7 +58,7 @@ module Protocol
55
58
  if @remaining > 0
56
59
  if @connection
57
60
  # `readpartial` will raise `EOFError` if the connection is finished, or `IOError` if the connection is closed.
58
- chunk = @connection.readpartial(@remaining)
61
+ chunk = @connection.readpartial([@remaining, BLOCK_SIZE].min)
59
62
 
60
63
  @remaining -= chunk.bytesize
61
64
 
@@ -68,7 +71,7 @@ module Protocol
68
71
  end
69
72
 
70
73
  # If the connection has been closed before we have read the expected length, raise an error:
71
- raise EOFError, "connection closed before expected length was read!"
74
+ raise EOFError, "Connection closed before expected length was read!"
72
75
  end
73
76
  end
74
77
 
@@ -8,8 +8,8 @@
8
8
  # Copyright, 2024, by Anton Zhuravsky.
9
9
 
10
10
  require "protocol/http/headers"
11
+ require "protocol/http/status"
11
12
 
12
- require_relative "reason"
13
13
  require_relative "error"
14
14
  require_relative "body"
15
15
 
@@ -277,7 +277,7 @@ module Protocol
277
277
  # @parameter headers [Hash] the HTTP headers.
278
278
  # @parameter reason [String] the reason phrase, defaults to the standard reason phrase for the status code.
279
279
  def write_response(version, status, headers, reason = nil)
280
- reason ||= Reason::DESCRIPTIONS[status]
280
+ reason ||= Protocol::HTTP::Status.description(status)
281
281
 
282
282
  unless @state == :open or @state == :half_closed_remote
283
283
  raise ProtocolError, "Cannot write response in state: #{@state}!"
@@ -297,7 +297,7 @@ module Protocol
297
297
  # @parameter reason [String] the reason phrase, defaults to the standard reason phrase for the status code.
298
298
  # @raises [ProtocolError] if the connection is not in the open or half-closed remote state.
299
299
  def write_interim_response(version, status, headers, reason = nil)
300
- reason ||= Reason::DESCRIPTIONS[status]
300
+ reason ||= Protocol::HTTP::Status.description(status)
301
301
 
302
302
  unless @state == :open or @state == :half_closed_remote
303
303
  raise ProtocolError, "Cannot write interim response in state: #{@state}!"
@@ -5,6 +5,6 @@
5
5
 
6
6
  module Protocol
7
7
  module HTTP1
8
- VERSION = "0.39.0"
8
+ VERSION = "0.40.1"
9
9
  end
10
10
  end
data/readme.md CHANGED
@@ -30,6 +30,14 @@ Please see the [project documentation](https://socketry.github.io/protocol-http1
30
30
 
31
31
  Please see the [project releases](https://socketry.github.io/protocol-http1/releases/index) for all releases.
32
32
 
33
+ ### v0.40.1
34
+
35
+ - Bound fixed-length and chunked body reads, and validate chunk terminators.
36
+
37
+ ### v0.40.0
38
+
39
+ - Use `Protocol::HTTP::Status` for standard HTTP status descriptions and remove the duplicate `Protocol::HTTP1::Reason` table.
40
+
33
41
  ### v0.39.0
34
42
 
35
43
  - Rename `RequestRefusedError` -\> `RefusedError`.
@@ -64,14 +72,6 @@ Please see the [project releases](https://socketry.github.io/protocol-http1/rele
64
72
  - Fix connection state handling to allow idempotent response body closing.
65
73
  - Add `kisaten` fuzzing integration for improved security testing.
66
74
 
67
- ### v0.34.0
68
-
69
- - Support empty header values in HTTP parsing for better compatibility.
70
-
71
- ### v0.33.0
72
-
73
- - Support high-byte characters in HTTP headers for improved international compatibility.
74
-
75
75
  ## Contributing
76
76
 
77
77
  We welcome contributions to this project.
@@ -82,6 +82,22 @@ We welcome contributions to this project.
82
82
  4. Push to the branch (`git push origin my-new-feature`).
83
83
  5. Create new Pull Request.
84
84
 
85
+ ### Running Tests
86
+
87
+ To run the test suite:
88
+
89
+ ``` shell
90
+ bundle exec sus
91
+ ```
92
+
93
+ ### Making Releases
94
+
95
+ To make a new release:
96
+
97
+ ``` shell
98
+ bundle exec bake gem:release:patch # or minor or major
99
+ ```
100
+
85
101
  ### Developer Certificate of Origin
86
102
 
87
103
  In order to protect users of this project, we require all contributors to comply with the [Developer Certificate of Origin](https://developercertificate.org/). This ensures that all contributions are properly licensed and attributed.
data/releases.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Releases
2
2
 
3
+ ## v0.40.1
4
+
5
+ - Bound fixed-length and chunked body reads, and validate chunk terminators.
6
+
7
+ ## v0.40.0
8
+
9
+ - Use `Protocol::HTTP::Status` for standard HTTP status descriptions and remove the duplicate `Protocol::HTTP1::Reason` table.
10
+
3
11
  ## v0.39.0
4
12
 
5
13
  - Rename `RequestRefusedError` -\> `RefusedError`.
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: protocol-http1
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.39.0
4
+ version: 0.40.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -49,14 +49,14 @@ dependencies:
49
49
  requirements:
50
50
  - - "~>"
51
51
  - !ruby/object:Gem::Version
52
- version: '0.62'
52
+ version: '0.68'
53
53
  type: :runtime
54
54
  prerelease: false
55
55
  version_requirements: !ruby/object:Gem::Requirement
56
56
  requirements:
57
57
  - - "~>"
58
58
  - !ruby/object:Gem::Version
59
- version: '0.62'
59
+ version: '0.68'
60
60
  executables: []
61
61
  extensions: []
62
62
  extra_rdoc_files: []
@@ -70,7 +70,6 @@ files:
70
70
  - lib/protocol/http1/body/remainder.rb
71
71
  - lib/protocol/http1/connection.rb
72
72
  - lib/protocol/http1/error.rb
73
- - lib/protocol/http1/reason.rb
74
73
  - lib/protocol/http1/version.rb
75
74
  - lib/traces/provider/protocol/http1.rb
76
75
  - lib/traces/provider/protocol/http1/connection.rb
@@ -97,7 +96,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
97
96
  - !ruby/object:Gem::Version
98
97
  version: '0'
99
98
  requirements: []
100
- rubygems_version: 4.0.6
99
+ rubygems_version: 4.0.10
101
100
  specification_version: 4
102
101
  summary: A low level implementation of the HTTP/1 protocol.
103
102
  test_files: []
metadata.gz.sig CHANGED
Binary file
@@ -1,84 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Released under the MIT License.
4
- # Copyright, 2019-2025, by Samuel Williams.
5
-
6
- require "protocol/http/error"
7
-
8
- module Protocol
9
- module HTTP1
10
- # Reason phrases for HTTP status codes.
11
- module Reason
12
- # Get the reason phrase for the given status code.
13
- DESCRIPTIONS = {
14
- 100 => "Continue",
15
- 101 => "Switching Protocols",
16
- 102 => "Processing",
17
- 103 => "Early Hints",
18
-
19
- 200 => "OK",
20
- 201 => "Created",
21
- 202 => "Accepted",
22
- 203 => "Non-Authoritative Information",
23
- 204 => "No Content",
24
- 205 => "Reset Content",
25
- 206 => "Partial Content",
26
- 207 => "Multi-Status",
27
- 208 => "Already Reported",
28
- 226 => "IM Used",
29
-
30
- 300 => "Multiple Choices",
31
- 301 => "Moved Permanently",
32
- 302 => "Found",
33
- 303 => "See Other",
34
- 304 => "Not Modified",
35
- 305 => "Use Proxy",
36
-
37
- # no longer used, but included for completeness
38
- 306 => "Switch Proxy",
39
- 307 => "Temporary Redirect",
40
- 308 => "Permanent Redirect",
41
-
42
- 400 => "Bad Request",
43
- 401 => "Unauthorized",
44
- 402 => "Payment Required",
45
- 403 => "Forbidden",
46
- 404 => "Not Found",
47
- 405 => "Method Not Allowed",
48
- 406 => "Not Acceptable",
49
- 407 => "Proxy Authentication Required",
50
- 408 => "Request Timeout",
51
- 409 => "Conflict",
52
- 410 => "Gone",
53
- 411 => "Length Required",
54
- 412 => "Precondition Failed",
55
- 413 => "Payload Too Large",
56
- 414 => "URI Too Long",
57
- 415 => "Unsupported Media Type",
58
- 416 => "Range Not Satisfiable",
59
- 417 => "Expectation Failed",
60
- 421 => "Misdirected Request",
61
- 422 => "Unprocessable Entity",
62
- 423 => "Locked",
63
- 424 => "Failed Dependency",
64
- 426 => "Upgrade Required",
65
- 428 => "Precondition Required",
66
- 429 => "Too Many Requests",
67
- 431 => "Request Header Fields Too Large",
68
- 451 => "Unavailable for Legal Reasons",
69
-
70
- 500 => "Internal Server Error",
71
- 501 => "Not Implemented",
72
- 502 => "Bad Gateway",
73
- 503 => "Service Unavailable",
74
- 504 => "Gateway Timeout",
75
- 505 => "HTTP Version Not Supported",
76
- 506 => "Variant Also Negotiates",
77
- 507 => "Insufficient Storage",
78
- 508 => "Loop Detected",
79
- 510 => "Not Extended",
80
- 511 => "Network Authentication Required"
81
- }.freeze
82
- end
83
- end
84
- end