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.4"

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.



357
358
359
360
# File 'lib/uri/ni.rb', line 357

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:



262
263
264
265
266
267
# File 'lib/uri/ni.rb', line 262

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



273
274
275
276
277
278
279
280
# File 'lib/uri/ni.rb', line 273

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
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# File 'lib/uri/ni.rb', line 200

def self.ingest algorithm = nil, digest
  if digest.is_a? Digest::Instance
    # this will complain
    algo_for digest, algorithm
    return compute(digest)
  elsif digest.is_a? URI::NI
    raise ArgumentError,
      "digest algorithm #{digest.algorithm} does not match #{algorithm}" if
      algorithm && algorithm != digest.algorithm
    return digest
  end

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

  # just parse if it's already a ni: URI string
  if /^ni:/i.match? digest
    digest = URI(digest)
    raise ArgumentError,
      "digest algorithm #{digest.algorithm} does not match #{algorithm}" if
      algorithm && algorithm != digest.algorithm

    return digest
  end

  # 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)


623
624
625
# File 'lib/uri/ni.rb', line 623

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



615
616
617
# File 'lib/uri/ni.rb', line 615

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)


366
367
368
369
# File 'lib/uri/ni.rb', line 366

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)


375
376
377
378
379
380
# File 'lib/uri/ni.rb', line 375

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



386
387
388
389
390
# File 'lib/uri/ni.rb', line 386

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)


396
397
398
399
400
401
402
403
# File 'lib/uri/ni.rb', line 396

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



502
503
504
# File 'lib/uri/ni.rb', line 502

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



512
513
514
# File 'lib/uri/ni.rb', line 512

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



525
526
527
# File 'lib/uri/ni.rb', line 525

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



535
536
537
# File 'lib/uri/ni.rb', line 535

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:



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
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'lib/uri/ni.rb', line 284

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



419
420
421
422
# File 'lib/uri/ni.rb', line 419

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



468
469
470
# File 'lib/uri/ni.rb', line 468

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



480
481
482
# File 'lib/uri/ni.rb', line 480

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



490
491
492
# File 'lib/uri/ni.rb', line 490

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



434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
# File 'lib/uri/ni.rb', line 434

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)


604
605
606
# File 'lib/uri/ni.rb', line 604

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)


593
594
595
596
# File 'lib/uri/ni.rb', line 593

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.



547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
# File 'lib/uri/ni.rb', line 547

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)


348
349
350
# File 'lib/uri/ni.rb', line 348

def truncated?
  TRUNCATED.include? algorithm
end

#valid?false, true

Returns true if the digest length matches the algorithm.

Returns:

  • (false, true)


340
341
342
# File 'lib/uri/ni.rb', line 340

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