File Coverage

blib/lib/WWW/TypePad.pm
Criterion Covered Total %
statement 69 159 43.4
branch 0 38 0.0
condition 0 17 0.0
subroutine 23 31 74.1
pod 0 6 0.0
total 92 251 36.6


line stmt bran cond sub pod time code
1             package WWW::TypePad;
2 1     1   35765 use strict;
  1         2  
  1         37  
3 1     1   28 use 5.008_001;
  1         4  
  1         51  
4              
5             our $VERSION = '0.4002';
6              
7 1     1   921 use Any::Moose;
  1         37934  
  1         8  
8 1     1   574 use Carp qw( croak );
  1         1  
  1         67  
9 1     1   171523 use HTTP::Request::Common;
  1         50358  
  1         97  
10 1     1   1118 use HTTP::Status;
  1         3650  
  1         421  
11 1     1   1752 use JSON;
  1         15683  
  1         8  
12 1     1   1152 use LWP::UserAgent;
  1         22134  
  1         44  
13 1     1   1156 use Net::OAuth::Simple;
  1         1639380  
  1         45  
14 1     1   951 use WWW::TypePad::Error;
  1         4  
  1         38  
15              
16             # TODO import flag to preload them all
17 1     1   825 use WWW::TypePad::ApiKeys;
  1         3  
  1         31  
18 1     1   842 use WWW::TypePad::Applications;
  1         10  
  1         29  
19 1     1   1307 use WWW::TypePad::Assets;
  1         100  
  1         47  
20 1     1   1089 use WWW::TypePad::AuthTokens;
  1         3  
  1         33  
21 1     1   1753 use WWW::TypePad::Blogs;
  1         4  
  1         50  
22 1     1   1176 use WWW::TypePad::Events;
  1         2  
  1         57  
23 1     1   1132 use WWW::TypePad::ExternalFeedSubscriptions;
  1         4  
  1         40  
24 1     1   1028 use WWW::TypePad::Favorites;
  1         5  
  1         148  
25 1     1   1544 use WWW::TypePad::Groups;
  1         5  
  1         48  
26 1     1   2428 use WWW::TypePad::ImportJobs;
  1         3  
  1         54  
27 1     1   1739 use WWW::TypePad::Relationships;
  1         4  
  1         584  
28 1     1   1728 use WWW::TypePad::Users;
  1         9  
  1         3318  
29              
30             has 'consumer_key' => ( is => 'rw' );
31             has 'consumer_secret' => ( is => 'rw' );
32             has 'access_token' => ( is => 'rw' );
33             has 'access_token_secret' => ( is => 'rw' );
34             has 'host' => ( is => 'rw', default => 'api.typepad.com' );
35             has '_oauth' => ( is => 'rw' );
36              
37             has 'ua' => (
38             is => 'rw',
39             isa => 'LWP::UserAgent',
40              
41             # All browsers must be an instance of an LWP::UserAgent, so that
42             # we can guarantee that we can disable redirects.
43             default => sub {
44             my $ua = LWP::UserAgent->new;
45             $ua->max_redirect( 0 );
46             return $ua;
47             },
48             trigger => sub {
49             my( $self, $ua, $attr ) = @_;
50             $ua->max_redirect( 0 );
51             },
52             );
53              
54             sub oauth {
55 0     0 0   my $api = shift;
56 0 0         unless ( defined $api->_oauth ) {
57 0           my $apikey = $api->get_apikey( $api->consumer_key );
58 0           my $app = $apikey->{owner};
59              
60 0           my $oauth = Net::OAuth::Simple::AuthHeader->new(
61             tokens => {
62             consumer_key => $api->consumer_key,
63             consumer_secret => $api->consumer_secret,
64             access_token => $api->access_token,
65             access_token_secret => $api->access_token_secret,
66             },
67             urls => {
68             authorization_url => $app->{oauthAuthorizationUrl},
69             request_token_url => $app->{oauthRequestTokenUrl},
70             access_token_url => $app->{oauthAccessTokenUrl},
71             },
72             );
73              
74             # Substitute our own LWP::UserAgent instance for the OAuth browser.
75 0           $oauth->{browser} = $api->ua;
76              
77 0           $api->_oauth( $oauth );
78             }
79 0           return $api->_oauth;
80             }
81              
82             sub get_apikey {
83 0     0 0   my $api = shift;
84 0           my( $key ) = @_;
85 0           return $api->call_anon( GET => '/api-keys/' . $key . '.json' );
86             }
87              
88             sub uri_for {
89 0     0 0   my $api = shift;
90 0           my( $path ) = @_;
91 0 0         $path = '/' . $path unless $path =~ /^\//;
92 0           return 'http://' . $api->host . $path;
93             }
94              
95             sub call {
96 0     0 0   my $api = shift;
97 0           return $api->_call(0, @_);
98             }
99              
100             sub call_anon {
101 0     0 0   my $api = shift;
102 0           return $api->_call(1, @_);
103             }
104              
105             sub _call {
106 0     0     my $api = shift;
107 0           my( $anon, $method, $uri, $qs ) = @_;
108 0 0         unless ( $uri =~ /^http/ ) {
109 0           $uri = $api->uri_for( $uri );
110             }
111 0 0 0       if ( $method eq 'GET'&& $qs ) {
112 0           $uri = URI->new( $uri );
113 0           $uri->query_form( $qs );
114             }
115 0           my $res;
116 0 0 0       if ( $api->access_token && !$anon ) {
117 0           my %extra;
118 0 0 0       if (($method eq 'POST' or $method eq 'PUT') and $qs) {
      0        
119 0           $extra{ContentBody} = JSON::encode_json($qs);
120 0           $extra{ContentType} = 'application/json';
121             }
122              
123 0           my $oauth = $api->oauth;
124 0           $res = $oauth->make_restricted_request( $uri, $method, %extra );
125            
126 0 0         if ( $res->is_redirect ) {
127 0           $res = $oauth->make_restricted_request(
128             $res->header( 'Location' ), $method, %extra
129             );
130             }
131             } else {
132 0           my $req = HTTP::Request->new( $method => $uri );
133 0           $res = $api->ua->request( $req );
134            
135 0 0         if ( $res->is_redirect ) {
136 0           $req = HTTP::Request->new( $method => $res->header( 'Location' ) );
137 0           $res = $api->ua->request( $req );
138             }
139             }
140              
141 0 0         unless ( $res->is_success ) {
142 0           WWW::TypePad::Error::HTTP->throw( $res->code, $res->content );
143             }
144              
145 0 0         return 1 if $res->code == 204;
146 0           return JSON::decode_json( $res->content );
147             }
148              
149             sub call_upload {
150 0     0 0   my $api = shift;
151 0           my( $form ) = @_;
152              
153 0 0         croak "call_upload requires an access token"
154             unless $api->access_token;
155              
156 0 0         my $target_uri = delete $form->{target_url}
157             or croak "call_upload requires a target_url";
158              
159 0 0         my $filename = delete $form->{filename}
160             or croak "call_upload requires a filename";
161              
162 0   0       my $asset = delete $form->{asset} || {};
163 0           $asset = JSON::encode_json( $asset );
164              
165 0           my $uri = URI->new( $api->uri_for( '/browser-upload.json' ) );
166 0           $uri->scheme( 'https' );
167              
168             # Construct the OAuth parameters to get a signature.
169 0           my $nonce = Net::OAuth::Simple::AuthHeader->_nonce;
170 0           my $oauth_req = Net::OAuth::ProtectedResourceRequest->new(
171             consumer_key => $api->consumer_key,
172             consumer_secret => $api->consumer_secret,
173             token => $api->access_token,
174             token_secret => $api->access_token_secret,
175             request_url => $uri->as_string,
176             request_method => 'POST',
177             signature_method => 'HMAC-SHA1',
178             timestamp => time,
179             nonce => $nonce,
180             );
181 0           $oauth_req->sign;
182              
183             # Send all of the OAuth parameters in the query string.
184 0           $uri->query_form( $oauth_req->to_hash );
185              
186             # And now, construct the actual HTTP::Request object that contains
187             # all of the fields we need to send.
188 0           my $req = POST $uri,
189             'Content-Type' => 'multipart/form-data',
190             Content => [
191             # Fake the redirect_to, since we just want to capture the
192             # 302 response, and not actually follow the redirect.
193             redirect_to => 'http://example.com/none',
194              
195             target_url => $target_uri,
196             asset => $asset,
197             file => [ $filename ],
198             ];
199              
200             # The response to an upload is always a redirect; if it's anything
201             # else, this indicates some internal error we weren't planning for,
202             # so bail early.
203 0           my $res = $api->ua->request( $req );
204 0 0 0       unless ( $res->code == RC_FOUND && $res->header( 'Location' ) ) {
205 0           WWW::TypePad::Error::HTTP->throw( $res );
206             }
207              
208             # Otherwise, extract the response from the Location header. Successful
209             # uploads will result in a status=201 query string parameter...
210 0           my $loc = URI->new( $res->header( 'Location' ) );
211 0           my %form = $loc->query_form;
212 0 0         unless ( $form{status} == RC_CREATED ) {
213 0           WWW::TypePad::Error::HTTP->throw( $form{status}, $form{error} );
214             }
215              
216             # ... and an asset_url, which we can GET to get back an asset
217             # dictionary.
218 0           my $asset_uri = $form{asset_url};
219 0           return $api->call_anon( GET => $asset_uri );
220             }
221              
222             package Net::OAuth::Simple::AuthHeader;
223             # we need Net::OAuth::Simple to make requests with the OAuth credentials
224             # in an Authorization header, as required by the API, rather than the query string
225              
226 1     1   14 use base qw( Net::OAuth::Simple );
  1         2  
  1         525  
227              
228             sub make_restricted_request {
229 0     0     my $self = shift;
230 0 0         croak $Net::OAuth::Simple::UNAUTHORIZED unless $self->authorized;
231              
232 0           my( $url, $method, %extras ) = @_;
233             # Use SSL.
234 0           $url =~ s/^http:/https:/;
235              
236 0           my $uri = URI->new( $url );
237 0           my %query = $uri->query_form;
238 0           $uri->query_form( {} );
239              
240 0           $method = lc $method;
241              
242 0           my $content_body = delete $extras{ContentBody};
243 0           my $content_type = delete $extras{ContentType};
244              
245 0 0         my $request = Net::OAuth::ProtectedResourceRequest->new(
246             consumer_key => $self->consumer_key,
247             consumer_secret => $self->consumer_secret,
248             request_url => $uri,
249             request_method => uc( $method ),
250             signature_method => $self->signature_method,
251             protocol_version => $self->oauth_1_0a ?
252             Net::OAuth::PROTOCOL_VERSION_1_0A :
253             Net::OAuth::PROTOCOL_VERSION_1_0,
254             timestamp => time,
255             nonce => $self->_nonce,
256             token => $self->access_token,
257             token_secret => $self->access_token_secret,
258             extra_params => { %query, %extras },
259             );
260 0           $request->sign;
261 0 0         die "COULDN'T VERIFY! Check OAuth parameters.\n"
262             unless $request->verify;
263              
264 0           my $request_url = URI->new( $url );
265              
266 0           my $req = HTTP::Request->new(uc($method) => $request_url);
267 0           $req->header('Authorization' => $request->to_authorization_header);
268 0 0         if ($content_body) {
269 0           $req->content_type($content_type);
270 0           $req->content_length(length $content_body);
271 0           $req->content($content_body);
272             }
273              
274 0           my $response = $self->{browser}->request($req);
275 0           return $response;
276             }
277              
278             1;
279             __END__