protocol-http 0.16.2 → 0.16.3
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
- data/lib/protocol/http/body/buffered.rb +7 -10
- data/lib/protocol/http/body/rewindable.rb +11 -0
- data/lib/protocol/http/header/authorization.rb +1 -5
- data/lib/protocol/http/header/cache_control.rb +16 -1
- data/lib/protocol/http/header/connection.rb +0 -1
- data/lib/protocol/http/header/etag.rb +39 -0
- data/lib/protocol/http/header/etags.rb +1 -9
- data/lib/protocol/http/header/vary.rb +0 -1
- data/lib/protocol/http/headers.rb +83 -23
- data/lib/protocol/http/request.rb +6 -0
- data/lib/protocol/http/response.rb +6 -0
- data/lib/protocol/http/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8f3dfaca48112b9b8d52096c8cab996403736391102e44a2f9ed6f5e71c2ce7d
|
4
|
+
data.tar.gz: 611c1ba35c482cba1e4fbeb98daadaf7872b8d2198717c795dfa94a4343e7b38
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8d6988b4b2f2c1218c441d05788b88db0aa8890ac70877acc6f87c8ce2f18e32bae5d20505532ece25576c792c1935d42f69bb11ea3e34a8908211ed3fadc88f
|
7
|
+
data.tar.gz: 111ec27937755f580826ff37e2cdfcce11b8fcc571434e31c12822e355694b5d4431fc378c3e91476368006bf657bcb0be164392302274221586074e0193b03a
|
@@ -62,7 +62,12 @@ module Protocol
|
|
62
62
|
end
|
63
63
|
|
64
64
|
def digest
|
65
|
-
@digest
|
65
|
+
if @digest.nil?
|
66
|
+
@digest = Digest::SHA256.new
|
67
|
+
@chunks.each{|chunk| @digest.update(chunk)}
|
68
|
+
end
|
69
|
+
|
70
|
+
@digest.hexdigest
|
66
71
|
end
|
67
72
|
|
68
73
|
def finish
|
@@ -86,7 +91,7 @@ module Protocol
|
|
86
91
|
end
|
87
92
|
|
88
93
|
def write(chunk)
|
89
|
-
@digest
|
94
|
+
@digest&.update(chunk)
|
90
95
|
@chunks << chunk
|
91
96
|
end
|
92
97
|
|
@@ -97,14 +102,6 @@ module Protocol
|
|
97
102
|
def inspect
|
98
103
|
"\#<#{self.class} #{@chunks.size} chunks, #{self.length} bytes>"
|
99
104
|
end
|
100
|
-
|
101
|
-
private
|
102
|
-
|
103
|
-
def compute_digest
|
104
|
-
algorithm = Digest::SHA256.new
|
105
|
-
@chunks.each{|chunk| algorithm.update(chunk)}
|
106
|
-
return algorithm.hexdigest
|
107
|
-
end
|
108
105
|
end
|
109
106
|
end
|
110
107
|
end
|
@@ -33,6 +33,16 @@ module Protocol
|
|
33
33
|
|
34
34
|
@chunks = []
|
35
35
|
@index = 0
|
36
|
+
@digest = nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def digest
|
40
|
+
if @digest.nil?
|
41
|
+
@digest = Digest::SHA256.new
|
42
|
+
@chunks.each{|chunk| @digest.update(chunk)}
|
43
|
+
end
|
44
|
+
|
45
|
+
@digest.hexdigest
|
36
46
|
end
|
37
47
|
|
38
48
|
def empty?
|
@@ -51,6 +61,7 @@ module Protocol
|
|
51
61
|
else
|
52
62
|
if chunk = super
|
53
63
|
@chunks << chunk
|
64
|
+
@digest&.update(chunk)
|
54
65
|
@index += 1
|
55
66
|
end
|
56
67
|
end
|
@@ -26,7 +26,7 @@ module Protocol
|
|
26
26
|
module HTTP
|
27
27
|
module Header
|
28
28
|
# Used for basic authorization.
|
29
|
-
# @example headers.add(
|
29
|
+
# @example headers.add('authorization', Authorization.new("samuel", "password"))
|
30
30
|
class Authorization
|
31
31
|
KEY = "Authorization"
|
32
32
|
|
@@ -35,10 +35,6 @@ module Protocol
|
|
35
35
|
@password = password
|
36
36
|
end
|
37
37
|
|
38
|
-
def to_ary
|
39
|
-
return KEY, self.to_str
|
40
|
-
end
|
41
|
-
|
42
38
|
def encoded
|
43
39
|
"#{@username}:#{@password}"
|
44
40
|
end
|
@@ -25,7 +25,6 @@ require_relative 'split'
|
|
25
25
|
module Protocol
|
26
26
|
module HTTP
|
27
27
|
module Header
|
28
|
-
# Header value which is split by newline charaters (e.g. cookies).
|
29
28
|
class CacheControl < Split
|
30
29
|
PRIVATE = 'private'
|
31
30
|
PUBLIC = 'public'
|
@@ -33,6 +32,10 @@ module Protocol
|
|
33
32
|
NO_STORE = 'no-store'
|
34
33
|
MAX_AGE = 'max-age'
|
35
34
|
|
35
|
+
STATIC = 'static'
|
36
|
+
DYNAMIC = 'dynamic'
|
37
|
+
STREAMING = 'streaming'
|
38
|
+
|
36
39
|
def initialize(value)
|
37
40
|
super(value.downcase)
|
38
41
|
end
|
@@ -41,6 +44,18 @@ module Protocol
|
|
41
44
|
super(value.downcase)
|
42
45
|
end
|
43
46
|
|
47
|
+
def static?
|
48
|
+
self.include?(STATIC)
|
49
|
+
end
|
50
|
+
|
51
|
+
def dynamic?
|
52
|
+
self.include?(DYNAMIC)
|
53
|
+
end
|
54
|
+
|
55
|
+
def streaming?
|
56
|
+
self.include?(STREAMING)
|
57
|
+
end
|
58
|
+
|
44
59
|
def private?
|
45
60
|
self.include?(PRIVATE)
|
46
61
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright, 2020, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
require_relative 'split'
|
24
|
+
|
25
|
+
module Protocol
|
26
|
+
module HTTP
|
27
|
+
module Header
|
28
|
+
class ETag < String
|
29
|
+
def << value
|
30
|
+
replace(value)
|
31
|
+
end
|
32
|
+
|
33
|
+
def weak?
|
34
|
+
self.start_with('\W')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -25,16 +25,8 @@ require_relative 'split'
|
|
25
25
|
module Protocol
|
26
26
|
module HTTP
|
27
27
|
module Header
|
28
|
-
#
|
28
|
+
# This implementation is not strictly correct according to the RFC-specified format.
|
29
29
|
class ETags < Split
|
30
|
-
def initialize(value)
|
31
|
-
super(value.downcase)
|
32
|
-
end
|
33
|
-
|
34
|
-
def << value
|
35
|
-
super(value.downcase)
|
36
|
-
end
|
37
|
-
|
38
30
|
def wildcard?
|
39
31
|
self.include?('*')
|
40
32
|
end
|
@@ -25,6 +25,7 @@ require_relative 'header/multiple'
|
|
25
25
|
require_relative 'header/cookie'
|
26
26
|
require_relative 'header/connection'
|
27
27
|
require_relative 'header/cache_control'
|
28
|
+
require_relative 'header/etag'
|
28
29
|
require_relative 'header/etags'
|
29
30
|
require_relative 'header/vary'
|
30
31
|
|
@@ -34,43 +35,93 @@ module Protocol
|
|
34
35
|
class Headers
|
35
36
|
Split = Header::Split
|
36
37
|
Multiple = Header::Multiple
|
38
|
+
TRAILERS = 'trailers'
|
37
39
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
if fields
|
44
|
-
@fields = fields.dup
|
40
|
+
# Construct an instance from a headers Array or Hash. No-op if already an instance of `Headers`.
|
41
|
+
# @return [Headers] an instance of headers.
|
42
|
+
def self.[] headers
|
43
|
+
if headers.is_a?(self)
|
44
|
+
headers
|
45
45
|
else
|
46
|
-
|
46
|
+
self.new(headers.to_a)
|
47
47
|
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def initialize(fields = [], indexed = nil)
|
51
|
+
@fields = fields
|
52
|
+
@indexed = indexed
|
48
53
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
@indexed = nil
|
53
|
-
end
|
54
|
+
# Marks where trailers start in the @fields array.
|
55
|
+
@tail = nil
|
56
|
+
@deferred = []
|
54
57
|
end
|
55
58
|
|
56
|
-
def
|
57
|
-
|
59
|
+
def initialize_dup(other)
|
60
|
+
super
|
61
|
+
|
62
|
+
@fields = @fields.dup
|
63
|
+
@indexed = @indexed.dup
|
64
|
+
@deferred = @deferred.dup
|
58
65
|
end
|
59
66
|
|
60
67
|
def clear
|
61
68
|
@fields.clear
|
62
69
|
@indexed = nil
|
70
|
+
@tail = nil
|
71
|
+
@deferred.clear
|
63
72
|
end
|
64
73
|
|
65
74
|
# An array of `[key, value]` pairs.
|
66
75
|
attr :fields
|
67
76
|
|
77
|
+
# Mark the subsequent headers as trailers.
|
78
|
+
def trailers!
|
79
|
+
@tail ||= @fields.size
|
80
|
+
end
|
81
|
+
|
82
|
+
# @return the trailers if there are any.
|
83
|
+
def trailers?
|
84
|
+
@tail != nil
|
85
|
+
end
|
86
|
+
|
87
|
+
def flatten!
|
88
|
+
unless @deferred.empty?
|
89
|
+
@tail ||= @fields.size
|
90
|
+
|
91
|
+
@deferred.each do |key, value|
|
92
|
+
self.add(key, value.call)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Enumerate all trailers, including evaluating all deferred headers.
|
98
|
+
def trailers(&block)
|
99
|
+
return nil unless self.include?(TRAILERS)
|
100
|
+
|
101
|
+
return to_enum(:trailers) unless block_given?
|
102
|
+
|
103
|
+
flatten!
|
104
|
+
|
105
|
+
if @tail
|
106
|
+
@fields.drop(@tail).each(&block)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
68
110
|
def freeze
|
69
111
|
return if frozen?
|
70
112
|
|
113
|
+
# Ensure all deferred headers are evaluated:
|
114
|
+
self.flatten!
|
115
|
+
|
71
116
|
# Ensure @indexed is generated:
|
72
117
|
self.to_h
|
73
118
|
|
119
|
+
# Remove all trailers:
|
120
|
+
self.delete(TRAILERS)
|
121
|
+
|
122
|
+
# No longer has stateful trailers:
|
123
|
+
@tail = nil
|
124
|
+
|
74
125
|
@fields.freeze
|
75
126
|
@indexed.freeze
|
76
127
|
|
@@ -89,6 +140,10 @@ module Protocol
|
|
89
140
|
self[key] != nil
|
90
141
|
end
|
91
142
|
|
143
|
+
def keys
|
144
|
+
self.to_h.keys
|
145
|
+
end
|
146
|
+
|
92
147
|
def extract(keys)
|
93
148
|
deleted, @fields = @fields.partition do |field|
|
94
149
|
keys.include?(field.first.downcase)
|
@@ -103,14 +158,17 @@ module Protocol
|
|
103
158
|
return deleted
|
104
159
|
end
|
105
160
|
|
106
|
-
# This is deprecated.
|
107
|
-
alias slice! extract
|
108
|
-
|
109
161
|
# Add the specified header key value pair.
|
110
162
|
# @param key [String] the header key.
|
111
163
|
# @param value [String] the header value to assign.
|
112
|
-
|
113
|
-
|
164
|
+
# @yield dynamically generate the value when used as a trailer.
|
165
|
+
def add(key, value = nil, &block)
|
166
|
+
if block_given?
|
167
|
+
@deferred << [key, block]
|
168
|
+
self[TRAILERS] = key
|
169
|
+
else
|
170
|
+
self[key] = value
|
171
|
+
end
|
114
172
|
end
|
115
173
|
|
116
174
|
# Set the specified header key to the specified value, replacing any existing header keys with the same name.
|
@@ -145,7 +203,7 @@ module Protocol
|
|
145
203
|
@fields << [key, value]
|
146
204
|
end
|
147
205
|
|
148
|
-
|
206
|
+
POLICY = {
|
149
207
|
# Headers which may only be specified once.
|
150
208
|
'content-type' => false,
|
151
209
|
'content-disposition' => false,
|
@@ -161,6 +219,7 @@ module Protocol
|
|
161
219
|
'location' => false,
|
162
220
|
'max-forwards' => false,
|
163
221
|
|
222
|
+
# Custom headers:
|
164
223
|
'connection' => Header::Connection,
|
165
224
|
'cache-control' => Header::CacheControl,
|
166
225
|
'vary' => Header::Vary,
|
@@ -170,6 +229,7 @@ module Protocol
|
|
170
229
|
'x-forwarded-for' => Split,
|
171
230
|
|
172
231
|
# Cache validations:
|
232
|
+
'etag' => Header::ETag,
|
173
233
|
'if-match' => Header::ETags,
|
174
234
|
'if-none-match' => Header::ETags,
|
175
235
|
|
@@ -194,7 +254,7 @@ module Protocol
|
|
194
254
|
|
195
255
|
if @indexed
|
196
256
|
return @indexed.delete(key)
|
197
|
-
elsif policy =
|
257
|
+
elsif policy = POLICY[key]
|
198
258
|
(key, value), *tail = deleted
|
199
259
|
merged = policy.new(value)
|
200
260
|
|
@@ -208,7 +268,7 @@ module Protocol
|
|
208
268
|
end
|
209
269
|
|
210
270
|
protected def merge_into(hash, key, value)
|
211
|
-
if policy =
|
271
|
+
if policy = POLICY[key]
|
212
272
|
if current_value = hash[key]
|
213
273
|
current_value << value
|
214
274
|
else
|
@@ -48,6 +48,12 @@ module Protocol
|
|
48
48
|
attr_accessor :body
|
49
49
|
attr_accessor :protocol
|
50
50
|
|
51
|
+
def trailers
|
52
|
+
if @headers.respond_to?(:trailers)
|
53
|
+
@headers.trailers
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
51
57
|
# Send the request to the given connection.
|
52
58
|
def call(connection)
|
53
59
|
connection.call(self)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: protocol-http
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.16.
|
4
|
+
version: 0.16.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-04-
|
11
|
+
date: 2020-04-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: covered
|
@@ -99,6 +99,7 @@ files:
|
|
99
99
|
- lib/protocol/http/header/cache_control.rb
|
100
100
|
- lib/protocol/http/header/connection.rb
|
101
101
|
- lib/protocol/http/header/cookie.rb
|
102
|
+
- lib/protocol/http/header/etag.rb
|
102
103
|
- lib/protocol/http/header/etags.rb
|
103
104
|
- lib/protocol/http/header/multiple.rb
|
104
105
|
- lib/protocol/http/header/split.rb
|