linzer 0.7.0.beta2 → 0.7.0.beta4

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: fd7b5e233ec42d94c7040f774097c612850eef9c5795a2c8d3251bc618c8df5f
4
- data.tar.gz: 53cc818e2fc68fe1f6b3c89414a5b0d2393e1b8cfa8e4c9f3ba9076ce537f81c
3
+ metadata.gz: 5cfa8303792377ef25d8a81300b1e22c2e6593237436e152ec04e99176eea1f7
4
+ data.tar.gz: e2f89b7b4d771a9912000961862bf77cda5ccf2caf40a3906394afa1b1595adc
5
5
  SHA512:
6
- metadata.gz: 93e5fb0d4fd0cd534691102a02efa8ae14589d12ee3636f86d10d19e57104b1bb122f950c9191c5fb40264fc453a31d33aa1369fac0fe34451e74108ce9c6a5c
7
- data.tar.gz: e5b12b53e46f7958c560cb579111c14eab85da6a047effcdb599d39f955fe38b89175faef1937a963890c8d8254763c54314ea95c4eff70815fc0c0b8c396551
6
+ metadata.gz: 3881ae41ea932485f39838bba647f60fce089f740dabe7db7e01d34c368ebaf85ec71788d997d64d9cc662811dd703babd62300f7d02c6a21072c1dafbb45ee5
7
+ data.tar.gz: 7a9589f81440612f7e3bc959973aa947614c132f67db247e043f911d1653c7621c376ade8005181fa6fc4814e542ec82556bf79ec5f2b0d3ede739d48d4ffeb0
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.7.0.beta4] - 2025-05-17
4
+
5
+ - Provide integration with http.rb gem to allow signing outgoing HTTP requests.
6
+ - Add simple HTTP client module.
7
+
8
+ ## [0.7.0.beta3] - 2025-05-06 (aka the ["MiniDebConf Hamburg 2025"](https://wiki.debian.org/DebianEvents/de/2025/MiniDebConfHamburg) release)
9
+
10
+ - Refactor to improve Linzer APIs and streamline its usage along with different
11
+ HTTP libraries.
12
+
3
13
  ## [0.7.0.beta2] - 2025-04-13
4
14
 
5
15
  - Refactor and improve Rack::Auth::Signature code organization.
data/README.md CHANGED
@@ -23,7 +23,7 @@ Or just `gem install linzer`.
23
23
 
24
24
  ### TL;DR: I just want to protect my application!!
25
25
 
26
- Add the following middleware to you run Rack application and configure it
26
+ Add the following middleware to your Rack application and configure it
27
27
  as needed, e.g.:
28
28
 
29
29
  ```ruby
@@ -58,46 +58,65 @@ look at
58
58
 
59
59
  To learn about more specific scenarios or use cases, keep reading on below.
60
60
 
61
- ### To sign a HTTP message:
61
+ ### To sign a HTTP request:
62
62
 
63
- ```ruby
64
- key = Linzer.generate_ed25519_key
65
- # => #<Linzer::Ed25519::Key:0x00000fe13e9bd208
63
+ There are several options:
66
64
 
67
- headers = {
68
- "date" => "Fri, 23 Feb 2024 17:57:23 GMT",
69
- "x-custom-header" => "foo"
70
- }
65
+ #### If you are using http gem:
71
66
 
72
- request = Linzer.new_request(:post, "/some_uri", {}, headers)
73
- # => #<Rack::Request:0x0000000104c1c8c0
74
- # @env={"HTTP_DATE"=>"Fri, 23 Feb 2024 17:57:23 GMT", "HTTP_X_CUSTOM..."
75
- # @params=nil>
67
+ ```ruby
68
+ # first require http signatures feature class ready to be used with http gem:
69
+ require "linzer/http/signature_feature"
76
70
 
77
- message = Linzer::Message.new(request)
78
- # => #<Linzer::Message:0x0000000104afa960
79
- # @operation=#<Rack::Request:0x00000001049754a0
80
- # @env={"HTTP_DATE"=>"Fri, 23 Feb 2024 17:57:23 GMT", "HTTP_X_CUSTOM..."
81
- # @params=nil>>
71
+ key = Linzer.generate_ed25519_key # generate a new key pair
72
+ # => #<Linzer::Ed25519::Key:0x00000fe13e9bd208
73
+ # or load an existing key with:
74
+ # key = Linzer.new_ed25519_key(IO.read("key"), "mykeyid")
75
+
76
+ # then send the request:
77
+ url = "https://example.org/api"
78
+ response = HTTP.headers(date: Time.now.to_s, foo: "bar")
79
+ .use(http_signature: {key: key} # <--- covered components
80
+ .get(url) # and signature params can also be customized on the client
81
+ => #<HTTP::Response/1.1 200 OK {"Content-Type" => ...
82
+ response.body.to_s
83
+ => "protected content..."
84
+ ```
82
85
 
83
- fields = %w[date x-custom-header @method @path]
86
+ #### If you are using plain old Net::HTTP:
84
87
 
85
- signature = Linzer.sign(key, message, fields)
86
- # => #<Linzer::Signature:0x0000000111f77ad0 ...
88
+ ```ruby
89
+ key = Linzer.generate_ed25519_key
90
+ # => #<Linzer::Ed25519::Key:0x00000fe13e9bd208
87
91
 
88
- pp signature.to_h
89
- # => {"signature"=>"sig1=:Cv1TUCxUpX+5SVa7pH0Xh...",
90
- # "signature-input"=>"sig1=(\"date\" \"x-custom-header\" ..."}
92
+ uri = URI("https://example.org/api/task")
93
+ request = Net::HTTP::Get.new(uri)
94
+ request["date"] = Time.now.to_s
95
+
96
+ Linzer.sign!(
97
+ request,
98
+ key: key,
99
+ components: %w[@method @request-target date],
100
+ label: "sig1",
101
+ params: {
102
+ created: Time.now.to_i
103
+ }
104
+ )
105
+
106
+ request["signature"]
107
+ # => "sig1=:Cv1TUCxUpX+5SVa7pH0Xh..."
108
+ request["signature-input"]
109
+ # => "sig1=(\"@method\" \"@request-target\" \"date\" ..."}
91
110
  ```
92
111
 
93
- ### Use the message signature with any HTTP client:
112
+ Then you can submit the signed request with Net::HTTP client:
94
113
 
95
114
  ```ruby
96
115
  require "net/http"
97
116
 
98
- http = Net::HTTP.new("localhost", 9292)
117
+ http = Net::HTTP.new(uri.host, uri.port)
99
118
  http.set_debug_output($stderr)
100
- response = http.post("/some_uri", "data", headers.merge(signature.to_h))
119
+ response = http.request(request)
101
120
  # opening connection to localhost:9292...
102
121
  # opened
103
122
  # <- "POST /some_uri HTTP/1.1\r\n
@@ -131,7 +150,144 @@ response = http.post("/some_uri", "data", headers.merge(signature.to_h))
131
150
  # => #<Net::HTTPOK 200 OK readbody=true>
132
151
  ```
133
152
 
134
- ### To verify a valid signature:
153
+ #### Or you can also use the simple HTTP client bundled with this library:
154
+
155
+ (This client is probably not suitable for production use but could be useful
156
+ enough to get started. It's build on top of Net::HTTP.)
157
+
158
+ ```ruby
159
+ key = Linzer.generate_rsa_pss_sha512_key(4096)
160
+ uri = URI("https://example.org/api/task")
161
+ headers = {"date" => Time.now.to_s}
162
+ response =
163
+ Linzer::HTTP
164
+ .post("http://httpbin.org/headers",
165
+ data: "foo",
166
+ debug: true,
167
+ key: key,
168
+ headers: headers)
169
+ ...
170
+ => #<Net::HTTPOK 200 OK readbody=true>
171
+ ```
172
+
173
+ ### To verify an incoming request on the server side:
174
+
175
+ The middleware `Rack::Auth::Signature` can be used for this scenario
176
+ [as shown above](#tldr-i-just-want-to-protect-my-application).
177
+
178
+ Or directly in the application controller (or routes), the incoming request can
179
+ be verified with the following approach:
180
+
181
+ ```ruby
182
+ post "/foo" do
183
+ request
184
+ # =>
185
+ # #<Sinatra::Request:0x000000011e5a5d60
186
+ # @env=
187
+ # {"GATEWAY_INTERFACE" => "CGI/1.1",
188
+ # "PATH_INFO" => "/api",
189
+ # ...
190
+
191
+ result = Linzer.verify!(request, key: some_client_key)
192
+ # => true
193
+ ...
194
+ end
195
+ ```
196
+
197
+ If the signature is missing or invalid, the verification method will raise an
198
+ exception with a message clarifying why the request signature failed verification.
199
+
200
+ Also, for additional flexibility on the server side, the method above can take
201
+ a block with the `keyid` parameter extracted from the signature (if any) as argument.
202
+ This can be useful to retrieve key data from databases/caches on the server side, e.g.:
203
+
204
+ ```ruby
205
+ get "/bar" do
206
+ ...
207
+ result = Linzer.verify!(request) do |keyid|
208
+ retrieve_pubkey_from_db(db_client, keyid)
209
+ end
210
+ # => true
211
+ ...
212
+ end
213
+ ```
214
+
215
+ ### To verify a received response on the client side:
216
+
217
+ It's similar to verifying requests, the same method is used, see example below:
218
+
219
+ ```ruby
220
+ response
221
+ # => #<Net::HTTPOK 200 OK readbody=true>
222
+ response.body
223
+ # => "protected"
224
+ pubkey = Linzer.new_ed25519_key(IO.read("pubkey.pem"))
225
+ result = Linzer.verify!(response, key: pubkey, no_older_than: 600)
226
+ # => true
227
+ ```
228
+
229
+ ### To sign an outgoing response on the server side:
230
+
231
+ Again, the same principle used to sign outgoing requests, the same method is used,
232
+ see example below:
233
+
234
+ ```ruby
235
+ put "/baz" do
236
+ ...
237
+ response
238
+ # => #<Sinatra::Response:0x0000000109ac40b8 ...
239
+ response.headers["x-custom-app-header"] = "..."
240
+ Linzer.sign!(response,
241
+ key: my_key,
242
+ components: %w[@status content-type content-digest x-custom-app-header],
243
+ label: "sig1",
244
+ params: {
245
+ created: Time.now.to_i
246
+ }
247
+ )
248
+ response["signature"]
249
+ # => "sig1=:2TPCzD4l48bg6LMcVXdV9u..."
250
+ response["signature-input"]
251
+ # => "sig1=(\"@status\" \"content-type\" \"content-digest\"..."
252
+ ...
253
+ end
254
+ ```
255
+
256
+ ### What do you do if you want to sign/verify requests and responses with your preferred HTTP ruby library/framework (not using Rack or `Net::HTTP`, for example)?
257
+
258
+ You can provide an adapter class and then register it with this library.
259
+ For guidance on how to implement such adapters, you can consult an
260
+ [example adapter for http gem response](https://github.com/nomadium/linzer/blob/master/lib/linzer/message/adapter/http_gem/response.rb)
261
+ included with this gem or the ones
262
+ [provided out of the box](https://github.com/nomadium/linzer/blob/master/lib/linzer/message/adapter).
263
+
264
+ For how to register a custom adapter and how to verify signatures in a response,
265
+ see this example:
266
+
267
+ ```ruby
268
+ Linzer::Message.register_adapter(HTTP::Response, Linzer::Message::Adapter::HTTPGem::Response)
269
+ # Linzer::Message.register_adapter(HTTP::Response, MyOwnResponseAdapter) # or use your own adapter
270
+ response = HTTP.get("http://www.example.com/api/service/task")
271
+ # => #<HTTP::Response/1.1 200 OK ...
272
+ response["signature"]
273
+ => "sig1=:oqzDlQmfejfT..."
274
+ response["signature-input"]
275
+ => "sig1=(\"@status\" \"foo\");created=1746480237"
276
+ result = Linzer.verify!(response, key: my_key)
277
+ # => true
278
+ ```
279
+ ---
280
+
281
+ Furthermore, on some low-level scenarios where a user wants or needs additional
282
+ control on how the signing and verification routines are performed, Linzer allows
283
+ to manipulate instances of internal HTTP messages (requests & responses, see
284
+ `Linzer::Message` class and available adapters), signature objects
285
+ (`Linzer::Signature`) and how to register additional message adapters for any
286
+ HTTP ruby library not supported out of the box by this gem.
287
+
288
+ See below for a few examples of these scenarios.
289
+
290
+ #### To verify a valid signature:
135
291
 
136
292
  ```ruby
137
293
  test_ed25519_key_pub = key.material.public_to_pem
@@ -140,12 +296,6 @@ test_ed25519_key_pub = key.material.public_to_pem
140
296
  pubkey = Linzer.new_ed25519_public_key(test_ed25519_key_pub, "some-key-ed25519")
141
297
  # => #<Linzer::Ed25519::Key:0x00000fe19b9384b0
142
298
 
143
- # if you have to, there is a helper method to build a request object on the server side
144
- # although any standard Ruby web server or framework (Sinatra, Rails, etc) should expose
145
- # a request object and this should not be required for most cases.
146
- #
147
- # request = Linzer.new_request(:post, "/some_uri", {}, headers)
148
-
149
299
  message = Linzer::Message.new(request)
150
300
 
151
301
  signature = Linzer::Signature.build(message.headers)
@@ -163,14 +313,14 @@ Linzer.verify(pubkey, message, signature, no_older_than: 500)
163
313
  `no_older_than` expects a number of seconds, but you can pass anything that to responds to `#to_i`, including an `ActiveSupport::Duration`.
164
314
  `::verify` will raise if the `created` parameter of the signature is older than the given number of seconds.
165
315
 
166
- ### What if an invalid signature if verified?
316
+ #### What if an invalid signature if verified?
167
317
 
168
318
  ```ruby
169
319
  result = Linzer.verify(pubkey, message, signature)
170
320
  lib/linzer/verifier.rb:38:in `verify_or_fail': Failed to verify message: Invalid signature. (Linzer::Error)
171
321
  ```
172
322
 
173
- ### HTTP responses are also supported
323
+ #### HTTP responses are also supported
174
324
 
175
325
  HTTP responses can also be signed and verified in the same way as requests.
176
326
 
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Linzer
4
+ module HTTP
5
+ module Bootstrap
6
+ class << self
7
+ def require_dependencies
8
+ require "http"
9
+ require_relative "../message/adapter/http_gem/request"
10
+ end
11
+
12
+ def load_dependencies
13
+ require_dependencies
14
+ rescue LoadError
15
+ msg = "http gem is required to be installed to use this feature."
16
+ raise Linzer::Error, msg
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "bootstrap"
4
+
5
+ module Linzer
6
+ module HTTP
7
+ class << self
8
+ def register_adapter
9
+ request_class = ::HTTP::Request
10
+ adapter_class = Linzer::Message::Adapter::HTTPGem::Request
11
+ Linzer::Message.register_adapter(request_class, adapter_class)
12
+ end
13
+ end
14
+
15
+ Bootstrap.load_dependencies
16
+ register_adapter
17
+
18
+ class SignatureFeature < ::HTTP::Feature
19
+ def initialize(key:, params: {}, covered_components: default_components)
20
+ @fields = Array(covered_components)
21
+ @key = validate_key(key)
22
+ @params = Hash(params)
23
+ end
24
+
25
+ attr_reader :fields, :params
26
+
27
+ def wrap_request(request)
28
+ message = Linzer::Message.new(request)
29
+ signature = Linzer.sign(key, message, fields, **params)
30
+ request.headers.merge!(signature.to_h)
31
+ request
32
+ end
33
+
34
+ def default_covered_components
35
+ Linzer::Options::DEFAULT[:covered_components]
36
+ end
37
+
38
+ alias_method :default_components, :default_covered_components
39
+
40
+ private
41
+
42
+ attr_reader :key
43
+
44
+ def validate_key(key)
45
+ raise ::HTTP::Error, "Key can not be nil!" if !key
46
+ raise ::HTTP::Error, "Key object is invalid!" if !key.respond_to?(:sign)
47
+ key
48
+ end
49
+
50
+ ::HTTP::Options.register_feature(:http_signature, self)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+
5
+ module Linzer
6
+ module HTTP
7
+ extend self
8
+
9
+ def self.known_http_methods
10
+ Net::HTTP
11
+ .constants
12
+ .map { |const| Net::HTTP.const_get(const) }
13
+ .select { |klass| klass.is_a?(Class) && klass.const_defined?(:METHOD) }
14
+ .map { |klass| klass::METHOD }
15
+ .freeze
16
+ end
17
+
18
+ known_http_methods.each do |http_method| # e.g.:
19
+ method = http_method.downcase.to_sym #
20
+ define_method(method) do |uri, options| # def post(uri, **options)
21
+ options ||= {} # request :post, uri, options
22
+ request method, uri, options # end
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def request(verb, uri, options = {})
29
+ validate_verb(verb)
30
+
31
+ key = options[:key]
32
+ validate_key(key)
33
+
34
+ req_uri = URI(uri)
35
+ http = Net::HTTP.new(req_uri.host, req_uri.port)
36
+ http.set_debug_output($stderr) if options[:debug]
37
+
38
+ headers = build_headers(options[:headers] || {})
39
+ request = build_request(verb, uri, headers)
40
+ message = Linzer::Message.new(request)
41
+ components = options[:covered_components] || default_components
42
+ params = options[:params] || {}
43
+ signature = Linzer.sign(key, message, components, **params)
44
+
45
+ do_request(http, uri, verb, options[:data], signature, headers)
46
+ end
47
+
48
+ def default_components
49
+ Linzer::Options::DEFAULT[:covered_components]
50
+ end
51
+
52
+ def validate_verb(verb)
53
+ method_name = verb.to_s.upcase
54
+ if !known_http_methods.include?(method_name)
55
+ raise Linzer::Error, "Unknown/unsupported HTTP method: '#{method_name}'"
56
+ end
57
+ end
58
+
59
+ def validate_key(key)
60
+ raise Linzer::Error, "Key can not be nil!" if !key
61
+ raise Linzer::Error, "Key object is invalid!" if !key.respond_to?(:sign)
62
+ key
63
+ end
64
+
65
+ def build_headers(headers)
66
+ headers.merge({"user-agent" => "Linzer/#{Linzer::VERSION}"})
67
+ end
68
+
69
+ def build_request(method, uri, headers)
70
+ request_class = Net::HTTP.const_get(method.to_s.capitalize)
71
+ request = request_class.new(URI(uri))
72
+ headers.map { |k, v| request[k] = v }
73
+ request
74
+ end
75
+
76
+ def with_body?(verb)
77
+ # common HTTP
78
+ return false if %i[get head options trace delete].include?(verb)
79
+ # WebDAV
80
+ return false if %i[copy move].include?(verb)
81
+
82
+ # everything else that could have a body:
83
+ # common HTTP: post, put, patch
84
+ # WebDAV: lock, unlock, mkcol, propfind, proppatch
85
+ true
86
+ end
87
+
88
+ def do_request(http, uri, verb, data, signature, headers)
89
+ if with_body?(verb)
90
+ if !data
91
+ missed_body = "Missing request body on HTTP request: '#{verb.upcase}'"
92
+ raise Linzer::Error, missed_body
93
+ end
94
+ http.public_send(verb, uri, data, headers.merge(signature.to_h))
95
+ else
96
+ http.public_send(verb, uri, headers.merge(signature.to_h))
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Linzer
4
+ class Message
5
+ module Adapter
6
+ class Abstract
7
+ def initialize(operation, **options)
8
+ raise Linzer::Error, "Cannot instantiate an abstract class!"
9
+ end
10
+
11
+ def request?
12
+ self.class.to_s.include?("Request")
13
+ end
14
+
15
+ def response?
16
+ self.class.to_s.include?("Response")
17
+ end
18
+
19
+ # XXX: attached request as specified in RFC has to be tested for Net::HTTP classes
20
+ # and custom HTTP message classes
21
+ def attached_request?
22
+ response? && !!@attached_request
23
+ end
24
+
25
+ def field?(f)
26
+ !!self[f]
27
+ end
28
+
29
+ def [](field_name)
30
+ name = parse_field_name(field_name)
31
+ return nil if name.nil?
32
+
33
+ if field_name.start_with?("@")
34
+ retrieve(name, :derived)
35
+ else
36
+ retrieve(name, :field)
37
+ end
38
+ end
39
+
40
+ def headers
41
+ raise Linzer::Error, "Sub-classes are required to implement this method!"
42
+ end
43
+
44
+ def attach!(signature)
45
+ raise Linzer::Error, "Sub-classes are required to implement this method!"
46
+ end
47
+
48
+ private
49
+
50
+ def parse_field_name(field_name)
51
+ if field_name&.start_with?("@")
52
+ Starry.parse_item(field_name[1..])
53
+ else
54
+ Starry.parse_item(field_name)
55
+ end
56
+ rescue => _
57
+ nil
58
+ end
59
+
60
+ def validate_attached_request(message)
61
+ msg = "The attached message is not a valid HTTP request!"
62
+ raise Linzer::Error, msg unless message.request?
63
+ end
64
+
65
+ def validate_parameters(name, method)
66
+ has_unknown = name.parameters.any? { |p, _| !KNOWN_PARAMETERS.include?(p) }
67
+ return nil if has_unknown
68
+
69
+ has_name = name.parameters["name"]
70
+ has_req = name.parameters["req"]
71
+ has_sf = name.parameters["sf"] || name.parameters.key?("key")
72
+ has_bs = name.parameters["bs"]
73
+ value = name.value
74
+
75
+ # Section 2.2.8 of RFC 9421
76
+ return nil if has_name && value != :"query-param"
77
+
78
+ # No derived values come from trailers section
79
+ return nil if method == :derived && name.parameters["tr"]
80
+
81
+ # From: 2.1. HTTP Fields:
82
+ # The bs parameter, which requires the raw bytes of the field values
83
+ # from the message, is not compatible with the use of the sf or key
84
+ # parameters, which require the parsed data structures of the field
85
+ # values after combination
86
+ return nil if has_sf && has_bs
87
+
88
+ # req param only makes sense on responses with an associated request
89
+ # return nil if has_req && (!response? || !attached_request?)
90
+ return nil if has_req && !response?
91
+
92
+ name
93
+ end
94
+
95
+ KNOWN_PARAMETERS = %w[sf key bs req tr name]
96
+ private_constant :KNOWN_PARAMETERS
97
+
98
+ def retrieve(name, method)
99
+ if !name.parameters.empty?
100
+ valid_params = validate_parameters(name, method)
101
+ return nil if !valid_params
102
+ end
103
+
104
+ has_req = name.parameters["req"]
105
+ has_sf = name.parameters["sf"] || name.parameters.key?("key")
106
+ has_bs = name.parameters["bs"]
107
+
108
+ if has_req
109
+ name.parameters.delete("req")
110
+ return req(name, method)
111
+ end
112
+
113
+ value = send(method, name)
114
+
115
+ case
116
+ when has_sf
117
+ key = name.parameters["key"]
118
+ sf(value, key)
119
+ when has_bs then bs(value)
120
+ else value
121
+ end
122
+ end
123
+
124
+ def sf(value, key = nil)
125
+ dict = Starry.parse_dictionary(value)
126
+
127
+ if key
128
+ obj = dict[key]
129
+ Starry.serialize(obj.is_a?(Starry::InnerList) ? [obj] : obj)
130
+ else
131
+ Starry.serialize(dict)
132
+ end
133
+ end
134
+
135
+ def bs(value)
136
+ Starry.serialize(value.encode(Encoding::ASCII_8BIT))
137
+ end
138
+
139
+ def tr(trailer)
140
+ @operation.body.trailers[trailer.value.to_s]
141
+ end
142
+
143
+ def req(field, method)
144
+ case method
145
+ when :derived then @attached_request["@#{field}"]
146
+ when :field then @attached_request[field.to_s]
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Linzer
4
+ class Message
5
+ module Adapter
6
+ module HTTPGem
7
+ class Request < Linzer::Message::Adapter::NetHTTP::Request
8
+ def headers
9
+ @operation.headers.to_h
10
+ end
11
+
12
+ private
13
+
14
+ def derived(name)
15
+ return @operation.verb.to_s.upcase if name.value == :method
16
+ super
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Example HTTP message adapter for HTTP::Response class from http ruby gem.
4
+ # https://github.com/httprb/http
5
+ # It's not required automatically to avoid making http gem a dependency.
6
+ #
7
+ module Linzer
8
+ class Message
9
+ module Adapter
10
+ module HTTPGem
11
+ class Response < Abstract
12
+ def initialize(operation, **options)
13
+ @operation = operation
14
+ freeze
15
+ end
16
+
17
+ def headers
18
+ @operation.headers
19
+ end
20
+
21
+ # XXX: this implementation is incomplete, e.g.: ;tr parameter is not supported yet
22
+ def [](field_name)
23
+ return @operation.code if field_name == "@status"
24
+ @operation[field_name]
25
+ end
26
+
27
+ def attach!(signature)
28
+ signature.to_h.each { |h, v| @operation[h] = v }
29
+ @operation
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end