linzer 0.7.9.beta1 → 0.7.9.beta3

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.
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Linzer
4
+ class Message
5
+ module Adapter
6
+ module Faraday
7
+ # Adapter for {::Faraday::Response} objects from the faraday gem.
8
+ #
9
+ # Extends the generic response adapter with faraday-specific
10
+ # derived component retrieval (e.g. +@status+) and header
11
+ # attachment.
12
+ #
13
+ # @note Not loaded automatically to avoid making faraday a hard
14
+ # dependency. Require +"linzer/faraday"+ to register this adapter.
15
+ #
16
+ # @see Generic::Response
17
+ # @see https://github.com/lostisland/faraday faraday gem
18
+ class Response < Generic::Response
19
+ # Attaches a signature to the underlying response headers.
20
+ #
21
+ # @param signature [Linzer::Signature] the signature to attach
22
+ # @return [::Faraday::Response] the underlying response object
23
+ def attach!(signature)
24
+ signature.to_h.each { |h, v| @operation.headers[h] = v }
25
+ @operation
26
+ end
27
+
28
+ private
29
+
30
+ # Resolves a derived component value from the response.
31
+ #
32
+ # @param name [Starry::Item] the parsed component identifier
33
+ # @return [Integer, nil] the HTTP status code for +@status+,
34
+ # or +nil+ if the component is unknown
35
+ def derived(name)
36
+ case name.value
37
+ when "@status" then @operation.status.to_i
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -51,31 +51,35 @@ module Linzer
51
51
  private
52
52
 
53
53
  def derived(name)
54
- unimplemented_method = 'Derived field "@method" lookup is not implemented!'
54
+ unimplemented_method = 'Derived field "%s" lookup is not implemented!'
55
+
56
+ uri = @operation.uri rescue nil
57
+ raise Error, unimplemented_method % name.value if uri.nil?
58
+
55
59
  case name.value
56
- when "@method" then raise Error, unimplemented_method
57
- when "@target-uri" then @operation.uri.to_s
58
- when "@authority" then @operation.uri.authority.downcase
59
- when "@scheme" then @operation.uri.scheme.downcase
60
- when "@request-target" then @operation.uri.request_uri
61
- when "@path" then @operation.uri.path
62
- when "@query" then "?%s" % String(@operation.uri.query)
63
- when "@query-param" then query_param(name)
60
+ when "@method" then raise Error, unimplemented_method % name.value
61
+ when "@target-uri" then uri.to_s
62
+ when "@authority" then uri.authority.downcase
63
+ when "@scheme" then uri.scheme.downcase
64
+ when "@request-target" then uri.request_uri
65
+ when "@path" then uri.path
66
+ when "@query" then "?%s" % String(uri.query)
67
+ when "@query-param" then query_param(uri.query, name)
64
68
  end
65
69
  end
66
70
 
67
- def query_param(name)
71
+ def query_param(uri_query, name)
68
72
  param_name = name.parameters["name"]
69
73
  return nil if !param_name
70
74
  decoded_param_name = URI.decode_uri_component(param_name)
71
- params = CGI.parse(@operation.uri.query)
75
+ params = CGI.parse(uri_query)
72
76
  URI.encode_uri_component(params[decoded_param_name]&.first)
73
77
  end
74
78
 
75
79
  def field(name)
76
80
  has_tr = name.parameters["tr"]
77
81
  return nil if has_tr # HTTP requests don't have trailer fields
78
- value = @operation[name.value.to_s]
82
+ value = header(name.value.to_s)
79
83
  value.dup&.strip
80
84
  end
81
85
  end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Linzer
4
+ class Message
5
+ module Adapter
6
+ # http.rb gem message adapters.
7
+ #
8
+ # Provides adapters for {HTTP::Request} and {HTTP::Response} objects
9
+ # from the http.rb gem.
10
+ #
11
+ # @note These adapters are loaded on-demand when using the
12
+ # {Linzer::HTTP::SignatureFeature}.
13
+ module HTTPGem
14
+ # Shared functionality for http.rb request and response adapters.
15
+ module Common
16
+ # Retrieves a header value by name.
17
+ # @param name [String] The header name
18
+ # @return [String, nil] The header value
19
+ def header(name)
20
+ @operation.headers[name]
21
+ end
22
+
23
+ # Attaches a signature to the response.
24
+ # @param signature [Signature] The signature to attach
25
+ # @return [Object] The underlying response object
26
+ def attach!(signature)
27
+ signature.to_h.each { |h, v| @operation.headers[h] = v }
28
+ @operation
29
+ end
30
+
31
+ private
32
+
33
+ # Retrieves an HTTP field value from the request or response headers.
34
+ #
35
+ # @param name [Starry::Item] the parsed component identifier
36
+ # @return [String, nil] the stripped header value, or +nil+ if the
37
+ # field has a +tr+ (trailer) parameter or is not present
38
+ def field(name)
39
+ has_tr = name.parameters["tr"]
40
+ return nil if has_tr # XXX: is there a library actually supporting trailers?
41
+ value = @operation.headers[name.value.to_s]
42
+ value.dup&.strip
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -1,24 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "linzer/message/adapter/http_gem/common"
4
+
3
5
  module Linzer
4
6
  class Message
5
7
  module Adapter
6
- # http.rb gem message adapters.
7
- #
8
- # Provides adapters for {HTTP::Request} and {HTTP::Response} objects
9
- # from the http.rb gem.
10
- #
11
- # @note These adapters are loaded on-demand when using the
12
- # {Linzer::HTTP::SignatureFeature}.
13
8
  module HTTPGem
14
9
  # Adapter for {HTTP::Request} objects from http.rb gem.
15
10
  #
16
11
  # Extends the generic request adapter with http.rb-specific
17
12
  # method name retrieval.
18
13
  class Request < Generic::Request
14
+ include HTTPGem::Common
15
+
19
16
  private
20
17
 
18
+ # Resolves a derived component value from the request.
19
+ #
20
+ # Overrides the generic implementation for http.rb-specific
21
+ # accessor methods: +uri.host+ for +@authority+ and
22
+ # +verb+ for +@method+.
23
+ #
24
+ # @param name [Starry::Item] the parsed component identifier
25
+ # @return [String, nil] the derived value
21
26
  def derived(name)
27
+ return @operation.uri.host if name.value == "@authority"
22
28
  return @operation.verb.to_s.upcase if name.value == "@method"
23
29
  super
24
30
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "linzer/message/adapter/http_gem/common"
4
+
3
5
  module Linzer
4
6
  class Message
5
7
  module Adapter
@@ -13,8 +15,17 @@ module Linzer
13
15
  #
14
16
  # @see https://github.com/httprb/http http.rb gem
15
17
  class Response < Generic::Response
18
+ include HTTPGem::Common
19
+
16
20
  private
17
21
 
22
+ # Resolves a derived component value from the response.
23
+ #
24
+ # Uses +HTTP::Response#status+ converted to Integer for
25
+ # the +@status+ component.
26
+ #
27
+ # @param name [Starry::Item] the parsed component identifier
28
+ # @return [Integer, nil] the HTTP status code, or +nil+ if unknown
18
29
  def derived(name)
19
30
  case name.value
20
31
  when "@status" then @operation.status.to_i
@@ -14,6 +14,14 @@ module Linzer
14
14
  class Request < Generic::Request
15
15
  private
16
16
 
17
+ # Resolves a derived component value from the request.
18
+ #
19
+ # Overrides the generic implementation to use
20
+ # +Net::HTTPRequest#method+ for the +@method+ component,
21
+ # which returns the HTTP verb as an uppercase string.
22
+ #
23
+ # @param name [Starry::Item] the parsed component identifier
24
+ # @return [String, nil] the derived value
17
25
  def derived(name)
18
26
  return @operation.method if name.value == "@method"
19
27
  super
@@ -11,6 +11,13 @@ module Linzer
11
11
  class Response < Generic::Response
12
12
  private
13
13
 
14
+ # Resolves a derived component value from the response.
15
+ #
16
+ # Uses +Net::HTTPResponse#code+ (a String) converted to Integer
17
+ # for the +@status+ component.
18
+ #
19
+ # @param name [Starry::Item] the parsed component identifier
20
+ # @return [Integer, nil] the HTTP status code, or +nil+ if unknown
14
21
  def derived(name)
15
22
  case name.value
16
23
  when "@status" then @operation.code.to_i
@@ -24,11 +24,19 @@ module Linzer
24
24
 
25
25
  private
26
26
 
27
+ # Validates that the operation is exclusively a request or response.
28
+ # @raise [Error] if the operation is both or neither
27
29
  def validate
28
30
  msg = "Message instance must be an HTTP request or response"
29
31
  raise Error.new msg if response? == request?
30
32
  end
31
33
 
34
+ # Validates that a header name is non-empty.
35
+ #
36
+ # @param name [String] the header name
37
+ # @return [String] the validated header name
38
+ # @raise [ArgumentError] if the name is blank
39
+ # @raise [Linzer::Error] if the name is otherwise invalid
32
40
  def validate_header_name(name)
33
41
  raise ArgumentError.new, "Blank header name." if name.empty?
34
42
  name.to_str
@@ -40,6 +48,13 @@ module Linzer
40
48
  # :nocov:
41
49
  end
42
50
 
51
+ # Converts an HTTP header name to Rack's environment key format.
52
+ #
53
+ # Rack stores headers as uppercase with underscores and an +HTTP_+
54
+ # prefix, except for +Content-Type+ and +Content-Length+.
55
+ #
56
+ # @param field_name [String] the HTTP header name (e.g. +"content-type"+)
57
+ # @return [String] the Rack env key (e.g. +"CONTENT_TYPE"+ or +"HTTP_ACCEPT"+)
43
58
  def rack_header_name(field_name)
44
59
  validate_header_name field_name
45
60
 
@@ -52,6 +67,10 @@ module Linzer
52
67
  end
53
68
  end
54
69
 
70
+ # Resolves a derived component value from the Rack request/response.
71
+ #
72
+ # @param name [Starry::Item] the parsed component identifier
73
+ # @return [String, nil] the derived value, or +nil+ if unknown
55
74
  def derived(name)
56
75
  method = DERIVED_COMPONENT[name.value]
57
76
 
@@ -64,6 +83,11 @@ module Linzer
64
83
  value || derive(@operation, method)
65
84
  end
66
85
 
86
+ # Retrieves an HTTP field value from the Rack request or response.
87
+ #
88
+ # @param name [Starry::Item] the parsed component identifier
89
+ # @return [String, nil] the stripped header value, or +nil+ if the
90
+ # field has a +tr+ (trailer) parameter or is not present
67
91
  def field(name)
68
92
  has_tr = name.parameters["tr"]
69
93
  return nil if has_tr
@@ -79,6 +103,14 @@ module Linzer
79
103
  field_value.dup&.strip
80
104
  end
81
105
 
106
+ # Invokes a method on the Rack operation to extract a derived value.
107
+ #
108
+ # Applies post-processing for +@query+ (prepends +?+) and
109
+ # +@authority+/+@scheme+ (downcases).
110
+ #
111
+ # @param operation [Rack::Request, Rack::Response] the Rack object
112
+ # @param method [Symbol] the method to call
113
+ # @return [String, nil] the derived value
82
114
  def derive(operation, method)
83
115
  return nil unless operation.respond_to?(method)
84
116
  value = operation.public_send(method)
@@ -87,6 +119,11 @@ module Linzer
87
119
  value
88
120
  end
89
121
 
122
+ # Extracts a single query parameter value by name.
123
+ #
124
+ # @param name [Starry::Item] the component with a +name+ parameter
125
+ # @return [String, nil] the percent-encoded parameter value, or
126
+ # +nil+ if the parameter is missing or not found
90
127
  def query_param(name)
91
128
  param_name = name.parameters["name"]
92
129
  return nil if !param_name
@@ -39,6 +39,14 @@ module Linzer
39
39
 
40
40
  private
41
41
 
42
+ # Parses an unserialized component identifier with parameters.
43
+ #
44
+ # Splits on +;+ to separate the field name from parameters,
45
+ # then serializes the field name and collects parameters.
46
+ #
47
+ # @param field_name [String] e.g. +"content-type;bs"+ or
48
+ # +"example-dict;key=\"a\""+
49
+ # @return [Starry::Item] the parsed item with parameters
42
50
  def parse_unserialized_input(field_name)
43
51
  field, *raw_params = field_name.split(";")
44
52
  item = Starry.parse_item(Starry.serialize(field))
@@ -46,6 +54,13 @@ module Linzer
46
54
  item
47
55
  end
48
56
 
57
+ # Parses raw parameter strings into a merged Hash.
58
+ #
59
+ # Handles both boolean parameters (+";bs"+ → +{"bs" => true}+)
60
+ # and key-value parameters (+";key=\"a\""+ → +{"key" => "a"}+).
61
+ #
62
+ # @param str [Array<String>] raw parameter strings
63
+ # @return [Hash] merged parameter hash
49
64
  def collect_parameters(str)
50
65
  params = str.map do |param|
51
66
  if (tokens = param.split("=")) == [param] # e.g.: ";bs"
@@ -48,8 +48,14 @@ module Linzer
48
48
 
49
49
  attr_reader :adapters
50
50
 
51
- # Finds an adapter by checking if operation inherits from a known class.
52
- # This allows subclasses of Net::HTTPRequest etc. to work automatically.
51
+ # Finds an adapter by checking the operation's ancestry.
52
+ #
53
+ # This allows subclasses of registered classes (e.g.
54
+ # +Net::HTTP::Get < Net::HTTPRequest+) to use the parent's adapter
55
+ # without explicit registration.
56
+ #
57
+ # @param operation [Object] the HTTP message object
58
+ # @return [Class, nil] the adapter class, or +nil+ if no ancestor matches
53
59
  def find_ancestor(operation)
54
60
  adapters
55
61
  .select { |klz, adpt| operation.is_a? klz }
@@ -57,6 +63,10 @@ module Linzer
57
63
  .first
58
64
  end
59
65
 
66
+ # Raises an error for unsupported HTTP message types.
67
+ #
68
+ # @param operation [Object] the unsupported HTTP message
69
+ # @raise [Linzer::Error] with a message suggesting +register_adapter+
60
70
  def fail_with_unsupported(operation)
61
71
  err_msg = <<~EOM
62
72
  Unknown/unsupported HTTP message class: '#{operation.class}'!
@@ -102,6 +102,26 @@ module Linzer
102
102
  (Time.now.to_i - created) > seconds
103
103
  end
104
104
 
105
+ # Checks if the signature has expired based on the `expires` parameter.
106
+ #
107
+ # If the `expires` parameter is not present, the signature is considered
108
+ # not expired (returns false). If the parameter is present but not a valid
109
+ # integer, an error is raised.
110
+ #
111
+ # @return [Boolean] true if the signature has expired
112
+ # @raise [Error] If the `expires` parameter is not a valid integer
113
+ #
114
+ # @example Check if a signature has expired
115
+ # signature.expired? # => true or false
116
+ #
117
+ # @see https://www.rfc-editor.org/rfc/rfc9421.html#section-2.3 RFC 9421 Section 2.3
118
+ def expired?
119
+ return false if !parameters.key?("expires")
120
+ Time.now.to_i >= Integer(parameters["expires"])
121
+ rescue ArgumentError, TypeError
122
+ raise Error.new "Signature has a non-integer `expires` parameter"
123
+ end
124
+
105
125
  # Converts the signature to HTTP header format.
106
126
  #
107
127
  # Returns a hash suitable for setting as HTTP headers on a request or
@@ -23,6 +23,7 @@ module Linzer
23
23
  # - All covered components exist in the message
24
24
  # - The signature base matches what was signed
25
25
  # - The cryptographic signature is valid for the public key
26
+ # - The signature has not expired (if `expires` parameter is present)
26
27
  # - The signature is not older than `no_older_than` (if specified)
27
28
  #
28
29
  # @param key [Linzer::Key] The public key to verify with. Must respond to
@@ -85,6 +86,13 @@ module Linzer
85
86
  raise VerifyError, ex.message, cause: ex
86
87
  end
87
88
 
89
+ begin
90
+ exp_sig_msg = "Signature has expired or is invalid"
91
+ raise VerifyError, exp_sig_msg if signature.expired?
92
+ rescue Error => ex
93
+ raise VerifyError, ex.message, cause: ex
94
+ end
95
+
88
96
  return unless no_older_than
89
97
  old_sig_msg = "Signature created more than #{no_older_than} seconds ago"
90
98
  begin
@@ -3,5 +3,5 @@
3
3
  module Linzer
4
4
  # Current version of the Linzer gem.
5
5
  # @return [String]
6
- VERSION = "0.7.9.beta1"
6
+ VERSION = "0.7.9.beta3"
7
7
  end
@@ -5,42 +5,82 @@ require "yaml"
5
5
  module Rack
6
6
  module Auth
7
7
  class Signature
8
+ # Shared helpers for the Rack signature verification middleware.
9
+ #
10
+ # Organizes functionality into three sub-modules:
11
+ # - {Parameters} — validates required signature parameters
12
+ # - {Configuration} — loads and merges middleware options
13
+ # - {Key} — resolves verification keys by keyid
14
+ #
15
+ # @api private
8
16
  module Helpers
17
+ # Validates the presence of required signature parameters.
18
+ #
19
+ # Each method checks whether a specific parameter is required
20
+ # (per configuration) and, if so, whether it is present and valid
21
+ # in the current signature.
22
+ #
23
+ # @api private
9
24
  module Parameters
10
25
  private
11
26
 
27
+ # Checks if the +created+ parameter requirement is satisfied.
28
+ # @return [Boolean] +true+ if not required or present and valid
12
29
  def created?
13
30
  !options[:signatures][:created_required] || !!Integer(params.fetch("created"))
14
31
  end
15
32
 
33
+ # Checks if the +expires+ parameter requirement is satisfied.
34
+ # @return [Boolean] +true+ if not required or present and not yet expired
16
35
  def expires?
17
36
  return true if !options[:signatures][:expires_required]
18
37
  Integer(params.fetch("expires")) > Time.now.to_i
19
38
  end
20
39
 
40
+ # Checks if the +keyid+ parameter requirement is satisfied.
41
+ # @return [Boolean] +true+ if not required or present
21
42
  def keyid?
22
43
  !options[:signatures][:keyid_required] || String(params.fetch("keyid"))
23
44
  end
24
45
 
46
+ # Checks if the +nonce+ parameter requirement is satisfied.
47
+ # @return [Boolean] +true+ if not required or present
25
48
  def nonce?
26
49
  !options[:signatures][:nonce_required] || String(params.fetch("nonce"))
27
50
  end
28
51
 
52
+ # Checks if the +alg+ parameter requirement is satisfied.
53
+ # @return [Boolean] +true+ if not required or present
29
54
  def alg?
30
55
  !options[:signatures][:alg_required] || String(params.fetch("alg"))
31
56
  end
32
57
 
58
+ # Checks if the +tag+ parameter requirement is satisfied.
59
+ # @return [Boolean] +true+ if not required or present
33
60
  def tag?
34
61
  !options[:signatures][:tag_required] || String(params.fetch("tag"))
35
62
  end
36
63
  end
37
64
 
65
+ # Handles loading and merging of middleware configuration.
66
+ #
67
+ # Configuration can come from three sources (in order of precedence):
68
+ # 1. Options passed directly to the middleware constructor
69
+ # 2. A YAML configuration file (via +:config_path+)
70
+ # 3. {DEFAULT_OPTIONS}
71
+ #
72
+ # @api private
38
73
  module Configuration
74
+ # Returns the default covered components for signature verification.
75
+ # @return [Array<String>] the default components from {Linzer::Options::DEFAULT}
39
76
  def default_covered_components
40
77
  Linzer::Options::DEFAULT[:covered_components]
41
78
  end
42
79
  module_function :default_covered_components
43
80
 
81
+ # Default middleware configuration.
82
+ #
83
+ # @api private
44
84
  DEFAULT_OPTIONS = {
45
85
  signatures: {
46
86
  reject_older_than: 900,
@@ -62,6 +102,10 @@ module Rack
62
102
 
63
103
  private
64
104
 
105
+ # Loads and merges options from all sources.
106
+ #
107
+ # @param options [Hash] options passed to the middleware constructor
108
+ # @return [Hash] the merged configuration
65
109
  def load_options(options)
66
110
  options_from_file = load_options_from_config_file(options)
67
111
  {
@@ -78,6 +122,10 @@ module Rack
78
122
  }
79
123
  end
80
124
 
125
+ # Loads configuration from a YAML file.
126
+ #
127
+ # @param options [Hash] options containing +:config_path+
128
+ # @return [Hash] parsed configuration, or empty hash if unavailable
81
129
  def load_options_from_config_file(options)
82
130
  config_path = options[:config_path]
83
131
  YAML.safe_load_file(config_path, symbolize_names: true)
@@ -86,13 +134,29 @@ module Rack
86
134
  end
87
135
  end
88
136
 
137
+ # Resolves verification keys from the middleware configuration.
138
+ #
139
+ # Keys can be configured inline (with +:material+) or via file path
140
+ # (with +:path+). When a +keyid+ is present in the signature, the
141
+ # corresponding key is looked up in the +:keys+ hash. If not found,
142
+ # the +:default_key+ is used as fallback.
143
+ #
144
+ # @api private
89
145
  module Key
90
146
  private
91
147
 
148
+ # Returns the verification key for the current signature.
149
+ # @return [Linzer::Key] the resolved key
150
+ # @raise [Linzer::Error] if no key can be found
92
151
  def key
93
152
  build_key(params["keyid"])
94
153
  end
95
154
 
155
+ # Builds a key instance from configuration.
156
+ #
157
+ # @param keyid [String, nil] the key identifier from the signature
158
+ # @return [Linzer::Key] the resolved key
159
+ # @raise [Linzer::Error] if no matching key configuration is found
96
160
  def build_key(keyid)
97
161
  key_data = if keyid.nil? ||
98
162
  (!options[:keys].key?(keyid.to_sym) && options[:default_key])
@@ -114,6 +178,14 @@ module Rack
114
178
  instantiate_key(keyid || :default, alg, key_data)
115
179
  end
116
180
 
181
+ # Instantiates the appropriate key class for the given algorithm.
182
+ #
183
+ # @param keyid [String, Symbol] the key identifier
184
+ # @param alg [String, Symbol] the algorithm identifier
185
+ # (e.g. +"ed25519"+, +"rsa-pss-sha512"+)
186
+ # @param key_data [Hash] key configuration with +:material+
187
+ # @return [Linzer::Key] the instantiated key
188
+ # @raise [Linzer::Error] if the algorithm is unsupported
117
189
  def instantiate_key(keyid, alg, key_data)
118
190
  key_methods = {
119
191
  "rsa-pss-sha512" => :new_rsa_pss_sha512_key,
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: linzer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.9.beta1
4
+ version: 0.7.9.beta3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Miguel Landaeta
@@ -182,10 +182,14 @@ files:
182
182
  - examples/sinatra/myapp.rb
183
183
  - flake.lock
184
184
  - flake.nix
185
+ - lib/faraday/http_signature.rb
186
+ - lib/faraday/http_signature/middleware.rb
185
187
  - lib/linzer.rb
186
188
  - lib/linzer/common.rb
187
189
  - lib/linzer/ecdsa.rb
188
190
  - lib/linzer/ed25519.rb
191
+ - lib/linzer/faraday.rb
192
+ - lib/linzer/faraday/utils.rb
189
193
  - lib/linzer/helper.rb
190
194
  - lib/linzer/hmac.rb
191
195
  - lib/linzer/http.rb
@@ -197,8 +201,11 @@ files:
197
201
  - lib/linzer/message.rb
198
202
  - lib/linzer/message/adapter.rb
199
203
  - lib/linzer/message/adapter/abstract.rb
204
+ - lib/linzer/message/adapter/faraday/request.rb
205
+ - lib/linzer/message/adapter/faraday/response.rb
200
206
  - lib/linzer/message/adapter/generic/request.rb
201
207
  - lib/linzer/message/adapter/generic/response.rb
208
+ - lib/linzer/message/adapter/http_gem/common.rb
202
209
  - lib/linzer/message/adapter/http_gem/request.rb
203
210
  - lib/linzer/message/adapter/http_gem/response.rb
204
211
  - lib/linzer/message/adapter/net_http/request.rb