File Coverage

blib/lib/XML/Atom/Client.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             # $Id$
2              
3             package XML::Atom::Client;
4 1     1   245166 use strict;
  1         2  
  1         43  
5              
6 1     1   532 use XML::Atom;
  0            
  0            
7             use base qw( XML::Atom::ErrorHandler );
8             use LWP::UserAgent;
9             use XML::Atom::Entry;
10             use XML::Atom::Feed;
11             use XML::Atom::Util qw( first textValue );
12             use Digest::SHA1 qw( sha1 );
13             use MIME::Base64 qw( encode_base64 );
14             use DateTime;
15              
16             use constant NS_SOAP => 'http://schemas.xmlsoap.org/soap/envelope/';
17              
18             sub new {
19             my $class = shift;
20             my $client = bless { }, $class;
21             $client->init(@_) or return $class->error($client->errstr);
22             $client;
23             }
24              
25             sub init {
26             my $client = shift;
27             my %param = @_;
28             $client->{ua} = LWP::UserAgent::AtomClient->new($client);
29             $client->{ua}->agent('XML::Atom/' . XML::Atom->VERSION);
30             $client->{ua}->parse_head(0);
31             $client;
32             }
33              
34             sub username {
35             my $client = shift;
36             $client->{username} = shift if @_;
37             $client->{username};
38             }
39              
40             sub password {
41             my $client = shift;
42             $client->{password} = shift if @_;
43             $client->{password};
44             }
45              
46             sub use_soap {
47             my $client = shift;
48             $client->{use_soap} = shift if @_;
49             $client->{use_soap};
50             }
51              
52             sub auth_digest {
53             my $client = shift;
54             $client->{auth_digest} = shift if @_;
55             $client->{auth_digest};
56             }
57              
58             sub getEntry {
59             my $client = shift;
60             my($url) = @_;
61             my $req = HTTP::Request->new(GET => $url);
62             my $res = $client->make_request($req);
63             return $client->error("Error on GET $url: " . $res->status_line)
64             unless $res->code == 200;
65             XML::Atom::Entry->new(Stream => \$res->content);
66             }
67              
68             sub createEntry {
69             my $client = shift;
70             my($uri, $entry) = @_;
71             return $client->error("Must pass a PostURI before posting")
72             unless $uri;
73             my $req = HTTP::Request->new(POST => $uri);
74             $req->content_type($entry->content_type);
75             my $xml = $entry->as_xml;
76             _utf8_off($xml);
77             $req->content_length(length $xml);
78             $req->content($xml);
79             my $res = $client->make_request($req);
80             return $client->error("Error on POST $uri: " . $res->status_line)
81             unless $res->code == 201;
82             $res->header('Location') || 1;
83             }
84              
85             sub updateEntry {
86             my $client = shift;
87             my($url, $entry) = @_;
88             my $req = HTTP::Request->new(PUT => $url);
89             $req->content_type($entry->content_type);
90             my $xml = $entry->as_xml;
91             _utf8_off($xml);
92             $req->content_length(length $xml);
93             $req->content($xml);
94             my $res = $client->make_request($req);
95             return $client->error("Error on PUT $url: " . $res->status_line)
96             unless $res->code == 200;
97             1;
98             }
99              
100             sub deleteEntry {
101             my $client = shift;
102             my($url) = @_;
103             my $req = HTTP::Request->new(DELETE => $url);
104             my $res = $client->make_request($req);
105             return $client->error("Error on DELETE $url: " . $res->status_line)
106             unless $res->code == 200;
107             1;
108             }
109              
110             sub getFeed {
111             my $client = shift;
112             my($uri) = @_;
113             return $client->error("Must pass a FeedURI before retrieving feed")
114             unless $uri;
115             my $req = HTTP::Request->new(GET => $uri);
116             my $res = $client->make_request($req);
117             return $client->error("Error on GET $uri: " . $res->status_line)
118             unless $res->code == 200;
119             my $feed = XML::Atom::Feed->new(Stream => \$res->content)
120             or return $client->error(XML::Atom::Feed->errstr);
121             $feed;
122             }
123              
124             sub make_request {
125             my $client = shift;
126             my($req) = @_;
127             $client->munge_request($req);
128             my $res = $client->{ua}->request($req);
129             $client->munge_response($res);
130             $client->{response} = $res;
131             $res;
132             }
133              
134             sub munge_request {
135             my $client = shift;
136             my($req) = @_;
137             $req->header(
138             Accept => 'application/atom+xml, application/x.atom+xml, application/xml, text/xml, */*',
139             );
140             my $nonce = $client->make_nonce;
141             my $nonce_enc = encode_base64($nonce, '');
142             my $now = DateTime->now->iso8601 . 'Z';
143             my $digest = encode_base64(sha1($nonce . $now . ($client->password || '')), '');
144             if ($client->use_soap) {
145             my $xml = $req->content || '';
146             $xml =~ s!^(<\?xml.*?\?>)!!;
147             my $method = $req->method;
148             $xml = ($1 || '') . <
149            
150             xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
151             xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility"
152             xmlns:wsse="http://schemas.xmlsoap.org/ws/2002/07/secext">
153            
154            
155            
156             @{[ $client->username || '' ]}
157             $digest
158             $nonce_enc
159             $now
160            
161            
162            
163            
164             <$method xmlns="http://schemas.xmlsoap.org/wsdl/http/">
165             $xml
166            
167            
168            
169             SOAP
170             $req->content($xml);
171             $req->content_length(length $xml);
172             $req->header('SOAPAction', 'http://schemas.xmlsoap.org/wsdl/http/' . $method);
173             $req->method('POST');
174             $req->content_type('text/xml');
175             } else {
176             if ($client->username) {
177             $req->header('X-WSSE', sprintf
178             qq(UsernameToken Username="%s", PasswordDigest="%s", Nonce="%s", Created="%s"),
179             $client->username || '', $digest, $nonce_enc, $now);
180             $req->header('Authorization', 'WSSE profile="UsernameToken"');
181             }
182             }
183             }
184              
185             sub munge_response {
186             my $client = shift;
187             my($res) = @_;
188             if ($client->use_soap && (my $xml = $res->content)) {
189             my $doc;
190             if (LIBXML) {
191             my $parser = $client->libxml_parser;
192             $doc = $parser->parse_string($xml);
193             } else {
194             my $xp = XML::XPath->new(xml => $xml);
195             $doc = ($xp->find('/')->get_nodelist)[0];
196             }
197             my $body = first($doc, NS_SOAP, 'Body');
198             if (my $fault = first($body, NS_SOAP, 'Fault')) {
199             $res->code(textValue($fault, undef, 'faultcode'));
200             $res->message(textValue($fault, undef, 'faultstring'));
201             $res->content('');
202             $res->content_length(0);
203             } else {
204             $xml = join '', map $_->toString(LIBXML ? 1 : 0),
205             LIBXML ? $body->childNodes : $body->getChildNodes;
206             $res->content($xml);
207             $res->content_length(1);
208             }
209             }
210             }
211              
212             sub make_nonce { sha1(sha1(time() . {} . rand() . $$)) }
213              
214             sub _utf8_off {
215             if ($] >= 5.008) {
216             require Encode;
217             Encode::_utf8_off($_[0]);
218             }
219             }
220              
221             sub libxml_parser { XML::Atom->libxml_parser }
222              
223             package LWP::UserAgent::AtomClient;
224             use strict;
225             use Scalar::Util;
226              
227             use base qw( LWP::UserAgent );
228              
229             my %ClientOf;
230             sub new {
231             my($class, $client) = @_;
232             my $ua = $class->SUPER::new;
233             $ClientOf{$ua} = $client;
234             Scalar::Util::weaken($ClientOf{$ua});
235             $ua;
236             }
237              
238             sub get_basic_credentials {
239             my($ua, $realm, $url, $proxy) = @_;
240             my $client = $ClientOf{$ua} or die "Cannot find $ua";
241             return $client->username, $client->password;
242             }
243              
244             sub DESTROY {
245             my $self = shift;
246             delete $ClientOf{$self};
247             }
248              
249             1;
250             __END__