File Coverage

blib/lib/URI/otpauth.pm
Criterion Covered Total %
statement 95 96 98.9
branch 23 24 95.8
condition 2 3 66.6
subroutine 22 22 100.0
pod 11 11 100.0
total 153 156 98.0


line stmt bran cond sub pod time code
1             package URI::otpauth;
2              
3 1     1   10 use warnings;
  1         1  
  1         89  
4 1     1   9 use strict;
  1         2  
  1         29  
5 1     1   767 use MIME::Base32();
  1         1305  
  1         30  
6 1     1   730 use URI::Split();
  1         4  
  1         29  
7 1     1   7 use URI::Escape();
  1         3  
  1         28  
8              
9 1     1   6 use parent qw( URI URI::_query );
  1         2  
  1         10  
10              
11             our $VERSION = '5.34';
12              
13             sub new {
14 5     5 1 4505 my ($class, @parameters) = @_;
15 5         19 my %fields = $class->_set(@parameters);
16             my $uri = URI::Split::uri_join(
17             'otpauth', $fields{type},
18 5         32 $class->_path(%fields),
19             $class->_query(%fields),
20             );
21 4         25 return bless \$uri, $class;
22             }
23              
24             sub _parse {
25 68     68   123 my $self = shift;
26 68         131 my ($scheme, $type, $path, $query, $frag) = URI::Split::uri_split(${$self});
  68         277  
27 68         380 $path =~ s/^\///smxg;
28 68         239 my @path_parts = split /:/smx, $path;
29 68         122 my ($issuer_prefix, $account_name);
30 68 100       198 if (scalar @path_parts == 1) {
31 16         35 $account_name = $path_parts[0];
32             }
33             else {
34 52         94 $issuer_prefix = $path_parts[0];
35 52         102 $account_name = $path_parts[1];
36             }
37 68         365 my %fields = (label => $path, type => $type, account_name => $account_name);
38 68         248 my $issuer_parameter = $self->query_param('issuer');
39 68 100       189 if (defined $issuer_parameter) {
    100          
40 45 50 66     156 if ((defined $issuer_prefix) && ($issuer_prefix ne $issuer_parameter)) {
41 0         0 Carp::carp(
42             "Issuer prefix from label '$issuer_prefix' does not match issuer parameter '$issuer_parameter'"
43             );
44             }
45 45         127 $fields{issuer} = $issuer_parameter;
46             }
47             elsif (defined $issuer_prefix) {
48 9         21 $fields{issuer} = URI::Escape::uri_unescape($issuer_prefix);
49             }
50 68 100       185 if (my $encoded_secret = $self->query_param('secret')) {
51 60         173 $fields{secret} = MIME::Base32::decode_base32($encoded_secret);
52             }
53 68         2905 foreach my $name (qw(algorithm digits counter period)) {
54 272 100       665 if (my $value = $self->query_param($name)) {
55 56         240 $fields{$name} = $value;
56             }
57             }
58 68         299 %fields = $self->_set(%fields);
59 68         418 return ($scheme, $fields{type}, \%fields, $query, $frag);
60             }
61              
62             my $label_escape_regex = qr/[^[:alnum:]@.]/smx;
63              
64             sub _set {
65 73     73   426 my ($self, %fields) = @_;
66 73         154 delete $fields{label};
67 73 100       190 if (defined $fields{account_name}) {
68 64 100       130 if (defined $fields{issuer}) {
69 56         155 $fields{label} = $fields{issuer} . q[:] . $fields{account_name};
70             }
71             else {
72 8         20 $fields{label} = $fields{account_name};
73             }
74             }
75 73 100       172 if (!length $fields{type}) {
76 2         7 $fields{type} = 'totp';
77             }
78 73         553 return %fields;
79             }
80              
81             my %field_names = map { $_ => 1 }
82             qw(secret label counter algorithm period digits issuer type account_name);
83             my @query_names = qw(secret issuer algorithm digits counter period);
84             my %defaults = (algorithm => 'SHA1', digits => 6, type => 'totp', period => 30);
85              
86             sub _field {
87 68     68   194 my ($self, $name, @remainder) = @_;
88 68         238 my ($scheme, $type, $fields, $query, $frag) = $self->_parse();
89              
90 68 100       193 if (!@remainder) {
91 67 100       180 if (defined $fields->{$name}) {
92 42         383 return $fields->{$name};
93             }
94             else {
95 25         217 return $defaults{$name};
96             }
97             }
98 1         4 $fields->{$name} = shift @remainder;
99 1         2 ${$self} = URI::Split::uri_join(
100             $scheme, $fields->{type},
101 1         7 $self->_path(%{$fields}),
102 1         3 $self->_query(%{$fields}), $frag
  1         5  
103             );
104 1         8 return $self;
105             }
106              
107             sub _query {
108 6     6   23 my ($class, %fields) = @_;
109 6 100       22 if (defined $fields{secret}) {
110 5         28 $fields{secret} = MIME::Base32::encode_base32($fields{secret});
111             }
112             else {
113 1         259 Carp::croak('secret is a mandatory parameter for ' . __PACKAGE__);
114             }
115             return join q[&],
116 11         68 map { join q[=], $_ => $fields{$_} }
117 5         384 grep { exists $fields{$_} } @query_names;
  30         66  
118             }
119              
120             sub _path {
121 6     6   27 my ($class, %fields) = @_;
122 6         16 my $path = $fields{label};
123 6         35 return $path;
124             }
125              
126             sub type {
127 13     13 1 38 my ($self, @parameters) = @_;
128 13         46 return $self->_field('type', @parameters);
129             }
130              
131 4     4 1 17 sub authority { return shift->type(@_); }
132              
133             sub label {
134 6     6 1 21 my ($self, @parameters) = @_;
135 6         31 return $self->_field('label', @parameters);
136             }
137              
138             sub account_name {
139 3     3 1 11 my ($self, @parameters) = @_;
140 3         12 return $self->_field('account_name', @parameters);
141             }
142              
143             sub issuer {
144 8     8 1 30 my ($self, @parameters) = @_;
145 8         31 return $self->_field('issuer', @parameters);
146             }
147              
148             sub secret {
149 9     9 1 34 my ($self, @parameters) = @_;
150 9         35 return $self->_field('secret', @parameters);
151             }
152              
153             sub algorithm {
154 8     8 1 834 my ($self, @parameters) = @_;
155 8         36 return $self->_field('algorithm', @parameters);
156             }
157              
158             sub counter {
159 8     8 1 33 my ($self, @parameters) = @_;
160 8         35 return $self->_field('counter', @parameters);
161             }
162              
163             sub digits {
164 5     5 1 20 my ($self, @parameters) = @_;
165 5         18 return $self->_field('digits', @parameters);
166             }
167              
168             sub period {
169 8     8 1 30 my ($self, @parameters) = @_;
170 8         29 return $self->_field('period', @parameters);
171             }
172              
173             1;
174              
175             __END__