uri-ni 0.1.0 → 0.1.1

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: 602b4c24ac2df2c79fc1a49a609097b79cddcc1c093a59c4d6fb145879eb2912
4
- data.tar.gz: cf4a624236856b43a2dbb64ebe7a48e12d92289c9fe6c4c6cee5ad9aae5c86af
3
+ metadata.gz: eff8af7f69a76b24ce06948c53ad963d39552ec2dee0b08179496a1a8eb5d2a1
4
+ data.tar.gz: d7d285ac8eb47acf9acd0cd3de7dbcad6110c9e80f1860817534b18c1e25d87c
5
5
  SHA512:
6
- metadata.gz: 42bfd075838370ed2a2c2098d3249b7587d148c49da3ace39019448aec20937d3c2a6f0c53b456490c1cacaf3c3d9d026329e9f1a6929d7eaa3a728f3a7df618
7
- data.tar.gz: 16f3de0b2522d6fae59499151b4942b0c1cdb0174a5196ab534685209e9a23b462306b5f2b37c00281dbe518000717cbf6db717638e39f9951052d9baf0b9346
6
+ metadata.gz: 4bbdf0f3d6fd23db734e13403e20d96f6e29730c43f93351556a47ab10250508b743497c02870bcb7447d643e31254686a889277e5bbf10941caef329b6b71d2
7
+ data.tar.gz: b777dc59e94aff9e947cfd4661b64aecc727baeae4a2c5cc14fed58c8b59fd4495bb6ae935864a2e61a8fb9dbb86098e3665475baa2c59127975d97b734f5046
data/.gitignore CHANGED
@@ -7,6 +7,7 @@
7
7
  /spec/reports/
8
8
  /tmp/
9
9
  Gemfile.lock
10
+ *.gem
10
11
 
11
12
  # rspec failure tracking
12
13
  .rspec_status
data/README.md CHANGED
@@ -48,20 +48,28 @@ result and compute a new digest with it:
48
48
  ni
49
49
  # => #<URI::NI ni:///lol;wut>
50
50
  ni.compute 'derp'
51
- ArgumentError: lol is not a supported digest algorithm.
51
+ # URI::InvalidComponentError: Can't resolve a Digest context for the algorithm lol.
52
52
  ```
53
53
 
54
- Similarly, the digest component of the URI can be anything going in to
55
- the parser, but only base64 is valid for subsequent manipulation:
54
+ The purpose of this configuration is so that the parser doesn't croak
55
+ on unexpected input, but otherwise assumes you know what you're
56
+ doing. As such, there is no attempt to measure or otherwise divine the
57
+ representation of any updates to the `digest` component:
56
58
 
57
- ```
58
- ni.digest = '$#!%$%'
59
- ArgumentError: Data $#!%$% is not in base64
59
+ ```ruby
60
+ ni = URI::NI.compute 'some data'
61
+ # => #<URI::NI ni:///sha-256;EweZDmulyhRes16ZGCqb7EZTG8VN32VqYCx4D6AkDe4>
62
+ ni.digest = 'whatever'
63
+ # => "whatever"
64
+ ni
65
+ # => #<URI::NI ni:///sha-256;d2hhdGV2ZXI>
60
66
  ```
61
67
 
62
68
  In addition to computing new digest URIs, this module will return the
63
- interesting part of its contents in binary, base64, hexadecimal, and
69
+ interesting part of its contents in binary, hexadecimal, base64, and
64
70
  (with a soft dependency), [base32](https://rubygems.org/gems/base32).
71
+ There are accessors and mutators for `digest`, `hexdigest`,
72
+ `b32digest`, and `b64digest`.
65
73
 
66
74
  Finally, this module will also reuse any extant `Digest::Instance`
67
75
  object as long as it is in the inventory, and furthermore the
@@ -3,7 +3,7 @@ require 'uri/generic'
3
3
 
4
4
  module URI
5
5
  class NI < Generic
6
- VERSION = "0.1.0"
6
+ VERSION = "0.1.1"
7
7
  end
8
8
 
9
9
  # might as well put this here
data/lib/uri/ni.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # -*- coding: utf-8 -*-
1
2
  require 'uri/ni/version'
2
3
  require 'uri'
3
4
  require 'uri/generic'
@@ -79,6 +80,65 @@ class URI::NI < URI::Generic
79
80
  m.captures
80
81
  end
81
82
 
83
+ def assert_radix radix
84
+ raise ArgumentError,
85
+ "Radix must be 16, 32, 64, or 256, not #{radix.inspect}" unless
86
+ [256, 64, 32, 16].include? radix
87
+ radix
88
+ end
89
+
90
+ # assertions about data representation
91
+ ASSERT = {
92
+ 256 => [/.*/, ''],
93
+ 64 => [/^[0-9A-Za-z+\/_-]*=*$/, 'Data %s is not in base64'],
94
+ 32 => [/^[2-7A-Za-z]*=*$/, 'Data %s is not in base32'],
95
+ 16 => [/^[0-9A-Fa-f]*$/, 'Data %s is not in hexadecimal'],
96
+ }
97
+
98
+ def assert_repr data, radix
99
+ re, error = ASSERT[radix]
100
+ raise ArgumentError, error % data unless re.match data
101
+ end
102
+
103
+ # from whatever to binary
104
+ DECODE = {
105
+ 256 => -> x { x },
106
+ 64 => -> x { Base64.decode64 x.tr('-_', '+/') },
107
+ 32 => -> x { require 'base32'; Base32.decode x },
108
+ 16 => -> x { [x].pack 'H*' },
109
+ }
110
+
111
+ # from binary to whatever
112
+ ENCODE = {
113
+ 256 => -> x { x },
114
+ 64 => -> x { Base64.urlsafe_encode64(x).tr '=', '' },
115
+ 32 => -> x { require 'base32'; Base32.encode(x).tr '=', '' },
116
+ 16 => -> x { x.unpack1 'H*' },
117
+ }
118
+
119
+ # canonical and alternative representations
120
+ CANON = {
121
+ 256 => -> x { x },
122
+ 64 => -> x { x.tr('=', '').tr '+/', '-_' },
123
+ 32 => -> x { x.tr('=', '').upcase },
124
+ 16 => -> x { x.downcase },
125
+ }
126
+
127
+ # note if we put the padding here then we sanitize input as well
128
+
129
+ ALT = {
130
+ 256 => -> x { x },
131
+ 64 => -> x { x.tr('=', '').tr '-_', '+/' },
132
+ 32 => -> x { x.tr('=', '').downcase },
133
+ 16 => -> x { x.upcase },
134
+ }
135
+
136
+ def transcode data, from: 256, to: 256, alt: false
137
+ assert_repr data, from
138
+ data = ENCODE[to].call(DECODE[from].call data) unless from == to
139
+ alt ? ALT[to].call(data) : CANON[to].call(data)
140
+ end
141
+
82
142
  protected
83
143
 
84
144
  # holy crap you can override these?
@@ -127,8 +187,8 @@ class URI::NI < URI::Generic
127
187
  else
128
188
  # make sure we're all on the same page hurr
129
189
  self.algorithm = algorithm ||= self.algorithm
130
- raise ArgumentError,
131
- "#{algorithm} is not a supported digest algorithm." unless
190
+ raise URI::InvalidComponentError,
191
+ "Can't resolve a Digest context for the algorithm #{algorithm}." unless
132
192
  ctx = DIGESTS[algorithm]
133
193
  ctx = ctx.new
134
194
  end
@@ -157,8 +217,7 @@ class URI::NI < URI::Generic
157
217
  end
158
218
 
159
219
  self.set_path("/#{algorithm};" +
160
- ctx.base64digest.gsub(/[+\/]/, ?+ => ?-, ?/ => ?_).gsub(/=/, ''))
161
-
220
+ ctx.base64digest.tr('+/', '-_').tr('=', ''))
162
221
  self
163
222
  end
164
223
 
@@ -184,7 +243,7 @@ class URI::NI < URI::Generic
184
243
  def algorithm= algo
185
244
  a, b = assert_path
186
245
  self.path = "/#{algo}"
187
- self.digest = b if b
246
+ self.set_digest(b, radix: 64) if b
188
247
  a.to_sym if a
189
248
  end
190
249
 
@@ -223,46 +282,51 @@ class URI::NI < URI::Generic
223
282
  # @return [String] The digest of the URI in the given representation
224
283
  #
225
284
  def digest radix: 256, alt: false
226
- case radix
227
- when 256
228
- # XXX do not use urlsafe_decode64; it will complain if the
229
- # thingies aren't aligned
230
- Base64.decode64(raw_digest.tr('-_', '+/'))
231
- when 64
232
- b64digest alt: alt
233
- when 32
234
- b32digest alt: alt
235
- when 16
236
- hexdigest alt: alt
237
- else
238
- raise ArgumentError, "Radix must be 16, 32, 64, 256, not #{radix}"
239
- end
285
+ assert_radix radix
286
+ transcode raw_digest, from: 64, to: radix, alt: alt
240
287
  end
241
288
 
242
- # Set the digest to the data. Data may either be a
243
- # +Digest::Instance+ or a base64 string. String representations will
244
- # be normalized to {https://tools.ietf.org/html/rfc3548#section-4
245
- # RFC 3548} base64url, i.e. +\+/+ will be replaced with +-_+ and
246
- # padding (+=+) will be removed. +Digest::Instance+ objects will
289
+ # Set the digest to the data, with an optional radix. Data may
290
+ # either be a +Digest::Instance+—in which case the radix is
291
+ # ignored–a string, or +nil+. +Digest::Instance+ objects will
247
292
  # just be run through #compute, with all that entails.
248
- def digest= data
249
- a = assert_path.first
250
- case data
293
+ #
294
+ # @param value [String, nil, Digest::Instance] The new digest
295
+ # @param radix [256, 64, 32, 16] The radix of the encoding (default 256)
296
+ # @return [String] The _old_ digest in the given radix
297
+ #
298
+ def set_digest value, radix: 256
299
+ assert_radix radix
300
+
301
+ a, d = assert_path
302
+
303
+ case value
251
304
  when Digest::Instance
252
- compute data
305
+ compute value
253
306
  when String
254
- raise ArgumentError, "Data #{data} is not in base64" unless
255
- /^[0-9A-Za-z+\/_-]*=*$/.match(data)
256
- data = data.tr('+/', '-_').tr('=', '')
257
- self.path = a ? "/#{a};#{data}" : "/;#{data}"
307
+ value = transcode value, from: radix, to: 64
308
+ self.path = a ? "/#{a};#{value}" : "/;#{value}"
258
309
  when nil
259
310
  self.path = a ? "/#{a}" : ?/
260
311
  else
261
312
  raise ArgumentError,
262
- "Data must be a string or Digest::Instance, not #{data.class}"
313
+ "Value must be a string or Digest::Instance, not #{value.class}"
263
314
  end
264
315
 
265
- data
316
+ # bail out if nil
317
+ return unless d
318
+ transcode d, from: 64, to: radix
319
+ end
320
+
321
+ # Set the digest to the data. Data may either be a
322
+ # +Digest::Instance+ or a _binary_ string. +Digest::Instance+
323
+ # objects will just be run through #compute, with all that entails.
324
+ #
325
+ # @param value [String, nil, Digest::Instance] the new digest
326
+ # @return [String, nil, Digest::Instance] the value passed in
327
+ #
328
+ def digest= value
329
+ return set_digest value
266
330
  end
267
331
 
268
332
  # Return the digest in its hexadecimal notation. Optionally give
@@ -273,44 +337,59 @@ class URI::NI < URI::Generic
273
337
  # @return [String] The hexadecimal digest
274
338
  #
275
339
  def hexdigest alt: false
276
- str = digest.unpack('H*').first
277
- return str.upcase if alt
278
- str
340
+ transcode raw_digest, from: 64, to: 16, alt: alt
341
+ end
342
+
343
+ # Set the digest value, assuming a hexadecimal input.
344
+ # @param value [String, nil, Digest::Instance] the new digest
345
+ # @return [String, nil, Digest::Instance] the value passed in
346
+ def hexdigest= value
347
+ set_digest value, radix: 16
279
348
  end
280
349
 
281
350
  # Return the digest in its base32 notation. Optionally give
282
351
  # +alt:+ a truthy value to return an alternate (lowercase)
283
- # representation. Note this method requires
352
+ # representation. Note this method requires the base32 module.
284
353
  #
285
354
  # @param alt [false, true] Return the alternative representation
286
355
  # @return [String] The base32 digest
287
356
  #
288
357
  def b32digest alt: false
289
- require 'base32'
290
- ret = Base32.encode(digest).gsub(/=+/, '')
291
- return ret.downcase if alt
292
- ret.upcase
358
+ transcode raw_digest, from: 64, to: 32, alt: alt
293
359
  end
294
360
 
295
- # Return the digest in its base64 notation. Optionally give
296
- # +alt:+ a truthy value to return an alternate (URL-safe)
297
- # representation.
361
+ # Set the digest value, assuming a base32 input (requires base32).
362
+ # @param value [String, nil, Digest::Instance] the new digest
363
+ # @return [String, nil, Digest::Instance] the value passed in
364
+ def b32digest= value
365
+ set_digest value, radix: 32
366
+ end
367
+
368
+ # Return the digest in its base64 notation. Note it is the
369
+ # _default_ representation that is URL-safe, for parity with the
370
+ # identifier itself. Give +alt:+ a truthy value to return a plain
371
+ # (_non_-URL-safe) base64 representation.
298
372
  #
299
373
  # @param alt [false, true] Return the alternative representation
300
374
  # @return [String] The base64 digest
301
375
  #
302
376
  def b64digest alt: false
303
- ret = raw_digest
304
- return ret.gsub(/[-_]/, ?- => ?+, ?_ => ?/) unless alt
305
- ret
377
+ transcode raw_digest, from: 64, to: 64, alt: alt
378
+ end
379
+
380
+ # Set the digest value, assuming a base64 input.
381
+ # @param value [String, nil, Digest::Instance] the new digest
382
+ # @return [String, nil, Digest::Instance] the value passed in
383
+ def b64digest= value
384
+ set_digest value, radix: 64
306
385
  end
307
386
 
308
387
  # Returns a +/.well-known/...+, either HTTPS or HTTP URL, given the
309
388
  # contents of the +ni:+ URI.
310
389
  #
311
390
  # @param authority [#to_s, URI] Override the authority part of the URI
312
- # @param https [true, false] whether the URL is to be HTTPS.
313
- # @return [URI::HTTPS, URI::HTTP]
391
+ # @param https [true, false] Whether the URL is to be HTTPS.
392
+ # @return [URI::HTTPS, URI::HTTP] The generated URL.
314
393
  #
315
394
  def to_www https: true, authority: nil
316
395
  a, d = assert_path
@@ -327,13 +406,13 @@ class URI::NI < URI::Generic
327
406
  if authority
328
407
  uhp = []
329
408
  if authority.is_a? URI
330
- raise ArgumentError, "Bad authority #{authority}" unless
409
+ raise URI::InvalidComponentError, "Bad authority #{authority}" unless
331
410
  %i[userinfo host port].all? {|c| authority.respond_to? c }
332
411
  uhp = [authority.userinfo, authority.host, authority.port]
333
412
  uhp[2] = nil if authority.port == authority.class::DEFAULT_PORT
334
413
  else
335
414
  authority = authority.to_s
336
- uhp = AUTH_RE.match(authority) or raise ArgumentError,
415
+ uhp = AUTH_RE.match(authority) or raise URI::InvalidComponentError,
337
416
  "Invalid authority #{authority}"
338
417
  uhp = uhp.captures
339
418
  end
data/uri-ni.gemspec CHANGED
@@ -24,10 +24,11 @@ Gem::Specification.new do |spec|
24
24
  spec.require_paths = ['lib']
25
25
 
26
26
  # ruby
27
- spec.required_ruby_version = Gem::Dependency.new('>= 2.3.0')
27
+ spec.required_ruby_version = '>= 2.3.0'
28
28
 
29
29
  # dev/test dependencies
30
30
  spec.add_development_dependency 'bundler', '~> 2'
31
+ spec.add_development_dependency 'base32', '~> 0.3'
31
32
  # bundler put these in the gemfile i dunno wtf
32
33
  #spec.add_development_dependency 'rake', '~> 12.0'
33
34
  #spec.add_development_dependency 'rspec', '~> 3.0'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: uri-ni
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dorian Taylor
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-01-04 00:00:00.000000000 Z
11
+ date: 2020-01-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: base32
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.3'
27
41
  description: ''
28
42
  email:
29
43
  - code@doriantaylor.com
@@ -57,7 +71,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
57
71
  requirements:
58
72
  - - ">="
59
73
  - !ruby/object:Gem::Version
60
- version: '0'
74
+ version: 2.3.0
61
75
  required_rubygems_version: !ruby/object:Gem::Requirement
62
76
  requirements:
63
77
  - - ">="