File Coverage

blib/lib/URI/di.pm
Criterion Covered Total %
statement 81 123 65.8
branch 21 54 38.8
condition 2 9 22.2
subroutine 16 25 64.0
pod 8 8 100.0
total 128 219 58.4


line stmt bran cond sub pod time code
1             package URI::di;
2              
3             require URI;
4             require URI::_query;
5             require URI::_punycode;
6             require URI::QueryParam;
7             @ISA=qw(URI::_query URI);
8              
9             $VERSION = '0.03';
10              
11             # not sure why the module is laid out like this, oh well.
12              
13             =head1 NAME
14              
15             URI::di - URI scheme for digital signatures
16              
17             =head1 SYNOPSIS
18              
19             use URI;
20              
21             $u = URI->new('di: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
32             L.
33              
34             =cut
35              
36 2     2   24738 use strict;
  2         5  
  2         79  
37 2     2   10 use warnings; # FATAL => 'all';
  2         3  
  2         60  
38 2     2   2019 use utf8;
  2         32  
  2         12  
39              
40 2     2   1925 use MIME::Base64 ();
  2         2111  
  2         59  
41 2     2   800 use URI::Escape ();
  2         1329  
  2         45  
42 2     2   1911 use Digest ();
  2         1150  
  2         45  
43 2     2   14 use Carp ();
  2         3  
  2         30  
44 2     2   9 use Scalar::Util ();
  2         5  
  2         3179  
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 di: 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 $di = URI->new('di:sha-256;');
62             $di->compute(sub { shift->add($obj->as_string) });
63              
64             # Alternatively:
65              
66             use URI::di;
67              
68             my $di = URI::di->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 $di = URI::di->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 2415 my ($self, $data, $algo, $query) = @_;
99 1 50       5 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       4 $algo = $algo ? lc $algo : 'sha-256';
107 1 50       12 $self = ref $self ? $self->clone : URI->new("di:$algo");
108             # one last time
109 1         41 $algo = lc $self->algorithm;
110              
111             # easy out for exotic Digest subclasses
112 1 50 33     6 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         9 my $ctx = Digest->new(uc $algo);
117              
118 1 50       17186 if (ref $data) {
119 1 50       5 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   19 GLOB => sub { binmode $_[0]; $ctx->addfile($_[0]) },
  1         16  
126 0     0   0 SCALAR => sub { $ctx->add(${shift()}) },
  0         0  
127 0     0   0 CODE => sub { shift->($ctx) },
128 1         20 );
129              
130 1         2 my $ok;
131 1         8 for my $type (keys %handler) {
132             # XXX is there a less dumb way to do this?
133 3 50       12 $ok = $is_blessed ? $data->isa($type) : ref $data eq $type;
134 3 100       46 if ($ok) {
135 1         2433 $handler{$type}->($data);
136 1         78 last;
137             }
138             }
139 1 50       25 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         27 my $digest = $ctx->b64digest;
148 1         3 $digest =~ tr!+/!-_!;
149              
150 1         12 $self->opaque("$algo;$digest");
151             # XXX do something smarter with the query
152 1 50       137 $self->query_form_hash($query) if $query;
153              
154 1         8 $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('di:')>), 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 4 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       6 $algo = $algo ? lc $algo : 'sha-256';
203 1 50       13 $self = ref $self ? $self->clone : URI->new("di:$algo");
204             # one last time
205 1         58 $algo = lc $self->algorithm;
206              
207 1 50       5 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 0         0 $digest = $digest->b64digest;
212             }
213             else {
214 1         2 utf8::downgrade($digest);
215 1         2 my $op;
216 1 50       3 if (defined $kind) {
217 1 50 33     1277 $op = $OP{$kind} || $OP{$KINDS{$kind}}
218             or Carp::croak("Unrecognized representation '$kind'");
219             }
220             else {
221 0 0       0 my $x = $digest =~ /[\x80-\xff]/ ? 256
    0          
222             : $digest =~ /[^0-9A-Fa-f]/ ? 64 : 16;
223 0         0 $op = $OP{$x};
224             }
225              
226 1         6 $digest = $op->($digest);
227             # per Digest::base
228 1         6 $digest =~ s/=+$//;
229             }
230              
231             # XXX should probably compartmentalize this with the above method
232              
233 1         3 $digest =~ tr!+/!-_!;
234              
235 1         8 $self->opaque("$algo;$digest");
236             # XXX do something smarter with the query
237 1 50       26 $self->query_form_hash($query) if $query;
238              
239 1         3 $self;
240             }
241              
242             =head2 algorithm
243              
244             Retrieves the hash algorithm. This method is read-only, since it makes
245             no sense to change the algorithm of an already-computed hash.
246              
247             =cut
248              
249             sub algorithm {
250 3     3 1 6 my $self = shift;
251 3         18 my $o = $self->opaque;
252 3 50       94 return unless defined $o;
253 3         25 $o =~ s/^(.*?)(;.*)?$/$1/;
254 3         13 $o;
255             }
256              
257             =head2 b64digest [$RAW]
258              
259             Returns the digest encoded in Base64. An optional C<$RAW> argument
260             will return the digest without first translating from I
261             (section 5 in L).
262              
263             Like everything else in this module that pertains to the hash itself,
264             this accessor is read-only.
265              
266             =cut
267              
268             sub b64digest {
269 2     2 1 6 my ($self, $raw) = @_;
270 2         7 my $hash = $self->opaque;
271 2         53 $hash =~ s/^(?:.*?;)(.*?)(?:\?.*)?$/$1/;
272 2 50       10 $hash =~ tr!-_!+/! unless $raw;
273 2         30 $hash;
274             }
275              
276             =head2 hexdigest
277              
278             Returns the hexadecimal cryptographic digest we're all familiar with.
279              
280             =cut
281              
282             sub hexdigest {
283 1     1 1 6 unpack 'H*', shift->digest;
284             }
285              
286             =head2 digest
287              
288             Retrieves a binary digest, in keeping with the nomenclature in
289             L.
290              
291             =cut
292              
293             sub digest {
294 2     2 1 1448 MIME::Base64::decode_base64(shift->b64digest);
295             }
296              
297             =head2 locators
298              
299             This is a convenience method to instantiate any locators defined in L
300             2.1.4|http://tools.ietf.org/html/draft-hallambaker-digesturi-02#section-2.1.4>
301             as URI objects. If you want to set these values, use L
302             with the C or C keys. Returns all locators in list
303             context, and the first one in scalar context (which of course may be
304             undef).
305              
306             =cut
307              
308             sub locators {
309 0     0 1   my $self = shift;
310 0           my $algo = $self->algorithm;
311 0           my $digest = $self->b64digest(1);
312              
313 0           my @loc;
314 0           for my $scheme (qw(http https)) {
315 0           for my $host ($self->query_param($scheme)) {
316             # RFC 5785 kinda gives me the creeps.
317 0           push @loc, URI->new(sprintf '%s://%s/.well-known/di/%s/%s',
318             $scheme, $host, $algo, $digest);
319             }
320             }
321              
322 0 0         return wantarray ? @loc : $loc[0];
323             }
324              
325             =head2 crypto
326              
327             Returns the cryptography spec embedded in the C or C
328             parameters. A key is kind of a weird thing to embed in a URI, but
329             whatever floats your boat. As such, I have yet to implement this in
330             any sane way.
331              
332             =cut
333              
334             sub crypto {
335 0     0 1   my ($self, $which, $new) = @_;
336 0 0         Carp::croak("Only 'enc' and 'menc' are valid values.")
337             unless $which =~ /^m?enc/i;
338              
339 0           my ($old) = $self->query_param($which);
340 0 0         $old = URI::di::CryptoSpec->new($old) if defined $old;
341              
342 0 0         if (defined $new) {
343 0           $new = URI::di::CryptoSpec->new($new);
344 0           $self->query_param(lc $which => "$new");
345             # i always thought this behaviour was weird.
346 0           return $old;
347             }
348              
349 0           $old;
350             }
351              
352             package URI::di::CryptoSpec;
353              
354 2     2   1723 use overload '""' => \&as_string;
  2         1920  
  2         19  
355              
356             sub new {
357 0     0     my ($class, $string) = @_;
358 0           bless \$string, $class;
359             }
360              
361             sub cipher {
362 0     0     my $self = shift;
363 0           my $s = $$self;
364 0           $s =~ /^(.*?)(:.*)?$/;
365 0           $1;
366             }
367              
368             sub key {
369 0     0     my $self = shift;
370 0           my $s = $$self;
371 0           $s =~ /^(?:[^:]+:)([^:]*?)(:.*)?$/;
372 0           $1;
373             }
374              
375             sub iv {
376 0     0     my $self = shift;
377 0           my $s = $$self;
378 0           $s =~ /^(?:[^:]+:){2}(.*?)$/;
379 0           $1;
380             }
381              
382             sub as_string {
383 0     0     ${$_[0]};
  0            
384             }
385              
386             =head1 SEE ALSO
387              
388             =over 4
389              
390             =item L
391              
392             =item L
393              
394             =item L
395              
396             =back
397              
398             =head1 AUTHOR
399              
400             Dorian Taylor, C<< >>
401              
402             =head1 BUGS
403              
404             Please report any bugs or feature requests to C, or through
405             the web interface at L. I will be notified, and then you'll
406             automatically be notified of progress on your bug as I make changes.
407              
408              
409             =head1 SUPPORT
410              
411             You can find documentation for this module with the perldoc command.
412              
413             perldoc URI::di
414              
415              
416             You can also look for information at:
417              
418             =over 4
419              
420             =item * RT: CPAN's request tracker (report bugs here)
421              
422             L
423              
424             =item * AnnoCPAN: Annotated CPAN documentation
425              
426             L
427              
428             =item * CPAN Ratings
429              
430             L
431              
432             =item * Search CPAN
433              
434             L
435              
436             =back
437              
438             =head1 LICENSE AND COPYRIGHT
439              
440             Copyright 2012 Dorian Taylor.
441              
442             Licensed under the Apache License, Version 2.0 (the "License"); you
443             may not use this file except in compliance with the License. You may
444             obtain a copy of the License at
445              
446             L
447              
448             Unless required by applicable law or agreed to in writing, software
449             distributed under the License is distributed on an "AS IS" BASIS,
450             WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
451             implied. See the License for the specific language governing
452             permissions and limitations under the License.
453              
454             =cut
455              
456             1; # End of URI::di