linzer 0.8.0.beta1 → 0.8.0.beta2

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: 0330e72cbbfb7cc56b9fa124a4f60a18bb7fbdab4eb98a072598c8fc3680cf76
4
- data.tar.gz: 321630c979fc736cc64284eb535b22752e50a42c8bbfae399cbf0db56efecdd3
3
+ metadata.gz: 7a917fda2b4d4646e642ba3a52d11bd41a500b5bfed044142a6e0434e0ef1c94
4
+ data.tar.gz: 8d9296ba21595cba696532a41b20f123569d3b31de0730ba6e7e6f693e87e009
5
5
  SHA512:
6
- metadata.gz: e6e99bf6a409b960d0432f702b286f7b71b06a34b61ff242d2d75eebc9c125bc02c6daa573efc5af69dfeb7c2f788811fb91d2f6f6decd1a869db26e6a5462e6
7
- data.tar.gz: 9c76877de5ba5164f677aad7be9ffb3f905f2fd874523b9f2aaff287abd0ebdd8d8c7272da22eddfcacb56a7eb325392e52e1b2a3b551e8940627b146190a895
6
+ metadata.gz: 2f6b61e4a340594891976d77b651527b8e0d49ff9ab11d73c56f01b5baecc1ad3fdf8ad23d987652496929ef30495cb116ba04a53f1b7ef2d881e8c1c1cfa1c9
7
+ data.tar.gz: fb4800f527d14823e9ed76b776056fe20a60f35fc3e08c30ce15c05758648476f8489c2d8bb89851fe2c77eeff56891b96ec955663bf08b7b6326cdf4425c881
data/.standard.yml CHANGED
@@ -9,3 +9,4 @@ ignore:
9
9
  - Layout/ArgumentAlignment
10
10
  - Style/EmptyCaseCondition
11
11
  - Style/RescueModifier
12
+ - Layout/MultilineMethodCallBraceLayout
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.8.0.beta2] - 2026-05-20
4
+
5
+ - Add Web Bot Auth support, implementing the current IETF draft
6
+ (draft-meunier-web-bot-auth-architecture-05).
7
+ Includes recommended signature parameter defaults, nonce generation,
8
+ and optional Signature-Agent header handling.
9
+
3
10
  ## [0.8.0.beta1] - 2026-05-07
4
11
 
5
12
  - Optimize signature parsing, serialization, and validation performance
data/README.md CHANGED
@@ -490,6 +490,61 @@ anything that to responds to `#to_i`, including an `ActiveSupport::Duration`.
490
490
  If the signature is older than the allowed window, verification
491
491
  fails with an error.
492
492
 
493
+ ## Web Bot Auth
494
+
495
+ Linzer supports the Web Bot Auth authentication mechanism, which allows
496
+ automated clients to identify themselves using HTTP Message Signatures
497
+ (as defined in RFC 9421).
498
+
499
+ This is useful for distinguishing legitimate automated traffic from
500
+ anonymous or potentially abusive requests.
501
+
502
+ For more details on Web Bot Auth, refer to the
503
+ [relevant IETF drafts](https://datatracker.ietf.org/wg/webbotauth/documents/)
504
+ or to additional resources such as
505
+ [this Cloudflare article](https://blog.cloudflare.com/web-bot-auth/).
506
+
507
+ When enabled, as shown in the examples below, Linzer adds the required
508
+ signature headers to identify the client as an automated agent:
509
+
510
+ - Plain Linzer:
511
+
512
+ ```ruby
513
+
514
+ Linzer.sign!(
515
+ request,
516
+ key: key,
517
+ label: "sig1",
518
+ profile: :web_bot_auth
519
+ # or override/set specific parameters like the following:
520
+ # profile: Linzer::Signing::Profile.web_bot_auth(agent: "https://...")
521
+ )
522
+ ```
523
+
524
+ - http.rb gem:
525
+
526
+ ```ruby
527
+ require "linzer/http/signature_feature"
528
+
529
+ response = HTTP.headers(date: Time.now.utc.httpdate, foo: "bar")
530
+ .use(http_signature: {key: key, profile: :web_bot_auth}
531
+ .get(url)
532
+ ```
533
+
534
+ - Faraday:
535
+
536
+ ```ruby
537
+ require "linzer/faraday"
538
+
539
+ conn = Faraday.new(url: api_url) do |builder|
540
+ builder.request :http_signature, key: signing_key,
541
+ components: components,
542
+ profile: :web_bot_auth,
543
+ params: signature_params
544
+ end
545
+ response = conn.post("/task")
546
+ ```
547
+
493
548
  ## Supported algorithms
494
549
 
495
550
  Linzer currently supports the following signature algorithms:
@@ -107,7 +107,16 @@ module Faraday
107
107
  # @return [Boolean] when +true+ (default), raises
108
108
  # {VerifyError} on verification failure; when +false+,
109
109
  # sets +env[:http_signature_verified]+ to +false+ and continues
110
- class Options < Faraday::Options.new(:key, :sign_request, :sign_key, :components, :verify_response, :verify_key, :params, :strict)
110
+ # @!attribute [rw] profile
111
+ # Optional HTTP Message Signatures signing profile.
112
+ #
113
+ # When set, the profile is passed to {Linzer.sign!} and may provide
114
+ # default covered components and signature parameters.
115
+ #
116
+ # @return [Symbol, Linzer::Signature::Profile::Base, nil]
117
+ # a registered profile name, a profile instance, or +nil+ to use
118
+ # the default signing behavior
119
+ class Options < Faraday::Options.new(:key, :sign_request, :sign_key, :components, :verify_response, :verify_key, :params, :strict, :profile)
111
120
  # Returns the generic key.
112
121
  # @return [Linzer::Key, nil]
113
122
  def key
@@ -146,6 +155,13 @@ module Faraday
146
155
  def params
147
156
  Hash(self[:params])
148
157
  end
158
+
159
+ # Returns the signing profile configuration.
160
+ #
161
+ # @return [Symbol, Linzer::Signature::Profile::Base, nil]
162
+ def profile
163
+ self[:profile]
164
+ end
149
165
  end
150
166
 
151
167
  # Creates a new middleware instance.
@@ -186,10 +202,15 @@ module Faraday
186
202
 
187
203
  key = resolve_signing_key
188
204
  request = Linzer::Faraday::Utils.create_request(env)
189
- message = Linzer::Message.new(request)
190
205
 
191
- signature = Linzer.sign(key, message, options.components, options.params)
192
- env.request_headers.merge!(signature.to_h)
206
+ Linzer.sign! request,
207
+ key: key,
208
+ components: options.components,
209
+ params: options.params,
210
+ profile: options.profile
211
+
212
+ signature_headers = request.headers.slice("signature", "signature-input")
213
+ env.request_headers.merge!(signature_headers)
193
214
  env
194
215
  rescue Linzer::Error => e
195
216
  raise SigningError, e if options.strict?
data/lib/linzer/common.rb CHANGED
@@ -67,7 +67,7 @@ module Linzer
67
67
  # @param serialized_components [Array<String>] The covered components
68
68
  # @param parameters [Hash] Signature parameters
69
69
  # @return [String] The formatted @signature-params line
70
- SERIALIZED_SIGNATURE_PARAMS = Starry.serialize("@signature-params").freeze
70
+ SERIALIZED_SIGNATURE_PARAMS = HTTP::StructuredField.serialize("@signature-params").freeze
71
71
  private_constant :SERIALIZED_SIGNATURE_PARAMS
72
72
 
73
73
  def signature_params_line(serialized_components, parameters)
@@ -136,7 +136,7 @@ module Linzer
136
136
  .flat_map
137
137
  .with_index do |group, idx|
138
138
  group
139
- .map { |comp| Starry.parse_item(idx.zero? ? comp[1..] : comp) }
139
+ .map { |comp| HTTP::StructuredField.parse_item(idx.zero? ? comp[1..] : comp) }
140
140
  .uniq { |comp| [comp.value, comp.parameters] }
141
141
  end
142
142
 
data/lib/linzer/helper.rb CHANGED
@@ -16,12 +16,22 @@ module Linzer
16
16
  #
17
17
  # @param request_or_response [Net::HTTPRequest, Net::HTTPResponse, Rack::Request,
18
18
  # Rack::Response, HTTP::Request] The HTTP message to sign
19
- # @param args [Hash] Keyword arguments
20
- # @option args [Linzer::Key] :key The private key to sign with (required)
21
- # @option args [Array<String>] :components The components to include in the
22
- # signature (required). Example: `%w[@method @path content-type]`
23
- # @option args [String] :label Optional signature label (defaults to "sig1")
24
- # @option args [Hash] :params Additional signature parameters (created, nonce, etc.)
19
+ #
20
+ # @param key [Linzer::Key]
21
+ # The private key to sign with (required)
22
+ #
23
+ # @param components [Array<String>]
24
+ # The components to include in the signature (required).
25
+ # Example: `%w[@method @path content-type]`
26
+ #
27
+ # @param label [String, nil]
28
+ # Optional signature label (defaults to "sig1")
29
+ #
30
+ # @param params [Hash]
31
+ # Additional signature parameters (created, nonce, etc.)
32
+ #
33
+ # @param profile [Symbol, Linzer::Signature::Profile::Base, nil]
34
+ # Optional signing profile
25
35
  #
26
36
  # @return [Object] The original HTTP message with signature headers attached
27
37
  #
@@ -46,17 +56,26 @@ module Linzer
46
56
  # label: "my-sig",
47
57
  # params: { nonce: SecureRandom.hex(16), tag: "my-app" }
48
58
  # )
49
- def sign!(request_or_response, **args)
50
- message = Message.new(request_or_response)
51
- options = {}
59
+ def sign!(request_or_response, key:, components: nil, label: nil, params: {}, profile: nil)
60
+ ctx = Signature::Context.new(
61
+ message: Message.new(request_or_response),
62
+ key: key,
63
+ label: label,
64
+ components: Array(components),
65
+ params: Hash(params)
66
+ )
67
+
68
+ resolved_profile = Signature::Profile.resolve(profile)
69
+ resolved_profile&.apply(ctx)
52
70
 
53
- label = args[:label]
54
- options[:label] = label if label
55
- options.merge!(args.fetch(:params, {}))
71
+ signature = Linzer::Signer.sign(
72
+ ctx.key,
73
+ ctx.message,
74
+ ctx.components,
75
+ ctx.params
76
+ )
56
77
 
57
- key = args.fetch(:key)
58
- signature = Linzer::Signer.sign(key, message, args.fetch(:components), options)
59
- message.attach!(signature)
78
+ ctx.message.attach!(signature)
60
79
  end
61
80
 
62
81
  # Verifies a signed HTTP request or response.
@@ -53,12 +53,17 @@ module Linzer
53
53
  # @param covered_components [Array<String>] Components to include
54
54
  # in the signature. Defaults to `@method`, `@request-target`,
55
55
  # `@authority`, and `date`.
56
+ # @param profile [Symbol, Linzer::Signature::Profile::Base, nil]
57
+ # Optional signing profile used when generating signatures.
58
+ # When provided, the profile may supply default covered components
59
+ # and signature parameters.
56
60
  #
57
61
  # @raise [HTTP::Error] If key is nil or invalid
58
- def initialize(key:, params: {}, covered_components: default_components)
62
+ def initialize(key:, params: {}, covered_components: default_components, profile: nil)
59
63
  @fields = Array(covered_components)
60
64
  @key = validate_key(key)
61
65
  @params = Hash(params)
66
+ @profile = profile
62
67
  end
63
68
 
64
69
  # @return [Array<String>] The components to include in signatures
@@ -67,6 +72,10 @@ module Linzer
67
72
  # @return [Hash] Additional signature parameters
68
73
  attr_reader :params
69
74
 
75
+ # @return [Linzer::Signature::Profile::Base, Symbol, nil]
76
+ # Optional signing profile used during signature generation
77
+ attr_reader :profile
78
+
70
79
  # Wraps an outgoing request to add signature headers.
71
80
  #
72
81
  # Called automatically by http.rb for each request.
@@ -74,9 +83,11 @@ module Linzer
74
83
  # @param request [HTTP::Request] The outgoing request
75
84
  # @return [HTTP::Request] The request with signature headers added
76
85
  def wrap_request(request)
77
- message = Linzer::Message.new(request)
78
- signature = Linzer.sign(key, message, fields, **params)
79
- request.headers.merge!(signature.to_h)
86
+ Linzer.sign! request,
87
+ key: key,
88
+ components: fields,
89
+ params: params,
90
+ profile: profile
80
91
  request
81
92
  end
82
93
 
@@ -10,38 +10,135 @@ module Linzer
10
10
  # @see https://www.rfc-editor.org/rfc/rfc8941 RFC 8941
11
11
  # @see https://www.rfc-editor.org/rfc/rfc9421 RFC 9421
12
12
  module StructuredField
13
- # Serializes signature parameters to the RFC 8941 string format.
13
+ InnerList = Starry::InnerList
14
+ Item = Starry::Item
15
+
16
+ # Parses an RFC 8941 Structured Field Dictionary.
17
+ #
18
+ # @param str [String] the serialized dictionary value
19
+ # @param field_name [String, nil] optional field name for contextual errors
20
+ # @return [Hash<String, Object>] parsed structured field dictionary
21
+ # @raise [Linzer::Error] if the field cannot be parsed
22
+ #
23
+ # @see https://www.rfc-editor.org/rfc/rfc8941 RFC 8941
24
+ def self.parse_dictionary(str, field_name: nil)
25
+ Starry.parse_dictionary(str)
26
+ # rescue Starry::ParseError => ex
27
+ # https://github.com/takemar/starry/pull/4
28
+ rescue => ex
29
+ cannot_parse = "Cannot parse %sfield!"
30
+ raise Error,
31
+ cannot_parse % [field_name ? "\"#{field_name}\" " : nil],
32
+ cause: ex
33
+ end
34
+
35
+ # Parses an RFC 8941 Structured Field Item.
36
+ #
37
+ # @param item [String] serialized structured field item
38
+ # @return [Starry::Item] parsed structured field item
39
+ # @raise [Linzer::Error] if the item is invalid or unparseable
40
+ #
41
+ # @see https://www.rfc-editor.org/rfc/rfc8941 RFC 8941
42
+ def self.parse_item(item)
43
+ Starry.parse_item(item)
44
+ # rescue Starry::ParseError => ex
45
+ # https://github.com/takemar/starry/pull/4
46
+ rescue => ex
47
+ raise Error, "Invalid/unparseable HTTP field item", cause: ex
48
+ end
49
+
50
+ # Parses an RFC 8941 Structured Field List.
51
+ #
52
+ # @param list [String] serialized structured field list
53
+ # @return [Array<Object>] parsed structured field list members
54
+ # @raise [Linzer::Error] if the list is invalid or unparseable
55
+ #
56
+ # @see https://www.rfc-editor.org/rfc/rfc8941 RFC 8941
57
+ def self.parse_list(list)
58
+ Starry.parse_list(list)
59
+ # rescue Starry::ParseError => ex
60
+ # https://github.com/takemar/starry/pull/4
61
+ rescue => ex
62
+ raise Error, "Invalid/unparseable HTTP field list", cause: ex
63
+ end
64
+
65
+ # Serializes a dictionary into RFC 8941 Structured Field format.
66
+ #
67
+ # @param hsh [Hash] structured field dictionary
68
+ # @return [String] serialized structured field dictionary
69
+ # @raise [Linzer::Error] if the dictionary cannot be serialized
14
70
  #
15
- # Integers are bare, strings are double-quoted. This covers all
16
- # parameter types used in RFC 9421 signatures (created, expires,
17
- # keyid, nonce, alg, tag).
71
+ # @see https://www.rfc-editor.org/rfc/rfc8941 RFC 8941
72
+ def self.serialize_dictionary(hsh)
73
+ Starry.serialize_dictionary(hsh)
74
+ # rescue Starry::SerializeError => ex
75
+ # https://github.com/takemar/starry/pull/4
76
+ rescue => ex
77
+ raise Error, ex.message, cause: ex
78
+ end
79
+
80
+ # Serializes a list into RFC 8941 Structured Field format.
81
+ #
82
+ # @param arr [Array] structured field list members
83
+ # @return [String] serialized structured field list
84
+ # @raise [Linzer::Error] if the list cannot be serialized
85
+ #
86
+ # @see https://www.rfc-editor.org/rfc/rfc8941 RFC 8941
87
+ def self.serialize_list(arr)
88
+ Starry.serialize_list(arr)
89
+ # rescue Starry::SerializeError => ex
90
+ # https://github.com/takemar/starry/pull/4
91
+ rescue => ex
92
+ raise Error, ex.message, cause: ex
93
+ end
94
+
95
+ # Serializes an object into RFC 8941 Structured Field format.
96
+ #
97
+ # @param obj [Object] structured field value
98
+ # @return [String] serialized structured field value
99
+ # @raise [Linzer::Error] if the object cannot be serialized
18
100
  #
19
- # @example Serialize signature parameters
20
- # StructuredField.serialize_parameters(
21
- # created: 1700000000,
22
- # keyid: "my-key"
23
- # )
24
- # # => ';created=1700000000;keyid="my-key"'
101
+ # @see https://www.rfc-editor.org/rfc/rfc8941 RFC 8941
102
+ def self.serialize(obj)
103
+ Starry.serialize(obj)
104
+ rescue Starry::SerializeError => ex
105
+ raise Error, ex.message, cause: ex
106
+ end
107
+
108
+ # Serializes a Structured Field Item into RFC 8941 format.
109
+ #
110
+ # This helper serializes a single Structured Field Item, such as
111
+ # a string, integer, token, boolean, date, byte sequence, or
112
+ # an already constructed {Starry::Item}.
113
+ #
114
+ # @param item [Object] structured field item value
115
+ # @return [String] serialized structured field item
116
+ # @raise [Linzer::Error] if the item cannot be serialized
117
+ #
118
+ # @see https://www.rfc-editor.org/rfc/rfc8941 RFC 8941
119
+ def self.serialize_item(item)
120
+ Starry.serialize_item(item)
121
+ rescue Starry::SerializeError => ex
122
+ raise Error, ex.message, cause: ex
123
+ end
124
+
125
+ # Serializes Structured Field Parameters into RFC 8941 format.
25
126
  #
26
- # @param parameters [Hash{Symbol,String => Object}]
27
- # The parameters to serialize.
127
+ # Parameters are serialized according to the Structured Fields
128
+ # parameter syntax defined in RFC 8941 Section 3.1.2.
28
129
  #
29
- # @return [String]
30
- # The serialized structured field parameter string.
130
+ # @param parameters [Hash{String,Symbol => Object}] parameter names
131
+ # and values
132
+ # @return [String] serialized structured field parameters
133
+ # @raise [Linzer::Error] if the parameters cannot be serialized
31
134
  #
135
+ # @see https://www.rfc-editor.org/rfc/rfc8941 RFC 8941
32
136
  def self.serialize_parameters(parameters)
33
- params_str = +""
34
- parameters.each do |key, value|
35
- params_str << case value
36
- when Integer
37
- ";#{key}=#{value}"
38
- when String
39
- ";#{key}=\"#{value}\""
40
- else
41
- ";#{key}=#{value}"
42
- end
43
- end
44
- params_str
137
+ Starry.serialize_parameters(parameters)
138
+ # rescue Starry::SerializeError => ex
139
+ # https://github.com/takemar/starry/pull/4
140
+ rescue => ex
141
+ raise Error, ex.message, cause: ex
45
142
  end
46
143
  end
47
144
  end
data/lib/linzer/http.rb CHANGED
@@ -100,12 +100,18 @@ module Linzer
100
100
 
101
101
  headers = build_headers(options[:headers] || {})
102
102
  request = build_request(verb, uri, headers)
103
- message = Linzer::Message.new(request)
104
103
  components = options[:covered_components] || default_components
105
104
  params = options[:params] || {}
106
- signature = Linzer.sign(key, message, components, **params)
107
105
 
108
- do_request(http, uri, verb, options[:data], signature, headers)
106
+ Linzer.sign! request,
107
+ key: key,
108
+ components: components,
109
+ params: params,
110
+ profile: options[:profile]
111
+
112
+ signature_headers = request.each_header.to_h.slice("signature-input", "signature")
113
+
114
+ do_request(http, uri, verb, options[:data], signature_headers, headers)
109
115
  end
110
116
 
111
117
  # Returns the default covered components for signing.
@@ -180,19 +186,19 @@ module Linzer
180
186
  # @param uri [String] the request URI
181
187
  # @param verb [Symbol] the HTTP method
182
188
  # @param data [String, nil] the request body
183
- # @param signature [Linzer::Signature] the generated signature
189
+ # @param signature_headers [Hash] the generated signature headers
184
190
  # @param headers [Hash] request headers
185
191
  # @return [Net::HTTPResponse] the response
186
192
  # @raise [Linzer::Error] if a body is required but not provided
187
- def do_request(http, uri, verb, data, signature, headers)
193
+ def do_request(http, uri, verb, data, signature_headers, headers)
188
194
  if with_body?(verb)
189
195
  if !data
190
196
  missed_body = "Missing request body on HTTP request: '#{verb.upcase}'"
191
197
  raise Linzer::Error, missed_body
192
198
  end
193
- http.public_send(verb, uri, data, headers.merge(signature.to_h))
199
+ http.public_send(verb, uri, data, headers.merge(signature_headers))
194
200
  else
195
- http.public_send(verb, uri, headers.merge(signature.to_h))
201
+ http.public_send(verb, uri, headers.merge(signature_headers))
196
202
  end
197
203
  end
198
204
  end
@@ -97,26 +97,35 @@ module Linzer
97
97
  # Attaches a signature to the underlying HTTP message.
98
98
  #
99
99
  # @param signature [Signature] The signature to attach
100
+ # @param additional_headers [#each]
101
+ # Additional headers to attach after signature processing.
102
+ # Header values overwrite existing values with the same field name.
103
+ #
100
104
  # @return [Object] The underlying HTTP message
101
- def attach!(signature)
105
+ def attach!(signature, additional_headers: {})
102
106
  signature_headers = signature.to_h
103
107
 
104
- unless has_signature?
108
+ if !has_signature?
105
109
  signature_headers.each { |h, v| set_header!(h, v) }
106
- return @operation
110
+ else
111
+ begin
112
+ signature_headers.each do |hdr, value|
113
+ merged = HTTP::StructuredField.parse_dictionary(String(header(hdr)))
114
+ merged.merge!(HTTP::StructuredField.parse_dictionary(value))
115
+ set_header!(hdr, HTTP::StructuredField.serialize_dictionary(merged))
116
+ end
117
+ rescue Error => ex
118
+ raise Error,
119
+ "Cannot attach signature, invalid signature header(s)!",
120
+ cause: ex
121
+ end
107
122
  end
108
123
 
109
- signature_headers.each do |hdr, value|
110
- merged = Starry.parse_dictionary(String(header(hdr)))
111
- merged.merge!(Starry.parse_dictionary(value))
112
- set_header!(hdr, Starry.serialize_dictionary(merged))
124
+ if !additional_headers.empty?
125
+ additional_headers.each { |h, v| set_header!(h, v) }
113
126
  end
114
127
 
115
128
  @operation
116
- rescue Starry::ParseError => e
117
- raise Error,
118
- "Cannot attach signature, invalid signature header(s)!",
119
- cause: e
120
129
  end
121
130
 
122
131
  private
@@ -201,8 +210,10 @@ module Linzer
201
210
  has_bs = name.parameters["bs"]
202
211
 
203
212
  if has_req
204
- name.parameters.delete("req")
205
- return req(name, method)
213
+ request_field =
214
+ HTTP::StructuredField::Item.new(name.value,
215
+ name.parameters.except("req"))
216
+ return req(request_field, method)
206
217
  end
207
218
 
208
219
  value = send(method, name)
@@ -223,14 +234,16 @@ module Linzer
223
234
  # @return [String] the serialized structured field value
224
235
  # @see https://www.rfc-editor.org/rfc/rfc9421.html#section-2.1.1
225
236
  def sf(value, key = nil)
226
- dict = Starry.parse_dictionary(value)
237
+ dict = HTTP::StructuredField.parse_dictionary(value)
227
238
 
228
239
  if key
229
240
  obj = dict[key]
230
- Starry.serialize(obj.is_a?(Starry::InnerList) ? [obj] : obj)
241
+ HTTP::StructuredField.serialize(obj.is_a?(HTTP::StructuredField::InnerList) ? [obj] : obj)
231
242
  else
232
- Starry.serialize(dict)
243
+ HTTP::StructuredField.serialize(dict)
233
244
  end
245
+ rescue Error => _ex
246
+ nil
234
247
  end
235
248
 
236
249
  # Binary-wraps a field value as a byte sequence.
@@ -239,7 +252,7 @@ module Linzer
239
252
  # @return [String] the serialized byte sequence
240
253
  # @see https://www.rfc-editor.org/rfc/rfc9421.html#section-2.1.3
241
254
  def bs(value)
242
- Starry.serialize(value.encode(Encoding::ASCII_8BIT))
255
+ HTTP::StructuredField.serialize(value.encode(Encoding::ASCII_8BIT))
243
256
  end
244
257
 
245
258
  # Retrieves a trailer field value.
@@ -72,6 +72,7 @@ module Linzer
72
72
  end
73
73
 
74
74
  def query_param(uri_query, name)
75
+ return nil if !uri_query
75
76
  param_name = name.parameters["name"]
76
77
  return nil if !param_name
77
78
  decoded_param_name = URI.decode_uri_component(param_name)
@@ -24,15 +24,15 @@ module Linzer
24
24
  def parse(field_name)
25
25
  case
26
26
  when field_name.match?(/";/), field_name.start_with?('"')
27
- Starry.parse_item(field_name)
27
+ HTTP::StructuredField.parse_item(field_name)
28
28
  when field_name.match?(/;/)
29
29
  parse_unserialized_input(field_name)
30
30
  when field_name.start_with?("@"), field_name.match?(/^[a-z]/)
31
- Starry.parse_item(Starry.serialize(field_name))
31
+ HTTP::StructuredField.parse_item(HTTP::StructuredField.serialize(field_name))
32
32
  else
33
33
  raise Error, "Invalid component identifier: '#{field_name}'!"
34
34
  end
35
- rescue Starry::ParseError => ex
35
+ rescue Error => ex
36
36
  parse_error = "Failed to parse component identifier: '#{field_name}'!"
37
37
  raise Error, parse_error, cause: ex
38
38
  end
@@ -49,7 +49,7 @@ module Linzer
49
49
  # @return [Starry::Item] the parsed item with parameters
50
50
  def parse_unserialized_input(field_name)
51
51
  field, *raw_params = field_name.split(";")
52
- item = Starry.parse_item(Starry.serialize(field))
52
+ item = HTTP::StructuredField.parse_item(HTTP::StructuredField.serialize(field))
53
53
  item.parameters = collect_parameters(raw_params)
54
54
  item
55
55
  end
@@ -67,7 +67,7 @@ module Linzer
67
67
  {param => true}
68
68
  else
69
69
  Hash[*tokens.first(2)] # e.g.: ";key=\"foo\""
70
- .transform_values! { |v| Starry.parse_item(v).value }
70
+ .transform_values! { |v| HTTP::StructuredField.parse_item(v).value }
71
71
  end
72
72
  end
73
73
  params.reduce({}, :merge)
@@ -34,7 +34,7 @@ module Linzer
34
34
  # @raise [Error] If the component identifier is invalid
35
35
  def serialize
36
36
  raise Error, "Invalid component identifier: '#{field_name}'!" unless item
37
- @serialized || Starry.serialize(@item)
37
+ @serialized || HTTP::StructuredField.serialize(@item)
38
38
  end
39
39
  end
40
40
 
@@ -113,7 +113,7 @@ module Linzer
113
113
  # build the Item and serialized string directly,
114
114
  # bypassing Starry.parse_item + Starry.serialize
115
115
  quoted = "\"#{c}\""
116
- item = Starry::Item.new(c, {})
116
+ item = HTTP::StructuredField::Item.new(c, {})
117
117
  field_ids[i] = FastIdentifier.new(quoted, item)
118
118
  serialized[i] = quoted
119
119
  end
@@ -127,8 +127,8 @@ module Linzer
127
127
  # @return [Array<String>] Component names
128
128
  def deserialize_components(components)
129
129
  components.map do |c|
130
- item = Starry.parse_item(c)
131
- item.parameters.empty? ? item.value : Starry.serialize(item)
130
+ item = HTTP::StructuredField.parse_item(c)
131
+ item.parameters.empty? ? item.value : HTTP::StructuredField.serialize(item)
132
132
  end
133
133
  end
134
134
  end