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   210053 use v5.42;
  5         16  
3 5     5   25 no warnings qw[experimental::try];
  5         8  
  5         296  
4 5     5   58 use feature 'try';
  5         8  
  5         664  
5 5     5   561 use At::Error qw[register throw];
  5         9  
  5         26  
6 5     5   2552 use parent -norequire => 'Exporter';
  5         1481  
  5         31  
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   528 };
  5         8  
  5         42  
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 677107 sub ensureValidDid ($did) {
  209         441  
  209         337  
36              
37             # check that all chars are boring ASCII
38 209 100       1397 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         774 my @parts = split ':', $did, -1; # negative limit, ftw
42 200 100       565 throw InvalidDidError('DID requires prefix, method, and method-specific content') if @parts < 3;
43             #
44 191 100       8029 throw InvalidDidError('DID requires "did:" prefix') if $parts[0] ne 'did';
45             #
46 185 100       2144 throw InvalidDidError('DID method must be lower-case letters') if $parts[1] !~ /^[a-z]+$/;
47             #
48 181 100       773 throw InvalidDidError('DID can not end with ":" or "%"') if $did =~ /[:%]$/;
49 169 100       407 throw InvalidDidError('DID is too long (2048 characters max)') if length $did > 2 * 1024;
50 167         832 1;
51             }
52              
53 187     187 1 19009 sub ensureValidDidRegex ($did) {
  187         349  
  187         388  
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       1455 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       411 throw InvalidDidError('DID is too long (2048 characters max)') if length $did > 2 * 1024;
59             #
60 133         442 1;
61             }
62              
63             # fatal error
64             register 'InvalidDidError', 1;
65             };
66             1;
67             __END__