uri-ni 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.rspec +3 -0
- data/.travis.yml +6 -0
- data/Gemfile +8 -0
- data/LICENSE +202 -0
- data/README.md +108 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/uri/ni/version.rb +11 -0
- data/lib/uri/ni.rb +374 -0
- data/lib/uri-ni.rb +1 -0
- data/uri-ni.gemspec +36 -0
- metadata +71 -0
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
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
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
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
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: []
|