Perl SOAP::Lite not setting cookes - perl

I'm trying to use SOAP::Lite to interact with the Zimbra SOAP interface. Apparently, for admin functions, it needs an auth token sent as a cookie. When I use the documented form (apparently inherited from LWP::UserAgent), it's not working:
...
use SOAP::Lite +trace => [ transport => \&log_requests ];
...
my $cookies = HTTP::Cookies->new(ignore_discard => 1, ZM_ADMIN_AUTH_TOKEN => $auth_token);
$soap->transport->cookie_jar($cookies);
my $som = $soap->call('NoOpRequest',
SOAP::Data->name('authToken')->value($auth_token),
);
print Dumper($som->result);
...
sub log_requests {
open LOGFILE,">request.log";
print LOGFILE Dumper(#_);
close LOGFILE;
}
The relevant part of the request log is the headers in the request, which don't contain any cookies (and Zimbra is complaining about no auth token):
'_headers' => bless( {
'user-agent' => 'SOAP::Lite/Perl/1.27',
'soapaction' => '"urn:zimbraAdmin#NoOpRequest"',
'content-type' => 'text/xml; charset=utf-8',
'accept' => [
'text/xml',
'multipart/*',
'application/soap'
],
'content-length' => 827
}, 'HTTP::Headers' ),

ikegami's comment was the right answer (needed to call set_cookie, not pass the cookie as an argument to setting up the cookie jar), however the documentation on set_cookie() is ... incomplete; here are the notes I made after looking at the source to figure out what was needed to get the cookie to actually get set:
#
# $cookie_jar->set_cookie( $version, $key, $val, $path, $domain, $port, $path_spec, $secure, $maxage, $discard, \%rest )
# $version should be 0
# $key, $val, $path must be set
# $key must not start with $
# $path must start with /
# $port should be undef
# $maxage must be > 0
#
$cookies->set_cookie(0, 'ZM_ADMIN_AUTH_TOKEN', $admin_token, '/', 'zmail-admin.peak.org', undef, 1, 86400, 1);

Related

How to force CGI to double-quote cookie value

For a simple demonstration, let's use the following Perl code:
use strict;
use warnings;
use CGI qw /:standard/;
use MIME::Base64;
# ...
my $cookie = cookie( -name => 'token',
-value => '"' . encode_base64($token, '') . '"',
-expires => '+1y',
);
print header( -status => 302,
'Location' => $uri,
-cookie => $cookie,
);
This script results in the following HTTP header:
However, I expected
Set-Cookie: token="lrv5Jy5KhkXb8qIWpgd3bA=="; path=/; expires=...
What should I do to get the desired cookie format?
Quotes around the cookie value are optional. The CGI module's authors choose not to include quotes, nor provide an API option to turn them on.
Your attempt is trying to include the quotes are part of the value instead of as characters that delimit it.
If you want to include the optional quotes, then you'll need to reimplement the cookie sub.
Solution #1:
print header(
-status => 302,
'Location' => $uri,
'Set-Cookie' => 'token="' . encode_base64($token, '') . '"; ' .
'path=/; expires=' . (POSIX::strftime
"%a, %d-%b-%Y %T GMT", gmtime (time + 365*24*60*60)) # +1y
);
Solution #2:
my $cookie = cookie( -name => 'token',
-value => '',
-expires => '+1y',
)
=~ s/=;/{ '="' . encode_base64($token, '') . '";' }/er;
print header( -status => 302,
'Location' => $uri,
-cookie => $cookie,
);
Not tested, however you can try
use CGI qw /:standard/;
$CGI::Q = $CGI::DefaultClass->new;
$CGI::Q->{escape} = 0;
# ...
https://metacpan.org/module/CGI::Util/source#L83
push #$result, make_attributes( $leftover, defined $CGI::Q ? $CGI::Q->{escape} : 1 )

mod_perl and CGI behavior

This has got to be something silly I'm doing wrong. It's such a newbie type problem.
The original script is something that sits and waits for a 3rd party to connect and POST some xml to it, it takes that xml, does some validation, and stores it in a db. That part is fine. The problem is my response. I'm trying to use the header() function from CGI and it's just not behaving. It comes up blank. Obviously I could just do this manually and just print the header string, but now I'm really curious why this is behaving so strangely.
Here is a stripped down test version of the cgi script:
use strict;
use warnings;
use Data::Dumper::Names;
use CGI qw(:standard);
use Apache2::Connection ();
use Apache2::RequestRec ();
$| = 1;
# Grab the request object provided by mod_perl.
our $request_obj = shift;
our $connection = $request_obj->connection;
our $remote_ip = $connection->client_ip();
my $cgi = CGI->new($request_obj->args());
print STDERR Dumper($cgi);
my $input = $cgi->param('POSTDATA');
print STDERR Dumper($input);
my $cgi_header = $cgi->header();
print STDERR Dumper($cgi_header);
my $cgi_full_header = $cgi->header(-type => 'application/xml');
print STDERR Dumper($cgi_full_header);
my $q = CGI->new({});
print STDERR Dumper($q);
my $q_header = $q->header();
print STDERR Dumper($q_header);
my $q_full_header = $q->header(-type => 'application/xml' );
print STDERR Dumper($q_full_header);
And the output:
$cgi = bless( {
'.r' => bless( do{\(my $o = '94118860562256')}, 'Apache2::RequestRec' ),
'param' => {
'POSTDATA' => [
'test'
],
'XForms:Model' => [
'test'
]
},
'use_tempfile' => 1,
'.fieldnames' => {},
'.charset' => 'ISO-8859-1',
'escape' => 1,
'.parameters' => [
'XForms:Model',
'POSTDATA'
]
}, 'CGI' );
$input = 'test';
$cgi_header = '';
$cgi_full_header = '';
$q = bless( {
'.parameters' => [
'XForms:Model',
'POSTDATA'
],
'escape' => 1,
'.fieldnames' => {},
'.charset' => 'ISO-8859-1',
'use_tempfile' => 1,
'.r' => bless( do{\(my $o = '94118860562256')}, 'Apache2::RequestRec' ),
'param' => {
'POSTDATA' => [
''
],
'XForms:Model' => [
''
]
}
}, 'CGI' );
$q_header = '';
$q_full_header = '';
And here is the simple test script I'm using to send the POST.
#!/perl/bin/perl
use strict;
use warnings;
use DBI;
use URI;
use LWP::UserAgent;
use Data::Dumper::Names;
my $ua = LWP::UserAgent->new;
$ua->max_size( 131072 );
$ua->agent('test_xml_pusher');
$ua->ssl_opts(verify_hostname => 0);
my $url = URI->new;
$url->scheme('https');
$url->host('xxxxxxxxxxxxxxxxxxxxxxxxx');
$url->port(443);
$url->path_segments('test.cgi');
# Yes, I know... it's not valid xml... don't care for the purposes of this test.
#
my $xml = 'test';
my $response = $ua->post( $url, Content => $xml, 'Content-Type' => 'application/xml' );
print Dumper($response);
my $status_line = $response->status_line;
print Dumper($status_line);
my $content = $response->content;
print Dumper($content);
So why is $cgi_header empty? And why does $q end up being a reference to the same thing as $cgi even though I tried initializing it as my $q = CGI->new({});? (I also tried empty quotes instead of empty brackets.)
Any thoughts?
Thanks!
My environment is a centos 7 server running apache httpd 2.4.34 with mod_perl 2.0.11 and perl 5.22.4. (httpd is installed from from SCL, but perl and mod_perl are installed from source.)
--
Andy

Perl JIRA POST error "headers must be presented as a hashref"

I am writing a Perl script to POST an attachment to JIRA using
REST::Client to access the API
but I am getting an error.
use REST::Client;
use warnings;
use strict;
use File::Slurp;
use MIME::Base64;
my $user = 'user';
my $pass = 'pass';
my $url = "http://******/rest/api/2/issue/BugID/attachments";
my $client = REST::Client->new();
$client->addHeader( 'Authorization', 'Basic' . encode_base64( $user . ':' . $pass ) );
$client->addHeader( 'X-Atlassian-Token', 'no-check' );
$client->setHost( $url );
# my %header = ('Authorization' => 'Basic'. encode_base64($user . ':' . $pass),'X-Atlassian-Token' => 'no-check');
my $attachment = "C:\\Folder\\Test.txt";
$client->POST(
$url,
'Content_Type' => 'form-data',
'Content' => [ 'file' => [$attachment] ]
);
if ( $client->responseCode() eq '200' ) {
print "Updated\n";
}
# print the result
print $client->responseContent() . "\n";
The error I get is
REST::Client exception: headers must be presented as a hashref at C:\Users\a\filename.pl line 24.
As shown in the code, I have tried setting headers in different ways but I still get same error.
Please suggest if there is any other method.
I have tried using JIRA module but it gives error too.
According to the documentation, the POST method:
Takes an optional body content and hashref of custom request headers.
You need to put your headers in a hashref, e.g.:
$client->POST($url, $content, {
foo => 'bar',
baz => 'qux'
});
But...it looks like you're expecting REST::Client to use HTTP::Request::Common to construct a multipart/form-data request. Unfortunately, that's not the case, so you'll have to build the content by hand.
You could use HTTP::Request::Common directly like this:
use strict;
use warnings 'all';
use 5.010;
use HTTP::Request::Common;
use REST::Client;
my $client = REST::Client->new;
my $url = 'http://www.example.com';
my $req = POST($url,
Content_Type => 'form-data',
Content => [ file => [ 'foo.txt' ] ]
);
$client->POST($url, $req->content(), {
$req->headers->flatten()
});
But this is a bit convoluted; I would recommend dropping REST::Client and using LWP::UserAgent instead. REST::Client is just a thin wrapper for LWP::UserAgent with a few convenience features, like prepending a default host to all requests. In this case, it's just getting in the way and I don't think the conveniences are worth the trouble.
From the documentation:
POST ( $url, [$body_content, %$headers] )
And you're doing:
$client->POST(
$url,
'Content_Type' => 'form-data',
'Content' => [ 'file' => [$attachment] ]
);
So - passing a list of scalars, with an arrayref at the end.
Perhaps you want something like:
$client->POST(
$url,
$attachment,
{ 'Content-Type' => 'form-data' }
);
Note the {} to construct an anonymous hash for the headers.
Although you probably want to open and include the 'attachment', because there's nothing in REST::Client about opening files and sending them automagically.

LWP GET large file download

I have a special case for file download. I need to do chunked download for large files and also need to pass parameters to the CGI script prior to download.
It is really a REST interface. I have searched all over the Internet, and there are lots of stuff on the download part, and lots of stuff on the parameter part, but when I go to put them together I get errors. Also note that I do a POST in a similar way, and it works fine. Here is my code snip:
# $filename, $target, $url, $bs, etc. are all set...
my $bytes_received = 0;
open (FH, ">", "$filename") or $logger->error("Couldn't open $filename for writing: $!" );
my $ua = LWP::UserAgent->new();
my $res = $ua->get(
$url,
':content_cb' => \&callback,
'Content' => {
"api" => 'olfs',
"cmd" => 'rfile',
"target" => $target,
"bs" => $bs});
print $logger->info("$bytes_received bytes received");
sub callback{
my($chunk, $res) = #_;
$bytes_received += length($chunk);
print FH $chunk;
}
Here are the errors:
Not a SCALAR reference at /usr/local/share/perl5/HTTP/Message.pm line 163.
at /usr/local/share/perl5/HTTP/Message.pm line 163
HTTP::Message::add_content('HTTP::Request=HASH(0x1956a88)', 'HASH(0x7fdfda565e88)') called at /usr/local/share/perl5/HTTP/Request/Common.pm line 111
HTTP::Request::Common::_simple_req(undef, undef) called at /usr/local/share/perl5/HTTP/Request/Common.pm line 20
HTTP::Request::Common::GET('http://10.0.0.15:8084/cgi-bin/olss.cgi', 'Content', 'HASH(0x7fdfda565e88)') called at /usr/local/share/perl5/LWP/UserAgent.pm line 410
LWP::UserAgent::get('LWP::UserAgent=HASH(0x191a220)', 'http://10.0.0.15:8084/cgi-bin/olss.cgi', ':content_cb', 'CODE(0x1845818)', 'Content', 'HASH(0x7fdfda565e88)') called at ./olfs_get.pl line 72
Debugged program terminated. Use q to quit or R to restart,
use o inhibit_exit to avoid stopping after program termination,
h q, h R or h o to get additional info.
DB<3> print oct("764")
500
DB<4>
$ua->get( $url )
$ua->get( $url , $field_name => $value, ... )
This method will dispatch a GET request on the given $url. Further arguments can be given to initialize the headers of the request.
There's no such thing as a Content header. ->post uses this to create the message-body, which is never used for GET requests. If you want to build a url, you can use URI.
$ua->post( $url, $field_name => $value,... Content => \%form )
$ua->post( $url, $field_name => $value,... Content => \#form )
$ua->post( $url, $field_name => $value,... Content => $content )

How do I work with just one key and value from Data::Dumper output

I have data dumper outputting a remotely hosted xml file into a local text file and I am getting the following info:
$VAR1 = {
'resource' => {
'005cd410-41d6-4e3a-a55f-c38732b73a24.xml' => {
'standard' => 'DITA',
'area' => 'holding',
'id' => 'Comp_UKCLRONLINE_UKCLR_2000UKCLR0278',
},
'003c2a5e-4af3-4e70-bf8b-382d0b4edda1.xml' => {
'standard' => 'DITA',
'area' => 'holding',
'id' => 'Comp_UKCLRONLINE_UKCLR_2000UKCLR0278',
},
etc. What I want to do is work with just one/key and value in each resource. Ie pick out the ID and then create a url from that.
I would normally use a regex on the file and pull the info I need from that but I'm thinking there must be an easier/proper way but can't think of the right term to use in a search and am therefore not finding it.
Here is the code I am using to write this output to a file:
#-----------------------------------------------
sub request_url {
#-----------------------------------------------
my $useragent = LWP::UserAgent->new;
my $request = HTTP::Request->new( GET => "http://digitalessence.net/resource.xml" );
$resource = $useragent->request( $request );
}
#-----------------------------------------------
sub file_write {
#-----------------------------------------------
open OUT, ">$OUT" or Log_message ("\n$DATE - $TIME - Could not create filelist.doc \t");
Log_message ("\n$DATE - $TIME - Opened the output file");
print OUT Dumper (XML::Simple->new()->XMLin( $resource->content ));
Log_message ("\n$DATE - $TIME - Written the output file");
}
thanks
I'm not really understanding your question, but I'm guessing you want to access some data from the hash.
You don't need a regex or other strage stuff; just `do` your data and get the value from the hassref you get back:
A simple one liner as an example (assuming your file is called `dumper.out`):
perl -Mstrict -wE 'my $hashref = do{ do "dumper.out" }; say $hashref->{resource}{"005cd410-41d6-4e3a-a55f-c38732b73a24.xml"}{id}'
HTH, Paul
Maybe you want to walk the data structure built by XML::Simple.
Each resource is inside an ARRAYREF you get using the resource key with $doc data structure.
use XML::Simple;
use LWP;
use Data::Dumper;
my $ua = LWP::UserAgent->new;
my $req = HTTP::Request->new( GET => "http://digitalessence.net/resource.xml" );
my $res = $ua->request( $req );
my $xs = XML::Simple->new();
my $doc = $xs->XMLin( $res->content );
printf "resources: %s\n", scalar keys %{ $doc->{ resource } };
foreach ( keys %{ $doc->{ resource } } ) {
printf "resource => %s, id => %s\n", $_, $doc->{ resource }->{ $_ }->{ id };
}
The output is this:
resources: 7
resource => 005cd410-41d6-4e3a-a55f-c38732b73a24.xml, id => Comp_UKCLRONLINE_UKCLR_2000UKCLR0278
resource => 003c2a5e-4af3-4e70-bf8b-382d0b4edda1.xml, id => Comp_UKCLRONLINE_UKCLR_2002UKCLR0059
resource => 0033d4d3-c397-471f-8cf5-16fb588b0951.xml, id => Comp_UKCLRONLINE_UKCLR_navParentTopic_67
resource => 002a770a-db47-41ef-a8bb-0c8aa45a8de5.xml, id => Comp_UKCLRONLINE_UKCLR_navParentTopic_308
resource => 000fff79-45b8-4ac3-8a57-def971790f16.xml, id => Comp_UKCLRONLINE_UKCLR_2002UKCLR0502
resource => 00493372-c090-4734-9a50-8f5a06489591.xml, id => Comp_UKCLRONLINE_COMPCS_2010_10_0002
resource => 004377bf-8e24-4a69-9411-7c6baca80b87.xml, id => Comp_CLJONLINE_CLJ_2002_01_11