File Coverage

blib/lib/WebShortcutUtil/Write.pm
Criterion Covered Total %
statement 125 125 100.0
branch 17 22 77.2
condition n/a
subroutine 27 27 100.0
pod 7 11 63.6
total 176 185 95.1


line stmt bran cond sub pod time code
1             package WebShortcutUtil::Write;
2              
3 1     1   39521 use 5.006_001;
  1         5  
  1         36  
4 1     1   4 use strict;
  1         1  
  1         28  
5 1     1   3 use warnings;
  1         7  
  1         38  
6              
7             our $VERSION = '0.21';
8              
9 1     1   3 use Carp;
  1         1  
  1         61  
10 1     1   4 use File::Basename;
  1         2  
  1         135  
11 1     1   499 use Encode qw/is_utf8 encode/;
  1         7621  
  1         978  
12              
13             require Exporter;
14              
15             our @ISA = qw(Exporter);
16              
17             our %EXPORT_TAGS = ( 'all' => [ qw(
18             create_desktop_shortcut_filename
19             create_url_shortcut_filename
20             create_webloc_shortcut_filename
21             write_desktop_shortcut_file
22             write_url_shortcut_file
23             write_webloc_binary_shortcut_file
24             write_webloc_xml_shortcut_file
25             write_desktop_shortcut_handle
26             write_url_shortcut_handle
27             write_webloc_binary_shortcut_handle
28             write_webloc_xml_shortcut_handle
29             ) ] );
30              
31             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
32              
33             our @EXPORT = qw(
34            
35             );
36              
37              
38             =head1 NAME
39              
40             WebShortcutUtil::Write - Utilities for writing web shortcut files
41              
42             =head1 SYNOPSIS
43              
44             use WebShortcutUtil::Write qw(
45             create_desktop_shortcut_filename
46             create_url_shortcut_filename
47             create_webloc_shortcut_filename
48             write_desktop_shortcut_file
49             write_url_shortcut_file
50             write_webloc_binary_shortcut_file
51             write_webloc_xml_shortcut_file);
52              
53             # Helpers to create a file name (with bad characters removed).
54             my $filename = create_desktop_shortcut_filename("Shortcut: Name");
55             my $filename = create_url_shortcut_filename("Shortcut: Name");
56             my $filename = create_webloc_shortcut_filename("Shortcut: Name");
57              
58             # Write shortcuts
59             write_desktop_shortcut_file("myshortcut.desktop", "myname", "http://myurl.com/");
60             write_url_shortcut_file("myshortcut.url", "myname", "http://myurl.com/");
61             write_webloc_binary_shortcut_file("myshortcut_binary.webloc", "myname", "http://myurl.com/");
62             write_webloc_xml_shortcut_file("myshortcut_xml.webloc", "myname", "http://myurl.com/");
63              
64             =head1 DESCRIPTION
65              
66             The following subroutines are provided:
67              
68             =over 4
69              
70             =cut
71              
72              
73             my $desktop_extension = ".desktop";
74             my $url_extension = ".url";
75             my $webloc_extension = ".webloc";
76             my $website_extension = ".website";
77              
78              
79             ### Subroutines for generating file names
80              
81             =item create_desktop_shortcut_filename( NAME [,LENGTH] )
82              
83             =item create_url_shortcut_filename( NAME [,LENGTH] )
84              
85             =item create_webloc_shortcut_filename( NAME [,LENGTH] )
86              
87             Creates a file name based on the specified shortcut name.
88             The goal is to allow the file to be stored on a wide variety
89             of filesystems without issues. The following rules are used:
90              
91             =over 8
92              
93             =item 1 An appropriate extension is added based on the shortcut type (e.g. ".url").
94              
95             =item 2 Removes characters which are prohibited in some file systems (such as "?" and ":").
96             Note there may still be characters left that will cause difficulty,
97             such as spaces and single quotes.
98              
99             =item 3 If the resulting name (after removing characters) is an empty string, the file will be named "_".
100              
101             =item 4 Unicode characters are B. If there are unicode characters,
102             they could cause problems on some file systems. If you do not
103             want unicode characters in the file name, you are responsible for
104             removing them or converting them to ASCII.
105              
106             =item 5 If the filename is longer than 100 characters (including the extension),
107             it will be truncated. This maximum length was chosen somewhat
108             arbitrarily. You may optionally override it by passing in a length
109             parameter.
110              
111             =back
112              
113             The following references discuss file name restrictions:
114              
115             =over 8
116              
117             =item * http://en.wikipedia.org/wiki/Filename
118              
119             =item * http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
120              
121             =item * http://support.grouplogic.com/?p=1607
122              
123             =item * https://www.dropbox.com/help/145/en
124              
125             =back
126              
127             =cut
128              
129             my $default_max_filename_length = 100;
130              
131             sub _create_filename {
132 20     20   32 my ($name, $length, $extension) = @_;
133            
134 20 100       42 if(!defined($length)) {
135 16         19 $length = $default_max_filename_length;
136             } else {
137 4         6 my $min_length = length($extension) + 1;
138 4 100       11 if($length < $min_length) {
139 1         183 croak("Length parameter must be greater than or equal to ${min_length}")
140             }
141             }
142            
143 19 100       37 if(!defined($name)) {
144 1         2 $name = "";
145             }
146            
147 19         30 my $max_basename_length = $length - length($extension);
148              
149             # The valid characters are listed below in ASCII order.
150             # Essentially this means we are excluding: "%*/<>?\^| (along with any control characters)
151             # Note that Unicode characters are allowed in the file name.
152 19         20 my $clean_name = $name;
153 19         71 $clean_name =~ s/[^ !#\$&'\(\)+,\-\.,0-9;=\@A-Z\[\]_`a-z\{\}~\x{0080}-\x{FFFF}]//g;
154              
155 19 100       40 if($clean_name eq "") {
156 2         3 $clean_name = "_";
157             }
158              
159 19         51 my $filename = substr($clean_name, 0, $max_basename_length) . $extension;
160            
161 19         58 return $filename;
162             }
163              
164             # $length Includes file name and extension (no path).
165             sub create_desktop_shortcut_filename {
166 11     11 1 3522 my ($name, $length) = @_;
167            
168 11         20 _create_filename($name, $length, $desktop_extension);
169             }
170              
171             sub create_url_shortcut_filename {
172 4     4 1 1235 my ($name, $length) = @_;
173            
174 4         7 _create_filename($name, $length, $url_extension);
175             }
176              
177             sub create_webloc_shortcut_filename {
178 5     5 1 10722 my ($name, $length) = @_;
179            
180 5         17 _create_filename($name, $length, $webloc_extension);
181             }
182              
183              
184              
185             ### The writers
186              
187             sub _check_file_already_exists {
188 14     14   18 my ( $filename ) = @_;
189            
190 14 100       242 if(-e $filename) {
191 4         503 croak "File ${filename} already exists";
192             }
193             }
194              
195             =item write_desktop_shortcut_file( FILENAME, NAME, URL )
196              
197             =item write_url_shortcut_file( FILENAME, NAME, URL )
198              
199             =item write_webloc_binary_shortcut_file( FILENAME, NAME, URL )
200              
201             =item write_webloc_xml_shortcut_file( FILENAME, NAME, URL )
202              
203             These routines write shortcut files of the specified type. The
204             shortcut will contain the specified name/title and URL.
205             Note that some shortcuts do not contain a name inside the file, in
206             which case the name parameter is ignored.
207              
208             If your URL contains unicode characters, it is recommended that
209             you convert it to an ASCII-only URL
210             (see http://en.wikipedia.org/wiki/Internationalized_domain_name ).
211             That being said, write_desktop_shortcut_file and write_url_shortcut_file
212             will write unicode URLs. The webloc writers should as well,
213             although this functionality requires more testing.
214              
215             Note: The Mac::PropertyList module (http://search.cpan.org/~bdfoy/Mac-PropertyList/)
216             must be installed in order to write ".webloc" files.
217              
218             =cut
219              
220             # SEE REFERENCES IN WebShortcutUtil.pm
221              
222             sub write_desktop_shortcut_file {
223 5     5 1 1087 my ( $filename, $name, $url ) = @_;
224            
225 5         10 _check_file_already_exists ( $filename );
226 1 50   1   7 open (my $file, ">:encoding(UTF-8)", $filename) or die "Error opening file \"${filename}\": $!";
  1         1  
  1         6  
  3         136  
227            
228 3         1273 write_desktop_shortcut_handle($file, $name, $url);
229            
230 3         2 close ($file);
231            
232 3         21 return 1;
233             }
234              
235             sub write_url_shortcut_file {
236 5     5 1 1168 my ( $filename, $name, $url ) = @_;
237            
238 5         16 _check_file_already_exists ( $filename );
239 3 50       135 open (my $file, ">", $filename) or die "Error opening file \"${filename}\": $!";
240            
241 3         8 write_url_shortcut_handle($file, $name, $url);
242            
243 3         4 close ($file);
244            
245 3         23 return 1;
246             }
247              
248             sub write_webloc_binary_shortcut_file {
249 2     2 1 32 my ( $filename, $name, $url ) = @_;
250            
251 2         6 _check_file_already_exists ( $filename );
252            
253 2 50       125 open (my $file, ">:encoding(UTF-8)", $filename) or die "Error opening file \"${filename}\": $!";
254 2         118 binmode $file;
255 2         7 write_webloc_binary_shortcut_handle($file, $name, $url);
256 2         4 close ($file);
257            
258 2         18 return 1;
259             }
260              
261             sub write_webloc_xml_shortcut_file {
262 2     2 1 30 my ( $filename, $name, $url ) = @_;
263            
264 2         5 _check_file_already_exists ( $filename );
265            
266 2 50       108 open (my $file, ">:encoding(UTF-8)", $filename) or die "Error opening file \"${filename}\": $!";
267 2         113 write_webloc_xml_shortcut_handle($file, $name, $url);
268 2         3 close ($file);
269            
270 2         14 return 1;
271             }
272              
273              
274              
275             sub write_desktop_shortcut_handle {
276 3     3 0 4 my ( $handle, $name, $url ) = @_;
277            
278             # Assume all the writes will be done in UTF-8.
279 3         13 print $handle "[Desktop Entry]\n";
280 3         4 print $handle "Encoding=UTF-8\n";
281 3         11 print $handle "Name=${name}\n";
282 3         5 print $handle "Type=Link\n";
283 3         5 print $handle "URL=${url}\n";
284            
285 3         121 close ($handle);
286            
287 3         7 return 1;
288             }
289              
290             sub write_url_shortcut_handle {
291 3     3 0 5 my ( $handle, $name, $url ) = @_;
292            
293 3         3 my $ascii_url = $url;
294             # Generate a URL where non-ASCII characters are placed with a question mark
295 3         14 $ascii_url =~ s/[x{0080}-\x{FFFF}]/?/g;
296              
297 3         17 print $handle "[InternetShortcut]\r\n";
298 3         7 print $handle "URL=${ascii_url}\r\n";
299            
300             # If the url contains non-ascii characters, print the extra sections
301 3 100       7 if($url ne $ascii_url) {
302 1         1 print $handle "[InternetShortcut.A]\r\n";
303 1         3 print $handle "URL=${ascii_url}\r\n";
304              
305 1         2 print $handle "[InternetShortcut.W]\r\n";
306 1         5 my $url_utf7 = encode("UTF-7", $url);
307 1         6083 print $handle "URL=${url_utf7}\r\n";
308             }
309              
310 3         92 close ($handle);
311            
312 3         7 return 1;
313             }
314              
315             # TODO: Fix this eval to not use an expression. This causes it to fail perlcritic.
316             sub _try_load_module_for_webloc {
317 6     6   13 my ( $module, $list ) = @_;
318              
319 1 50   1   526 eval ( "use ${module} ${list}; 1" ) or
  1     1   25376  
  1     1   161  
  1     1   531  
  1     1   3883  
  1     1   38  
  1         10  
  1         3  
  1         137  
  1         6  
  1         1  
  1         95  
  1         5  
  1         1  
  1         25  
  1         5  
  1         1  
  1         90  
  6         431  
320             die "Could not load ${module} module. This module is required in order to read/write webloc files. Error: $@";
321             }
322              
323             sub write_webloc_binary_shortcut_handle {
324 2     2 0 5 my ( $handle, $name, $url ) = @_;
325            
326 2         5 _try_load_module_for_webloc ( "Mac::PropertyList", "qw(:all)" );
327 2         7 _try_load_module_for_webloc ( "Mac::PropertyList::WriteBinary", "" );
328              
329 2         15 my $data = new Mac::PropertyList::dict({ "URL" => $url });
330 2         28 my $buf = Mac::PropertyList::WriteBinary::as_string($data);
331 2         424 print $handle $buf;
332 2         90 close ($handle);
333            
334 2         14 return 1;
335             }
336              
337             sub write_webloc_xml_shortcut_handle {
338 2     2 0 4 my ( $handle, $name, $url ) = @_;
339            
340 2         5 _try_load_module_for_webloc ( "Mac::PropertyList", "qw(:all)" );
341            
342 2         10 my $str = create_from_hash({ "URL" => $url });
343 2         197 print $handle $str;
344 2         108 close ($handle);
345            
346 2         5 return 1;
347             }
348              
349              
350             1;
351             __END__