File Coverage

blib/lib/Fetch/Image.pm
Criterion Covered Total %
statement 69 71 97.1
branch 18 20 90.0
condition 6 15 40.0
subroutine 13 13 100.0
pod 2 2 100.0
total 108 121 89.2


line stmt bran cond sub pod time code
1             package Fetch::Image;
2 1     1   29004 use strict;
  1         2  
  1         39  
3 1     1   7 use warnings;
  1         1  
  1         27  
4              
5 1     1   1261 use LWPx::ParanoidAgent;
  1         264466  
  1         63  
6 1     1   1219 use Data::Validate::Image;
  1         4329  
  1         37  
7 1     1   1569 use Data::Validate::URI qw/is_web_uri/;
  1         51118  
  1         62  
8 1     1   1328 use File::Temp;
  1         18552  
  1         74  
9 1     1   688 use Exception::Simple;
  1         883  
  1         5  
10 1     1   54 use URI;
  1         2  
  1         887  
11              
12             our $VERSION = '0.006001';
13             $VERSION = eval $VERSION;
14              
15             sub new{
16 2     2 1 1738 my ( $invocant, $config ) = @_;
17              
18 2   33     17 my $class = ref( $invocant ) || $invocant;
19 2         5 my $self = {};
20 2         5 bless( $self, $class );
21              
22 2         23 $self->{'image_validator'} = Data::Validate::Image->new;
23              
24 2         26 $self->{'config'} = $config;
25              
26             # setup some defaults
27 2 100       10 if ( !defined($self->{'config'}->{'max_filesize'}) ){
28 1         3 $self->{'config'}->{'max_filesize'} = 524_288;
29             }
30              
31             # default allowed image types if none defined
32 2 100       10 if ( !defined($self->{'config'}->{'allowed_types'}) ){
33 1         9 $self->{'config'}->{'allowed_types'} = {
34             'image/png' => 1,
35             'image/jpg' => 1,
36             'image/jpeg' => 1,
37             'image/pjpeg' => 1,
38             'image/bmp' => 1,
39             'image/gif' => 1,
40             };
41             }
42              
43 2         8 return $self;
44             }
45              
46             sub fetch{
47 8     8 1 12910 my ( $self, $url ) = @_;
48              
49 8         78 my $uri = URI->new( $url );
50 8         21077 $url = $uri->as_string;
51              
52 8 50       1323 if ( !defined( $url ) ){
    100          
53 0         0 Exception::Simple->throw("no url");
54             } elsif ( !defined( is_web_uri( $url ) ) ){
55 1         2361 Exception::Simple->throw("invalid url");
56             }
57              
58 7         12870 my $ua = $self->_setup_ua( $url );
59              
60 7         37 my $head = $self->_head( $ua, $url );
61 1   33     8 return $self->_save( $ua, $url )
62             || Exception::Simple->throw("generic error");
63             }
64              
65             #sets up the LWPx::ParanoidAgent
66             sub _setup_ua{
67 8     8   2734 my ( $self, $url ) = @_;
68              
69 8         75 my $ua = LWPx::ParanoidAgent->new;
70              
71 8 100       11011 if ( defined( $self->{'config'}->{'user_agent'} ) ){
72 7         2166 $ua->agent( $self->{'config'}->{'user_agent'} );
73             }
74              
75 8 50       965 if ( defined( $self->{'config'}->{'timeout'} ) ){
76 0         0 $ua->timeout( $self->{'config'}->{'timeout'} );
77             }
78 8         63 $ua->cookie_jar( {} ); #don't care for cookies
79              
80 8         17753 $ua->default_header( 'Referer' => $url ); #naughty, maybe, but will get around 99% of anti-leach protection :D
81              
82 8         693 return $ua;
83             }
84              
85             # returns a HTTP::Response for a HTTP HEAD request
86             sub _head{
87 7     7   16 my ( $self, $ua, $url ) = @_;
88              
89 7         53 my $head = $ua->head( $url );
90              
91 7 100       1883906 $head->is_error && Exception::Simple->throw("transfer error");
92              
93 4 100       108 exists( $self->{'config'}->{'allowed_types'}->{ $head->header('content-type') } )
94             || Exception::Simple->throw("invalid content-type");
95              
96 2 100 66     96 if (
97             $head->header('content-length')
98             && ( $head->header('content-length') > $self->{'config'}->{'max_filesize'} )
99             ){
100             #file too big
101 1         90 Exception::Simple->throw("filesize exceeded");
102             }
103              
104 1         80 return $head;
105             }
106              
107             # returns a File::Temp copy of the requested url
108             sub _save{
109 2     2   47 my ( $self, $ua, $url ) = @_;
110              
111 2   33     21 my $response = $ua->get( $url )
112             || Exception::Simple->throw("download Failed");
113              
114 2   33     180313 my $temp_file = File::Temp->new
115             || Exception::Simple->throw("temp file save failed");
116 2         52394 $temp_file->print( $response->content );
117 2         454 $temp_file->close;
118              
119 2         206 my $image_info = $self->{'image_validator'}->validate($temp_file->filename);
120              
121 2 100       1299451 if ( !$image_info ){
122 1         39 $temp_file->DESTROY;
123 1         251 Exception::Simple->throw("not an image");
124             };
125              
126 1         4 $image_info->{'temp_file'} = $temp_file;
127 1         166 return $image_info;
128             }
129              
130             1;
131              
132             =head1 NAME
133              
134             Fetch::Image - fetch a remote image into a L
135              
136             =head1 SYNOPSIS
137              
138             use Fetch::Image;
139             use Try::Tiny; #or just use eval {}, it's all good
140              
141             my $fetcher = Fetch::Image->new( {
142             'max_filesize' => 524_288,
143             'user_agent' => 'mozilla firefox or something...',
144             'allowed_types' => {
145             'image/png' => 1,
146             'image/jpg' => 1,
147             'image/jpeg' => 1,
148             'image/pjpeg' => 1,
149             'image/bmp' => 1,
150             'image/gif' => 1,
151             },
152             } );
153              
154             my $image_info = try{
155             $fetcher->fetch('http://www.google.com/logos/2011/trevithick11-hp.jpg');
156             } catch {
157             #error gets caught here...
158             warn $_; #this
159             warn $_->error; #or this
160             };
161              
162             use Data::Dumper;
163             warn Dumper( $image_info );
164              
165             #the image is now a Temp::File in $image_info->{'temp_file'};
166              
167             =head1 DESCRIPTION
168              
169             Class that will fetch a remote image and return a hash of the image_info and the L
170              
171             =head1 METHODS
172              
173             =head2 new
174              
175             takes 3 options
176              
177             my $fetcher = Fetch::Image->new( {
178             'max_filesize' => 524_288, #default value (bytes)
179             'user_agent' => 'mozilla firefox or something...',
180             'allowed_types' => { #allowed content types (default all of these)
181             'image/png' => 1,
182             'image/jpg' => 1,
183             'image/jpeg' => 1,
184             'image/pjpeg' => 1,
185             'image/bmp' => 1,
186             'image/gif' => 1,
187             },
188             } );
189              
190             =head2 fetch
191              
192             takes 1 argument, the url of the image to fetch
193              
194             returns a hash of the image info, from L, with an extra property, 'temp_file' which is the L
195              
196             =head1 AUTHORS
197              
198             Mark Ellis Emarkellis@cpan.orgE
199              
200             =head1 SEE ALSO
201              
202             L, L
203              
204             =head1 LICENSE
205              
206             Copyright 2014 by Mark Ellis Emarkellis@cpan.orgE
207              
208             This library is free software, you can redistribute it and/or modify
209             it under the same terms as Perl itself.
210              
211             =cut