File Coverage

blib/lib/URI/PackageURL/Util.pm
Criterion Covered Total %
statement 161 210 76.6
branch 34 76 44.7
condition 30 91 32.9
subroutine 21 24 87.5
pod 2 2 100.0
total 248 403 61.5


line stmt bran cond sub pod time code
1             package URI::PackageURL::Util;
2              
3 9     9   151930 use feature ':5.10';
  9         20  
  9         1360  
4 9     9   55 use strict;
  9         17  
  9         217  
5 9     9   668 use utf8;
  9         356  
  9         54  
6 9     9   223 use warnings;
  9         17  
  9         452  
7              
8 9     9   47 use File::Spec;
  9         15  
  9         308  
9 9     9   45 use File::Basename qw(dirname basename);
  9         15  
  9         634  
10 9     9   53 use Exporter qw(import);
  9         16  
  9         405  
11 9     9   62 use Carp ();
  9         16  
  9         39365  
12              
13             our $VERSION = '2.25';
14             our @EXPORT = qw(purl_to_urls purl_types);
15              
16             sub purl_types {
17              
18 0     0 1 0 my @list = ();
19              
20 0         0 my $spec_dir = File::Spec->catfile(dirname(__FILE__), 'types');
21              
22 0 0       0 opendir(my $dh, $spec_dir) or Carp::croak "Can't open spec dir: $!";
23              
24 0         0 while (my $file = readdir $dh) {
25 0 0       0 next unless -f File::Spec->catfile($spec_dir, $file);
26 0         0 $file =~ s/\-definition\.json//;
27 0         0 push @list, $file;
28             }
29              
30 0         0 closedir $dh;
31              
32 0         0 @list = sort @list;
33              
34 0 0       0 return wantarray ? @list : \@list;
35              
36             }
37              
38             sub purl_to_urls {
39              
40 1872     1872 1 282215 my $purl = shift;
41              
42 1872 100       5208 if (ref $purl ne 'URI::PackageURL') {
43 17         1029 require URI::PackageURL;
44 17         104 $purl = URI::PackageURL->from_string($purl);
45             }
46              
47 1872         20337 my %TYPES = (
48             bitbucket => \&_to_bitbucket_urls,
49             cargo => \&_to_cargo_urls,
50             composer => \&_to_composer_urls,
51             cpan => \&_to_cpan_urls,
52             docker => \&_to_docker_urls,
53             gem => \&_to_gem_urls,
54             github => \&_to_github_urls,
55             gitlab => \&_to_gitlab_urls,
56             golang => \&_to_golang_urls,
57             luarocks => \&_to_luarocks_urls,
58             maven => \&_to_maven_urls,
59             npm => \&_to_npm_urls,
60             nuget => \&_to_nuget_urls,
61             pypi => \&_to_pypi_urls,
62             );
63              
64 1872         3557 my $urls = {};
65              
66 1872 50       5613 if (defined $TYPES{$purl->type}) {
67 1872         4153 $urls = $TYPES{$purl->type}->($purl);
68             }
69              
70 1872 50       5436 if (my $download_url = $purl->qualifiers->{download_url}) {
71 0         0 $urls->{download} = $download_url;
72             }
73              
74 1872         11021 return $urls;
75              
76             }
77              
78             sub _to_bitbucket_urls {
79              
80 1     1   3 my $purl = shift;
81              
82 1         4 my $name = $purl->name;
83 1         6 my $namespace = $purl->namespace;
84 1         5 my $version = $purl->version;
85 1         5 my $qualifiers = $purl->qualifiers;
86 1   50     8 my $file_ext = $qualifiers->{ext} || 'tar.gz';
87              
88 1         3 my $urls = {};
89              
90 1 50 33     7 if ($name && $namespace) {
91 1         5 $urls->{repository} = "https://bitbucket.org/$namespace/$name";
92             }
93              
94 1 50       4 if ($version) {
95 1         4 $urls->{download} = "https://bitbucket.org/$namespace/$name/get/$version.$file_ext";
96             }
97              
98 1         4 return $urls;
99              
100             }
101              
102             sub _to_cargo_urls {
103              
104 1     1   4 my $purl = shift;
105              
106 1         11 my $name = $purl->name;
107 1         4 my $version = $purl->version;
108              
109 1 50 33     9 if ($name && $version) {
110             return {
111 1         9 repository => "https://crates.io/crates/$name/$version",
112             download => "https://crates.io/api/v1/crates/$name/$version/download"
113             };
114             }
115              
116 0         0 return {repository => "https://crates.io/crates/$name"};
117              
118             }
119              
120             sub _to_composer_urls {
121              
122 1     1   3 my $purl = shift;
123              
124 1         6 my $name = $purl->name;
125 1         5 my $namespace = $purl->namespace;
126              
127 1 50 33     10 return unless ($name && $namespace);
128              
129 1         7 return {repository => "https://packagist.org/packages/$namespace/$name"};
130              
131             }
132              
133             sub _to_cpan_urls {
134              
135 1859     1859   3533 my ($purl, $purl_type) = @_;
136              
137 1859         4708 my $name = $purl->name;
138 1859         4575 my $version = $purl->version;
139 1859         4132 my $qualifiers = $purl->qualifiers;
140 1859   33     4399 my $author = $purl->namespace // $qualifiers->{author};
141 1859   50     7670 my $file_ext = $qualifiers->{ext} || 'tar.gz';
142 1859   33     6841 my $repository_url = $qualifiers->{repository_url} || $purl->definition->default_repository_url;
143 1859         3899 my $distpath = $qualifiers->{distpath};
144 1859         3370 my $distdir = $qualifiers->{distdir};
145              
146 1859         11050 $repository_url =~ s{/$}{};
147              
148 1859 50       11035 if ($repository_url !~ /^(http|https|file|ftp):\/\//) {
149 0         0 $repository_url = 'https://' . $repository_url;
150             }
151              
152 1859         6164 my $urls = {repository => "https://metacpan.org/dist/$name"};
153              
154 1859 50 33     8482 if ($name && $version && $author) {
      33        
155              
156 1859         4382 $urls->{repository} = "https://metacpan.org/release/$author/$name-$version";
157              
158 1859         3899 my $author_a = substr($author, 0, 1);
159 1859         3018 my $author_au = substr($author, 0, 2);
160              
161 1859         3040 my $download_base_url = "$repository_url/authors/id";
162              
163 1859 50 66     3624 if (!$distpath && !$distdir) {
164 2         14 $urls->{download} = "$download_base_url/$author_a/$author_au/$author/$name-$version.$file_ext";
165             }
166              
167 1859 100 66     5821 if ($distpath && !$distdir) {
168              
169 1857         3420 $distpath =~ s{^/}{};
170 1857         2787 $distpath =~ s{^CPAN/}{};
171 1857         2597 $distpath =~ s{^id/}{};
172 1857         2635 $distpath =~ s{^authors/id/}{};
173              
174 1857 100       6566 if ($distpath !~ /^([A-Z]{1})\/([A-Z]{2})/) {
175              
176 928         3048 my @parts = split '/', $distpath;
177 928         2281 my $author_a = substr($parts[0], 0, 1);
178 928         2006 my $author_au = substr($parts[0], 0, 2);
179              
180 928         3504 $distpath = join '/', $author_a, $author_au, $distpath;
181              
182             }
183              
184 1857         4414 $urls->{download} = "$download_base_url/$distpath";
185              
186             }
187              
188 1859 50 33     4858 if ($distdir && !$distpath) {
189 0         0 $urls->{download} = "$download_base_url/$author_a/$author_au/$author/$distdir/$name-$version.$file_ext";
190             }
191              
192             }
193              
194 1859         5724 return $urls;
195              
196             }
197              
198             sub _to_docker_urls {
199              
200 2     2   5 my $purl = shift;
201              
202 2         6 my $name = $purl->name;
203 2         8 my $namespace = $purl->namespace;
204 2         8 my $version = $purl->version;
205 2         7 my $qualifiers = $purl->qualifiers;
206 2   50     14 my $repository_url = $qualifiers->{repository_url} || 'https://hub.docker.com';
207              
208 2 50       17 if ($repository_url !~ /^(http|https):\/\//) {
209 0         0 $repository_url = 'https://' . $repository_url;
210             }
211              
212 2         5 my $urls = {};
213              
214 2 50       32 if ($repository_url !~ /hub.docker.com/) {
215 0         0 return $urls;
216             }
217              
218 2 100       6 if (!$namespace) {
219 1         6 $urls->{repository} = "$repository_url/_/$name";
220             }
221              
222 2 100 66     13 if ($name && $namespace) {
223 1         6 $urls->{repository} = "$repository_url/r/$namespace/$name";
224             }
225              
226 2         8 return $urls;
227              
228             }
229              
230             sub _to_gem_urls {
231              
232 1     1   4 my $purl = shift;
233              
234 1         5 my $name = $purl->name;
235 1         6 my $version = $purl->version;
236              
237 1 50 33     7 if ($name && $version) {
238             return {
239 1         9 repository => "https://rubygems.org/gems/$name/versions/$version",
240             download => "https://rubygems.org/downloads/$name-$version.gem"
241             };
242             }
243              
244 0         0 return {repository => "https://rubygems.org/gems/$name"};
245              
246             }
247              
248             sub _to_github_urls {
249              
250 2     2   6 my $purl = shift;
251              
252 2         7 my $name = $purl->name;
253 2         7 my $namespace = $purl->namespace;
254 2         8 my $version = $purl->version;
255 2         6 my $qualifiers = $purl->qualifiers;
256 2   50     13 my $file_ext = $qualifiers->{ext} || 'tar.gz';
257              
258 2         4 my $urls = {};
259              
260 2 50 33     15 if ($name && $namespace) {
261 2         10 $urls->{repository} = "https://github.com/$namespace/$name";
262             }
263              
264 2 50       7 if ($version) {
265              
266 2         10 my $is_sha1 = ($version =~ /^[a-fA-F0-9]{40}$/);
267              
268 2 100       6 if ($is_sha1) {
269 1         5 $urls->{download} = "https://github.com/$namespace/$name/archive/$version.$file_ext";
270             }
271             else {
272 1         5 $urls->{download} = "https://github.com/$namespace/$name/archive/refs/tags/$version.$file_ext";
273             }
274              
275             }
276              
277 2         8 return $urls;
278              
279             }
280              
281             sub _to_gitlab_urls {
282              
283 1     1   3 my $purl = shift;
284              
285 1         5 my $name = $purl->name;
286 1         5 my $namespace = $purl->namespace;
287 1         5 my $version = $purl->version;
288 1         4 my $qualifiers = $purl->qualifiers;
289 1   50     9 my $file_ext = $qualifiers->{ext} || 'tar.gz';
290              
291 1         3 my $urls = {};
292              
293 1 50 33     17 if ($name && $namespace) {
294 1         5 $urls->{repository} = "https://gitlab.com/$namespace/$name";
295             }
296              
297 1 50       4 if ($version) {
298 1         46 $urls->{download} = "https://gitlab.com/$namespace/$name/-/archive/$version/$name-$version.$file_ext";
299             }
300              
301 1         6 return $urls;
302              
303             }
304              
305             sub _to_golang_urls {
306              
307 0     0   0 my $purl = shift;
308              
309 0         0 my $name = $purl->name;
310 0         0 my $namespace = $purl->namespace;
311 0         0 my $version = $purl->version;
312              
313 0         0 my $urls = {};
314              
315 0 0 0     0 if ($name && $namespace) {
316 0         0 $urls->{repository} = "https://pkg.go.dev/$namespace/$name";
317             }
318              
319             # TODO ???
320             # if ($name && $namespace && $version) {
321             # $urls->{repository} = "https://pkg.go.dev/$namespace/$name\@v$version";
322             # }
323              
324 0         0 return $urls;
325              
326             }
327              
328             sub _to_luarocks_urls {
329              
330 0     0   0 my $purl = shift;
331              
332 0         0 my $name = $purl->name;
333 0         0 my $namespace = $purl->namespace;
334 0         0 my $version = $purl->version;
335 0         0 my $qualifiers = $purl->qualifiers;
336 0   0     0 my $repository_url = $qualifiers->{repository_url} || 'https://luarocks.org';
337              
338 0 0       0 if ($repository_url !~ /^(http|https):\/\//) {
339 0         0 $repository_url = 'https://' . $repository_url;
340             }
341              
342 0         0 my $urls = {};
343              
344 0 0       0 if (!$namespace) {
345 0         0 $urls->{repository} = "$repository_url/modules/$name";
346             }
347              
348 0 0 0     0 if ($name && $namespace) {
349 0         0 $urls->{repository} = "$repository_url/modules/$namespace/$name";
350             }
351              
352 0         0 return $urls;
353              
354             }
355              
356             sub _to_maven_urls {
357              
358 1     1   4 my $purl = shift;
359              
360 1         5 my $namespace = $purl->namespace;
361 1         5 my $name = $purl->name;
362 1         5 my $version = $purl->version;
363 1         4 my $qualifiers = $purl->qualifiers;
364 1   50     8 my $extension = $qualifiers->{extension} // 'jar';
365 1   50     8 my $repository_url = $qualifiers->{repository_url} // 'https://repo.maven.apache.org/maven2';
366              
367 1 50       10 if ($repository_url !~ /^(http|https):\/\//) {
368 0         0 $repository_url = 'https://' . $repository_url;
369             }
370              
371 1 50 33     17 if ($namespace && $name && $version) {
      33        
372              
373 1         8 (my $ns_url = $namespace) =~ s/\./\//g;
374              
375             return {
376 1         12 repository => "https://mvnrepository.com/artifact/$namespace/$name/$version",
377             download => "$repository_url/$ns_url/$name/$version/$name-$version.$extension"
378             };
379              
380             }
381              
382 0 0 0     0 if ($namespace && $name) {
383 0         0 return {repository => "https://mvnrepository.com/artifact/$namespace/$name"};
384             }
385              
386             }
387              
388             sub _to_npm_urls {
389              
390 1     1   3 my $purl = shift;
391              
392 1         6 my $namespace = $purl->namespace;
393 1         5 my $name = $purl->name;
394 1         5 my $version = $purl->version;
395              
396 1 50 33     47 if ($namespace && $name && $version) {
      33        
397             return {
398 1         14 repository => "https://www.npmjs.com/package/$namespace/$name/v/$version",
399             download => "https://registry.npmjs.org/$namespace/$name/-/$name-$version.tgz"
400             };
401             }
402              
403 0 0 0     0 if ($name && $version) {
404             return {
405 0         0 repository => "https://www.npmjs.com/package/$name/v/$version",
406             download => "https://registry.npmjs.org/$name/-/$name-$version.tgz"
407             };
408             }
409              
410 0 0 0     0 if ($namespace && $name) {
411 0         0 return {repository => "https://www.npmjs.com/package/$namespace/$name"};
412             }
413              
414 0         0 return {repository => "https://www.npmjs.com/package/$name"};
415              
416             }
417              
418             sub _to_nuget_urls {
419              
420 1     1   3 my $purl = shift;
421              
422 1         5 my $name = $purl->name;
423 1         4 my $version = $purl->version;
424              
425 1 50 33     9 if ($name && $version) {
426             return {
427 1         10 repository => "https://www.nuget.org/packages/$name/$version",
428             download => "https://www.nuget.org/api/v2/package/$name/$version"
429             };
430             }
431              
432 0         0 return {repository => "https://www.nuget.org/packages/$name"};
433              
434             }
435              
436             sub _to_pypi_urls {
437              
438 1     1   4 my $purl = shift;
439              
440 1         5 my $name = $purl->name;
441 1         6 my $version = $purl->version;
442              
443 1 50 33     9 if ($name && $version) {
444 1         8 return {repository => "https://pypi.org/project/$name/$version"};
445             }
446              
447 0           return {repository => "https://pypi.org/project/$name"};
448              
449             }
450              
451             1;
452              
453             __END__