Class: URI::NI

Inherits:
Generic
  • Object
show all
Defined in:
lib/uri/ni/version.rb,
lib/uri/ni.rb

Constant Summary collapse

VERSION =
"0.2.3"

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.algorithms(truncated: false) ⇒ Array

Display the available algorithms.

Returns:

  • (Array)

    containing the symbols representing the available digest algorithms.



342
343
344
345
# File 'lib/uri/ni.rb', line 342

def self.algorithms truncated: false
  out = DIGESTS.keys.sort
  truncated ? out : out - TRUNCATED
end

.compute(data = nil, algorithm: :"sha-256", blocksize: 65536, authority: nil, query: nil) {|ctx, buf| ... } ⇒ URI::NI

Compute an RFC6920 URI from a data source.

Parameters:

  • data (#to_s, IO, Digest, nil) (defaults to: nil)
  • algorithm (Symbol) (defaults to: :"sha-256")

    See available algorithms. Default: :“sha-256”

  • blocksize (Integer) (defaults to: 65536)

    The number or bytes per call to the Digest

  • authority (String, nil) (defaults to: nil)

    Optional authority (user, host, port)

  • query (String, nil) (defaults to: nil)

    Optional query string

Yields:

  • (ctx, buf)

    Passes the Digest and (maybe) the buffer

Yield Parameters:

  • ctx (Digest::Instance)

    The digest instance to the block

  • buf (String, nil)

    The current read buffer (if data is set)

Yield Returns:

  • (nil)

    The result of the block is ignored

Returns:



247
248
249
250
251
252
# File 'lib/uri/ni.rb', line 247

def self.compute data = nil, algorithm: :"sha-256", blocksize: 65536,
    authority: nil, query: nil, &block

  build({ scheme: 'ni' }).compute data, algorithm: algorithm,
    blocksize: blocksize, authority: authority, query: query, &block
end

.context(algorithm) ⇒ Digest:Instance

Return a Digest::Instance for a supported algorithm.

Parameters:

  • The (#to_s, #to_sym)

    algorithm

Returns:

  • (Digest:Instance)

    The digest context

Raises:

  • (ArgumentError)

    if the algorithm is unrecognized



258
259
260
261
262
263
264
265
# File 'lib/uri/ni.rb', line 258

def self.context algorithm
  raise ArgumentError, "Cannot coerce #{algorithm} to a symbol" unless
    algorithm.respond_to? :to_s # cheat: symbols respond to to_s
  algorithm = algorithm.to_s.to_sym unless algorithm.is_a? Symbol
  raise ArgumentError, "Unsupported digest algorithm #{algorithm}" unless
    ctx = DIGESTS[algorithm]
  ctx.new
end

.ingest(algorithm = nil, digest) ⇒ URI::NI

Transform a digest for a known algorithm into a URI::NI. Takes a binary, Base64, Base32, or hexadecimal string.

Parameters:

  • algorithm (Symbol, #to_sym) (defaults to: nil)

    a supported algorithm

  • digest (String, #to_s, Digest::Instance)

    the digest (or context)

Returns:

  • (URI::NI)

    the transformed identifier

Raises:

  • (ArgumentError)

    if the algorithm is unsupported

  • (ArgumentError)

    if the input string is not the right length for the algorithm

  • (ArgumentError)

    if the input string is incorrectly encoded



200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/uri/ni.rb', line 200

def self.ingest algorithm = nil, digest
  return compute(digest) if digest.is_a? Digest::Instance
  return digest if digest.is_a? URI::NI # noop

  # make sure we're indeed dealing with a string
  digest = digest.to_s

  # just parse if it's already a ni: URI string
  return URI(digest) if /^ni:/i.match? digest

  # get the expected length of the digest for the algorithm
  len = LENGTHS[algorithm.to_s.downcase.to_sym] or raise ArgumentError,
    "unsupported algorithm #{algorithm}"

  # the length of the string is used to determine the expected encoding
  radix = case digest.length
          when len then 256
          when -> x { x >= (len * 8 / 6.0).ceil && x < (len * 8 / 5.0).ceil }
            # note that a length longer than base64 but shorter than base32
            # can be assumed to be base64 with padding
            64
          when (len * 8 / 5.0).ceil then 32
          when len * 2 then 16
          else
            raise ArgumentError, "invalid string length: #{digest.length}"
          end

  # then we lint it to see if it's correct
  digest = transcode digest, from: radix, to: 64

  # then we transcode
  URI("ni:///#{algorithm};#{digest}")
end

.truncated?(algorithm) ⇒ false, true

Returns true if the supplied algorithm is a truncated one.

Returns:

  • (false, true)


608
609
610
# File 'lib/uri/ni.rb', line 608

def self.truncated? algorithm
  valid_algo?(algorithm) && TRUNCATED.include?(algorithm.to_s.downcase.to_sym)
end

.valid_algo?(algorithm) ⇒ true, false

Returns true if the algorithm is supported.

Parameters:

  • algorithm (Symbol, String)

    the algorithm identifier to test

Returns:

  • (true, false)

    whether it is supported



600
601
602
# File 'lib/uri/ni.rb', line 600

def self.valid_algo? algorithm
  DIGESTS.has_key? algorithm.to_s.downcase.to_sym
end

Instance Method Details

#algorithmSymbol?

Obtain the algorithm of the digest. May be nil.

Returns:

  • (Symbol, nil)


351
352
353
354
# File 'lib/uri/ni.rb', line 351

def algorithm
  algo = assert_path.first
  return algo.to_sym if algo
end

#algorithm=(algo) ⇒ Symbol?

Set the algorithm of the digest. Will croak if the path is malformed.

Returns:

  • (Symbol, nil)


360
361
362
363
364
365
# File 'lib/uri/ni.rb', line 360

def algorithm= algo
  a, b = assert_path
  self.path   = "/#{algo}"
  self.set_digest(b, radix: 64) if b
  a.to_sym if a
end

#authorityString?

Obtain the authority (userinfo@host:port) if present.

Returns:

  • (String, nil)

    the authority



371
372
373
374
375
# File 'lib/uri/ni.rb', line 371

def authority
  out = userinfo ? "#{userinfo}@#{host}" : host
  out += "#{out}:#{port}" if port
  out
end

#authority=(authority) ⇒ String?

Set the authority of the URI.

Returns:

  • (String, nil)


381
382
383
384
385
386
387
388
# File 'lib/uri/ni.rb', line 381

def authority= authority
  old = self.authority
  u, h, p = assert_authority authority unless authority.nil?
  set_userinfo u
  set_host     h
  set_port     p
  old
end

#b32digest(alt: false) ⇒ String

Return the digest in its base32 notation. Optionally give alt: a truthy value to return an alternate (lowercase) representation. Note this method requires the base32 module.

Parameters:

  • alt (false, true) (defaults to: false)

    Return the alternative representation

Returns:

  • (String)

    The base32 digest



487
488
489
# File 'lib/uri/ni.rb', line 487

def b32digest alt: false
  transcode raw_digest, from: 64, to: 32, alt: alt
end

#b32digest=(value) ⇒ String, ...

Set the digest value, assuming a base32 input (requires base32).

Parameters:

  • value (String, nil, Digest::Instance)

    the new digest

Returns:

  • (String, nil, Digest::Instance)

    the value passed in



497
498
499
# File 'lib/uri/ni.rb', line 497

def b32digest= value
  set_digest value, radix: 32
end

#b64digest(alt: false) ⇒ String

Return the digest in its base64 notation. Note it is the default representation that is URL-safe, for parity with the identifier itself. Give alt: a truthy value to return a plain (non-URL-safe) base64 representation.

Parameters:

  • alt (false, true) (defaults to: false)

    Return the alternative representation

Returns:

  • (String)

    The base64 digest



510
511
512
# File 'lib/uri/ni.rb', line 510

def b64digest alt: false
  transcode raw_digest, from: 64, to: 64, alt: alt
end

#b64digest=(value) ⇒ String, ...

Set the digest value, assuming a base64 input.

Parameters:

  • value (String, nil, Digest::Instance)

    the new digest

Returns:

  • (String, nil, Digest::Instance)

    the value passed in



520
521
522
# File 'lib/uri/ni.rb', line 520

def b64digest= value
  set_digest value, radix: 64
end

#compute(data = nil, algorithm: nil, blocksize: 65536, authority: nil, query: nil, &block) ⇒ Object

(Re)-compute a digest using existing information from an instance.

Raises:

  • (ArgumentError)

See Also:



269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
# File 'lib/uri/ni.rb', line 269

def compute data = nil, algorithm: nil, blocksize: 65536,
    authority: nil, query: nil, &block

  # enforce block size
  raise ArgumentError,
    "Blocksize must be an integer >0, not #{blocksize}" unless
    blocksize.is_a? Integer and blocksize > 0

  # special case for when the data is a digest
  ctx = nil
  if data.is_a? Digest::Instance
    # note the self; this would have been a bug lol
    self.algorithm = algorithm ||= algo_for data, algorithm
    ctx  = data
    data = nil # unset data
  else
    # make sure we're all on the same page hurr
    self.algorithm = algorithm ||= self.algorithm || :"sha-256"
    raise URI::InvalidComponentError,
      "Can't resolve a Digest context for the algorithm #{algorithm}." unless
      ctx = DIGESTS[algorithm]
    ctx = ctx.new
  end

  # deal with authority component
  if authm = AUTH_RE.match(authority.to_s)
    userinfo, host, port = authm.captures
    set_userinfo userinfo
    set_host     host.to_s
    set_port     port
  end

  # coerce data to something non-null
  data = data.to_s if (data.class.ancestors & [String, IO, NilClass]).empty?
  if data
    data = StringIO.new data unless data.is_a? IO

    # give us a default block
    block ||= -> x, y { x << y } # unless block_given?

    while buf = data.read(blocksize)
      block.call ctx, buf
    end
  elsif block
    block.call ctx, nil
  end

  set_digest ctx.digest

  self
end

#digest(radix: 256, alt: false) ⇒ String

Return the digest in the hash. Optionally takes a radix: argument to specify binary, base64, base32, or hexadecimal representations. Another optional flag will return alternative representations for each: base64url (vanilla base64 is canonical), base32 in lowercase (uppercase is canonical), hexadecimal in uppercase (lowercase is canonical). The binary representation naturally has no alternative form. Base64/base32 values will be appropriately padded.

Parameters:

  • radix (256, 64, 32, 16) (defaults to: 256)

    The radix of the representation

  • alt (false, true) (defaults to: false)

    Return the alternative representation

Returns:

  • (String)

    The digest of the URI in the given representation



404
405
406
407
# File 'lib/uri/ni.rb', line 404

def digest radix: 256, alt: false
  assert_radix radix
  transcode raw_digest, from: 64, to: radix, alt: alt
end

#digest=(value) ⇒ String, ...

Set the digest to the data. Data may either be a Digest::Instance or a binary string. Digest::Instance objects will just be run through #compute, with all that entails.

Parameters:

  • value (String, nil, Digest::Instance)

    the new digest

Returns:

  • (String, nil, Digest::Instance)

    the value passed in



453
454
455
# File 'lib/uri/ni.rb', line 453

def digest= value
  return set_digest value
end

#hexdigest(alt: false) ⇒ String

Return the digest in its hexadecimal notation. Optionally give alt: a truthy value to return an alternate (uppercase) representation.

Parameters:

  • alt (false, true) (defaults to: false)

    Return the alternative representation

Returns:

  • (String)

    The hexadecimal digest



465
466
467
# File 'lib/uri/ni.rb', line 465

def hexdigest alt: false
  transcode raw_digest, from: 64, to: 16, alt: alt
end

#hexdigest=(value) ⇒ String, ...

Set the digest value, assuming a hexadecimal input.

Parameters:

  • value (String, nil, Digest::Instance)

    the new digest

Returns:

  • (String, nil, Digest::Instance)

    the value passed in



475
476
477
# File 'lib/uri/ni.rb', line 475

def hexdigest= value
  set_digest value, radix: 16
end

#set_digest(value, radix: 256) ⇒ String

Set the digest to the data, with an optional radix. Data may either be a Digest::Instance—in which case the radix is ignored—a string, or nil. Digest::Instance objects will just be run through #compute, with all that entails.

Parameters:

  • value (String, nil, Digest::Instance)

    The new digest

  • radix (256, 64, 32, 16) (defaults to: 256)

    The radix of the encoding (default 256)

Returns:

  • (String)

    The old digest in the given radix



419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
# File 'lib/uri/ni.rb', line 419

def set_digest value, radix: 256
  assert_radix radix

  a, d = assert_path

  case value
  when Digest::Instance
    compute value
  when String
    value = transcode value, from: radix
    value = value[0, LENGTHS[a.to_sym]] # deal with truncation
    value = transcode value, to: 64

    self.path = a ? "/#{a};#{value}" : "/;#{value}"
  when nil
    self.path = a ? "/#{a}" : ?/
  else
    raise ArgumentError,
      "Value must be a string or Digest::Instance, not #{value.class}"
  end

  # bail out if nil
  return unless d
  transcode d, from: 64, to: radix
end

#to_http(authority: nil) ⇒ URI::HTTP

Unconditionally returns an HTTP URL.

Parameters:

  • authority (#to_s, URI) (defaults to: nil)

    Override the authority part of the URI

Returns:

  • (URI::HTTP)


589
590
591
# File 'lib/uri/ni.rb', line 589

def to_http authority: nil
  to_www https: false, authority: authority
end

#to_https(authority: nil) ⇒ URI::HTTPS

Unconditionally returns an HTTPS URL.

Parameters:

  • authority (#to_s, URI) (defaults to: nil)

    Override the authority part of the URI

Returns:

  • (URI::HTTPS)


578
579
580
581
# File 'lib/uri/ni.rb', line 578

def to_https authority: nil
  # note we don't simply alias this
  to_www authority: authority
end

#to_www(https: true, authority: nil) ⇒ URI::HTTPS, URI::HTTP

Returns a /.well-known/..., either HTTPS or HTTP URL, given the contents of the ni: URI.

Parameters:

  • authority (#to_s, URI) (defaults to: nil)

    Override the authority part of the URI

  • https (true, false) (defaults to: true)

    Whether the URL is to be HTTPS.

Returns:

  • (URI::HTTPS, URI::HTTP)

    The generated URL.



532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
# File 'lib/uri/ni.rb', line 532

def to_www https: true, authority: nil
  a, d = assert_path
  components = {
    scheme:   "http#{https ? ?s : ''}",
    userinfo: userinfo,
    host:     host,
    port:     port,
    path:     "/.well-known/ni/#{a}/#{d}",
    query:    query,
    fragment: fragment,
  }

  if authority
    uhp = []
    if authority.is_a? URI
      raise URI::InvalidComponentError, "Bad authority #{authority}" unless
        %i[userinfo host port].all? {|c| authority.respond_to? c }
      uhp = [authority.userinfo, authority.host, authority.port]
      uhp[2] = nil if authority.port == authority.class::DEFAULT_PORT
    else
      authority = authority.to_s
      uhp = AUTH_RE.match(authority) or raise URI::InvalidComponentError,
        "Invalid authority #{authority}"
      uhp = uhp.captures
    end
    components[:userinfo] = uhp[0]
    components[:host]     = uhp[1]
    components[:port]     = uhp[2]
  end

  # pick the class
  cls = https ? URI::HTTPS : URI::HTTP

  # `normalize` should do this but doesn't
  components[:port] = nil if
    components[:port] and components[:port] == cls::DEFAULT_PORT

  cls.build(components).normalize
end

#truncated?false, true

Returns true if the algorithm is a truncated one.

Returns:

  • (false, true)


333
334
335
# File 'lib/uri/ni.rb', line 333

def truncated?
  TRUNCATED.include? algorithm
end

#valid?false, true

Returns true if the digest length matches the algorithm.

Returns:

  • (false, true)


325
326
327
# File 'lib/uri/ni.rb', line 325

def valid?
  LENGTHS[algorithm] and LENGTHS[algorithm] == digest.length
end