uri-ni 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 602b4c24ac2df2c79fc1a49a609097b79cddcc1c093a59c4d6fb145879eb2912
4
+ data.tar.gz: cf4a624236856b43a2dbb64ebe7a48e12d92289c9fe6c4c6cee5ad9aae5c86af
5
+ SHA512:
6
+ metadata.gz: 42bfd075838370ed2a2c2098d3249b7587d148c49da3ace39019448aec20937d3c2a6f0c53b456490c1cacaf3c3d9d026329e9f1a6929d7eaa3a728f3a7df618
7
+ data.tar.gz: 16f3de0b2522d6fae59499151b4942b0c1cdb0174a5196ab534685209e9a23b462306b5f2b37c00281dbe518000717cbf6db717638e39f9951052d9baf0b9346
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ Gemfile.lock
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
13
+ .\#*
14
+ \#*\#
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ ---
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.5.5
6
+ before_install: gem install bundler -v 2.1.2
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in uri-ni.gemspec
4
+ gemspec
5
+
6
+ # not sure why we put these in both places
7
+ gem "rake", "~> 12.0"
8
+ gem "rspec", "~> 3.0"
data/LICENSE ADDED
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # URI::NI - RFC6920 Named Identifiers
2
+
3
+ This module implements the `ni:` URI scheme from [RFC
4
+ 6920](https://tools.ietf.org/html/rfc6920).
5
+
6
+ ```ruby
7
+ require 'uri'
8
+ require 'uri-ni' # or 'uri/ni', if you prefer
9
+
10
+ ni = URI::NI.compute 'some data'
11
+ # => #<URI::NI ni:///sha-256;EweZDmulyhRes16ZGCqb7EZTG8VN32VqYCx4D6AkDe4>
12
+ ni.hexdigest
13
+ # => "1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee"
14
+ ```
15
+
16
+ This of course corresponds to:
17
+
18
+ ```bash
19
+ $ echo -n some data | sha256sum
20
+ 1307990e6ba5ca145eb35e99182a9bec46531bc54ddf656a602c780fa0240dee -
21
+ ```
22
+
23
+ This works as expected:
24
+
25
+ ```ruby
26
+ ni = URI('ni:///sha-256;4wwup6dvg7fBqXXdwkKGtnXnFOu7xyzNXwQBcwIxq1c')
27
+ # => #<URI::NI ni:///sha-256;4wwup6dvg7fBqXXdwkKGtnXnFOu7xyzNXwQBcwIxq1c>
28
+ ```
29
+
30
+ RFC 6920 [specifies a
31
+ registry](https://www.iana.org/assignments/named-information/named-information.xhtml)
32
+ for algorithm designators. Of that list, `sha-256`, `sha-384` and
33
+ `sha-512` are implemented. Eventually I will get around to doing the
34
+ SHA-3 digests as well as the truncated SHA-256 ones. Implemented but
35
+ _not_ in the registry are `md5`, `sha-1` and `rmd-160`. Really these
36
+ identifiers only matter when you are trying to `compute` a new
37
+ digest. For instance you can do this:
38
+
39
+ ```ruby
40
+ ni = URI('ni:///lol;wut')
41
+ # => #<URI::NI ni:///lol;wut>
42
+ ```
43
+
44
+ …and the parser won't complain. But, if you then tried to take this
45
+ result and compute a new digest with it:
46
+
47
+ ```ruby
48
+ ni
49
+ # => #<URI::NI ni:///lol;wut>
50
+ ni.compute 'derp'
51
+ ArgumentError: lol is not a supported digest algorithm.
52
+ ```
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:
56
+
57
+ ```
58
+ ni.digest = '$#!%$%'
59
+ ArgumentError: Data $#!%$% is not in base64
60
+ ```
61
+
62
+ In addition to computing new digest URIs, this module will return the
63
+ interesting part of its contents in binary, base64, hexadecimal, and
64
+ (with a soft dependency), [base32](https://rubygems.org/gems/base32).
65
+
66
+ Finally, this module will also reuse any extant `Digest::Instance`
67
+ object as long as it is in the inventory, and furthermore the
68
+ `compute` method takes a block:
69
+
70
+ ```ruby
71
+ ctx = Digest::SHA256.new
72
+ # => #<Digest::SHA256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855>
73
+ ctx << 'hello world'
74
+ # => #<Digest::SHA256: b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9>
75
+ ni = URI::NI.compute ctx
76
+ # => #<URI::NI ni:///sha-256;uU0nuZNNPgilLlLX2n2r-sSE7-N6U4DukIj3rOLvzek>
77
+ ni.hexdigest
78
+ # => "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
79
+ ni = URI::NI.compute do |ctx|
80
+ ctx << 'hello world'
81
+ end
82
+ # => #<URI::NI ni:///sha-256;uU0nuZNNPgilLlLX2n2r-sSE7-N6U4DukIj3rOLvzek>
83
+ ```
84
+
85
+ ## Documentation
86
+
87
+ Generated and deposited [in the usual
88
+ place](http://www.rubydoc.info/github/doriantaylor/rb-uri-ni/master).
89
+
90
+ ## Installation
91
+
92
+ You know how to do this:
93
+
94
+ $ gem install uri-ni
95
+
96
+ Or, [download it off rubygems.org](https://rubygems.org/gems/uri-ni).
97
+
98
+ ## Contributing
99
+
100
+ Bug reports and pull requests are welcome at
101
+ [the GitHub repository](https://github.com/doriantaylor/rb-uri-ni).
102
+
103
+ ## Copyright & License
104
+
105
+ ©2019 [Dorian Taylor](https://doriantaylor.com/)
106
+
107
+ This software is provided under
108
+ the [Apache License, 2.0](https://www.apache.org/licenses/LICENSE-2.0).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "uri/ni"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,11 @@
1
+ # we need this or what follows it complains
2
+ require 'uri/generic'
3
+
4
+ module URI
5
+ class NI < Generic
6
+ VERSION = "0.1.0"
7
+ end
8
+
9
+ # might as well put this here
10
+ @@schemes['NI'] = NI
11
+ end
data/lib/uri/ni.rb ADDED
@@ -0,0 +1,374 @@
1
+ require 'uri/ni/version'
2
+ require 'uri'
3
+ require 'uri/generic'
4
+
5
+ require 'digest'
6
+ require 'base64'
7
+ require 'stringio'
8
+
9
+ class URI::NI < URI::Generic
10
+ private
11
+
12
+ # URI.rb has rfc2396 not 3986 so let's make an authority pattern
13
+ AUTHORITY = "(#{URI::PATTERN::USERINFO}@)?(#{URI::PATTERN::HOST})?" \
14
+ "(?::(#{URI::PATTERN::PORT}))?".freeze
15
+
16
+ AUTH_RE = /^#{AUTHORITY}$/o.freeze
17
+ HOST_RE = /^#{URI::PATTERN::HOST}?$/o.freeze
18
+
19
+ # this is slightly more relaxed than rfc 6920, allowing for an empty
20
+ # value for the digest such that we can initialize ni:///algo and compute
21
+ ALG_VAL = "([#{URI::PATTERN::UNRESERVED}]+)" \
22
+ "(?:;([#{URI::PATTERN::UNRESERVED}]*))?".freeze
23
+
24
+ PATH = "/(?:#{ALG_VAL})?".freeze
25
+ PATH_RE = /^(?:#{PATH})?$/o.freeze
26
+
27
+ # put it together
28
+ PATTERN =
29
+ "^([NnIi])://#{AUTHORITY}/#{ALG_VAL}(?:\\?#{URI::PATTERN::QUERY})?$".freeze
30
+
31
+ # and bake it
32
+ REGEXP = Regexp.new(PATTERN).freeze
33
+
34
+ # map these onto upstream properties
35
+ COMPONENT = %i[scheme userinfo host port path query]
36
+
37
+ DIGESTS = {
38
+ "md5": Digest::MD5,
39
+ "rmd-160": Digest::RMD160,
40
+ "sha-1": Digest::SHA1,
41
+ "sha-256": Digest::SHA256,
42
+ "sha-384": Digest::SHA384,
43
+ "sha-512": Digest::SHA512,
44
+ }
45
+
46
+ # resolve first against digest length and then class
47
+ DIGEST_REV = {
48
+ 64 => { Digest::SHA512 => :"sha-512", Digest::SHA2 => :"sha-512" },
49
+ 48 => { Digest::SHA384 => :"sha-384", Digest::SHA2 => :"sha-384" },
50
+ 32 => { Digest::SHA256 => :"sha-256", Digest::SHA2 => :"sha-256" },
51
+ 20 => { Digest::RMD160 => :"rmd-160", Digest::SHA1 => :"sha-1" },
52
+ 16 => { Digest::MD5 => :md5 },
53
+ }
54
+
55
+ def algo_for ctx, algo = nil
56
+ raise NotImplementedError, "Unknown digest type #{ctx.class}" unless
57
+ d = DIGEST_REV[ctx.digest_length] and d[ctx.class]
58
+ raise ArgumentError,
59
+ "algorithm #{algo} does not match digest type #{ctx.class}" if
60
+ algo and algo != d[ctx.class]
61
+ d[ctx.class]
62
+ end
63
+
64
+ def raw_digest
65
+ PATH_RE.match(path).captures[1] || ''
66
+ end
67
+
68
+ def assert_authority authority = nil
69
+ authority ||= self.authority
70
+ m = AUTH_RE.match(authority) or raise ArgumentError,
71
+ "Invalid authority #{authority}"
72
+ m.captures
73
+ end
74
+
75
+ def assert_path path = nil
76
+ path ||= self.path
77
+ m = PATH_RE.match(path) or raise ArgumentError,
78
+ "Path #{path} does not match constraint"
79
+ m.captures
80
+ end
81
+
82
+ protected
83
+
84
+ # holy crap you can override these?
85
+
86
+ # our host can be an empty string
87
+ def check_host host
88
+ !!HOST_RE.match(host)
89
+ end
90
+
91
+ # our path has constraints
92
+ def check_path path
93
+ !!PATH_RE.match(path)
94
+ end
95
+
96
+ # make sure the host is always set to the empty string
97
+ def set_host v
98
+ @host = v.to_s
99
+ end
100
+
101
+ public
102
+
103
+ # Compute an RFC6920 URI from a data source.
104
+ # @param data [#to_s, IO, Digest, nil]
105
+ # @param algorithm [Symbol] See algorithms
106
+ def self.compute data = nil, algorithm: :"sha-256", blocksize: 65536,
107
+ authority: nil, query: nil, &block
108
+
109
+ build({ scheme: 'ni' }).compute data, algorithm: algorithm,
110
+ blocksize: blocksize, authority: authority, query: query, &block
111
+ end
112
+
113
+ def compute data = nil, algorithm: nil, blocksize: 65536,
114
+ authority: nil, query: nil, &block
115
+
116
+ # enforce block size
117
+ raise ArgumentError,
118
+ "Blocksize must be an integer >0, not #{blocksize}" unless
119
+ blocksize.is_a? Integer and blocksize > 0
120
+
121
+ # special case for when the data is a digest
122
+ ctx = nil
123
+ if data.is_a? Digest::Instance
124
+ algorithm ||= algo_for data, algorithm
125
+ ctx = data
126
+ data = nil # unset data
127
+ else
128
+ # make sure we're all on the same page hurr
129
+ self.algorithm = algorithm ||= self.algorithm
130
+ raise ArgumentError,
131
+ "#{algorithm} is not a supported digest algorithm." unless
132
+ ctx = DIGESTS[algorithm]
133
+ ctx = ctx.new
134
+ end
135
+
136
+ # deal with authority component
137
+ if authm = AUTH_RE.match(authority.to_s)
138
+ userinfo, host, port = authm.captures
139
+ set_userinfo userinfo
140
+ set_host host.to_s
141
+ set_port port
142
+ end
143
+
144
+ # coerce data to something non-null
145
+ data = data.to_s if (data.class.ancestors & [String, IO, NilClass]).empty?
146
+ if data
147
+ data = StringIO.new data unless data.is_a? IO
148
+
149
+ # give us a default block
150
+ block ||= -> x, y { x << y } # unless block_given?
151
+
152
+ while buf = data.read(blocksize)
153
+ block.call ctx, buf
154
+ end
155
+ elsif block
156
+ block.call ctx, nil
157
+ end
158
+
159
+ self.set_path("/#{algorithm};" +
160
+ ctx.base64digest.gsub(/[+\/]/, ?+ => ?-, ?/ => ?_).gsub(/=/, ''))
161
+
162
+ self
163
+ end
164
+
165
+ # Display the available algorithms.
166
+ #
167
+ # @return [Array] containing the symbols representing the available
168
+ # digest algorithms.
169
+ def self.algorithms
170
+ DIGESTS.keys.sort
171
+ end
172
+
173
+ # Obtain the algorithm of the digest. May be nil.
174
+ #
175
+ # @return [Symbol, nil]
176
+ def algorithm
177
+ algo = assert_path.first
178
+ return algo.to_sym if algo
179
+ end
180
+
181
+ # Set the algorithm of the digest. Will croak if the path is malformed.
182
+ #
183
+ # @return [Symbol, nil] the old algorithm
184
+ def algorithm= algo
185
+ a, b = assert_path
186
+ self.path = "/#{algo}"
187
+ self.digest = b if b
188
+ a.to_sym if a
189
+ end
190
+
191
+ # Obtain the authority (userinfo@host:port) if present.
192
+ #
193
+ # @return [String, nil] the authority
194
+ def authority
195
+ out = userinfo ? "#{userinfo}@#{host}" : host
196
+ out += "#{out}:#{port}" if port
197
+ out
198
+ end
199
+
200
+ # Set the authority of the URI.
201
+ #
202
+ # @return [String, nil] the old authority
203
+ def authority= authority
204
+ old = self.authority
205
+ u, h, p = assert_authority authority unless authority.nil?
206
+ set_userinfo u
207
+ set_host h
208
+ set_port p
209
+ old
210
+ end
211
+
212
+ # Return the digest in the hash. Optionally takes a +radix:+
213
+ # argument to specify binary, base64, base32, or hexadecimal
214
+ # representations. Another optional flag will return alternative
215
+ # representations for each: base64url (vanilla base64 is canonical),
216
+ # base32 in lowercase (uppercase is canonical), hexadecimal in
217
+ # uppercase (lowercase is canonical). The binary representation
218
+ # naturally has no alternative form. Base64/base32 values will be
219
+ # appropriately padded.
220
+ #
221
+ # @param radix [256, 64, 32, 16] The radix of the representation
222
+ # @param alt [false, true] Return the alternative representation
223
+ # @return [String] The digest of the URI in the given representation
224
+ #
225
+ 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
240
+ end
241
+
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
247
+ # just be run through #compute, with all that entails.
248
+ def digest= data
249
+ a = assert_path.first
250
+ case data
251
+ when Digest::Instance
252
+ compute data
253
+ 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}"
258
+ when nil
259
+ self.path = a ? "/#{a}" : ?/
260
+ else
261
+ raise ArgumentError,
262
+ "Data must be a string or Digest::Instance, not #{data.class}"
263
+ end
264
+
265
+ data
266
+ end
267
+
268
+ # Return the digest in its hexadecimal notation. Optionally give
269
+ # +alt:+ a truthy value to return an alternate (uppercase)
270
+ # representation.
271
+ #
272
+ # @param alt [false, true] Return the alternative representation
273
+ # @return [String] The hexadecimal digest
274
+ #
275
+ def hexdigest alt: false
276
+ str = digest.unpack('H*').first
277
+ return str.upcase if alt
278
+ str
279
+ end
280
+
281
+ # Return the digest in its base32 notation. Optionally give
282
+ # +alt:+ a truthy value to return an alternate (lowercase)
283
+ # representation. Note this method requires
284
+ #
285
+ # @param alt [false, true] Return the alternative representation
286
+ # @return [String] The base32 digest
287
+ #
288
+ def b32digest alt: false
289
+ require 'base32'
290
+ ret = Base32.encode(digest).gsub(/=+/, '')
291
+ return ret.downcase if alt
292
+ ret.upcase
293
+ end
294
+
295
+ # Return the digest in its base64 notation. Optionally give
296
+ # +alt:+ a truthy value to return an alternate (URL-safe)
297
+ # representation.
298
+ #
299
+ # @param alt [false, true] Return the alternative representation
300
+ # @return [String] The base64 digest
301
+ #
302
+ def b64digest alt: false
303
+ ret = raw_digest
304
+ return ret.gsub(/[-_]/, ?- => ?+, ?_ => ?/) unless alt
305
+ ret
306
+ end
307
+
308
+ # Returns a +/.well-known/...+, either HTTPS or HTTP URL, given the
309
+ # contents of the +ni:+ URI.
310
+ #
311
+ # @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]
314
+ #
315
+ def to_www https: true, authority: nil
316
+ a, d = assert_path
317
+ components = {
318
+ scheme: "http#{https ? ?s : ''}",
319
+ userinfo: userinfo,
320
+ host: host,
321
+ port: port,
322
+ path: "/.well-known/ni/#{a}/#{d}",
323
+ query: query,
324
+ fragment: fragment,
325
+ }
326
+
327
+ if authority
328
+ uhp = []
329
+ if authority.is_a? URI
330
+ raise ArgumentError, "Bad authority #{authority}" unless
331
+ %i[userinfo host port].all? {|c| authority.respond_to? c }
332
+ uhp = [authority.userinfo, authority.host, authority.port]
333
+ uhp[2] = nil if authority.port == authority.class::DEFAULT_PORT
334
+ else
335
+ authority = authority.to_s
336
+ uhp = AUTH_RE.match(authority) or raise ArgumentError,
337
+ "Invalid authority #{authority}"
338
+ uhp = uhp.captures
339
+ end
340
+ components[:userinfo] = uhp[0]
341
+ components[:host] = uhp[1]
342
+ components[:port] = uhp[2]
343
+ end
344
+
345
+ # pick the class
346
+ cls = https ? URI::HTTPS : URI::HTTP
347
+
348
+ # `normalize` should do this but doesn't
349
+ components[:port] = nil if
350
+ components[:port] and components[:port] == cls::DEFAULT_PORT
351
+
352
+ cls.build(components).normalize
353
+ end
354
+
355
+ # Unconditionally returns an HTTPS URL.
356
+ #
357
+ # @param authority [#to_s, URI] Override the authority part of the URI
358
+ # @return [URI::HTTPS]
359
+ #
360
+ def to_https authority: nil
361
+ # note we don't simply alias this
362
+ to_www authority: authority
363
+ end
364
+
365
+ # Unconditionally returns an HTTP URL.
366
+ #
367
+ # @param authority [#to_s, URI] Override the authority part of the URI
368
+ # @return [URI::HTTP]
369
+ #
370
+ def to_http authority: nil
371
+ to_www https: false, authority: authority
372
+ end
373
+
374
+ end
data/lib/uri-ni.rb ADDED
@@ -0,0 +1 @@
1
+ require 'uri/ni'
data/uri-ni.gemspec ADDED
@@ -0,0 +1,36 @@
1
+ # -*- mode: enh-ruby -*-
2
+ require_relative 'lib/uri/ni/version'
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = 'uri-ni'
6
+ spec.version = URI::NI::VERSION
7
+ spec.authors = ['Dorian Taylor']
8
+ spec.email = ['code@doriantaylor.com']
9
+ spec.license = 'Apache-2.0'
10
+ spec.homepage = 'https://github.com/doriantaylor/rb-uri-ni'
11
+ spec.summary = 'URI handler for RFC6920 ni:/// URIs'
12
+ spec.description = <<-DESC
13
+ DESC
14
+
15
+ spec.metadata['homepage_uri'] = spec.homepage
16
+
17
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
18
+ `git ls-files -z`.split("\x0").reject do |f|
19
+ f.match(%r{^(test|spec|features)/})
20
+ end
21
+ end
22
+ spec.bindir = 'exe'
23
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
+ spec.require_paths = ['lib']
25
+
26
+ # ruby
27
+ spec.required_ruby_version = Gem::Dependency.new('>= 2.3.0')
28
+
29
+ # dev/test dependencies
30
+ spec.add_development_dependency 'bundler', '~> 2'
31
+ # bundler put these in the gemfile i dunno wtf
32
+ #spec.add_development_dependency 'rake', '~> 12.0'
33
+ #spec.add_development_dependency 'rspec', '~> 3.0'
34
+
35
+ # we need uri and digest and base64 but those are all in core
36
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: uri-ni
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Dorian Taylor
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-01-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2'
27
+ description: ''
28
+ email:
29
+ - code@doriantaylor.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - ".gitignore"
35
+ - ".rspec"
36
+ - ".travis.yml"
37
+ - Gemfile
38
+ - LICENSE
39
+ - README.md
40
+ - Rakefile
41
+ - bin/console
42
+ - bin/setup
43
+ - lib/uri-ni.rb
44
+ - lib/uri/ni.rb
45
+ - lib/uri/ni/version.rb
46
+ - uri-ni.gemspec
47
+ homepage: https://github.com/doriantaylor/rb-uri-ni
48
+ licenses:
49
+ - Apache-2.0
50
+ metadata:
51
+ homepage_uri: https://github.com/doriantaylor/rb-uri-ni
52
+ post_install_message:
53
+ rdoc_options: []
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ requirements: []
67
+ rubygems_version: 3.1.2
68
+ signing_key:
69
+ specification_version: 4
70
+ summary: URI handler for RFC6920 ni:/// URIs
71
+ test_files: []