File Coverage

blib/lib/URI/ni.pm
Criterion Covered Total %
statement 79 103 76.7
branch 21 54 38.8
condition 5 23 21.7
subroutine 15 18 83.3
pod 7 7 100.0
total 127 205 61.9


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