File Coverage

lib/At/Protocol/DID.pm
Criterion Covered Total %
statement 34 48 70.8
branch 16 16 100.0
condition n/a
subroutine 8 10 80.0
pod 3 3 100.0
total 61 77 79.2


line stmt bran cond sub pod time code
1             package At::Protocol::DID 1.0 {
2 5     5   217191 use v5.42;
  5         19  
3 5     5   26 no warnings qw[experimental::try];
  5         11  
  5         276  
4 5     5   27 use feature 'try';
  5         7  
  5         693  
5 5     5   356 use At::Error qw[register throw];
  5         9  
  5         47  
6 5     5   2901 use parent -norequire => 'Exporter';
  5         1552  
  5         34  
7             use overload
8 0     0   0 '""' => sub ( $s, $u, $q ) {
  0         0  
  0         0  
  0         0  
  0         0  
9 0         0 $$s;
10 5     5   485 };
  5         9  
  5         38  
11             our %EXPORT_TAGS = ( all => [ our @EXPORT_OK = qw[ensureValidDid ensureValidDidRegex] ] );
12              
13 0     0 1 0 sub new( $class, $did ) {
  0         0  
  0         0  
  0         0  
14 0         0 try {
15 0         0 ensureValidDid($did);
16             }
17 0         0 catch ($err) { return; }
18 0         0 bless \$did, $class;
19             }
20              
21             #~ Taken from https://github.com/bluesky-social/atproto/blob/main/packages/syntax/src/did.ts
22             #~ Human-readable constraints:
23             #~ - valid W3C DID (https://www.w3.org/TR/did-core/#did-syntax)
24             #~ - entire URI is ASCII: [a-zA-Z0-9._:%-]
25             #~ - always starts "did:" (lower-case)
26             #~ - method name is one or more lower-case letters, followed by ":"
27             #~ - remaining identifier can have any of the above chars, but can not end in ":"
28             #~ - it seems that a bunch of ":" can be included, and don't need spaces between
29             #~ - "%" is used only for "percent encoding" and must be followed by two hex characters (and thus can't end in "%")
30             #~ - query ("?") and fragment ("#") stuff is defined for "DID URIs", but not as part of identifier itself
31             #~ - "The current specification does not take a position on the maximum length of a DID"
32             #~ - in current atproto, only allowing did:plc and did:web. But not *forcing* this at lexicon layer
33             #~ - hard length limit of 8KBytes
34             #~ - not going to validate "percent encoding" here
35 209     209 1 529887 sub ensureValidDid ($did) {
  209         441  
  209         261  
36              
37             # check that all chars are boring ASCII
38 209 100       1250 throw InvalidDidError('Disallowed characters in DID (ASCII letters, digits, and a couple other characters only)')
39             unless $did =~ /^[a-zA-Z0-9._:%-]*$/;
40             #
41 200         856 my @parts = split ':', $did, -1; # negative limit, ftw
42 200 100       600 throw InvalidDidError('DID requires prefix, method, and method-specific content') if @parts < 3;
43             #
44 191 100       551 throw InvalidDidError('DID requires "did:" prefix') if $parts[0] ne 'did';
45             #
46 185 100       1800 throw InvalidDidError('DID method must be lower-case letters') if $parts[1] !~ /^[a-z]+$/;
47             #
48 181 100       756 throw InvalidDidError('DID can not end with ":" or "%"') if $did =~ /[:%]$/;
49 169 100       439 throw InvalidDidError('DID is too long (2048 characters max)') if length $did > 2 * 1024;
50 167         716 1;
51             }
52              
53 187     187 1 19326 sub ensureValidDidRegex ($did) {
  187         339  
  187         276  
54              
55             #~ simple regex to enforce most constraints via just regex and length.
56             #~ hand wrote this regex based on above constraints
57 187 100       1308 throw InvalidDidError(q[DID didn't validate via regex]) if $did !~ /^did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-]$/;
58 135 100       337 throw InvalidDidError('DID is too long (2048 characters max)') if length $did > 2 * 1024;
59             #
60 133         461 1;
61             }
62              
63             # fatal error
64             register 'InvalidDidError', 1;
65             };
66             1;
67             __END__