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

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.algorithmsArray

Returns the set of supported algorithms.

Returns:

  • (Array)

    the algorithms



308
309
310
# File 'lib/uri/ni.rb', line 308

def self.algorithms
  DIGESTS.keys.sort
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:



231
232
233
234
235
236
# File 'lib/uri/ni.rb', line 231

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



242
243
244
245
246
247
248
249
# File 'lib/uri/ni.rb', line 242

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



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/uri/ni.rb', line 184

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

.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



542
543
544
# File 'lib/uri/ni.rb', line 542

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)


315
316
317
318
# File 'lib/uri/ni.rb', line 315

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)

    the old algorithm



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

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



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

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)

    the old authority



342
343
344
345
346
347
348
349
# File 'lib/uri/ni.rb', line 342

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



437
438
439
# File 'lib/uri/ni.rb', line 437

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



444
445
446
# File 'lib/uri/ni.rb', line 444

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



456
457
458
# File 'lib/uri/ni.rb', line 456

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



463
464
465
# File 'lib/uri/ni.rb', line 463

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:



253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
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
# File 'lib/uri/ni.rb', line 253

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

  self.set_path("/#{algorithm};" +
    ctx.base64digest.tr('+/', '-_').tr('=', ''))
  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



364
365
366
367
# File 'lib/uri/ni.rb', line 364

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



408
409
410
# File 'lib/uri/ni.rb', line 408

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



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

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



426
427
428
# File 'lib/uri/ni.rb', line 426

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



378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
# File 'lib/uri/ni.rb', line 378

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


529
530
531
# File 'lib/uri/ni.rb', line 529

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)


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

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.



474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
# File 'lib/uri/ni.rb', line 474

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