File Coverage

blib/lib/FU/MultipartFormData.pm
Criterion Covered Total %
statement 61 89 68.5
branch 25 52 48.0
condition 3 12 25.0
subroutine 12 16 75.0
pod 12 12 100.0
total 113 181 62.4


line stmt bran cond sub pod time code
1             package FU::MultipartFormData 1.4;
2 1     1   90197 use v5.36;
  1         4  
3 1     1   6 use Carp 'confess';
  1         14  
  1         69  
4 1     1   551 use FU::Util 'utf8_decode';
  1         14  
  1         2294  
5              
6 11     11   20 sub _arg($d) { $d =~ s{^"(.+)"$}{$1 =~ s/\\([\\"])/$1/rg}er }
  11         32  
  11         14  
  11         57  
  6         44  
7              
8 1     1 1 141188 sub parse($pkg, $header, $data) {
  1         4  
  1         2  
  1         2  
  1         2  
9 1 50       10 confess "Invalid multipart header '$header'"
10             if $header !~ m{^multipart/form-data\s*;\s*boundary\s*=(.+)$};
11 1         6 my $boundary = _arg $1;
12 1 50       7 confess "Invalid multipart boundary '$boundary'" if $boundary !~ /^[\x21-\x7e]+$/;
13 1         7 utf8::encode($boundary);
14              
15 1         2 my @a;
16 1         51 while ($data =~ m{--\Q$boundary\E(?:--\r\n|\r\n((?:.+\r\n)+)\r\n)}xg) {
17 4         15 my $hdrs = $1;
18 4 100       28 $a[$#a]{length} = $-[0] - 2 - $a[$#a]{start} if @a;
19 4 100       13 if (!$hdrs) {
20 1 50       5 confess "Trailing garbage" if pos $data != length $data;
21 1         4 last;
22             }
23              
24 3         36 my $d = bless {
25             data => $data,
26             start => pos $data,
27             }, $pkg;
28              
29 3 50       28 confess "Missing content-disposition header" if $hdrs !~ /content-disposition:\s*form-data(.+)/i;
30 3         8 my $v = $1;
31 3         12 my $pvalue = qr/("(?:\\[\\"]|[^\\"\r\n]+)*"|[^\s;"]*)/;
32 3 50       120 confess "Missing 'name' parameter" if $v !~ /;\s*name\s*=\s*$pvalue/;
33 3         13 $d->{name} = utf8_decode _arg $1;
34 3 100       126 $d->{filename} = utf8_decode _arg $1 if $v =~ /;\s*filename\s*=\s*$pvalue/;
35              
36 3 50       219 if ($hdrs =~ /content-type:\s*$pvalue(?:\s*;\s*charset\s*=\s*$pvalue)?/i) {
37 3         12 $d->{mime} = utf8_decode _arg $1;
38 3 100       16 $d->{charset} = utf8_decode _arg $2 if $2;
39             }
40 3         47 push @a, $d;
41             }
42 1 50 33     8 confess "Missing end-of-data marker" if @a && !defined $a[$#a]{length};
43             \@a
44 1         5 }
45              
46 3     3 1 1249 sub name { $_[0]{name} }
47 3     3 1 19 sub filename { $_[0]{filename} }
48 3     3 1 32 sub mime { $_[0]{mime} }
49 3     3 1 15 sub charset { $_[0]{charset} }
50 3     3 1 16 sub length { $_[0]{length} }
51              
52 9     9 1 21 sub substr($o,$off,$len=undef) {
  9         20  
  9         12  
  9         17  
  9         13  
53 9 100       50 $off += $o->{length} if $off < 0;
54 9 100       27 $off = 0 if $off < 0;
55 9 50       28 $off = $o->{length} if $off > $o->{length};
56              
57 9   66     39 $len //= $o->{length} - $off;
58 9 100       23 $len += $o->{length} - 1 if $len < 0;
59 9 50       22 $len = 0 if $len < 0;
60 9 100       26 $len = $o->{length} - $off if $len > $o->{length} - $off;
61              
62 9         72 substr $o->{data}, $o->{start} + $off, $len;
63             }
64              
65 3     3 1 14 sub data { $_[0]->substr(0) }
66 0     0 1   sub value { utf8_decode $_[0]->data }
67              
68 0     0 1   sub syswrite($o, $fh) {
  0            
  0            
  0            
69 0           my $off = $o->{start};
70 0           my $end = $o->{start} + $o->{length};
71 0           while ($off < $end) {
72 0           my $r = syswrite $fh, $o->{data}, $end-$off, $off;
73 0 0         return if !defined $r;
74 0           $off += $r;
75             }
76 0           $o->{length};
77             }
78              
79 0     0 1   sub save($o, $fn) {
  0            
  0            
  0            
80 0 0         open my $F, '>', $fn or confess "Error opening '$fn': $!";
81 0 0         defined $o->syswrite($F) or confess "Error writing to '$fn': $!";
82             }
83              
84 0     0 1   sub describe($o) {
  0            
  0            
85 0           my $head = eval { utf8_decode $o->substr(0, 100) };
  0            
86 0 0 0       if (defined $head && $head =~ /\n/) {
    0 0        
87 0           ($head) = split /\n/, $head, 2;
88 0           $head .= '...';
89             } elsif (defined $head && $o->{length} > 100) {
90 0           $head .= '...';
91             }
92             $o->{name}.': '.join ' ',
93             $o->{filename} ? "filename=$o->{filename}" : (),
94             $o->{mime} ? "mime=$o->{mime}" : (),
95 0 0         $o->{charset} ? "charset=$o->{charset}" : (),
    0          
    0          
    0          
96             "length=$o->{length}",
97             defined $head ? "value=$head" : ();
98             }
99              
100             1;
101             __END__