File Coverage

blib/lib/URI/ni.pm
Criterion Covered Total %
statement 80 97 82.4
branch 23 52 44.2
condition 4 15 26.6
subroutine 15 18 83.3
pod 7 7 100.0
total 129 189 68.2


line stmt bran cond sub pod time code
1             package URI::ni;
2              
3             require URI;
4             require URI::_server;
5             require URI::_punycode;
6             require URI::QueryParam;
7             @ISA=qw(URI::_server URI);
8              
9             $VERSION = '0.04';
10              
11             # not sure why the module is laid out like this, oh well.
12              
13             =head1 NAME
14              
15             URI::ni - URI scheme for Named Information Identifiers
16              
17             =head1 SYNOPSIS
18              
19             use URI;
20              
21             $u = URI->new('ni:///sha-256');
22             $u->compute('some data');
23              
24             my $algo = $u->algorithm;
25             my $b64 = $u->b64digest;
26             my $hex = $u->hexdigest;
27             my $bin = $u->digest;
28              
29             =head1 DESCRIPTION
30              
31             This module implements the C URI scheme defined in L
32             6920|http://tools.ietf.org/html/rfc6920>.
33              
34             =cut
35              
36 1     1   1187 use strict;
  1         2  
  1         46  
37 1     1   6 use warnings; # FATAL => 'all';
  1         2  
  1         35  
38 1     1   1049 use utf8;
  1         10  
  1         6  
39              
40 1     1   846 use MIME::Base64 ();
  1         817  
  1         21  
41 1     1   6 use URI::Escape ();
  1         2  
  1         15  
42 1     1   1019 use Digest ();
  1         565  
  1         18  
43 1     1   7 use Carp ();
  1         2  
  1         15  
44 1     1   5 use Scalar::Util ();
  1         1  
  1         1441  
45              
46             # XXX please don't go away from Digest
47             my %ALGOS = map { lc $_ => 1 } keys %Digest::MMAP;
48              
49             =head2 compute $DATA [, $ALGO, \%QUERY]
50              
51             Compute a new ni: URI from some data. Since the data objects we're
52             typically interested in hashing tend to be bulky, this method will
53             optionally take GLOB or SCALAR references, even blessed ones if you
54             can be sure they'll behave, that is, globs treated like files and
55             scalars dereferenced. If not, C<$DATA> can also be a CODE reference as
56             well, with the L context as its first argument, enabling you
57             to specify your own behaviour, like this:
58              
59             my $obj = MyObj->new;
60              
61             my $ni = URI->new('ni:///sha-256;');
62             $ni->compute(sub { shift->add($obj->as_string) });
63              
64             # Alternatively:
65              
66             use URI::ni;
67              
68             my $ni = URI::ni->compute(sub { shift->add($obj->as_string) });
69              
70             It is also possible to supply your own L instance and the URI
71             will be generated from its current state, like this:
72              
73             my $ctx = Digest->new('SHA-1');
74             $ctx->add($some_stuff);
75              
76             # REMEMBER TO MATCH THE ALGORITHM IN THE CONSTRUCTOR!
77             # I CAN'T (RELIABLY) DO IT FOR YOU!
78              
79             my $ni = URI::ni->compute($ctx, 'sha-1')
80              
81             # now you can use $ctx for other stuff.
82              
83             # The URI doesn't store $ctx so if you modify it, the URI won't
84             # change.
85              
86             The algorithms supported are the same as the ones in L, which
87             will be coerced to lower-case in the URI. If omitted, the default
88             algorithm is SHA-256, per the draft spec.
89              
90             Optionally, you can pass in a string or HASH reference which will be
91             appended to the URI. The keys map as they do in L,
92             and so do the values, which can be either strings or ARRAY references
93             containing strings, to represent multiple values.
94              
95             =cut
96              
97             sub compute {
98 1     1 1 2531 my ($self, $data, $algo, $query) = @_;
99 1 50       7 Carp::croak('Compute constructor must have some sort of data source.')
100             unless defined $data;
101              
102             # we need these right away
103 1         4 my $is_blessed = Scalar::Util::blessed($data);
104 1 50       4 my $is_digest = $is_blessed and $data->isa('Digest::base');
105              
106 1 50       5 $algo = $algo ? lc $algo : 'sha-256';
107 1 50       8 $self = ref $self ? $self->clone : URI->new("ni:///$algo");
108             # one last time
109 1         54 $algo = lc $self->algorithm;
110              
111             # easy out for exotic Digest subclasses
112 1 50 33     5 Carp::croak("Algorithm $algo isn't on the menu.")
113             unless $ALGOS{$algo} or $is_digest;
114              
115             # of course the chief wants it in upper case
116 1         10 my $ctx = Digest->new(uc $algo);
117              
118 1 50       5018 if (ref $data) {
119 1 50       4 if ($is_digest) {
120 0         0 $ctx = $data;
121             }
122             else {
123             # oh man this is too damn clever. it is bound to screw up.
124             my %handler = (
125 1     1   10 GLOB => sub { binmode $_[0]; $ctx->addfile($_[0]) },
  1         31  
126 0     0   0 SCALAR => sub { $ctx->add(${shift()}) },
  0         0  
127 0     0   0 CODE => sub { shift->($ctx) },
128 1         12 );
129              
130 1         2 my $ok;
131 1         4 for my $type (keys %handler) {
132             # XXX is there a less dumb way to do this?
133 3 50       10 $ok = $is_blessed ? $data->isa($type) : ref $data eq $type;
134 3 100       8 if ($ok) {
135 1         4 $handler{$type}->($data);
136 1         37 last;
137             }
138             }
139 1 50       10 Carp::croak('If the data is a reference, it has to be' .
140             ' some kind of GLOB or SCALAR.') unless $ok;
141             }
142             }
143             else {
144 0         0 $ctx->add($data);
145             }
146              
147 1         23 my $digest = $ctx->b64digest;
148 1         3 $digest =~ tr!+/!-_!;
149              
150 1         9 $self->path("/$algo;$digest");
151             # XXX do something smarter with the query
152 1 50       69 $self->query_form_hash($query) if $query;
153              
154 1         4 $self;
155             }
156              
157             =head2 from_digest $DIGEST [, $ALGO, \%QUERY, $KIND ]
158              
159             Returns a C URI from an already-computed digest. As with
160             L, you need to supply C<$ALGO> only if you have either not
161             supplied one in the constructor (e.g. Cnew('ni:')>), or you
162             are using this as a class method.
163              
164             If C<$DIGEST> isn't a L object, this method will try to detect
165             the representation of the digest that is passed in with C<$DIGEST>. By
166             convention, it is biased toward the hexadecimal representation, since
167             that is how we typically find message digests in the wild. It is
168             I, though not likely, that Base64 or binary representations
169             only contain bits that correspond to C<[0-9A-Fa-f]>, so if you're
170             feeling paranoid, you can supply an additional $KIND parameter with
171             the radix of each character (e.g. C<16>, C<64> or C<256>), or the
172             strings C, C or C. Base64 digests can be supplied in
173             either conventional or
174             L forms.
175              
176             =over 4
177              
178             (NB: The difference between standard Base64 and base64url is simply
179             C.)
180              
181             =back
182              
183             =cut
184              
185             my %OP = (
186             16 => sub { MIME::Base64::encode_base64(pack('H*', $_[0]), '') },
187             64 => sub { $_[0] },
188             256 => sub { MIME::Base64::encode_base64($_[0], '') },
189             );
190              
191             my %KINDS = (
192             hex => 16,
193             b64 => 64,
194             bin => 256,
195             );
196              
197             sub from_digest {
198 1     1 1 3 my ($self, $digest, $algo, $query, $kind) = @_;
199 1 50       5 Carp::croak('Compute constructor must have some sort of data source.')
200             unless defined $digest;
201              
202 1 50       5 $algo = $algo ? lc $algo : 'sha-256';
203 1 50       23 $self = ref $self ? $self->clone : URI->new("ni:///$algo");
204             # one last time
205 1         85 $algo = lc $self->algorithm;
206              
207 1 50       4 if (ref $digest) {
208 0 0 0     0 Carp::croak("Digest must be a Digest::base subclass")
209             unless Scalar::Util::blessed $digest
210             and $digest->isa('Digest::base');
211             # WATCH OUT: the digest object state gets reset.
212 0         0 $digest = $digest->clone->b64digest;
213             }
214             else {
215 1         3 utf8::downgrade($digest);
216 1         2 my $op;
217 1 50       4 if (defined $kind) {
218 1 50 33     24 $op = $OP{$kind} || $OP{$KINDS{$kind}}
219             or Carp::croak("Unrecognized representation '$kind'");
220             }
221             else {
222 0 0       0 my $x = $digest =~ /[\x80-\xff]/ ? 256
    0          
223             : $digest =~ /[^0-9A-Fa-f]/ ? 64 : 16;
224 0         0 $op = $OP{$x};
225             }
226              
227 1         4 $digest = $op->($digest);
228             # per Digest::base
229 1         5 $digest =~ s/=+$//;
230             }
231              
232             # XXX should probably compartmentalize this with the above method
233              
234 1         3 $digest =~ tr!+/!-_!;
235              
236 1         6 $self->path("/$algo;$digest");
237             # XXX do something smarter with the query
238 1 50       33 $self->query_form_hash($query) if $query;
239              
240 1         15 $self;
241             }
242              
243             =head2 algorithm
244              
245             Retrieves the hash algorithm. This method is read-only, since it makes
246             no sense to change the algorithm of an already-computed hash.
247              
248             =cut
249              
250             sub algorithm {
251 3     3 1 6 my $self = shift;
252 3         29 my $o = $self->path;
253 3 50 33     58 return if !defined $o or $o =~ m!^/+$!;
254 3         21 $o =~ s!^/?(.*?)(;.*)?$!$1!;
255 3         12 $o;
256             }
257              
258             =head2 b64digest [$RAW]
259              
260             Returns the digest encoded in Base64. An optional C<$RAW> argument
261             will return the digest without first translating from I
262             (section 5 in L).
263              
264             Like everything else in this module that pertains to the hash itself,
265             this accessor is read-only.
266              
267             =cut
268              
269             sub b64digest {
270 2     2 1 4 my ($self, $raw) = @_;
271 2         6 my $hash = $self->path;
272 2 50 33     48 return if !defined $hash or $hash =~ m!^/+$!;
273 2         31 $hash =~ s!^/?(?:.*?;)(.*?)(?:\?.*)?$!$1!;
274 2 50       5 return unless defined $hash;
275 2 50       6 $hash =~ tr!-_!+/! unless $raw;
276 2         28 $hash;
277             }
278              
279             =head2 hexdigest
280              
281             Returns the hexadecimal cryptographic digest we're all familiar with.
282              
283             =cut
284              
285             sub hexdigest {
286 1     1 1 4 unpack 'H*', shift->digest;
287             }
288              
289             =head2 digest
290              
291             Retrieves a binary digest, in keeping with the nomenclature in
292             L.
293              
294             =cut
295              
296             sub digest {
297 2     2 1 1378 MIME::Base64::decode_base64(shift->b64digest);
298             }
299              
300             =head2 locators
301              
302             This is a convenience method to instantiate any locators defined in L
303             2.1.4|http://tools.ietf.org/html/draft-hallambaker-digesturi-02#section-2.1.4>
304             as URI objects. If you want to set these values, use L
305             with the C or C keys. Returns all locators in list
306             context, and the first one in scalar context (which of course may be
307             undef).
308              
309             =cut
310              
311             sub locators {
312 0     0 1   my $self = shift;
313 0           my $algo = $self->algorithm;
314 0           my $digest = $self->b64digest(1);
315              
316 0           my @loc;
317 0           for my $scheme (qw(http https)) {
318 0           for my $host ($self->query_param($scheme)) {
319             # RFC 5785 kinda gives me the creeps.
320 0           push @loc, URI->new(sprintf '%s://%s/.well-known/ni/%s/%s',
321             $scheme, $host, $algo, $digest);
322             }
323             }
324              
325 0 0         return wantarray ? @loc : $loc[0];
326             }
327              
328             =head1 SEE ALSO
329              
330             =over 4
331              
332             =item L
333              
334             =item L
335              
336             =item L
337              
338             =back
339              
340             =head1 AUTHOR
341              
342             Dorian Taylor, C<< >>
343              
344             =head1 BUGS
345              
346             Please report any bugs or feature requests to C
347             rt.cpan.org>, or through the web interface at
348             L. I will be
349             notified, and then you'll automatically be notified of progress on
350             your bug as I make changes.
351              
352              
353             =head1 SUPPORT
354              
355             You can find documentation for this module with the perldoc command.
356              
357             perldoc URI::ni
358              
359              
360             You can also look for information at:
361              
362             =over 4
363              
364             =item * RT: CPAN's request tracker (report bugs here)
365              
366             L
367              
368             =item * AnnoCPAN: Annotated CPAN documentation
369              
370             L
371              
372             =item * CPAN Ratings
373              
374             L
375              
376             =item * Search CPAN
377              
378             L
379              
380             =back
381              
382             =head1 LICENSE AND COPYRIGHT
383              
384             Copyright 2012 Dorian Taylor.
385              
386             Licensed under the Apache License, Version 2.0 (the "License"); you
387             may not use this file except in compliance with the License. You may
388             obtain a copy of the License at
389             L.
390              
391             Unless required by applicable law or agreed to in writing, software
392             distributed under the License is distributed on an "AS IS" BASIS,
393             WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
394             implied. See the License for the specific language governing
395             permissions and limitations under the License.
396              
397             =cut
398              
399             1; # End of URI::ni