File Coverage

blib/lib/URI/AnyService.pm
Criterion Covered Total %
statement 68 71 95.7
branch 26 36 72.2
condition 4 8 50.0
subroutine 12 13 92.3
pod 2 2 100.0
total 112 130 86.1


line stmt bran cond sub pod time code
1             package URI::AnyService;
2              
3             # ABSTRACT: Simple /etc/services-driven URI objects
4 2     2   324877 use version;
  2         1741  
  2         14  
5             our $VERSION = 'v0.90.0'; # VERSION
6              
7 2     2   161 use v5.16;
  2         11  
8 2     2   12 use strict;
  2         14  
  2         133  
9 2     2   13 use warnings;
  2         4  
  2         148  
10              
11             ### XXX: Is this abusing a private, undocumented class from an external module? Yes.
12             ###
13             ### Are the risks of copying entire functions wholesale, and having those functions get changed from
14             ### underneath us, far greater than the risk of this load-bearing and intertwined package name getting
15             ### renamed? Also yes.
16              
17 2     2   413 use parent 'URI::_server';
  2         236  
  2         12  
18              
19 2     2   25063 use URI ();
  2         6  
  2         30  
20 2     2   7 use Carp ();
  2         3  
  2         1708  
21              
22             #pod =head1 SYNOPSIS
23             #pod
24             #pod use URI;
25             #pod use URI::AnyService;
26             #pod
27             #pod my $uri = URI->new('smtp://example.com');
28             #pod say $uri->has_recognized_scheme; # !!0
29             #pod say $uri->port; # ''
30             #pod
31             #pod my $uas = URI::AnyService->new('smtp://example.com');
32             #pod say $uas->has_recognized_scheme; # !!1
33             #pod say $uas->port; # 25
34             #pod
35             #pod =head1 DESCRIPTION
36             #pod
37             #pod This module creates simple L-based objects for protocols that it doesn't yet have defined.
38             #pod It works just like L, except it just supports the L,
39             #pod and not anything scheme-specific. In other words, most of the methods that C/C
40             #pod objects support.
41             #pod
42             #pod Port numbers are looked up from a C file. If you don't have a C
43             #pod file, one will be provided for you, but there's no guarantee that it will be up-to-date or apply
44             #pod to your specific protocol.
45             #pod
46             #pod This allows you to use a URL like C without having to set a default port in
47             #pod the URL.
48             #pod
49             #pod =head1 GLOBAL VARIABLES
50             #pod
51             #pod =head2 %SERVICE_PORTS
52             #pod
53             #pod This is a hash of schemes/protocols to port numbers. It is loaded from the C file,
54             #pod as soon as you C this module.
55             #pod
56             #pod Most of the time, you shouldn't mess with this. But, if there is some new protocol that you need
57             #pod to add that happens to not exist in your C files, you can do so with:
58             #pod
59             #pod $URI::AnyService::SERVICE_PORTS{'my-protocol'} = 1234;
60             #pod
61             #pod This variable is not exportable, so you need to use the full package syntax.
62             #pod
63             #pod =cut
64              
65             our %SERVICE_PORTS;
66              
67             #pod =head1 USE OPTIONS
68             #pod
69             #pod =head2 :InternalServicesData
70             #pod
71             #pod use URI::AnyService ':InternalServicesData';
72             #pod
73             #pod This will force the internal services data to be loaded, even if the C file is
74             #pod available. This is useful for testing, or if you know that the C files on your
75             #pod servers are not consistent.
76             #pod
77             #pod Please note that there are no guarantees that the internal data is up-to-date or accurate. It's
78             #pod mostly used as a "last resort" fallback.
79             #pod
80             #pod =cut
81              
82             my $found_services_in;
83             my $force_internal_services_data = 0;
84              
85             sub _load_services {
86 1     1   2 my $fh;
87             # XXX: Windows support not tested. This is just the usual location where it's expected to be.
88 1 50       5 my $services_file = $^O eq 'MSWin32' ? 'C:\Windows\System32\drivers\etc\services' : '/etc/services';
89 1 50 33     5 if (!$force_internal_services_data && -f -r $services_file) {
90 0 0       0 open $fh, '<', $services_file or Carp::croak("Can't open /etc/services: $!");
91 0         0 $found_services_in = $services_file;
92             }
93             else {
94 1         2 $fh = \*DATA;
95 1         2 $found_services_in = '';
96             }
97              
98 1         4 while (my $line = <$fh>) {
99 373 100       457 next if $line =~ /^#/;
100 372         500 $line =~ s/#.*$//;
101 372 100 50     950 next unless ($line // '') =~ /^$URI::scheme_re\s+/;
102              
103 370         546 my ($service, $port_proto, @other_services) = split /\s+/, $line;
104 370         484 my ($port, $proto) = split m!/!, $port_proto;
105 370         632 $SERVICE_PORTS{$service} = $port;
106 370 100       748 $SERVICE_PORTS{@other_services} = $port if @other_services;
107             }
108 1         69 close $fh;
109             }
110              
111             sub import {
112 1     1   10 my ($class, @opts) = @_;
113 1 50       3 $force_internal_services_data = 1 if grep { $_ eq ':InternalServicesData' } @opts;
  1         6  
114 1         2 _load_services();
115             }
116              
117             #pod =head1 CONSTRUCTOR
118             #pod
119             #pod URI::AnyService->new($str);
120             #pod URI::AnyService->new($str, $scheme);
121             #pod
122             #pod This constructor is very similiar to L, including the
123             #pod somewhat-legacy two parameter form. In most cases, you should just use the whole URL string.
124             #pod
125             #pod A scheme that was defined in the services file is required.
126             #pod
127             #pod =cut
128              
129             # Less /o bugs, more modern usage, still using the URI scheme RE
130             my $scheme_re = qr/^($URI::scheme_re):/;
131             my $scheme_re_sans_colon = qr/^($URI::scheme_re)/;
132              
133             ### XXX: Most of this code for `new` and `_scheme` were copied from URI, but irrelevant parts
134             ### have been removed and optimized.
135              
136             sub new {
137 25     25 1 204642 my ($class, $uri, $scheme) = @_;
138              
139 25 50       67 $uri = defined $uri ? "$uri" : ""; # stringify
140              
141             # Get rid of potential wrapping
142 25         64 $uri =~ s/^<(?:URL:)?(.*)>$/$1/;
143 25         53 $uri =~ s/^"(.*)"$/$1/;
144 25         89 $uri =~ s/^\s+//;
145 25         53 $uri =~ s/\s+$//;
146              
147             # We ARE the implementor class
148 25 100       301 $scheme = $1 if $uri =~ s/$scheme_re//;
149 25 100       385 Carp::croak("No scheme defined in URI: $uri") unless $scheme;
150 23 100       183 Carp::croak("Scheme '$scheme' not found in $found_services_in") unless $SERVICE_PORTS{lc $scheme};
151              
152             # Find all funny characters and encode the bytes
153 22         84 $uri = URI->_uric_escape($uri);
154 22         597 $uri = "$scheme:$uri";
155 22         76 my $self = bless \$uri, $class;
156 22         46 $self;
157             }
158              
159 0     0   0 sub _no_scheme_ok { 0 }
160              
161             sub _scheme {
162 33     33   3814 my $self = shift;
163              
164 33 100       75 unless (@_) {
165 30 50       286 return undef unless $$self =~ $scheme_re;
166 30         97 return $1;
167             }
168              
169             # URI supports this weird setting of the scheme to create a new URI object, which is fine,
170             # but it should call us, not URI->new.
171 3         4 my $old;
172 3         4 my $new = shift;
173 3 100 66     14 if (defined $new && length $new) {
174             # Error checks before we mutate $self
175 2 50       66 Carp::croak("Bad scheme '$new'") unless $new =~ /$scheme_re_sans_colon$/;
176 2 100       251 Carp::croak("Scheme '$new' not found in $found_services_in") unless $SERVICE_PORTS{lc $new};
177              
178 1 50       8 $old = $1 if $$self =~ s/$scheme_re//;
179 1         6 my $newself = __PACKAGE__->new("$new:$$self");
180 1         3 $$self = $$newself;
181 1         4 bless $self, ref($newself);
182             }
183             else {
184 1 50       12 $old = $1 if $$self =~ $scheme_re;
185             }
186              
187 2         5 return $old;
188             }
189              
190             sub default_port {
191 18     18 1 15224 my ($self) = @_;
192 18         62 return $SERVICE_PORTS{$self->scheme};
193             }
194              
195             1;
196              
197             =pod
198              
199             =encoding UTF-8
200              
201             =head1 NAME
202              
203             URI::AnyService - Simple /etc/services-driven URI objects
204              
205             =head1 VERSION
206              
207             version v0.90.0
208              
209             =head1 SYNOPSIS
210              
211             use URI;
212             use URI::AnyService;
213              
214             my $uri = URI->new('smtp://example.com');
215             say $uri->has_recognized_scheme; # !!0
216             say $uri->port; # ''
217              
218             my $uas = URI::AnyService->new('smtp://example.com');
219             say $uas->has_recognized_scheme; # !!1
220             say $uas->port; # 25
221              
222             =head1 DESCRIPTION
223              
224             This module creates simple L-based objects for protocols that it doesn't yet have defined.
225             It works just like L, except it just supports the L,
226             and not anything scheme-specific. In other words, most of the methods that C/C
227             objects support.
228              
229             Port numbers are looked up from a C file. If you don't have a C
230             file, one will be provided for you, but there's no guarantee that it will be up-to-date or apply
231             to your specific protocol.
232              
233             This allows you to use a URL like C without having to set a default port in
234             the URL.
235              
236             =head1 GLOBAL VARIABLES
237              
238             =head2 %SERVICE_PORTS
239              
240             This is a hash of schemes/protocols to port numbers. It is loaded from the C file,
241             as soon as you C this module.
242              
243             Most of the time, you shouldn't mess with this. But, if there is some new protocol that you need
244             to add that happens to not exist in your C files, you can do so with:
245              
246             $URI::AnyService::SERVICE_PORTS{'my-protocol'} = 1234;
247              
248             This variable is not exportable, so you need to use the full package syntax.
249              
250             =head1 USE OPTIONS
251              
252             =head2 :InternalServicesData
253              
254             use URI::AnyService ':InternalServicesData';
255              
256             This will force the internal services data to be loaded, even if the C file is
257             available. This is useful for testing, or if you know that the C files on your
258             servers are not consistent.
259              
260             Please note that there are no guarantees that the internal data is up-to-date or accurate. It's
261             mostly used as a "last resort" fallback.
262              
263             =head1 CONSTRUCTOR
264              
265             URI::AnyService->new($str);
266             URI::AnyService->new($str, $scheme);
267              
268             This constructor is very similiar to L, including the
269             somewhat-legacy two parameter form. In most cases, you should just use the whole URL string.
270              
271             A scheme that was defined in the services file is required.
272              
273             =head1 AUTHOR
274              
275             Grant Street Group
276              
277             =head1 COPYRIGHT AND LICENSE
278              
279             This software is Copyright (c) 2025 by Grant Street Group.
280              
281             This is free software, licensed under:
282              
283             The Artistic License 2.0 (GPL Compatible)
284              
285             =cut
286              
287             __DATA__