File Coverage

blib/lib/HTTP/Tiny/Multipart.pm
Criterion Covered Total %
statement 75 75 100.0
branch 21 26 80.7
condition 6 12 50.0
subroutine 10 10 100.0
pod n/a
total 112 123 91.0


line stmt bran cond sub pod time code
1             package HTTP::Tiny::Multipart;
2              
3             # ABSTRACT: Add post_multipart to HTTP::Tiny
4              
5 4     4   122275 use strict;
  4         9  
  4         102  
6 4     4   18 use warnings;
  4         6  
  4         94  
7              
8 4     4   17 use HTTP::Tiny;
  4         7  
  4         71  
9 4     4   16 use File::Basename;
  4         10  
  4         248  
10 4     4   22 use Carp;
  4         6  
  4         172  
11 4     4   1001 use MIME::Base64;
  4         2130  
  4         1828  
12              
13             our $VERSION = 0.07;
14              
15             sub _get_boundary {
16 3     3   8 my ($headers, $content) = @_;
17            
18             # Generate and check boundary
19 3         4 my $boundary;
20 3         3 my $size = 1;
21              
22 3         5 while (1) {
23 3         73 $boundary = encode_base64 join('', map chr(rand 256), 1 .. $size++ * 3);
24 3         18 $boundary =~ s/\W/X/g;
25 3 50       5 last unless grep{ $_ =~ m{$boundary} }@{$content};
  3         38  
  3         7  
26             }
27            
28             # Add boundary to Content-Type header
29 3         7 my $before = 'multipart/form-data';
30 3         4 my $after = '';
31 3 100       9 if( defined $headers->{'content-type'} ) {
32 1 50       7 if( $headers->{'content-type'} =~ m!^(.*multipart/[^;]+)(.*)$! ) {
33 1         3 $before = $1;
34 1         3 $after = $2;
35             }
36             }
37              
38 3         18 $headers->{'content-type'} = "$before; boundary=$boundary$after";
39            
40 3         9 return "--$boundary\x0d\x0a";
41             }
42              
43             sub _build_content {
44 14     14   4559 my ($data) = @_;
45              
46 14 100       61 my @params = ref $data eq 'HASH' ? %$data : @$data;
47 12 100       161 @params % 2 == 0
48             or Carp::croak("form data reference must have an even number of terms\n");
49            
50 11         15 my @terms;
51 11         23 while( @params ) {
52 14         31 my ($key, $value) = splice(@params, 0, 2);
53 14 100       31 if ( ref $value eq 'ARRAY' ) {
54 1         3 unshift @params, map { $key => $_ } @$value;
  2         6  
55             }
56             else {
57 13         19 my $filename = '';
58 13         16 my $content = $value;
59 13         17 my $content_type = '';
60              
61 13 100 66     34 if ( ref $value and ref $value eq 'HASH' ) {
62 4 50       10 if ( $value->{content} ) {
63 4         6 $content = $value->{content};
64             }
65              
66 4 100       8 if ( $value->{filename} ) {
67 2         3 $filename = $value->{filename};
68             }
69             else {
70 2         3 $filename = $key;
71             }
72              
73 4         106 $filename = '; filename="' . basename( $filename ) . '"';
74              
75 4 100       12 if ( $value->{content_type} ) {
76 3         5 $content_type = "\x0d\x0aContent-Type: " . $value->{content_type};
77             }
78             }
79              
80 13         65 push @terms, sprintf "Content-Disposition: form-data; name=\"%s\"%s%s\x0d\x0a\x0d\x0a%s\x0d\x0a",
81             $key,
82             $filename,
83             $content_type,
84             $content;
85             }
86             }
87              
88 11         27 return \@terms;
89             }
90              
91 4     4   27 no warnings 'redefine';
  4         5  
  4         840  
92              
93             *HTTP::Tiny::post_multipart = sub {
94 3     3   2733 my ($self, $url, $data, $args) = @_;
95              
96 3 50 33     24 (@_ == 3 || @_ == 4 && ref $args eq 'HASH')
      66        
97             or Carp::croak(q/Usage: $http->post_multipart(URL, DATAREF, [HASHREF])/ . "\n");
98              
99 3 50 33     21 (ref $data eq 'HASH' || ref $data eq 'ARRAY')
100             or Carp::croak("form data must be a hash or array reference\n");
101            
102 3         8 my $headers = {};
103 3 100       4 while ( my ($key, $value) = each %{$args->{headers} || {}} ) {
  4         25  
104 1         4 $headers->{lc $key} = $value;
105             }
106              
107 3         7 delete $args->{headers};
108              
109 3         17 my $content_parts = _build_content($data);
110 3         8 my $boundary = _get_boundary($headers, $content_parts);
111              
112 3         4 my $last_boundary = $boundary;
113 3         9 substr $last_boundary, -2, 0, "--";
114            
115             return $self->request('POST', $url, {
116             %$args,
117 3         16 content => $boundary . join( $boundary, @{$content_parts}) . $last_boundary,
  3         21  
118             headers => {
119             %$headers,
120             },
121             }
122             );
123             };
124              
125             1;
126              
127             __END__