File Coverage

blib/lib/Net/SFTP/Server/Buffer.pm
Criterion Covered Total %
statement 26 121 21.4
branch 0 74 0.0
condition 0 19 0.0
subroutine 9 25 36.0
pod 0 16 0.0
total 35 255 13.7


line stmt bran cond sub pod time code
1             package Net::SFTP::Server::Buffer;
2              
3 2     2   10 use strict;
  2         3  
  2         65  
4 2     2   10 use warnings;
  2         4  
  2         50  
5 2     2   10 use Carp;
  2         3  
  2         107  
6              
7 2     2   1928 use Encode;
  2         23631  
  2         194  
8 2     2   16 use Net::SFTP::Server::Constants qw(:filexfer);
  2         4  
  2         413  
9              
10             require Exporter;
11             our @ISA = qw(Exporter);
12              
13             our @EXPORT = qw( buf_shift_uint32
14             buf_shift_uint64
15             buf_shift_uint8
16             buf_shift_str
17             buf_shift_utf8
18             buf_shift_attrs
19              
20             buf_push_uint32
21             buf_push_uint64
22             buf_push_uint8
23             buf_push_str
24             buf_push_utf8
25             buf_push_attrs
26             buf_push_name
27             buf_push_raw );
28              
29 2         4 use constant HAS_QUADS => do {
30 2         3 local $@;
31 2         10 local $SIG{__DIE__};
32 2     2   13 no warnings;
  2         5  
  2         85  
33 2         127 eval q{
34             pack(Q => 0x1122334455667788) eq "\x11\x22\x33\x44\x55\x66\x77\x88"
35             }
36 2     2   11 };
  2         4  
37              
38              
39 0     0 0   sub buf_shift_uint8 { unpack C => substr($_[0], 0, 1, '') }
40              
41 0     0 0   sub buf_shift_uint32 { unpack N => substr($_[0], 0, 4, '') }
42              
43 0     0 0   sub buf_shift_uint64_quads { unpack Q => substr(${$_[0]}, 0, 8, '') }
  0            
44              
45             sub buf_shift_uint64_no_quads {
46 0 0   0 0   length $_[0] >= 8 or return;
47 0           my ($big, $small) = unpack(NN => substr($_[0], 0, 8, ''));
48 0 0         if ($big) {
49             # too big for an integer, try to handle it as a float:
50 0           my $high = $big * 4294967296;
51 0           my $result = $high + $small;
52 0 0         unless ($result - $high == $small) {
53             # too big event for a float, use a BigInt;
54 0           require Math::BigInt;
55 0           $result = Math::BigInt->new($big);
56 0           $result <<= 32;
57 0           $result += $small;
58             }
59 0           return $result;
60             }
61 0           return $small;
62             }
63              
64             BEGIN {
65 2     2   1268 *buf_shift_uint64 = (HAS_QUADS
66             ? \&buf_shift_uint64_quads
67             : \&buf_shift_uint64_no_quads);
68             }
69              
70             sub buf_shift_str {
71 0 0   0 0   if (my ($len) = unpack N => substr($_[0], 0, 4, '')) {
72 0 0         return substr($_[0], 0, $len, '')
73             if (length $_[0] >= $len);
74             }
75             ()
76 0           }
77              
78             sub buf_shift_utf8 {
79 0 0   0 0   if (my ($len) = unpack N => substr($_[0], 0, 4, '')) {
80 0 0         return Encode::decode(utf8 => substr($_[0], 0, $len, ''))
81             if (length $_[0] >= $len);
82             }
83             ()
84 0           }
85              
86             sub buf_shift_attrs {
87 0     0 0   my %attrs;
88 0 0         my ($flags) = buf_shift_uint32($_[0]) or return;
89 0 0         if ($flags & SSH_FILEXFER_ATTR_SIZE) {
90 0 0         ($attrs{size}) = buf_shift_uint64($_[0]) or return;
91             }
92 0 0         if ($flags & SSH_FILEXFER_ATTR_UIDGID) {
93 0 0         ($attrs{uid}) = buf_shift_uint32($_[0]) or return;
94 0 0         ($attrs{gid}) = buf_shift_uint32($_[0]) or return;
95             }
96 0 0         if ($flags & SSH_FILEXFER_ATTR_PERMISSIONS) {
97 0 0         ($attrs{permissions}) = buf_shift_uint32($_[0]) or return;
98             }
99 0 0         if ($flags & SSH_FILEXFER_ATTR_ACMODTIME) {
100 0 0         ($attrs{atime}) = buf_shift_uint32($_[0]) or return;
101 0 0         ($attrs{mtime}) = buf_shift_uint32($_[0]) or return;
102             }
103 0 0         if ($flags & SSH_FILEXFER_ATTR_EXTENDED) {
104 0 0         my ($count) = buf_shift_uint32($_[0]) or return;
105 0           my @ext;
106 0           for (1..(2*$count)) {
107 0 0         my ($str) = buf_shift_str($_[0]) or return;
108 0           push @ext, $str;
109             }
110 0           $attrs{extended} = \@ext;
111             }
112 0           \%attrs;
113             }
114              
115 0     0 0   sub buf_push_uint8 { $_[0] .= pack(C => int $_[1]) }
116              
117 0     0 0   sub buf_push_uint32 { $_[0] .= pack(N => int $_[1]) }
118              
119 0     0 0   sub buf_push_uint64_quads { $_[0] .= pack(Q => int $_[1]) }
120              
121             sub buf_push_uint64_no_quads {
122 0     0 0   my $high = int ( $_[1] / 4294967296);
123 0           $_[0] .= pack(NN => $high, int ($_[1] - $high * 4294967296));
124             }
125              
126             BEGIN {
127 2     2   1517 *buf_push_uint64 = (HAS_QUADS
128             ? \&buf_push_uint64_quads
129             : \&buf_push_uint64_no_quads);
130             }
131              
132             sub buf_push_str {
133 0 0   0 0   utf8::downgrade($_[1]) or croak "unable to pack UTF8 data";
134 0           $_[0] .= pack(N => length $_[1]);
135 0           $_[0] .= $_[1];
136             }
137              
138             sub buf_push_utf8 {
139 0     0 0   my $octets = Encode::encode(utf8 => $_[1]);
140 0           $_[0] .= pack(N => length $octets);
141 0           $_[0] .= $octets;
142              
143             }
144              
145             sub buf_push_attrs {
146 0     0 0   my $attrs = $_[1];
147 0           my $b = '';
148 0           my $flags;
149 0 0         ref $attrs eq 'HASH' or croak "Internal error";
150 0 0         if (%$attrs) {
151 0 0         if (defined $attrs->{size}) {
152 0           $flags |= SSH_FILEXFER_ATTR_SIZE;
153 0           buf_push_uint64($b, $attrs->{size});
154             }
155            
156 0 0 0       if (defined $attrs->{uid} and defined $attrs->{gid}) {
    0 0        
157 0           $flags |= SSH_FILEXFER_ATTR_UIDGID;
158 0           buf_push_uint32($b, $attrs->{uid});
159 0           buf_push_uint32($b, $attrs->{gid});
160             }
161             elsif (defined $attrs->{uid} or defined $attrs->{gid}) {
162 0           croak "Internal error: invalid attributes specification, uid and gid go together";
163             }
164              
165 0 0         if (defined $attrs->{permissions}) {
166 0           $flags |= SSH_FILEXFER_ATTR_PERMISSIONS;
167 0           buf_push_uint32($b, $attrs->{permissions});
168             }
169              
170 0 0 0       if (defined $attrs->{atime} and defined $attrs->{mtime}) {
    0 0        
171 0           $flags |= SSH_FILEXFER_ATTR_ACMODTIME;
172 0           buf_push_uint32($b, $attrs->{atime});
173 0           buf_push_uint32($b, $attrs->{mtime});
174             }
175             elsif (defined $attrs->{atime} or defined $attrs->{mtime}) {
176 0           croak "Internal error: invalid attributes specification, atime and mtime go together";
177             }
178              
179 0           my $extended = $attrs->{extended};
180 0 0         $flags |= SSH_FILEXFER_ATTR_EXTENDED if defined $extended;
181              
182 0           buf_push_uint32 $_[0], $flags;
183 0           $_[0] .= $b;
184 0 0         if (defined $extended) {
185 0 0         if (ref $extended eq 'HASH') {
186 0           $extended = [%$extended];
187             }
188 0 0         if (ref $extended eq 'ARRAY') {
189 0 0         @$extended & 1 and croak "Internal error: odd number of extension fields";
190 0           buf_push_uint32($_[0], @$extended / 2);
191 0           buf_push_str($_[0], $_) for @$extended;
192             }
193             else {
194 0           croak "Internal error: extended field is not an ARRAY reference";
195             }
196             }
197             }
198             else {
199             # optimization for the common send-nothing
200 0           buf_push_uint32($_[0], 0);
201             }
202             }
203              
204             sub buf_push_name {
205 0     0 0   my $name = $_[1];
206 0 0         ref $name eq 'HASH' or croak "Internal error: name is not a HASH ref";
207 0   0       buf_push_str($_[0], $name->{filename} // '');
208 0   0       buf_push_str($_[0], $name->{longname} // '');
209 0           my $attrs = $name->{attrs};
210 0 0 0       ($attrs and %$attrs) ? buf_push_attrs($_[0], $attrs) : buf_push_uint32($_[0], 0);
211             }
212              
213             sub buf_push_raw {
214 0     0 0   utf8::downgrade($_[1]);
215 0           $_[0] .= $_[1];
216             }
217              
218              
219              
220             1;