File Coverage

blib/lib/WebShortcutUtil/Write.pm
Criterion Covered Total %
statement 121 121 100.0
branch 17 22 77.2
condition n/a
subroutine 27 27 100.0
pod 11 11 100.0
total 176 181 97.2


line stmt bran cond sub pod time code
1             package WebShortcutUtil::Write;
2              
3 1     1   56862 use 5.006_001;
  1         5  
  1         41  
4 1     1   5 use strict;
  1         2  
  1         41  
5 1     1   6 use warnings;
  1         8  
  1         94  
6              
7             our $VERSION = '0.20';
8              
9 1     1   7 use Carp;
  1         2  
  1         89  
10 1     1   5 use File::Basename;
  1         2  
  1         94  
11 1     1   725 use Encode qw/is_utf8 encode/;
  1         11606  
  1         1530  
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   34 my ($name, $length, $extension) = @_;
133              
134 20 100       48 if(!defined($length)) {
135 16         25 $length = $default_max_filename_length;
136             } else {
137 4         7 my $min_length = length($extension) + 1;
138 4 100       11 if($length < $min_length) {
139 1         179 croak("Length parameter must be greater than or equal to ${min_length}")
140             }
141             }
142              
143 19 100       52 if(!defined($name)) {
144 1         1 $name = "";
145             }
146              
147 19         28 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 19         25 my $clean_name = $name;
152 19         78 $clean_name =~ s/[^ !#\$&'\(\)+,\-\.,0-9;=\@A-Z\[\]_`a-z\{\}~\x{0080}-\x{FFFF}]//g;
153              
154 19 100       41 if($clean_name eq "") {
155 2         3 $clean_name = "_";
156             }
157              
158 19         58 my $filename = substr($clean_name, 0, $max_basename_length) . $extension;
159              
160 19         66 return $filename;
161             }
162              
163             # $length Includes file name and extension (no path).
164             sub create_desktop_shortcut_filename {
165 11     11 1 4530 my ($name, $length) = @_;
166              
167 11         23 _create_filename($name, $length, $desktop_extension);
168             }
169              
170             sub create_url_shortcut_filename {
171 4     4 1 1943 my ($name, $length) = @_;
172              
173 4         9 _create_filename($name, $length, $url_extension);
174             }
175              
176             sub create_webloc_shortcut_filename {
177 5     5 1 15928 my ($name, $length) = @_;
178              
179 5         19 _create_filename($name, $length, $webloc_extension);
180             }
181              
182              
183              
184             ### The writers
185              
186             sub _check_file_already_exists {
187 14     14   22 my ( $filename ) = @_;
188              
189 14 100       294 if(-e $filename) {
190 4         651 croak "File ${filename} already exists";
191             }
192             }
193              
194             =item write_desktop_shortcut_file( FILENAME, NAME, URL )
195              
196             =item write_url_shortcut_file( FILENAME, NAME, URL )
197              
198             =item write_webloc_binary_shortcut_file( FILENAME, NAME, URL )
199              
200             =item write_webloc_xml_shortcut_file( FILENAME, NAME, URL )
201              
202             These routines write shortcut files of the specified type. The
203             shortcut will contain the specified name/title and URL.
204             Note that some shortcuts do not contain a name inside the file, in
205             which case the name parameter is ignored.
206              
207             If your URL contains unicode characters, it is recommended that
208             you convert it to an ASCII-only URL
209             (see http://en.wikipedia.org/wiki/Internationalized_domain_name ).
210             That being said, write_desktop_shortcut_file and write_url_shortcut_file
211             will write unicode URLs. The webloc writers should as well,
212             although this functionality requires more testing.
213              
214             Note: The Mac::PropertyList module (http://search.cpan.org/~bdfoy/Mac-PropertyList/)
215             must be installed in order to write ".webloc" files.
216              
217             =cut
218              
219             # SEE REFERENCES IN WebShortcutUtil.pm
220              
221             sub write_desktop_shortcut_file {
222 5     5 1 1698 my ( $filename, $name, $url ) = @_;
223              
224 5         11 _check_file_already_exists ( $filename );
225 1 50   1   6 open (my $file, ">:encoding(UTF-8)", $filename) or die "Error opening file \"${filename}\": $!";
  1         1  
  1         6  
  3         168  
226              
227 3         1318 write_desktop_shortcut_handle($file, $name, $url);
228              
229 3         3 close ($file);
230              
231 3         24 return 1;
232             }
233              
234             sub write_url_shortcut_file {
235 5     5 1 1344 my ( $filename, $name, $url ) = @_;
236              
237 5         15 _check_file_already_exists ( $filename );
238 3 50       172 open (my $file, ">", $filename) or die "Error opening file \"${filename}\": $!";
239              
240 3         12 write_url_shortcut_handle($file, $name, $url);
241              
242 3         6 close ($file);
243              
244 3         35 return 1;
245             }
246              
247             sub write_webloc_binary_shortcut_file {
248 2     2 1 40 my ( $filename, $name, $url ) = @_;
249              
250 2         9 _check_file_already_exists ( $filename );
251              
252 2 50       165 open (my $file, ">:encoding(UTF-8)", $filename) or die "Error opening file \"${filename}\": $!";
253 2         162 binmode $file;
254 2         9 write_webloc_binary_shortcut_handle($file, $name, $url);
255 2         4 close ($file);
256              
257 2         19 return 1;
258             }
259              
260             sub write_webloc_xml_shortcut_file {
261 2     2 1 38 my ( $filename, $name, $url ) = @_;
262              
263 2         7 _check_file_already_exists ( $filename );
264              
265 2 50       148 open (my $file, ">:encoding(UTF-8)", $filename) or die "Error opening file \"${filename}\": $!";
266 2         175 write_webloc_xml_shortcut_handle($file, $name, $url);
267 2         4 close ($file);
268              
269 2         19 return 1;
270             }
271              
272              
273              
274             =item write_desktop_shortcut_handle( HANDLE, NAME, URL )
275              
276             =item write_url_shortcut_handle( HANDLE, NAME, URL )
277              
278             =item write_webloc_binary_shortcut_handle( HANDLE, NAME, URL )
279              
280             =item write_webloc_xml_shortcut_handle( HANDLE, NAME, URL )
281              
282             Similar to the corresponding file writers, but writes to an
283             IO::Handle object instead.
284              
285             =cut
286              
287             sub write_desktop_shortcut_handle {
288 3     3 1 7 my ( $handle, $name, $url ) = @_;
289              
290             # Assume all the writes will be done in UTF-8.
291 3         15 print $handle "[Desktop Entry]\n";
292 3         5 print $handle "Encoding=UTF-8\n";
293 3         13 print $handle "Name=${name}\n";
294 3         4 print $handle "Type=Link\n";
295 3         6 print $handle "URL=${url}\n";
296              
297 3         141 close ($handle);
298              
299 3         8 return 1;
300             }
301              
302             sub write_url_shortcut_handle {
303 3     3 1 5 my ( $handle, $name, $url ) = @_;
304              
305 3 100       13 if(is_utf8($url)) {
306 1         5 print $handle "[InternetShortcut.W]\r\n";
307 1         6 my $url_utf7 = encode("UTF-7", $url);
308 1         6708 print $handle "URL=${url_utf7}\r\n";
309             } else {
310 2         8 print $handle "[InternetShortcut]\r\n";
311 2         6 print $handle "URL=${url}\r\n";
312             }
313              
314 3         122 close ($handle);
315              
316 3         10 return 1;
317             }
318              
319             # TODO: Fix this eval to not use an expression. This causes it to fail perlcritic.
320             sub _try_load_module_for_webloc {
321 6     6   15 my ( $module, $list ) = @_;
322              
323 1 50   1   783 eval ( "use ${module} ${list}; 1" ) or
  1     1   31039  
  1     1   145  
  1     1   566  
  1     1   4213  
  1     1   36  
  1         10  
  1         1  
  1         191  
  1         7  
  1         2  
  1         140  
  1         5  
  1         1  
  1         26  
  1         6  
  1         2  
  1         103  
  6         514  
324             die "Could not load ${module} module. This module is required in order to read/write webloc files. Error: $@";
325             }
326              
327             sub write_webloc_binary_shortcut_handle {
328 2     2 1 6 my ( $handle, $name, $url ) = @_;
329              
330 2         7 _try_load_module_for_webloc ( "Mac::PropertyList", "qw(:all)" );
331 2         6 _try_load_module_for_webloc ( "Mac::PropertyList::WriteBinary", "" );
332              
333 2         18 my $data = new Mac::PropertyList::dict({ "URL" => $url });
334 2         31 my $buf = Mac::PropertyList::WriteBinary::as_string($data);
335 2         436 print $handle $buf;
336 2         99 close ($handle);
337              
338 2         13 return 1;
339             }
340              
341             sub write_webloc_xml_shortcut_handle {
342 2     2 1 5 my ( $handle, $name, $url ) = @_;
343              
344 2         6 _try_load_module_for_webloc ( "Mac::PropertyList", "qw(:all)" );
345              
346 2         14 my $str = create_from_hash({ "URL" => $url });
347 2         276 print $handle $str;
348 2         127 close ($handle);
349              
350 2         6 return 1;
351             }
352              
353              
354             1;
355             __END__