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