class URI::NI
Constants
- ALG_VAL
this is slightly more relaxed than rfc 6920, allowing for an empty value for the digest such that we can initialize ni:///algo and compute
- ALT
note if we put the padding here then we sanitize input as well
- ASSERT
assertions about data representation
- AUTHORITY
URI.rb has rfc2396 not 3986 so let's make an authority pattern
- AUTH_RE
- CANON
canonical and alternative representations
- COMPONENT
map these onto upstream properties
- DECODE
from whatever to binary
- DIGESTS
- DIGEST_REV
resolve first against digest length and then class
- ENCODE
from binary to whatever
- HOST_RE
- PATH
- PATH_RE
- PATTERN
put it together
- REGEXP
and bake it
- VERSION
Public Class Methods
Display the available algorithms.
@return [Array] containing the symbols representing the available
digest algorithms.
# File lib/uri/ni.rb, line 252 def self.algorithms DIGESTS.keys.sort end
Compute an RFC6920 URI
from a data source.
@param data [#to_s, IO, Digest, nil] @param algorithm [Symbol] See available algorithms. Default: +:“sha-256”+ @param blocksize [Integer] The number or bytes per call to the Digest @param authority [String, nil] Optional authority (user, host, port) @param query [String, nil] Optional query string @yield [ctx, buf] Passes the Digest and (maybe) the buffer @yieldparam ctx [Digest::Instance] The digest instance to the block @yieldparam buf [String, nil] The current read buffer (if data
is set) @yieldreturn [nil] The result of the block is ignored
@return [URI::NI]
# File lib/uri/ni.rb, line 175 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
Return a Digest::Instance for a supported algorithm. @param [#to_s, to_sym] The algorithm @return [Digest:Instance] The digest context @raise [ArgumentError] if the algorithm is unrecognized
# File lib/uri/ni.rb, line 186 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
Returns true if the algorithm is supported. @param algorithm [Symbol,String] the algorithm identifier to test @return [true, false] whether it is supported
# File lib/uri/ni.rb, line 486 def self.valid_algo? algorithm DIGESTS.has_key? algorithm.to_s.downcase.to_sym end
Public Instance Methods
Obtain the algorithm of the digest. May be nil.
@return [Symbol, nil]
# File lib/uri/ni.rb, line 259 def algorithm algo = assert_path.first return algo.to_sym if algo end
Set the algorithm of the digest. Will croak if the path is malformed.
@return [Symbol, nil] the old algorithm
# File lib/uri/ni.rb, line 267 def algorithm= algo a, b = assert_path self.path = "/#{algo}" self.set_digest(b, radix: 64) if b a.to_sym if a end
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.
@param alt [false, true] Return the alternative representation @return [String] The base32 digest
# File lib/uri/ni.rb, line 381 def b32digest alt: false transcode raw_digest, from: 64, to: 32, alt: alt end
Set the digest value, assuming a base32 input (requires base32). @param value [String, nil, Digest::Instance] the new digest @return [String, nil, Digest::Instance] the value passed in
# File lib/uri/ni.rb, line 388 def b32digest= value set_digest value, radix: 32 end
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.
@param alt [false, true] Return the alternative representation @return [String] The base64 digest
# File lib/uri/ni.rb, line 400 def b64digest alt: false transcode raw_digest, from: 64, to: 64, alt: alt end
Set the digest value, assuming a base64 input. @param value [String, nil, Digest::Instance] the new digest @return [String, nil, Digest::Instance] the value passed in
# File lib/uri/ni.rb, line 407 def b64digest= value set_digest value, radix: 64 end
(Re)-compute a digest using existing information from an instance. @see .compute
# File lib/uri/ni.rb, line 197 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
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.
@param radix [256, 64, 32, 16] The radix of the representation @param alt [false, true] Return the alternative representation @return [String] The digest of the URI
in the given representation
# File lib/uri/ni.rb, line 308 def digest radix: 256, alt: false assert_radix radix transcode raw_digest, from: 64, to: radix, alt: alt end
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.
@param value [String, nil, Digest::Instance] the new digest @return [String, nil, Digest::Instance] the value passed in
# File lib/uri/ni.rb, line 352 def digest= value return set_digest value end
Return the digest in its hexadecimal notation. Optionally give alt:
a truthy value to return an alternate (uppercase) representation.
@param alt [false, true] Return the alternative representation @return [String] The hexadecimal digest
# File lib/uri/ni.rb, line 363 def hexdigest alt: false transcode raw_digest, from: 64, to: 16, alt: alt end
Set the digest value, assuming a hexadecimal input. @param value [String, nil, Digest::Instance] the new digest @return [String, nil, Digest::Instance] the value passed in
# File lib/uri/ni.rb, line 370 def hexdigest= value set_digest value, radix: 16 end
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.
@param value [String, nil, Digest::Instance] The new digest @param radix [256, 64, 32, 16] The radix of the encoding (default 256) @return [String] The old digest in the given radix
# File lib/uri/ni.rb, line 322 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
Unconditionally returns an HTTP URL.
@param authority [#to_s, URI] Override the authority part of the URI
@return [URI::HTTP]
# File lib/uri/ni.rb, line 473 def to_http authority: nil to_www https: false, authority: authority end
Unconditionally returns an HTTPS URL.
@param authority [#to_s, URI] Override the authority part of the URI
@return [URI::HTTPS]
# File lib/uri/ni.rb, line 463 def to_https authority: nil # note we don't simply alias this to_www authority: authority end
Returns a /.well-known/...
, either HTTPS or HTTP URL, given the contents of the ni:
URI
.
@param authority [#to_s, URI] Override the authority part of the URI
@param https [true, false] Whether the URL is to be HTTPS. @return [URI::HTTPS, URI::HTTP] The generated URL.
# File lib/uri/ni.rb, line 418 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
Protected Instance Methods
our host can be an empty string
# File lib/uri/ni.rb, line 146 def check_host host !!HOST_RE.match(host) end
our path has constraints
# File lib/uri/ni.rb, line 151 def check_path path !!PATH_RE.match(path) end
make sure the host is always set to the empty string
# File lib/uri/ni.rb, line 156 def set_host v @host = v.to_s end
Private Instance Methods
# File lib/uri/ni.rb, line 55 def algo_for ctx, algo = nil raise NotImplementedError, "Unknown digest type #{ctx.class}" unless d = DIGEST_REV[ctx.digest_length] and d[ctx.class] raise ArgumentError, "algorithm #{algo} does not match digest type #{ctx.class}" if algo and algo != d[ctx.class] d[ctx.class] end
# File lib/uri/ni.rb, line 75 def assert_path path = nil path ||= self.path m = PATH_RE.match(path) or raise ArgumentError, "Path #{path} does not match constraint" m.captures end
# File lib/uri/ni.rb, line 82 def assert_radix radix raise ArgumentError, "Radix must be 16, 32, 64, or 256, not #{radix.inspect}" unless [256, 64, 32, 16].include? radix radix end
# File lib/uri/ni.rb, line 97 def assert_repr data, radix re, error = ASSERT[radix] raise ArgumentError, error % data unless re.match data end
# File lib/uri/ni.rb, line 64 def raw_digest PATH_RE.match(path).captures[1] || '' end
# File lib/uri/ni.rb, line 135 def transcode data, from: 256, to: 256, alt: false assert_repr data, from data = ENCODE[to].call(DECODE[from].call data) unless from == to alt ? ALT[to].call(data) : CANON[to].call(data) end