File Coverage

lib/SMB.pm
Criterion Covered Total %
statement 45 163 27.6
branch 11 92 11.9
condition 2 32 6.2
subroutine 11 20 55.0
pod 8 11 72.7
total 77 318 24.2


line stmt bran cond sub pod time code
1             # SMB-Perl library, Copyright (C) 2014-2018 Mikhael Goikhman, migo@cpan.org
2             #
3             # This program is free software: you can redistribute it and/or modify
4             # it under the terms of the GNU General Public License as published by
5             # the Free Software Foundation, either version 3 of the License, or
6             # (at your option) any later version.
7             #
8             # This program is distributed in the hope that it will be useful,
9             # but WITHOUT ANY WARRANTY; without even the implied warranty of
10             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11             # GNU General Public License for more details.
12             #
13             # You should have received a copy of the GNU General Public License
14             # along with this program. If not, see .
15              
16             package SMB;
17              
18 4     4   51192 use strict;
  4         7  
  4         87  
19 4     4   13 use warnings;
  4         5  
  4         305  
20              
21             our $VERSION = 0.07;
22              
23             use constant {
24 4         1187 STATUS_SUCCESS => 0x00000000,
25             STATUS_PENDING => 0x00000103,
26             STATUS_NOTIFY_ENUM_DIR => 0x0000010c,
27             STATUS_SMB_BAD_TID => 0x00050002,
28             STATUS_OS2_INVALID_LEVEL => 0x007c0001,
29             STATUS_NO_MORE_FILES => 0x80000006,
30             STATUS_INVALID_PARAMETER => 0xc000000d,
31             STATUS_NO_SUCH_DEVICE => 0xc000000e,
32             STATUS_NO_SUCH_FILE => 0xc000000f,
33             STATUS_END_OF_FILE => 0xc0000011,
34             STATUS_MORE_PROCESSING_REQUIRED => 0xc0000016,
35             STATUS_NO_FREE_MEMORY => 0xc0000017,
36             STATUS_ACCESS_DENIED => 0xc0000022,
37             STATUS_BUFFER_TOO_SMALL => 0xc0000023,
38             STATUS_OBJECT_NAME_NOT_FOUND => 0xc0000034,
39             STATUS_OBJECT_NAME_COLLISION => 0xc0000035,
40             STATUS_OBJECT_PATH_NOT_FOUND => 0xc000003a,
41             STATUS_SHARING_VIOLATION => 0xc0000043,
42             STATUS_DELETE_PENDING => 0xc0000056,
43             STATUS_PRIVILEGE_NOT_HELD => 0xc0000061,
44             STATUS_LOGON_FAILURE => 0xc000006d,
45             STATUS_DISK_FULL => 0xc000007f,
46             STATUS_FILE_IS_A_DIRECTORY => 0xc00000ba,
47             STATUS_BAD_NETWORK_NAME => 0xc00000cc,
48             STATUS_DIRECTORY_NOT_EMPTY => 0xc0000101,
49             STATUS_NOT_A_DIRECTORY => 0xc0000103,
50             STATUS_CANCELLED => 0xc0000120,
51             STATUS_CANNOT_DELETE => 0xc0000121,
52             STATUS_FILE_CLOSED => 0xc0000128,
53             STATUS_INVALID_LEVEL => 0xc0000148,
54             STATUS_FS_DRIVER_REQUIRED => 0xc000019c,
55             STATUS_NOT_A_REPARSE_POINT => 0xc0000275,
56 4     4   25 };
  4         11  
57              
58             use constant {
59 4         6614 LOG_LEVEL_NONE => 0,
60             LOG_LEVEL_ERROR => 1,
61             LOG_LEVEL_INFO => 2,
62             LOG_LEVEL_DEBUG => 3,
63             LOG_LEVEL_TRACE => 4,
64 4     4   22 };
  4         5  
65              
66             sub new ($%) {
67 31     31 1 51 my $class = shift;
68 31         132 my %options = @_;
69              
70 31   50     120 $options{log_level} ||= LOG_LEVEL_INFO;
71              
72 31         138 my $self = {
73             %options,
74             };
75              
76 31         159 bless $self, $class;
77             }
78              
79             sub log ($$@) {
80 7     7 1 11 my $self = shift;
81 7   50     15 my $level = shift || LOG_LEVEL_INFO;
82 7         10 my $format = shift;
83              
84 7 50       19 return if $level > $self->log_level;
85 7         16 $format =~ s/\r?\n$//;
86              
87 7 100       245 print sprintf("%s $format\n", $level == LOG_LEVEL_ERROR ? '!' : '*', @_);
88             }
89              
90 4     4 1 11 sub err ($@) { shift()->log(LOG_LEVEL_ERROR, @_); return }
  4         15  
91 3     3 1 18 sub msg ($@) { shift()->log(LOG_LEVEL_INFO, @_); return }
  3         13  
92 0     0 0 0 sub dbg ($@) { shift()->log(LOG_LEVEL_DEBUG, @_); return }
  0         0  
93 0     0 0 0 sub trc ($@) { shift()->log(LOG_LEVEL_TRACE, @_); return }
  0         0  
94              
95             my $MAX_DUMP_BYTES = 8 * 1024;
96             my $dump_line_format = "%03x | 00 53 54 52 49 4E 47 aa aa aa aa aa aa aa | _STRING. ...... |\n";
97              
98             sub mem ($$;$$) {
99 0     0 1 0 my $self = shift;
100 0         0 my $data = shift;
101 0   0     0 my $label = shift || "Data dump";
102 0   0     0 my $level = shift || LOG_LEVEL_TRACE;
103 0 0       0 return if $level > $self->log_level;
104              
105 0 0       0 if (!defined $data) {
106 0         0 $self->log($level, "$label (undef)");
107 0         0 return;
108             }
109              
110 0         0 my $len = length($data);
111 0 0       0 $self->log($level, sprintf("%s (%lu bytes%s):", $label, $len, $len > $MAX_DUMP_BYTES ? ", shorten" : ""), @_);
112 0 0       0 $len = $MAX_DUMP_BYTES if $len > $MAX_DUMP_BYTES;
113              
114 0         0 for (my $n = 0; $n < ($len + 15) / 16; $n++) {
115 0         0 for (my $i = 0; $i < 16; $i++) {
116 0         0 my $valid = $n * 16 + $i < $len;
117 0 0       0 my $b = $valid ? ord(substr($data, $n * 16 + $i, 1)) : undef;
118 0 0       0 substr($dump_line_format, 7 + $i * 3 + ($i >= 8), 2) = $valid ? sprintf("%02x", $b) : " ";
119 0 0 0     0 substr($dump_line_format, 58 + $i + ($i >= 8), 1) = $valid ? $b == 0 ? '_' : $b <= 32 || $b >= 127 || $b == 37 ? '.' : chr($b) : ' ';
    0          
    0          
120             }
121 0         0 printf $dump_line_format, $n;
122             }
123             }
124              
125             sub parse_share_uri ($$) {
126 0     0 0 0 my $self = shift;
127 0         0 my $share_uri = shift;
128              
129 0 0       0 unless ($share_uri) {
130 0         0 $self->err("No share uri supplied");
131 0         0 return;
132             }
133 0 0       0 unless ($share_uri =~ m~^([/\\])\1([\w.]+(?::\d+)?)\1([^/\\]+)(?:$|\1)~) {
134 0         0 $self->err("Invalid share uri ($share_uri)");
135 0         0 return;
136             }
137              
138 0 0       0 return wantarray ? ($2, $3) : $share_uri;
139             }
140              
141             our %dump_seen;
142             our $dump_is_newline = 1;
143             our $dump_level_limit = $ENV{DUMP_FULLY} || $ENV{DUMP_DEPTH_FULLY} ? 100 : 8;
144             our $dump_array_limit = $ENV{DUMP_FULLY} || $ENV{DUMP_ARRAY_FULLY} ? 10000 : 24;
145             our $dump_string_limit = $ENV{DUMP_FULLY} || $ENV{DUMP_STRING_FULLY} ? 100000 : 60;
146             our $dump_compress_array_elems = $ENV{DUMP_FULLY} || $ENV{DUMP_ARRAY_FULLY} ? 0 : 1;
147              
148             sub _dump_prefix ($) {
149 0     0   0 my $level = shift;
150              
151 0 0       0 return "" unless $dump_is_newline;
152 0         0 $dump_is_newline = 0;
153              
154 0         0 return " " x (4 * $level);
155             }
156              
157             sub _dump_eol () {
158 0     0   0 $dump_is_newline = 1;
159              
160 0         0 return "\n";
161             }
162              
163             sub dump_string ($) {
164 0     0 1 0 my $value = shift;
165              
166 0 0 0     0 my $quote_ch = $value =~ /"/ && $value !~ /'/ ? "'" : '"';
167              
168 0         0 my $len = length($value);
169 0 0       0 if ($len > $dump_string_limit) {
170 0         0 my $llen = length($len);
171 0         0 substr($value, $dump_string_limit - 3 - $llen) =
172             "..+" . ($len - $dump_string_limit + 3 + $llen);
173             }
174              
175 0         0 $value =~ s/([\\$quote_ch])/\\$1/g;
176 0         0 $value =~ s/([^ -\x7e])/sprintf("\\x%02x", ord($1))/ge;
  0         0  
177              
178 0         0 return "$quote_ch$value$quote_ch";
179             }
180              
181             sub dump_value ($) {
182 0     0 1 0 my $value = shift;
183 0   0     0 my $level = shift || 0;
184 0   0     0 my $inline = shift || 0;
185              
186 0 0       0 return '' if $level >= $dump_level_limit;
187              
188 0         0 my $type = ref($value);
189 0         0 my $dump = _dump_prefix($level);
190 0   0     0 my $is_seen = $type && $dump_seen{$value};
191 0 0       0 $dump_seen{$value} = 1 if $type;
192              
193 0 0       0 if (! $type) {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
194 0 0 0     0 $dump .= defined $value
    0          
195             ? $value =~ /^-?\d+(?:\.\d+)?$/ || $inline == 2 && $value =~ /^-?\w+$/
196             ? $value : dump_string($value)
197             : 'undef';
198             } elsif ($type eq 'ARRAY') {
199 0 0       0 if ($is_seen) {
200 0         0 $dump .= "ARRAY (seen)";
201             } else {
202 0         0 $dump .= "[" . _dump_eol();
203 0 0       0 my @array = @$value > $dump_array_limit ? (@$value)[0 .. $dump_array_limit - 2] : @$value;
204 0         0 my $prev_elem = '';
205 0         0 foreach (@array) {
206             # compress equal consecutive elements (does not look too good for non scalar elems)
207 0         0 my $elem = &dump_value($_, $level + 1, 1);
208 0 0 0     0 if ($dump_compress_array_elems && $elem eq $prev_elem) {
209 0         0 my ($elem_without_indent) = $elem =~ /^\s*(.*?)\s*$/s;
210 0   0     0 $dump =~ s/^(\s+)(?:\()?(\Q$elem_without_indent\E)(?:\) x (\d+))?,$(\n)\z/my $c = ($3 || 1) + 1; "$1($2) x $c," . _dump_eol()/me;
  0         0  
  0         0  
211 0         0 next;
212             }
213 0         0 $dump .= _dump_prefix($level + 1);
214 0         0 $dump .= $prev_elem = $elem;
215 0         0 $dump .= "," . _dump_eol();
216             }
217 0 0       0 if (@$value > $dump_array_limit) {
218 0         0 $dump .= _dump_prefix($level + 1);
219 0         0 $dump .= "...[+" . (@$value - $dump_array_limit + 1) . "]," . _dump_eol();
220             }
221 0         0 $dump .= _dump_prefix($level) . "]";
222             }
223             } elsif ($type eq 'HASH') {
224 0 0       0 if ($is_seen) {
225 0         0 $dump .= "HASH (seen)";
226             } else {
227 0         0 $dump .= "{" . _dump_eol();
228 0         0 my $idx = 0;
229 0         0 my @keys = sort keys %$value;
230 0         0 my $size = @keys;
231 0         0 foreach my $key (@keys) {
232 0         0 my $val = $value->{$key};
233 0 0 0     0 last if ++$idx == $dump_array_limit && $size > $dump_array_limit;
234 0         0 $dump .= _dump_prefix($level + 1);
235 0         0 $dump .= &dump_value($key, $level + 1, 2);
236 0         0 $dump .= " => ";
237 0         0 $dump .= &dump_value($val, $level + 1, 1);
238 0         0 $dump .= "," . _dump_eol();
239             }
240 0 0       0 if ($size > $dump_array_limit) {
241 0         0 $dump .= _dump_prefix($level + 1);
242 0         0 $dump .= "...[+" . ($size - $dump_array_limit + 1) . "]," . _dump_eol();
243             }
244 0         0 $dump .= _dump_prefix($level) . "}";
245             }
246             } elsif ($type eq 'REF') {
247 0         0 $dump .= "REF";
248             } elsif ($type eq 'CODE') {
249 0         0 $dump .= "CODE";
250             } elsif ($type eq 'GLOB') {
251 0         0 $dump .= "GLOB";
252             } elsif ($type eq 'SCALAR') {
253 0         0 $dump .= "\\";
254 0         0 $dump .= &dump_value($$value, $level + 1, 1);
255             } elsif ($type eq 'JSON::PP::Boolean') {
256 0         0 $dump .= $$value; # 0 or 1
257             } else {
258 0         0 $dump .= "$type ";
259 0         0 my $native_type;
260 0         0 foreach ('SCALAR', 'ARRAY', 'HASH', 'CODE', 'GLOB') {
261 0 0       0 $native_type = $_ if $value->isa($_);
262             }
263 0 0       0 die "Non-standard perl ref type to dump in $value\n" unless $native_type;
264              
265 0         0 $dump_seen{$value} = 0;
266 0         0 bless($value, $native_type);
267 0         0 $dump .= &dump_value($value, $level, 1);
268 0         0 bless($value, $type);
269             }
270              
271 0 0       0 $dump .= _dump_eol() unless $inline;
272              
273 0         0 return $dump;
274             }
275              
276             sub dump ($;$) {
277 0     0 1 0 my $self = shift;
278 0 0       0 my $value = @_ ? shift : $self;
279              
280 0         0 my $dump = dump_value($value);
281              
282 0         0 %dump_seen = ();
283              
284 0         0 return $dump;
285             }
286              
287             our $AUTOLOAD;
288              
289             sub AUTOLOAD ($;@) {
290 65     65   2804 my $self = shift;
291 65         102 my @params = @_;
292              
293 65         84 my $method = $AUTOLOAD;
294 65         272 $method =~ s/.*://g;
295              
296 65 100       499 return if $method eq 'DESTROY'; # ignore DESTROY messages
297              
298 35 50       70 die "Calling method $method for non-object '$self'\n"
299             unless ref($self);
300              
301 35 50       73 if (exists $self->{$method}) {
302             # define this accessor method explicitely if not yet
303 4     4   26 no strict 'refs';
  4         7  
  4         664  
304 35         125 *{$AUTOLOAD} = sub {
305 334     334   6679 my $self = shift;
306 334 50       524 warn "Skipping extraneous params (@_) on access of field '$method' in $self\n"
307             if @_ > 1;
308 334 100       492 $self->{$method} = shift if @_;
309 334         1748 return $self->{$method};
310 35 50       222 } unless $self->can($AUTOLOAD);
311              
312 35         54 return *{$AUTOLOAD}->($self, @params);
  35         73  
313             }
314              
315 0           die "Unknown method or field '$method' in $self\n";
316             }
317              
318             1;
319              
320             __END__