File Coverage

lib/App/MtAws/Utils.pm
Criterion Covered Total %
statement 193 205 94.1
branch 91 114 79.8
condition 40 56 71.4
subroutine 42 45 93.3
pod 0 25 0.0
total 366 445 82.2


line stmt bran cond sub pod time code
1             # mt-aws-glacier - Amazon Glacier sync client
2             # Copyright (C) 2012-2014 Victor Efimov
3             # http://mt-aws.com (also http://vs-dev.com) vs@vs-dev.com
4             # License: GPLv3
5             #
6             # This file is part of "mt-aws-glacier"
7             #
8             # mt-aws-glacier is free software: you can redistribute it and/or modify
9             # it under the terms of the GNU General Public License as published by
10             # the Free Software Foundation, either version 3 of the License, or
11             # (at your option) any later version.
12             #
13             # mt-aws-glacier is distributed in the hope that it will be useful,
14             # but WITHOUT ANY WARRANTY; without even the implied warranty of
15             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16             # GNU General Public License for more details.
17             #
18             # You should have received a copy of the GNU General Public License
19             # along with this program. If not, see <http://www.gnu.org/licenses/>.
20              
21             package App::MtAws::Utils;
22              
23             our $VERSION = '1.114_2';
24              
25 113     113   103741 use strict;
  113         170  
  113         3607  
26 113     113   537 use warnings;
  113         172  
  113         4320  
27 113     113   1355 use utf8;
  113         204  
  113         707  
28 113     113   2754 use File::Spec;
  113         158  
  113         2724  
29 113     113   516 use Cwd;
  113         142  
  113         7499  
30 113     113   77739 use File::stat;
  113         875232  
  113         637  
31 113     113   8069 use Carp;
  113         192  
  113         6747  
32 113     113   602 use Encode;
  113         185  
  113         9380  
33 113     113   97534 use LWP::UserAgent;
  113         5380583  
  113         4762  
34 113     113   1182 use Time::Local;
  113         217  
  113         8494  
35 113     113   636 use Config;
  113         170  
  113         4664  
36 113     113   559 use bytes ();
  113         202  
  113         2269  
37              
38 113     113   496 use Exporter 'import';
  113         166  
  113         3282  
39              
40 113     113   519 use constant INVENTORY_TYPE_CSV => 'CSV';
  113         171  
  113         8420  
41 113     113   616 use constant INVENTORY_TYPE_JSON => 'JSON';
  113         170  
  113         14717  
42              
43             our @EXPORT = qw/set_filename_encoding get_filename_encoding binaryfilename
44             sanity_relative_filename is_relative_filename abs2rel binary_abs_path open_file sysreadfull syswritefull sysreadfull_chk syswritefull_chk
45             hex_dump_string is_wide_string
46             characterfilename try_drop_utf8_flag dump_request_response file_size file_mtime file_exists file_inodev
47             is_64bit_os is_64bit_time is_y2038_supported
48             INVENTORY_TYPE_JSON INVENTORY_TYPE_CSV/;
49              
50              
51             BEGIN {
52 113 50   113   182320 if ($File::Spec::VERSION lt '3.13') {
53 0         0 our $__orig_abs_to_rel = File::Spec->can("abs2rel");
54 113     113   617 no warnings 'once';
  113         176  
  113         11604  
55             *File::Spec::abs2rel = sub {
56 0         0 my $r = $__orig_abs_to_rel->(@_);
57 0 0       0 return '.' if $r eq '';
58 0         0 $r;
59 0         0 };
60             }
61             }
62              
63              
64             # Does not work with directory names
65             sub sanity_relative_filename
66             {
67 3694     3694 0 6862 my ($filename) = @_;
68 3694 100       7425 return undef unless defined $filename;
69 1496 100       3659 return undef if $filename =~ m!^//!g;
70 1477         1886 $filename =~ s!^/!!;
71 1477 100       4075 return undef if $filename =~ m![\r\n\t]!g;
72 1472 100       7630 $filename = File::Spec->catdir( map {return undef if m!^\.\.?$!; $_; } split('/', File::Spec->canonpath($filename)) );
  2955         4553  
  2946         9173  
73             return undef
74 1463 100 66     6910 if !defined($filename) || # workaround https://rt.cpan.org/Public/Bug/Display.html?id=86624
75             $filename eq '';
76 1461         3997 return $filename;
77             }
78              
79             sub is_relative_filename
80             {
81 4538     4538 0 339449 my ($filename) = @_;
82 4538 100 100     16384 return unless (defined($filename) && length($filename));
83 2339 100 100     17206 return if $filename =~ tr{\r\n\t}{} or index($filename, '//') != -1 or substr($filename, 0, 1) eq '/';
      100        
84 2303 100       7565 return undef if $filename =~ m{
85             (^|/)\.\.?(/|$)
86             }x;
87 2285         6173 1;
88             }
89              
90             # TODO: test
91             sub binary_abs_path
92             {
93 99     99 0 2473 my ($path) = @_;
94              
95 99     0   601 local $SIG{__WARN__}=sub{};
96              
97 99         227 my $orig_id = file_inodev($path, use_filename_encoding => 0);
98              
99 98         2806 my $abspath = Cwd::abs_path($path);
100              
101 98 50       208 return undef unless defined $abspath;
102 98 50       189 return undef if $abspath eq ''; # workaround RT#47755
103              
104             # workaround RT#47755 - in case perms problem it tries to return File::Spec->rel2abs
105 103 50 33     1164 return undef unless -e $abspath && file_inodev($abspath, use_filename_encoding => 0) eq $orig_id;
106              
107 102         1515 return $abspath;
108             }
109              
110             our $_filename_encoding = 'UTF-8'; # global var
111              
112 617     621 0 129922 sub set_filename_encoding($) { $_filename_encoding = shift };
113 12880 100   12901 0 69111 sub get_filename_encoding() { $_filename_encoding || confess };
114              
115             sub binaryfilename(;$)
116             {
117 7284 100   7301 0 44581 encode(get_filename_encoding, @_ ? shift : $_, Encode::DIE_ON_ERR|Encode::LEAVE_SRC);
118             }
119              
120             sub characterfilename(;$)
121             {
122 2850 100   2849 0 8512 decode(get_filename_encoding, @_ ? shift : $_, Encode::DIE_ON_ERR|Encode::LEAVE_SRC);
123             }
124              
125             # TODO: test
126             sub abs2rel
127             {
128 2818     2803 0 4141 my ($path, $base) = (shift, shift);
129 2818 50 33     11123 confess "too few arguments" unless defined($path) && defined($base);
130 2802         7296 my (%args) = (use_filename_encoding => 1, @_);
131 2802 100       4537 if ($args{use_filename_encoding}) {
132 2753         3485 $path = binaryfilename $path;
133 2753         63578 $base = binaryfilename $base;
134             }
135 2803 50 66     58154 $args{allow_rel_base} or $base =~ m{^/} or confess "relative basedir not allowed";
136 2803         145955 my $result = File::Spec->abs2rel($path, $base);
137 2803 100       6804 $args{use_filename_encoding} ? characterfilename($result) : $result;
138             }
139              
140              
141             =pod
142              
143             open_file(my $f, $filename, %args)
144              
145             $args{mode} - mode to open, <, > or >>
146             $args{use_filename_encoding} - (TRUE) - encode to binary string, (FALSE) - don't tocuh (already a binary string). Default TRUE
147             $args{file_encoding} or $args{binary} - file content encoding or it's a binary file (mutual exclusive)
148             $args{not_empty} - assert that file is not empty after open
149              
150             Assertions made (using "confess"):
151              
152             1) Bad arguments (programmer's error)
153             2) File is not a plain file
154             3) File is not a plain file, but after open (race conditions)
155             4) File is empty and not_empty specified
156             5) File is empty and not_empty specified, but after open (race conditions)
157              
158             NOTE: If you want exceptions for (2) and (4) - check it before open_file. And additional checks inside open_file will
159             prevent race conditions
160              
161             =cut
162              
163             sub open_file($$%)
164             {
165 224     223 0 20715 (undef, my $filename, my %args) = @_;
166 224         996 %args = (use_filename_encoding => 1, %args);
167 224         449 my $original_filename = $filename;
168              
169 225         577 my %checkargs = %args;
170 225   66     2110 defined $checkargs{$_} && delete $checkargs{$_} for qw/use_filename_encoding mode file_encoding not_empty binary/;
171 225 100       665 confess "Unknown argument(s) to open_file: ".join(';', keys %checkargs) if %checkargs;
172              
173 224 100       827 confess 'Argument "mode" is required' unless defined($args{mode});
174 223 100       1661 confess "unknown mode $args{mode}" unless $args{mode} =~ m!^\+?(<|>>?)$!;
175 221         356 my $mode = $args{mode};
176              
177             confess "not_empty can be used in read mode only"
178 221 100 100     758 if ($args{not_empty} && $args{mode} ne '<');
179              
180              
181 220 100       536 if (defined($args{file_encoding})) {
    100          
182 184         444 $mode .= ":encoding($args{file_encoding})";
183 184 100       578 confess "cannot use binary and file_encoding at same time'" if $args{binary};
184             } elsif (!$args{binary}) {
185 3         205 confess "there should be file encoding or 'binary'";
186             }
187              
188 219 100       517 if ($args{use_filename_encoding}) {
189 218         545 $filename = binaryfilename $filename;
190             }
191              
192 218 100 100     12392 confess "File is not a plain file" if -e $filename && (! -f $filename);
193 216 100 100     979 confess "File should not be empty" if $args{not_empty} && (! -s $filename);
194              
195 215 100       6078 open ($_[0], $mode, $filename) or return;
196 214         14110 my $f = $_[0];
197              
198 214 50       1261 confess unless -f $f; # check for race condition - it was a file when we last checked, but now it's a directory
199 213 50 66     698 confess if $args{not_empty} && (! -s $f);
200              
201 213 100       1884 binmode $f if $args{binary};
202              
203 213         1441 return $f;
204             }
205              
206             sub file_size($%)
207             {
208 4     4 0 17 my $filename = shift;
209 4         16 my (%args) = (use_filename_encoding => 1, @_);
210 4 50       21 if ($args{use_filename_encoding}) {
211 3         9 $filename = binaryfilename $filename;
212             }
213 3 50       318 confess "file not exists" unless -f $filename;
214 3         30 return -s $filename;
215             }
216              
217             sub file_exists($%)
218             {
219 0     0 0 0 my $filename = shift;
220 0         0 my (%args) = (use_filename_encoding => 1, @_);
221 0 0       0 if ($args{use_filename_encoding}) {
222 0         0 $filename = binaryfilename $filename;
223             }
224 0         0 return -f $filename;
225             }
226              
227             sub file_mtime($%)
228             {
229 20     20 0 8319 my $filename = shift;
230 20         46 my (%args) = (use_filename_encoding => 1, @_);
231 20 50       48 if ($args{use_filename_encoding}) {
232 20         34 $filename = binaryfilename $filename;
233             }
234 20 50       1498 confess "file not exists" unless -f $filename;
235 20         59 return stat($filename)->mtime;
236             }
237              
238             # TODO: test
239             sub file_inodev($%)
240             {
241 294     294 0 810 my $filename = shift;
242 294         648 my (%args) = (use_filename_encoding => 1, @_);
243 294 100       540 if ($args{use_filename_encoding}) {
244 98         153 $filename = binaryfilename $filename;
245             }
246 294 50       5153 confess "file not exists" unless -e $filename;
247 294         679 my $s = stat($filename);
248 294         28072 $s->dev."-".$s->ino;
249             }
250              
251             sub is_wide_string
252             {
253 629 100 100 629 0 9830 defined($_[0]) && utf8::is_utf8($_[0]) && (bytes::length($_[0]) != length($_[0]))
254             }
255              
256             # if we have ASCII-only data, let's drop UTF-8 flag in order to optimize some regexp stuff
257             # TODO: write also version which does not check is_utf8 - it's faster when utf8 always set
258             sub try_drop_utf8_flag
259             {
260 2369 100 100 2369 0 10162 Encode::_utf8_off($_[0]) if utf8::is_utf8($_[0]) && (bytes::length($_[0]) == length($_[0]));
261             }
262              
263             sub sysreadfull_chk($$$)
264             {
265 40     40 0 54 my $len = $_[2];
266 40         81 sysreadfull(@_) == $len;
267             }
268              
269             sub sysreadfull($$$)
270             {
271 106     106 0 4368034 my ($file, $len) = ($_[0], $_[2]);
272 106         133 my $n = 0;
273 106         354 while ($len - $n) {
274 152         157394 my $i = sysread($file, $_[1], $len - $n, $n);
275 152 100       899 if (defined($i)) {
    100          
276 133 100       236 if ($i == 0) {
277 17         83 return $n;
278             } else {
279 116         465 $n += $i;
280             }
281 109     109   80027 } elsif ($!{EINTR}) {
  109         127216  
  109         104444  
282 14         978 redo;
283             } else {
284 5 100       45 return $n ? $n : undef;
285             }
286             }
287 84         464 return $n;
288             }
289              
290             sub syswritefull_chk($$)
291             {
292 40     40 0 71 my $length = length $_[1];
293 40         84 syswritefull(@_) == $length
294             }
295              
296             sub syswritefull($$)
297             {
298 64     64 0 13174 my ($file, $len) = ($_[0], length($_[1]));
299 64 50       361 confess if is_wide_string($_[1]);
300 64         776 my $n = 0;
301 64         153 while ($len - $n) {
302 76         26446 my $i = syswrite($file, $_[1], $len - $n, $n);
303 74 100       442 if (defined($i)) {
    100          
304 66         169 $n += $i;
305             } elsif ($!{EINTR}) {
306 3         214 redo;
307             } else {
308 5 100       42 return $n ? $n : undef;
309             }
310             }
311 57         431 return $n;
312             }
313              
314             sub hex_dump_string
315             {
316 510     510 0 5629 my ($str) = @_;
317 510         771 my $isutf = is_wide_string($str);
318 510         2254 Encode::_utf8_off($str);
319 510         833 $str =~ s/\\/\\\\/g;
320 510         516 $str =~ s/\r/\\r/g;
321 510         548 $str =~ s/\n/\\n/g;
322 510         1523 $str =~ s/\t/\\t/g;
323 510         491 $str =~ s/\"/\\\"/g;
324 510         8595 $str =~ s/([[:cntrl:]]|[[:^ascii:]])/sprintf("\\x%02X",ord($1))/eg;
  609         2750  
325 510         842 $str = "\"$str\"";
326 510 100       899 $str = "(UTF-8) ".$str if $isutf;
327 510         1604 $str;
328             }
329              
330             sub dump_request_response
331             {
332 19     19 0 24 my ($req, $resp) = @_;
333 19         29 my $out = '';
334 19         39 $out .= "===REQUEST:\n";
335 19         57 $out .= join(" ", $req->method, $req->uri)."\n";
336              
337 19         462 my $req_headers = $req->headers->as_string;
338              
339 19         1598 $req_headers =~ s!^(Authorization:.*Credential=)([A-Za-z0-9]+)/!$1***REMOVED***/!;
340 19         133 $req_headers =~ s!^(Authorization:.*Signature=)([A-Za-z0-9]+)!$1***REMOVED***!;
341              
342 19         43 $out .= $req_headers;
343              
344 19 50 33     74 if ($req->content_type ne 'application/octet-stream' && $req->content && length($req->content)) {
      33        
345 0         0 $out .= "\n".$req->content;
346             }
347              
348 19         656 $out .= "\n===RESPONSE:\n";
349 19 50       50 $out .= $resp->protocol." " if $resp->protocol;
350 19         195 $out .= $resp->status_line."\n";
351 19         245 $out .= $resp->headers->as_string;
352              
353 19 0 33     409 if ($resp->content_type eq 'application/json' && $resp->content && length($resp->content)) {
      33        
354 0         0 $out .= "\n".$resp->content;
355             }
356 19         301 $out .= "\n\n";
357 19         127 $out;
358             }
359              
360              
361             sub get_config_var($) # separate function so we can override it in tests
362             {
363 23728     23728 0 296674 $Config{shift()}
364             }
365              
366             sub is_64bit_os
367             {
368 23728     23728 0 28157 get_config_var('longsize') >= 8
369             }
370              
371             sub is_64bit_time
372             {
373 23728 50   23728 0 511487 is_64bit_os && ($^O =~ /^(freebsd|gnukfreebsd|netbsd|midnightbsd|linux|darwin|solaris)$/) # no OpenBSD for sure
374             # not sure about cygwin, solaris
375             }
376              
377              
378             our $_is_y2038_supported = undef;
379             sub is_y2038_supported
380             {
381 26255 100   26255 0 94382 return $_is_y2038_supported if defined $_is_y2038_supported;
382 7     0   55 local $SIG{__WARN__} = sub {};
383 7   100     12 $_is_y2038_supported = eval {
384             (timegm(0, 0, 0, 01, 01, 2038) == 2148595200) &&
385             (timegm(0, 0, 0, 01, 01, 4000) == 64063267200) &&
386             (join(",",gmtime(64063267200)) eq "0,0,0,1,1,2100,2,31,0") &&
387             (join(",",gmtime(2148595200)) eq "0,0,0,1,1,138,1,31,0")
388             } || 0;
389             }
390              
391             1;
392              
393             __END__