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 10 11 90.9
total 79 318 24.8


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