protocol-http1 0.20.0 → 0.22.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b49dc6301aa7d4aafb183cd1bea576bda5681e9d911245398c8c0c68ca38b1cc
4
- data.tar.gz: b819f86f731d8e68391994e709318460a9c50b64fece83f398868a443faa3a72
3
+ metadata.gz: 550b6e9b8f04f40fbba30a9260ead61bc1921130b069f8d4f6d7005077da0e61
4
+ data.tar.gz: 54a8009f974a0292b1500c21f7e8047d5af4d815e7b844fa46ec0d6f6d356fee
5
5
  SHA512:
6
- metadata.gz: fc1c721b351c094243953896088405b4f3800662ac9539bc599cf7d989a48623243d8e443cba789df43fa88d857cd1bacee8ae1fc520a1e5a8a4846bf23c4276
7
- data.tar.gz: c6bcb1f77924b2038167ea8c88cfcfcad9cf7cda91677b09ab2f264ff8f61ef7e782ce0a183144180978fa02a2de5d03bfe3f1f424624e1424efc451724384ba
6
+ metadata.gz: e44973d7e04933d1e6d3e8d50c98dcc57603fc4cd7491a1ab9ed490ef659551bbf1e0eb425222cc2e61f38b9fc5af864198970505a42750dad8c97bdb68331e7
7
+ data.tar.gz: 6fba9bb8ac29bc13966bbebed00ca72b60d9214ba1943185e44760abb72ad5b1fa8254f4222951e5bf983263fef015a78ecdca6e6c8396c3c0e9548bade51576
checksums.yaml.gz.sig CHANGED
Binary file
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2019-2023, by Samuel Williams.
4
+ # Copyright, 2019-2024, by Samuel Williams.
5
5
  # Copyright, 2023, by Thomas Morgan.
6
6
 
7
7
  require 'protocol/http/body/readable'
@@ -23,14 +23,17 @@ module Protocol
23
23
  end
24
24
 
25
25
  def empty?
26
- @finished
26
+ @stream.nil?
27
27
  end
28
28
 
29
29
  def close(error = nil)
30
- # We only close the connection if we haven't completed reading the entire body:
31
- unless @finished
32
- @stream.close
33
- @finished = true
30
+ if @stream
31
+ # We only close the connection if we haven't completed reading the entire body:
32
+ unless @finished
33
+ @stream.close_read
34
+ end
35
+
36
+ @stream = nil
34
37
  end
35
38
 
36
39
  super
@@ -40,35 +43,42 @@ module Protocol
40
43
 
41
44
  # Follows the procedure outlined in https://tools.ietf.org/html/rfc7230#section-4.1.3
42
45
  def read
43
- return nil if @finished
44
-
45
- length, _extensions = read_line.split(";", 2)
46
-
47
- unless length =~ VALID_CHUNK_LENGTH
48
- raise BadRequest, "Invalid chunk length: #{length.dump}"
49
- end
50
-
51
- # It is possible this line contains chunk extension, so we use `to_i` to only consider the initial integral part:
52
- length = Integer(length, 16)
53
-
54
- if length == 0
55
- @finished = true
56
-
57
- read_trailer
46
+ if !@finished
47
+ if @stream
48
+ length, _extensions = read_line.split(";", 2)
49
+
50
+ unless length =~ VALID_CHUNK_LENGTH
51
+ raise BadRequest, "Invalid chunk length: #{length.inspect}"
52
+ end
53
+
54
+ # It is possible this line contains chunk extension, so we use `to_i` to only consider the initial integral part:
55
+ length = Integer(length, 16)
56
+
57
+ if length == 0
58
+ read_trailer
59
+
60
+ # The final chunk has been read and the stream is now closed:
61
+ @stream = nil
62
+ @finished = true
63
+
64
+ return nil
65
+ end
66
+
67
+ # Read trailing CRLF:
68
+ chunk = @stream.read(length + 2)
69
+
70
+ # ...and chomp it off:
71
+ chunk.chomp!(CRLF)
72
+
73
+ @length += length
74
+ @count += 1
75
+
76
+ return chunk
77
+ end
58
78
 
59
- return nil
79
+ # If the stream has been closed before we have read the final chunk, raise an error:
80
+ raise EOFError, "Stream closed before expected length was read!"
60
81
  end
61
-
62
- # Read trailing CRLF:
63
- chunk = @stream.read(length + 2)
64
-
65
- # ...and chomp it off:
66
- chunk.chomp!(CRLF)
67
-
68
- @length += length
69
- @count += 1
70
-
71
- return chunk
72
82
  end
73
83
 
74
84
  def inspect
@@ -93,7 +103,7 @@ module Protocol
93
103
  if match = line.match(HEADER)
94
104
  @headers.add(match[1], match[2])
95
105
  else
96
- raise BadHeader, "Could not parse header: #{line.dump}"
106
+ raise BadHeader, "Could not parse header: #{line.inspect}"
97
107
  end
98
108
  end
99
109
  end
@@ -11,6 +11,7 @@ module Protocol
11
11
  class Fixed < HTTP::Body::Readable
12
12
  def initialize(stream, length)
13
13
  @stream = stream
14
+
14
15
  @length = length
15
16
  @remaining = length
16
17
  end
@@ -19,13 +20,17 @@ module Protocol
19
20
  attr :remaining
20
21
 
21
22
  def empty?
22
- @remaining == 0
23
+ @stream.nil? or @remaining == 0
23
24
  end
24
25
 
25
26
  def close(error = nil)
26
- # If we are closing the body without fully reading it, the underlying connection is now in an undefined state.
27
- if @remaining != 0
28
- @stream.close
27
+ if @stream
28
+ # If we are closing the body without fully reading it, the underlying connection is now in an undefined state.
29
+ if @remaining != 0
30
+ @stream.close_read
31
+ end
32
+
33
+ @stream = nil
29
34
  end
30
35
 
31
36
  super
@@ -34,25 +39,22 @@ module Protocol
34
39
  # @raises EOFError if the stream is closed before the expected length is read.
35
40
  def read
36
41
  if @remaining > 0
37
- # `readpartial` will raise `EOFError` if the stream is closed/finished:
38
- if chunk = @stream.readpartial(@remaining)
39
- @remaining -= chunk.bytesize
40
-
41
- return chunk
42
+ if @stream
43
+ # `readpartial` will raise `EOFError` if the stream is finished, or `IOError` if the stream is closed.
44
+ if chunk = @stream.readpartial(@remaining)
45
+ @remaining -= chunk.bytesize
46
+
47
+ return chunk
48
+ end
42
49
  end
50
+
51
+ # If the stream has been closed before we have read the expected length, raise an error:
52
+ raise EOFError, "Stream closed before expected length was read!"
43
53
  end
44
54
  end
45
55
 
46
- def join
47
- buffer = @stream.read(@remaining)
48
-
49
- @remaining = 0
50
-
51
- return buffer
52
- end
53
-
54
56
  def inspect
55
- "\#<#{self.class} length=#{@length} remaining=#{@remaining}>"
57
+ "\#<#{self.class} length=#{@length} remaining=#{@remaining} state=#{@stream ? 'open' : 'closed'}>"
56
58
  end
57
59
  end
58
60
  end
@@ -8,53 +8,39 @@ require 'protocol/http/body/readable'
8
8
  module Protocol
9
9
  module HTTP1
10
10
  module Body
11
+ # A body that reads all remaining data from the stream.
11
12
  class Remainder < HTTP::Body::Readable
12
13
  BLOCK_SIZE = 1024 * 64
13
14
 
14
15
  # block_size may be removed in the future. It is better managed by stream.
15
16
  def initialize(stream)
16
17
  @stream = stream
17
- @empty = false
18
18
  end
19
19
 
20
20
  def empty?
21
- @empty or @stream.closed?
21
+ @stream.nil?
22
22
  end
23
23
 
24
24
  def close(error = nil)
25
- # We can't really do anything in this case except close the connection.
26
- @stream.close
27
- @empty = true
25
+ if @stream
26
+ # We can't really do anything in this case except close the connection.
27
+ @stream.close_read
28
+ @stream = nil
29
+ end
28
30
 
29
31
  super
30
32
  end
31
33
 
32
- # TODO this is a bit less efficient in order to maintain compatibility with `IO`.
33
34
  def read
34
- @stream.readpartial(BLOCK_SIZE)
35
+ @stream&.readpartial(BLOCK_SIZE)
35
36
  rescue EOFError, IOError
36
- @empty = true
37
-
37
+ @stream = nil
38
38
  # I noticed that in some cases you will get EOFError, and in other cases IOError!?
39
39
  return nil
40
40
  end
41
41
 
42
- def call(stream)
43
- self.each do |chunk|
44
- stream.write(chunk)
45
- end
46
-
47
- stream.flush
48
- end
49
-
50
- def join
51
- @stream.read
52
- ensure
53
- @empty = true
54
- end
55
-
56
42
  def inspect
57
- "\#<#{self.class} #{@stream.closed? ? 'closed' : 'open'}>"
43
+ "\#<#{self.class} state=#{@stream ? 'open' : 'closed'}>"
58
44
  end
59
45
  end
60
46
  end
@@ -4,7 +4,7 @@
4
4
  # Copyright, 2019-2024, by Samuel Williams.
5
5
  # Copyright, 2019, by Brian Morearty.
6
6
  # Copyright, 2020, by Bruno Sutic.
7
- # Copyright, 2023, by Thomas Morgan.
7
+ # Copyright, 2023-2024, by Thomas Morgan.
8
8
  # Copyright, 2024, by Anton Zhuravsky.
9
9
 
10
10
  require 'protocol/http/headers'
@@ -105,7 +105,7 @@ module Protocol
105
105
  def write_upgrade_header(upgrade)
106
106
  @stream.write("connection: upgrade\r\nupgrade: #{upgrade}\r\n")
107
107
  end
108
-
108
+
109
109
  # Indicates whether the connection has been hijacked meaning its
110
110
  # IO has been handed over and is not usable anymore.
111
111
  # @return [Boolean] hijack status
@@ -240,7 +240,7 @@ module Protocol
240
240
  if match = line.match(HEADER)
241
241
  fields << [match[1], match[2]]
242
242
  else
243
- raise BadHeader, "Could not parse header: #{line.dump}"
243
+ raise BadHeader, "Could not parse header: #{line.inspect}"
244
244
  end
245
245
  end
246
246
 
@@ -423,6 +423,7 @@ module Protocol
423
423
  end
424
424
 
425
425
  def read_remainder_body
426
+ @persistent = false
426
427
  Body::Remainder.new(@stream)
427
428
  end
428
429
 
@@ -434,6 +435,12 @@ module Protocol
434
435
  read_remainder_body
435
436
  end
436
437
 
438
+ def read_upgrade_body
439
+ # When you have an incoming upgrade request body, we must be extremely careful not to start reading it until the upgrade has been confirmed, otherwise if the upgrade was rejected and we started forwarding the incoming request body, it would desynchronize the connection (potential security issue).
440
+ # We mitigate this issue by setting @persistent to false, which will prevent the connection from being reused, even if the upgrade fails (potential performance issue).
441
+ read_remainder_body
442
+ end
443
+
437
444
  HEAD = "HEAD"
438
445
  CONNECT = "CONNECT"
439
446
 
@@ -444,7 +451,7 @@ module Protocol
444
451
  if content_length =~ VALID_CONTENT_LENGTH
445
452
  yield Integer(content_length, 10)
446
453
  else
447
- raise BadRequest, "Invalid content length: #{content_length.dump}"
454
+ raise BadRequest, "Invalid content length: #{content_length.inspect}"
448
455
  end
449
456
  end
450
457
  end
@@ -469,6 +476,10 @@ module Protocol
469
476
  return nil
470
477
  end
471
478
 
479
+ if status == 101
480
+ return read_upgrade_body
481
+ end
482
+
472
483
  if (status >= 100 and status < 200) or status == 204 or status == 304
473
484
  return nil
474
485
  end
@@ -495,6 +506,11 @@ module Protocol
495
506
  return read_tunnel_body
496
507
  end
497
508
 
509
+ # A successful upgrade response implies that the connection will become a tunnel immediately after the empty line that concludes the header fields.
510
+ if headers[UPGRADE]
511
+ return read_upgrade_body
512
+ end
513
+
498
514
  # 6. If this is a request message and none of the above are true, then
499
515
  # the message body length is zero (no message body is present).
500
516
  return read_body(headers)
@@ -5,6 +5,6 @@
5
5
 
6
6
  module Protocol
7
7
  module HTTP1
8
- VERSION = "0.20.0"
8
+ VERSION = "0.22.0"
9
9
  end
10
10
  end
data/license.md CHANGED
@@ -4,7 +4,7 @@ Copyright, 2019-2024, by Samuel Williams.
4
4
  Copyright, 2019, by Brian Morearty.
5
5
  Copyright, 2020, by Olle Jonsson.
6
6
  Copyright, 2020, by Bruno Sutic.
7
- Copyright, 2023, by Thomas Morgan.
7
+ Copyright, 2023-2024, by Thomas Morgan.
8
8
  Copyright, 2024, by Anton Zhuravsky.
9
9
 
10
10
  Permission is hereby granted, free of charge, to any person obtaining a copy
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.20.0
4
+ version: 0.22.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -42,7 +42,7 @@ cert_chain:
42
42
  Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
43
43
  voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
44
44
  -----END CERTIFICATE-----
45
- date: 2024-08-26 00:00:00.000000000 Z
45
+ date: 2024-09-05 00:00:00.000000000 Z
46
46
  dependencies:
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: protocol-http
metadata.gz.sig CHANGED
Binary file