linzer 0.5.1 → 0.6.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: 1955734eb1a8115753448b579b0cbe84f7d1ad247677ae20dd49495841b4aef3
4
- data.tar.gz: abf9b2fbc465f10210c77f3d29e10c7e15af26a5badba90516f0b96050e3a548
3
+ metadata.gz: c09a63a7eae654921b40e0462b463b3e70867522fcedaf42f03373dd72ef55a5
4
+ data.tar.gz: b5fea79104954db4289fbf7e4df9f8e589827943cb62eb2836bd0b970611263a
5
5
  SHA512:
6
- metadata.gz: 5263ec042dd91dc5bc434f4bbccc8f17d0ec52c751b7b9adec613c87efa180175cc7e5b4f47da007e6e61298a2b153f9ca05250ba3d55786c2d5609725233903
7
- data.tar.gz: 45ad9720b910f441bf36f9766ec10eaaab64fb7a36f420b947a5170d9cae0d18d540f380996eba2265b6faa0df0b9537fb1c8b2b2e58c94bab45abcaccb19112
6
+ metadata.gz: 89572382dde42b597eeafecceca1572222cff5d20bba50345b0e3849f47d510cdbcfbc0988295e16b783aa9d28235a95e56b49302a53577e744dc8575253a419
7
+ data.tar.gz: bc8c2850994c211b4549dcd07d64ddb14fd54d948a05153d8011042eb4cd2b4adca4d8f80ab983a8c8c71deefc2473994fa479ec8b7ec00063dfa86ead7be7a4
data/.standard.yml CHANGED
@@ -3,9 +3,7 @@
3
3
  ruby_version: 2.6
4
4
 
5
5
  ignore:
6
- - 'lib/**/*':
7
- - Layout/ExtraSpacing
8
- - Layout/HashAlignment
9
- - 'spec/**/*':
6
+ - '**/*':
10
7
  - Layout/ExtraSpacing
11
8
  - Layout/HashAlignment
9
+ - Layout/ArgumentAlignment
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.6.0] - 2024-04-06
4
+
5
+ - Support parameters on HTTP field component identifiers.
6
+
7
+ - Add a work-around for failed unit-tests in ruby HEAD CI jobs.
8
+
9
+ - Set up simplecov and improve unit-tests coverage.
10
+
11
+ ## [0.5.2] - 2024-04-02
12
+
13
+ - Make all unit tests pass on Ruby 3.0:
14
+ * Set minimum required version on openssl and uri gems.
15
+ - Small refactor on ECDSA module.
16
+
3
17
  ## [0.5.1] - 2024-04-01
4
18
 
5
19
  - Add support for additional derived components:
data/README.md CHANGED
@@ -159,6 +159,9 @@ Please note that is still early days and extensive testing is still ongoing. For
159
159
  I'll be expanding the library to cover more functionality specified in the RFC
160
160
  in subsequent releases.
161
161
 
162
+ ## Ruby version compatibility
163
+
164
+ linzer is built in [Continuous Integration](https://github.com/nomadium/linzer/actions/workflows/main.yml) on Ruby 3.0+.
162
165
 
163
166
  ## Development
164
167
 
data/lib/linzer/ecdsa.rb CHANGED
@@ -18,23 +18,25 @@ module Linzer
18
18
 
19
19
  private
20
20
 
21
+ DIGEST_PARAMS = {
22
+ "SHA256" => {hex_format: "%.64x", hex_length: 64},
23
+ "SHA384" => {hex_format: "%.96x", hex_length: 96}
24
+ }
25
+ private_constant :DIGEST_PARAMS
26
+
21
27
  def der_signature(sig)
22
28
  digest = @params[:digest]
23
29
  msg = "Cannot verify invalid signature."
30
+ raise Linzer::Error.new(msg) unless DIGEST_PARAMS.key?(digest)
31
+ digest_params = DIGEST_PARAMS[digest]
32
+
33
+ l = digest_params[:hex_length]
34
+ raise Linzer::Error.new(msg) if sig.length != l
35
+ h = l / 2
36
+ fmt = "H#{l}"
24
37
 
25
- case digest
26
- when "SHA256"
27
- raise Linzer::Error.new(msg) if sig.length != 64
28
- r_bn = OpenSSL::BN.new(sig[0..31].unpack1("H64").to_i(16))
29
- s_bn = OpenSSL::BN.new(sig[32..63].unpack1("H64").to_i(16))
30
- when "SHA384"
31
- raise Linzer::Error.new(msg) if sig.length != 96
32
- r_bn = OpenSSL::BN.new(sig[0..47].unpack1("H96").to_i(16))
33
- s_bn = OpenSSL::BN.new(sig[48..95].unpack1("H96").to_i(16))
34
- else
35
- msg = "Cannot verify signature, unsupported digest algorithm: '%s'" % digest
36
- raise Linzer::Error.new(msg)
37
- end
38
+ r_bn = OpenSSL::BN.new(sig[0..(h - 1)].unpack1(fmt).to_i(16))
39
+ s_bn = OpenSSL::BN.new(sig[h..(l - 1)].unpack1(fmt).to_i(16))
38
40
 
39
41
  r = OpenSSL::ASN1::Integer(r_bn)
40
42
  s = OpenSSL::ASN1::Integer(s_bn)
@@ -46,17 +48,15 @@ module Linzer
46
48
  def decode_der_signature(der_sig)
47
49
  digest = @params[:digest]
48
50
  msg = "Unsupported digest algorithm: '%s'" % digest
51
+ raise Linzer::Error.new(msg) unless DIGEST_PARAMS.key?(digest)
52
+ digest_params = DIGEST_PARAMS[digest]
53
+ fmt = "H#{digest_params[:hex_length]}"
54
+
49
55
  OpenSSL::ASN1
50
56
  .decode(der_sig)
51
57
  .value
52
- .map do |n|
53
- case digest
54
- when "SHA256" then "%.64x" % n.value
55
- when "SHA384" then "%.96x" % n.value
56
- else raise Linzer::Error.new(msg)
57
- end
58
- end
59
- .map { |s| [s].pack("H#{s.length}") }
58
+ .map { |bn| digest_params[:hex_format] % bn.value }
59
+ .map { |hex| [hex].pack(fmt) }
60
60
  .reduce(:<<)
61
61
  .encode(Encoding::ASCII_8BIT)
62
62
  end
@@ -2,8 +2,10 @@
2
2
 
3
3
  module Linzer
4
4
  class Message
5
- def initialize(operation)
5
+ def initialize(operation, attached_request: nil)
6
6
  @operation = operation
7
+ validate
8
+ @attached_request = attached_request ? Message.new(attached_request) : nil
7
9
  freeze
8
10
  end
9
11
 
@@ -15,6 +17,10 @@ module Linzer
15
17
  @operation.is_a?(Rack::Response) || @operation.respond_to?(:status)
16
18
  end
17
19
 
20
+ def attached_request?
21
+ !!@attached_request
22
+ end
23
+
18
24
  def headers
19
25
  return @operation.headers if response? || @operation.respond_to?(:headers)
20
26
 
@@ -26,32 +32,26 @@ module Linzer
26
32
  end
27
33
 
28
34
  DERIVED_COMPONENT = {
29
- "@method" => :request_method,
30
- "@authority" => :authority,
31
- "@path" => :path_info,
32
- "@status" => :status,
33
- "@target-uri" => :url,
34
- "@scheme" => :scheme,
35
- "@request-target" => :fullpath,
36
- "@query" => :query_string
35
+ method: :request_method,
36
+ authority: :authority,
37
+ path: :path_info,
38
+ status: :status,
39
+ "target-uri": :url,
40
+ scheme: :scheme,
41
+ "request-target": :fullpath,
42
+ query: :query_string
37
43
  }.freeze
38
44
 
39
45
  def [](field_name)
40
- if !field_name.start_with?("@")
41
- return @operation.env[Request.rack_header_name(field_name)] if request?
42
- return @operation.headers[field_name] # if response?
43
- end
46
+ name = parse_field_name(field_name)
47
+ return nil if name.nil?
44
48
 
45
- method = DERIVED_COMPONENT[field_name]
46
-
47
- case field_name
48
- when "@query"
49
- return "?#{@operation.public_send(method)}"
50
- when /\A(?<field>(?<prefix>@query-param)(?<rest>;name=.+)\Z)/
51
- return parse_query_param Regexp.last_match
49
+ if field_name.start_with?("@")
50
+ return nil if name.parameters["tr"]
51
+ retrieve(name, :derived)
52
+ else
53
+ retrieve(name, :field)
52
54
  end
53
-
54
- method ? @operation.public_send(method) : nil
55
55
  end
56
56
 
57
57
  class << self
@@ -64,14 +64,105 @@ module Linzer
64
64
 
65
65
  private
66
66
 
67
- def parse_query_param(match_data)
68
- raw_item = '"%s"%s' % [match_data[:prefix], match_data[:rest]]
69
- parsed_item = Starry.parse_item(raw_item)
70
- fail unless parsed_item.value == "@query-param"
71
- param_name = URI.decode_uri_component(parsed_item.parameters["name"])
72
- URI.encode_uri_component(@operation.params.fetch(param_name))
67
+ def validate
68
+ msg = "Message instance must be an HTTP request or response"
69
+ raise Error.new msg if response? == request?
70
+ end
71
+
72
+ def parse_field_name(field_name)
73
+ if field_name&.start_with?("@")
74
+ Starry.parse_item(field_name[1..])
75
+ else
76
+ Starry.parse_item(field_name)
77
+ end
78
+ rescue => _
79
+ nil
80
+ end
81
+
82
+ def retrieve(name, method)
83
+ has_req = name.parameters["req"]
84
+ has_sf = name.parameters["sf"] || name.parameters.key?("key")
85
+ has_bs = name.parameters["bs"]
86
+
87
+ return nil if has_req && (!response? || !attached_request?)
88
+
89
+ if has_req
90
+ name.parameters.delete("req")
91
+ return req(name, method)
92
+ end
93
+
94
+ value = send(method, name)
95
+
96
+ key = name.parameters["key"]
97
+ value = sf(value, key) if has_sf
98
+ value = bs(value) if has_bs
99
+ value
100
+ end
101
+
102
+ def derived(name)
103
+ method = DERIVED_COMPONENT[name.value]
104
+
105
+ value = case name.value
106
+ when :query then derive(@operation, method)
107
+ when :"query-param" then query_param(name)
108
+ end
109
+
110
+ return nil if !method && !value
111
+ value || derive(@operation, method)
112
+ end
113
+
114
+ def field(name)
115
+ has_tr = name.parameters["tr"]
116
+ if has_tr
117
+ value = tr(name)
118
+ else
119
+ if request?
120
+ rack_header_name = Request.rack_header_name(name.value.to_s)
121
+ value = @operation.env[rack_header_name]
122
+ end
123
+ value = @operation.headers[name.value.to_s] if response?
124
+ end
125
+ value.dup&.strip
126
+ end
127
+
128
+ def derive(operation, method)
129
+ return nil unless operation.respond_to?(method)
130
+ value = operation.public_send(method)
131
+ return "?" + value if method == :query_string
132
+ value
133
+ end
134
+
135
+ def query_param(name)
136
+ param_name = name.parameters["name"]
137
+ return nil if !param_name
138
+ decoded_param_name = URI.decode_uri_component(param_name)
139
+ URI.encode_uri_component(@operation.params.fetch(decoded_param_name))
73
140
  rescue => _
74
141
  nil
75
142
  end
143
+
144
+ def sf(value, key = nil)
145
+ dict = Starry.parse_dictionary(value)
146
+
147
+ obj = dict[key] if key
148
+ return Starry.serialize(obj.is_a?(Starry::InnerList) ? [obj] : obj) if key
149
+
150
+ Starry.serialize(dict)
151
+ end
152
+
153
+ def bs(value)
154
+ Starry.serialize(value.encode(Encoding::ASCII_8BIT))
155
+ end
156
+
157
+ def tr(trailer)
158
+ @operation.body.trailers[trailer.value.to_s]
159
+ end
160
+
161
+ def req(field, method)
162
+ case method
163
+ when :derived then @attached_request["@#{field}"]
164
+ when :field then @attached_request[field.to_s]
165
+ end
166
+ end
76
167
  end
77
168
  end
@@ -84,6 +84,7 @@ module Linzer
84
84
  def build_rack_env(headers)
85
85
  headers
86
86
  .to_hash
87
+ .transform_values(&:to_s)
87
88
  .transform_keys { |k| k.upcase.tr("-", "_") }
88
89
  .transform_keys do |k|
89
90
  %w[CONTENT_TYPE CONTENT_LENGTH].include?(k) ? k : "HTTP_#{k}"
@@ -3,7 +3,7 @@
3
3
  module Linzer
4
4
  module Response
5
5
  def new_response(body = nil, status = 200, headers = {})
6
- Rack::Response.new(body, status, headers)
6
+ Rack::Response.new(body, status, headers.transform_values(&:to_s))
7
7
  end
8
8
  end
9
9
  end
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Linzer
4
- VERSION = "0.5.1"
4
+ VERSION = "0.6.0"
5
+
6
+ def self.ruby_dev?
7
+ RUBY_ENGINE == "ruby" && RUBY_PATCHLEVEL == -1 && /\Aruby 3.[0-9].0dev/ =~ RUBY_DESCRIPTION
8
+ end
5
9
  end
data/lib/linzer.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require "starry"
4
4
  require "openssl"
5
5
  require "rack"
6
+ require "uri"
6
7
 
7
8
  require_relative "linzer/version"
8
9
  require_relative "linzer/common"
data/linzer.gemspec CHANGED
@@ -29,7 +29,9 @@ Gem::Specification.new do |spec|
29
29
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
30
  spec.require_paths = ["lib"]
31
31
 
32
+ spec.add_runtime_dependency "openssl", "~> 3.0", ">= 3.0.0"
32
33
  spec.add_runtime_dependency "ed25519", "~> 1.3", ">= 1.3.0"
33
- spec.add_runtime_dependency "starry", "~> 0.1"
34
+ spec.add_runtime_dependency "starry", "~> 0.1" unless Linzer.ruby_dev?
34
35
  spec.add_runtime_dependency "rack", "~> 3.0"
36
+ spec.add_runtime_dependency "uri", "~> 0.12", ">= 0.12.0"
35
37
  end
metadata CHANGED
@@ -1,15 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: linzer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Miguel Landaeta
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-04-01 00:00:00.000000000 Z
11
+ date: 2024-04-06 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: openssl
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 3.0.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '3.0'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 3.0.0
13
33
  - !ruby/object:Gem::Dependency
14
34
  name: ed25519
15
35
  requirement: !ruby/object:Gem::Requirement
@@ -58,6 +78,26 @@ dependencies:
58
78
  - - "~>"
59
79
  - !ruby/object:Gem::Version
60
80
  version: '3.0'
81
+ - !ruby/object:Gem::Dependency
82
+ name: uri
83
+ requirement: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - "~>"
86
+ - !ruby/object:Gem::Version
87
+ version: '0.12'
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: 0.12.0
91
+ type: :runtime
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: '0.12'
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: 0.12.0
61
101
  description:
62
102
  email:
63
103
  - miguel@miguel.cc