Correct way of rewriting `curl ...` to perl - perl

I wrote a program that requests the source and the response header of a webpage, now I need it to run cross platform. I used the external command curl (in linux) to achieve it. I get the source like this::
#!/usr/bin/perl -w
use strict;
#declaring variables here#
my $result = `curl 'https://$host$request' -H 'Host: $host' -H 'User-Agent: $useragent' -H 'Accept: $accept' -H 'Accept-Language: $acceptlanguage' --compressed -H 'Cookie: $cookie' -H 'DNT: $dnt' -H 'Connection: $connection' -H 'Upgrade-Insecure-Requests: $upgradeinsecure' -H 'Cache-Control: $cachecontrol'`;
print "$result\n";
And the response header like this:
#!/usr/bin/perl -w
use strict;
#declaring variables here#
my $result = `curl -I 'https://$host$request' -H 'Host: $host' -H 'User-Agent: $useragent' -H 'Accept: $accept' -H 'Accept-Language: $acceptlanguage' --compressed -H 'Cookie: $cookie' -H 'DNT: $dnt' -H 'Connection: $connection' -H 'Upgrade-Insecure-Requests: $upgradeinsecure' -H 'Cache-Control: $cachecontrol'`;
print "$result\n";
These work fine, but I need to call these in perl and not as external commands.
I wrote some code using LWP::UserAgent to get the source:
#!/usr/bin/perl -w
use strict;
use LWP::UserAgent;
#declaring variables here#
my $ua = LWP::UserAgent->new;
my $req = HTTP::Request->new(GET => "https://$host$request HTTP/1.1");
$req->header('Host' => "$host");
$req->header('User-Agent' => "$useragent");
$req->header('Accept' => "$accept");
$req->header('Accept-Language' => "$acceptlanguage");
$req->header('Accept-Encoding' => "$acceptencoding");
$req->header('Cookie' => "$cookie");
$req->header('DNT' => "$dnt");
$req->header('Connection' => "$connection");
$req->header('Upgrade-Insecure-Requests' => "$upgradeinsecure");
$req->header('Cache-Control' => "$cachecontrol");
my $resp = $ua->request($req);
if ($resp->is_success) {
my $message = $resp->decoded_content;
print "$message\n";
}
This sometimes runs fine, but sometimes decoded_content returns nothing, I do get a response and i can print it using content, but its still encoded.
And requesting response headers using LWP::UserAgent is not possible so I wrote the request using Net::HTTP:
#!/usr/bin/perl -w
use strict;
use Net::HTTP;
#declaring variables here#
my $s = Net::HTTP->new(Host => "$host") || die $#;
$s->write_request(GET => "$request", 'Host' => "$host", 'User-Agent' => "$useragent", 'Accept' => "$accept", 'Accept-Language' => "$acceptlanguage", 'Accept-Encoding' => "$acceptencoding", 'Cookie' => "$cookie", 'DNT' => "$dnt", 'Connection' => "$connection", 'Upgrade-Insecure-Requests' => "$upgradeinsecure", 'Cache-Control' => "$cachecontrol");
my #headers;
while(my $line = <$s>) {
last unless $line =~ /\S/;
push #headers, $line;
}
print #headers;
This returns
HTTP/1.1 302 Found
Content-Type: text/html; charset=UTF-8
Connection: close
Content-Length: 0
Is the problem with my syntax of am I using the wrong tools? I know that WWW::Curl::Easy can request the source and the header at the same time, but I don't know how to pass my variables to its request. Could someone tell me what the problem is or just rewrite these requests correctly using the same variables with WWW:Curl::Easy? I'd appreciate a solution using WWW::Curl::Easy. Thanks in advance.

You can get the response headers in a couple of ways with LWP. Demonstrated here:
use LWP::UserAgent;
my($host,$request) = ('example.com', '/my/request');
my #header=( [Host => $host],
['User-Agent' => 'James Bond 2.0'],
[Accept => 'text/plain'],
[Cookie => 'cookie=x'],
);
my $ua = LWP::UserAgent->new;
my $req = HTTP::Request->new(GET => "https://$host$request"); #dont add HTTP/1.1
$req->header(#$_) for #header;
my $resp = $ua->request($req);
if ($resp->is_success) {
my %h; $resp->headers->scan( sub{ $h{shift()}=shift() } );
printf "Header name: %-30s Value: %-30s\n", $_, $h{$_} for sort keys %h;
print "\n<<<".$resp->headers()->as_string.">>>\n\n"; #all header lines in one big string
print $resp->header('Content-Type'),"\n\n"; #get one specific header line
my $content = $resp->decoded_content;
print "$content\n";
}
Note: "HTTP/1.1" should not be a part of the string after GET =>.
And with calling curl as a sub process you don't need to call it twice. You can get both headers and content at once by using -i like this:
my $response = ` curl -s -i "http://somewhere.com/path" -H 'User-Agent: Yes' `;
my($headers,$content) = split /\cM?\cJ\cM?\cJ/, $response, 2;
print "Headers: <<<$headers>>>\n\n";
print "Content: <<<$content>>>\n\n";

Related

How to make the post request in BOX API using LWP::UserAgent?

I have tried the following code
my $url = "https://api.box.com/2.0/users/";
use strict;
use warnings;
use LWP::UserAgent;
use HTTP::Request::Common qw{ POST };
use CGI;
my $ua = LWP::UserAgent->new();
my $request = POST( $url, [ 'name' => 'mkhun', 'is_platform_access_only' => 'true',"Authorization" => "Bearer <ACC TOK>" ] );
my $content = $ua->request($request)->as_string();
my $cgi = CGI->new();
print $cgi->header(), $content;
The above code always give the 400 error. And throwing the
{"type":"error","status":400,"code":"bad_request","context_info":{"errors":[{"reason":"invalid_parameter","name":"entity-body","message":"Invalid value 'is_platform_access_only=true&Authorization=Bearer+WcpZasitJWVDQ87Vs1OB9dQedRVyOrs6&name=mkhun'. Entity body should be a correctly nested resource attribute name\/value pair"}]},
I don't know what is the issue. The same thing with Linux curl is working.
curl https://api.box.com/2.0/users \
-H "Authorization: Bearer <TOKEN>" \
-d '{"name": "Ned Stark", "is_platform_access_only": true}' \
-X POST
The Box API documentation says:
Both request body data and response data are formatted as JSON.
Your code is sending form-encoded data instead.
Also, it looks like Authorization is supposed to be an HTTP header, not a form field.
Try this instead:
use strict;
use warnings;
use LWP::UserAgent;
use JSON::PP;
my $url = "https://api.box.com/2.0/users/";
my $payload = {
name => 'mkhun',
is_platform_access_only => \1,
};
my $ua = LWP::UserAgent->new;
my $response = $ua->post(
$url,
Authorization => 'Bearer <TOKEN>',
Content => encode_json($payload),
);

POST request with Perl and read response object within dancer route

Trying to write the exact equivalence in perl of the following:
curl -H "Content-Type: application/json" -X POST -d '{"user": { "uid":"13453"},"access_token":"3428D3194353750548196BA3FD83E58474E26B8B9"}' https://platform.gethealth.io/v1/health/account/user/
Unexperienced with perl, this is what I have tried:
use HTTP::Request::Common;
use LWP::UserAgent;
get '/gethealthadduser/:id' => sub {
my $ua = LWP::UserAgent->new;
$ua->request(POST 'https://platform.gethealth.io/v1/health/account/user', [{"user": { "uid":param("id")},"access_token":config->{gethealthtoken}}]);
};
I take it you are working with Dancer already, or you are adding something to an existing application, and the goal is to hide the POST request to another service behind your API.
In your curl example, you have the Content-Type application/json, but in your Perl code you are sending a form. That's likely going to be the Content-Type application/x-www-form-urlencoded. It might not be what the server wants.
In addition to that, you were passing the form data as an array reference, which makes POST believe they are headers. That not what you want.
In order to do the same thing you are doing with curl, you need a few more steps.
You need to convert the data to JSON. Luckily Dancer brings a nice DSL keyword to_json that does that easily.
You need to tell LWP::UserAgent to use the right Content-Type header. That's application/json and you can set it either at the request level, or as a default for the user agent object. I'll do the former.
In addition to that, I recommend not using HTTP::Request::Common to import keywords into a Dancer app. GET and POST and so on are upper-case and the Dancer DSL has get and post which is lower-case, but it's still confusing. Use HTTP::Request explicitly instead.
Here's the final thing.
use LWP::UserAgent;
use HTTP::Request;
get '/gethealthadduser/:id' => sub {
my $ua = LWP::UserAgent->new;
my $req = HTTP::Request->new(
POST => 'https://platform.gethealth.io/v1/health/account/user',
[ 'Content-Type' => 'application/json' ], # headers
to_json { user => param("id"), access_token => config->{gethealthtoken} }, # content
);
my $res = $ua->request($req);
# log the call with log level debug
debug sprintf( 'Posted to gethealth.io with user %s and got a %s back.',
param('id'),
$res->status_line
);
# do things with $res
};
Try using HTTP::Tiny (it's on CPAN). IMHO, it's a much cleaner module than LWP::UserAgent, although the latter is much more popular.
Here's some code that should work out of the box:
use HTTP::Tiny 0.064; # use a recent version or better
my $url = 'https://api.somewhere.com/api/users';
my $data = {
first_name => "joe",
last_name => "blow"
};
my $method = 'POST';
my $default_headers = {
'Authorization' => "Bearer ".$token, # if needed
'Accept' => 'application/json'
};
my $tiny = HTTP::Tiny->new(
agent => 'mywebsite.com',
default_headers => $default_headers,
timeout => 30
);
my $response;
if ( ($method eq 'POST') || ($method eq 'PUT') ) {
$response = $tiny->request($method, $url, {
headers => {
'Content-Type' => 'application/json'
},
content => &toJSON($data)
});
}
else {
if ($data) {
die "data cannot be included with method $method";
}
$response = $tiny->request($method, $url);
}
die unless $response->{'success'};
Good luck on your project!
Here is the solution with the correct format and structure of posted parameters:
get '/api/gethealthadduser/:id' => sub {
my %user = (
uid => param("id")
);
# my $user = {
# uid => param("id")
# };
my $ua = LWP::UserAgent->new;
my $req = HTTP::Request->new(
POST => 'https://platform.gethealth.io/v1/health/account/user/',
[ 'Content-Type' => 'application/json' ], # headers
JSON::to_json({ user => \%user, access_token => config->{gethealthtoken} }) # content
);
my $res = $ua->request($req);
print STDERR Dumper($res);
$res;
};

Get access token in perl

There is a working sample of getting token in bash
response=$(curl --fail --silent --insecure --data "username=test&password=test" \
--header "Authorization: Basic Y2xvdWQtYmVzcG9rZTo=" \
-X POST "https://lab7.local:8071/auth/token?grant_type=password")
token=`echo "$response" | awk '/access_token/{print $NF}' |sed 's/\"//g'`
echo $token
I'm trying to translate it in perl, but getting code 400
#!/usr/bin/env perl
use strict;
use warnings;
use HTTP::Request;
use LWP::UserAgent;
use LWP::Simple;
use JSON::XS;
use Try::Tiny;
my $ua = LWP::UserAgent->new;
my $req = HTTP::Request->new(POST => "https://lab7.local:8071/auth/token?grant_type=password");
my $post_data = "username=test&password=test";
$req->content($post_data);
my $resp = $ua->request($req);
if ($resp->is_success) {
my $mess = $resp->decoded_content;
print "$mess\n";
} else {
my $code = $resp->code;
print $code;
}
Your curl version is sending an Authentication header that is missing from the Perl version. You should add that.
$req->header(Authorization => 'Basic Y2xvdWQtYmVzcG9rZTo=');
You're adding a basic auth header, with a username. That string is just the base 64 encoded equivalent.
So you should probably include this in your LWP:
$req -> authorization_basic ( 'cloud-bespoke' );
And it should work.

Send a file with payload using curl in Perl

I am trying to post an image to tinyping.com, but I need this to be done inside PERL without shelling out to curl. This command works great.
curl -i --user api:****** --data-binary #myImage.png https://api.tinypng.com/shrink
How would I express this using LWP library in Perl? I am very basic in Perl.
So far I have:
use LWP::UserAgent;
use MIME::Base64;
my $img_target_dir = ...;
my $imgname = ...;
####
#not sure if i need to convert to BASE64
open (IMAGE, "$img_target_dir$imgname") or die "$!";
$raw_string = do{ local $/ = undef; <IMAGE>; };
$encoded = MIME::Base64::encode_base64( $raw_string );
####
my $content = post(
"https://api:***************************\#api.tinypng.com/shrink",
Content_Type => 'image/png',
Content =>[
]
) or die print "failure\n";
I ended up just shelling out to curl. Works great.
###### tinyPNG.com ######
my #file = "$img_target_dir$imgname";
print "Sending the PNG for compression at tinyPNG.com\n";
my $curl = `/usr/local/bin/curl -ki --user api:**************** --data-binary #"#file" https://api.tinypng.com/shrink`;
$curl=~ /Location: (.*)/;
my $url = "$1";
print "Image Compressed At: $url</b>\n";
my $curl2 = `/usr/local/bin/curl -k "$url" > "#file"`;
chmod(0775, "#file");
#########################

Posting Gzipped data with curl

im trying to use the system curl to post gzipped data to a server but i keep ending up with strange errors
`curl -sS -X POST -H "Content-Type: application/gzip" --data-binary $data $url`
gives
curl: no URL specified!
and
`curl -sS -X POST -H "Content-Type: application/gzip" --data-binary "$data" $url`
gives
sh: -c: line 0: unexpected EOF while looking for matching `"'
sh: -c: line 1: syntax error: unexpected end of file
Adding the " is a step in the right direction, but you didn't consider that $data might contains ", $, etc. You could use String::ShellQuote to address the issue.
use String::ShellQuote qw( shell_quote );
my $cmd = shell_quote(
curl => (
'-sS',
'-X' => 'POST',
'-H' => 'Content-Type: application/gzip',
'--data-binary' => $data,
$url,
),
);
my $output = `$cmd`;
Or you could avoid the shell entirely.
my #cmd = (
curl => (
'-sS',
'-X' => 'POST',
'-H' => 'Content-Type: application/gzip',
'--data-binary' => $data,
$url,
),
);
open(my $pipe, '-|', #cmd) or die $!;
my $output = do { local $/; <$pipe> };
close($pipe);
Or if you didn't actually need to capture the output, the following also avoids the shell entirely:
system(
curl => (
'-sS',
'-X' => 'POST',
'-H' => 'Content-Type: application/gzip',
'--data-binary' => $data,
$url,
),
);
That said, I don't see how you can possibly send strings containing NUL bytes, something a gzipped file is likely to have. I think your approach is inherently flawed.
Do you know that libcurl (the guts of curl) can be accessed via Net::Curl::Easy?
I did not succeed in getting curl to read the data straight from stdin, but process substitution did work, for example:
curl -sS -X POST -H "Content-Type: application/gzip" --data-binary #<(echo "Uncompressed data" | gzip) $url
This technique removes any need to having to write to a temporary file first.
This is because your binary data contains all kind of trash, including quotes and null bytes, which confuse the shell. Try putting your data into some file and post that file.