uri-ni 0.1.0 → 0.1.1

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: 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
  - - ">="