diff --git a/perl5/REST/Client.pm b/perl5/REST/Client.pm
new file mode 100644
index 0000000..fa35fec
--- /dev/null
+++ b/perl5/REST/Client.pm
@@ -0,0 +1,553 @@
+package REST::Client;
+
+=head1 NAME
+
+REST::Client - A simple client for interacting with RESTful http/https resources
+
+=head1 SYNOPSIS
+
+ use REST::Client;
+
+ #The basic use case
+ my $client = REST::Client->new();
+ $client->GET('http://example.com/dir/file.xml');
+ print $client->responseContent();
+
+ #A host can be set for convienience
+ $client->setHost('http://example.com');
+ $client->PUT('/dir/file.xml', 'new content');
+ if( $client->responseCode() eq '200' ){
+ print "Updated\n";
+ }
+
+ #custom request headers may be added
+ $client->addHeader('CustomHeader', 'Value');
+
+ #response headers may be gathered
+ print $client->responseHeader('ResponseHeader');
+
+ #X509 client authentication
+ $client->setCert('/path/to/ssl.crt');
+ $client->setKey('/path/to/ssl.key');
+
+ #add a CA to verify server certificates
+ $client->setCa('/path/to/ca.file');
+
+ #you may set a timeout on requests, in seconds
+ $client->setTimeout(10);
+
+ #options may be passed as well as set
+ $client = REST::Client->new({
+ host => 'https://example.com',
+ cert => '/path/to/ssl.crt',
+ key => '/path/to/ssl.key',
+ ca => '/path/to/ca.file',
+ timeout => 10,
+ });
+ $client->GET('/dir/file', {CustomHeader => 'Value'});
+
+ # Requests can be specificed directly as well
+ $client->request('GET', '/dir/file', 'request body content', {CustomHeader => 'Value'});
+
+ # Requests can optionally automatically follow redirects and auth, defaults to
+ # false
+ $client->setFollow(1);
+
+ #It is possible to access the L object REST::Client is using to
+ #make requests, and set advanced options on it, for instance:
+ $client->getUseragent()->proxy(['http'], 'http://proxy.example.com/');
+
+ # request responses can be written directly to a file
+ $client->setContentFile( "FileName" );
+
+ # or call back method
+ $client->setContentFile( \&callback_method );
+ # see LWP::UserAgent for how to define callback methods
+
+=head1 DESCRIPTION
+
+REST::Client provides a simple way to interact with HTTP RESTful resources.
+
+=cut
+
+=head1 METHODS
+
+=cut
+
+use strict;
+use warnings;
+use 5.008_000;
+
+use constant TRUE => 1;
+use constant FALSE => 0;
+
+our ($VERSION) = ('$Rev: 273 $' =~ /(\d+)/);
+
+use URI;
+use LWP::UserAgent;
+use Carp qw(croak carp);
+
+=head2 Construction and setup
+
+=head3 new ( [%$config] )
+
+Construct a new REST::Client. Takes an optional hash or hash reference or
+config flags. Each config flag also has get/set accessors of the form
+getHost/setHost, getUseragent/setUseragent, etc. These can be called on the
+instantiated object to change or check values.
+
+The config flags are:
+
+=over 4
+
+=item host
+
+A default host that will be prepended to all requests. Allows you to just
+specify the path when making requests.
+
+The default is undef - you must include the host in your requests.
+
+=item timeout
+
+A timeout in seconds for requests made with the client. After the timeout the
+client will return a 500.
+
+The default is 5 minutes.
+
+=item cert
+
+The path to a X509 certificate file to be used for client authentication.
+
+The default is to not use a certificate/key pair.
+
+=item key
+
+The path to a X509 key file to be used for client authentication.
+
+The default is to not use a certificate/key pair.
+
+=item ca
+
+The path to a certificate authority file to be used to verify host
+certificates.
+
+The default is to not use a certificates authority.
+
+=item pkcs12
+
+The path to a PKCS12 certificate to be used for client authentication.
+
+=item pkcs12password
+
+The password for the PKCS12 certificate specified with 'pkcs12'.
+
+=item follow
+
+Boolean that determins whether REST::Client attempts to automatically follow
+redirects/authentication.
+
+The default is false.
+
+=item useragent
+
+An L object, ready to make http requests.
+
+REST::Client will provide a default for you if you do not set this.
+
+=back
+
+=cut
+
+sub new {
+ my $class = shift;
+ my $config;
+
+ $class->_buildAccessors();
+
+ if(ref $_[0] eq 'HASH'){
+ $config = shift;
+ }elsif(scalar @_ && scalar @_ % 2 == 0){
+ $config = {@_};
+ }else{
+ $config = {};
+ }
+
+ my $self = bless({}, $class);
+ $self->{'_config'} = $config;
+
+ $self->_buildUseragent();
+
+ return $self;
+}
+
+=head3 addHeader ( $header_name, $value )
+
+Add a custom header to any requests made by this client.
+
+=cut
+
+sub addHeader {
+ my $self = shift;
+ my $header = shift;
+ my $value = shift;
+
+ my $headers = $self->{'_headers'} || {};
+ $headers->{$header} = $value;
+ $self->{'_headers'} = $headers;
+ return;
+}
+
+=head3 buildQuery ( [...] )
+
+A convienience wrapper around URI::query_form for building query strings from a
+variety of data structures. See L
+
+Returns a scalar query string for use in URLs.
+
+=cut
+
+sub buildQuery {
+ my $self = shift;
+
+ my $uri = URI->new();
+ $uri->query_form(@_);
+ return $uri->as_string();
+}
+
+
+
+=head2 Request Methods
+
+Each of these methods makes an HTTP request, sets the internal state of the
+object, and returns the object.
+
+They can be combined with the response methods, such as:
+
+ print $client->GET('/search/?q=foobar')->responseContent();
+
+=head3 GET ( $url, [%$headers] )
+
+Preform an HTTP GET to the resource specified. Takes an optional hashref of custom request headers.
+
+=cut
+
+sub GET {
+ my $self = shift;
+ my $url = shift;
+ my $headers = shift;
+ return $self->request('GET', $url, undef, $headers);
+}
+
+=head3 PUT ($url, [$body_content, %$headers] )
+
+Preform an HTTP PUT to the resource specified. Takes an optional body content and hashref of custom request headers.
+
+=cut
+
+sub PUT {
+ my $self = shift;
+ return $self->request('PUT', @_);
+}
+
+=head3 PATCH ( $url, [$body_content, %$headers] )
+
+Preform an HTTP PATCH to the resource specified. Takes an optional body content and hashref of custom request headers.
+
+=cut
+
+sub PATCH {
+ my $self = shift;
+ return $self->request('PATCH', @_);
+}
+
+=head3 POST ( $url, [$body_content, %$headers] )
+
+Preform an HTTP POST to the resource specified. Takes an optional body content and hashref of custom request headers.
+
+=cut
+
+sub POST {
+ my $self = shift;
+ return $self->request('POST', @_);
+}
+
+=head3 DELETE ( $url, [%$headers] )
+
+Preform an HTTP DELETE to the resource specified. Takes an optional hashref of custom request headers.
+
+=cut
+
+sub DELETE {
+ my $self = shift;
+ my $url = shift;
+ my $headers = shift;
+ return $self->request('DELETE', $url, undef, $headers);
+}
+
+=head3 OPTIONS ( $url, [%$headers] )
+
+Preform an HTTP OPTIONS to the resource specified. Takes an optional hashref of custom request headers.
+
+=cut
+
+sub OPTIONS {
+ my $self = shift;
+ my $url = shift;
+ my $headers = shift;
+ return $self->request('OPTIONS', $url, undef, $headers);
+}
+
+=head3 HEAD ( $url, [%$headers] )
+
+Preform an HTTP HEAD to the resource specified. Takes an optional hashref of custom request headers.
+
+=cut
+
+sub HEAD {
+ my $self = shift;
+ my $url = shift;
+ my $headers = shift;
+ return $self->request('HEAD', $url, undef, $headers);
+}
+
+=head3 request ( $method, $url, [$body_content, %$headers] )
+
+Issue a custom request, providing all possible values.
+
+=cut
+
+sub request {
+ my $self = shift;
+ my $method = shift;
+ my $url = shift;
+ my $content = shift;
+ my $headers = shift;
+
+ $self->{'_res'} = undef;
+ $self->_buildUseragent();
+
+
+ #error check
+ croak "REST::Client exception: First argument to request must be one of GET, PATCH, PUT, POST, DELETE, OPTIONS, HEAD" unless $method =~ /^(get|patch|put|post|delete|options|head)$/i;
+ croak "REST::Client exception: Must provide a url to $method" unless $url;
+ croak "REST::Client exception: headers must be presented as a hashref" if $headers && ref $headers ne 'HASH';
+
+
+ $url = $self->_prepareURL($url);
+
+ my $ua = $self->getUseragent();
+ if(defined $self->getTimeout()){
+ $ua->timeout($self->getTimeout);
+ }else{
+ $ua->timeout(300);
+ }
+ my $req = HTTP::Request->new( $method => $url );
+
+ #build headers
+ if(defined $content && length($content)){
+ $req->content($content);
+ $req->header('Content-Length', length($content));
+ }else{
+ $req->header('Content-Length', 0);
+ }
+
+ my $custom_headers = $self->{'_headers'} || {};
+ for my $header (keys %$custom_headers){
+ $req->header($header, $custom_headers->{$header});
+ }
+
+ for my $header (keys %$headers){
+ $req->header($header, $headers->{$header});
+ }
+
+
+ #prime LWP with ssl certfile if we have values
+ if($self->getCert){
+ carp "REST::Client exception: Certs defined but not using https" unless $url =~ /^https/;
+ croak "REST::Client exception: Cannot read cert and key file" unless -f $self->getCert && -f $self->getKey;
+
+ $ua->ssl_opts(SSL_cert_file => $self->getCert);
+ $ua->ssl_opts(SSL_key_file => $self->getKey);
+ }
+
+ #prime LWP with CA file if we have one
+ if(my $ca = $self->getCa){
+ croak "REST::Client exception: Cannot read CA file" unless -f $ca;
+ $ua->ssl_opts(SSL_ca_file => $ca);
+ }
+
+ #prime LWP with PKCS12 certificate if we have one
+ if($self->getPkcs12){
+ carp "REST::Client exception: PKCS12 cert defined but not using https" unless $url =~ /^https/;
+ croak "REST::Client exception: Cannot read PKCS12 cert" unless -f $self->getPkcs12;
+
+ $ENV{HTTPS_PKCS12_FILE} = $self->getPkcs12;
+ if($self->getPkcs12password){
+ $ENV{HTTPS_PKCS12_PASSWORD} = $self->getPkcs12password;
+ }
+ }
+
+ my $res = $self->getFollow ?
+ $ua->request( $req, $self->getContentFile ) :
+ $ua->simple_request( $req, $self->getContentFile );
+
+ $self->{_res} = $res;
+
+ return $self;
+}
+
+=head2 Response Methods
+
+Use these methods to gather information about the last requset
+performed.
+
+=head3 responseCode ()
+
+Return the HTTP response code of the last request
+
+=cut
+
+sub responseCode {
+ my $self = shift;
+ return $self->{_res}->code;
+}
+
+=head3 responseContent ()
+
+Return the response body content of the last request
+
+=cut
+
+sub responseContent {
+ my $self = shift;
+ return $self->{_res}->content;
+}
+
+=head3 responseHeaders()
+
+Returns a list of HTTP header names from the last response
+
+=cut
+
+sub responseHeaders {
+ my $self = shift;
+ return $self->{_res}->headers()->header_field_names();
+}
+
+
+
+=head3 responseHeader ( $header )
+
+Return a HTTP header from the last response
+
+=cut
+
+sub responseHeader {
+ my $self = shift;
+ my $header = shift;
+ croak "REST::Client exception: no header provided to responseHeader" unless $header;
+ return $self->{_res}->header($header);
+}
+
+=head3 responseXpath ()
+
+A convienience wrapper that returns a L xpath context for the body content. Assumes the content is XML.
+
+=cut
+
+sub responseXpath {
+ my $self = shift;
+
+ require XML::LibXML;
+
+ my $xml= XML::LibXML->new();
+ $xml->load_ext_dtd(0);
+
+ if($self->responseHeader('Content-type') =~ /html/){
+ return XML::LibXML::XPathContext->new($xml->parse_html_string( $self->responseContent() ));
+ }else{
+ return XML::LibXML::XPathContext->new($xml->parse_string( $self->responseContent() ));
+ }
+}
+
+# Private methods
+
+sub _prepareURL {
+ my $self = shift;
+ my $url = shift;
+
+ my $host = $self->getHost;
+ if($host){
+ $url = '/'.$url unless $url =~ /^\//;
+ $url = $host . $url;
+ }
+ unless($url =~ /^\w+:\/\//){
+ $url = ($self->getCert ? 'https://' : 'http://') . $url;
+ }
+
+ return $url;
+}
+
+sub _buildUseragent {
+ my $self = shift;
+
+ return if $self->getUseragent();
+
+ my $ua = LWP::UserAgent->new;
+ $ua->agent("REST::Client/$VERSION");
+ $self->setUseragent($ua);
+
+ return;
+}
+
+sub _buildAccessors {
+ my $self = shift;
+
+ return if $self->can('setHost');
+
+ my @attributes = qw(Host Key Cert Ca Timeout Follow Useragent Pkcs12 Pkcs12password ContentFile);
+
+ for my $attribute (@attributes){
+ my $set_method = "
+ sub {
+ my \$self = shift;
+ \$self->{'_config'}{lc('$attribute')} = shift;
+ return \$self->{'_config'}{lc('$attribute')};
+ }";
+
+ my $get_method = "
+ sub {
+ my \$self = shift;
+ return \$self->{'_config'}{lc('$attribute')};
+ }";
+
+
+ {
+ no strict 'refs';
+ *{'REST::Client::set'.$attribute} = eval $set_method ;
+ *{'REST::Client::get'.$attribute} = eval $get_method ;
+ }
+
+ }
+
+ return;
+}
+
+1;
+
+
+=head1 TODO
+
+Caching, content-type negotiation, readable handles for body content.
+
+=head1 AUTHOR
+
+Miles Crawford, Emcrawfor@cpan.orgE
+
+=head1 COPYRIGHT
+
+Copyright 2008 - 2010 by Miles Crawford
+
+This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
+
+=cut