How to prevent serialisation in one route in a perl dancer app? - perl

I 've got a perl dancer app (providing a rest api) which works fine with JSON (de-)serialization.
Now I'm need one additional special route, that provides a (dynamically created) csv file for download.
Here is the sample code:
#!/usr/bin/env perl
use Dancer2;
set serializer => 'JSON';
get '/normal' => sub {
{ 'I say ' => 'the json serializer works' };
};
get '/download' => sub {
content_type 'text/csv';
return generateCsv();
};
sub generateCsv {
return '
1,2,3
4,5,6
';
}
dance;
The response sent to the client has no body, only a http-header (with the correct content-type)
$> curl -I http://localhost:3000/download
HTTP/1.0 200 OK
Date: Fri, 23 Mar 2018 10:10:14 GMT
Server: Perl Dancer2 0.205002
Server: Perl Dancer2 0.205002
Content-Length: 0
Content-Type: text/csv
The dancer serializer is not happy with this:
Failed to serialize content: hash- or arrayref expected
(not a simple scalar, use allow_nonref to allow this)
at /usr/local/share/perl/5.22.1/Dancer2/Serializer/JSON.pm line 40.
in /usr/local/share/perl/5.22.1/Dancer2/Core/Response.pm
I can't find anything about the allow_nonref thing in the Dancer docs or in the source code.
Has anybody a hint for me?

Use send_as:
get '/download' => sub {
send_as Mutable => generateCsv();
};

I've found that send_file also works:
get '/download' => sub {
send_file (\&generateCsv(), content_type => 'text/csv', filename => 'articleEbayStatus.csv');
};

Related

Using KeyForge API with Perl

I'm trying to call the KeyForge API with a simple Perl program but it doesn't work. I'm using what's in the LWP::UserAgent documentation:
use strict;
use warnings;
use LWP::UserAgent ();
my $ua = LWP::UserAgent->new;
my $response = $ua->get('https://www.keyforgegame.com/api/decks/');
if ($response->is_success) {
print $response->decoded_content;
}
else {
die $response->status_line;
}
The program prints:
500 write failed: at test.pl line 16.
If I use the URL https://www.google.com or http://www.example.com, it works. The HTML is correctly displayed.
If I use this simple PowerShell program, it works too:
$Url = "https://www.keyforgegame.com/api/decks/"
$decks = Invoke-RestMethod ($url)
$decks
It displays:
count data
743719 {#{name=Dr. "The Old" Jeffries; expansion=341; power_level=0; chains=0; wins=0; losses=0; id=ec86db52-e41e-4e...
What am I missing?
PS: I'm using Perl 5.16.3 on Windows 10.
EDIT:
Thank you all for your help. I finally found out what was happening. It turns out I had a very old version of Net::HTTP (from 2013). I upgraded it and now it works out of the box, without configuring agent, cookies or e-mail. The error message I had was actually from the client and not from the server.
$ perl -MLWP::UserAgent -e'
my $ua = LWP::UserAgent->new();
my $response = $ua->get("https://www.keyforgegame.com/api/decks/");
print $response->as_string;
'
HTTP/1.1 403 Forbidden
...
Content-Type: text/html; charset=UTF-8
...
<!DOCTYPE html>
...
<title>Access denied | www.keyforgegame.com used Cloudflare to restrict access</title>
...
<h2 data-translate="what_happened">What happened?</h2>
<p>The owner of this website (www.keyforgegame.com) has banned your access based on your browser's signature (4bfe0c0e2e86ab84-ua22).</p>
...
But,
$ perl -MLWP::UserAgent -e'
use version; our $VERSION = qv("v1.0.0");
my $ua = LWP::UserAgent->new(
agent => "NameOfTool/$VERSION",
from => q{me#example.com},
);
my $response = $ua->get("https://www.keyforgegame.com/api/decks/");
print $response->as_string;
'
HTTP/1.1 200 OK
...
Content-Type: application/json
...
{"count":...
If they want to block you, they can. So it's your best interest to provide a unique application name, a proper version and a valid email address (even if providing junk for the agent and leaving out from field works). This gives them more options to resolve any issues they have with your program.

Perl CGI script print (utf-8 encoded - japanese html) over http (apache httpd) getting truncated

Environment settings
OS : RHEL 6.6 (kernel 2.6.32) - x86_64
httpd : httpd-2.2.15-39
perl : 5.10.1-136
CGI API :
perl-CGI-3.15-136
perl-CGI-Session-4.35-6
I am using a static html page with Perl-CGI defined variables in the static html. This html is read in through perl, and then passed to a perl CGI script for eval.
Note:
While reading the static html, I am using UTF-8 encoding like
open( IN ,"<:encoding(UTF-8)", $file_path )
After reading the status HTML page, the output is passed back to the CGI script through a variable and then pressed in to eval to evaluate the variables.
Finally, the eval(uated) output from CGI is print which can be read through http daemon.
In the CGI script I am using
binmode(STDIN, ':encoding(UTF-8)');
binmode(STDOUT, ':encoding(UTF-8)');
The static HTML looks something like this
When I check the output of print in the CGI script, I see the complete output as desired, like this
However on the Browser, the hidden input fields are getting truncated in an unwanted manner. Like this
When I checked the wireshark output for the text/html, which is being printed back from the server to the browser, this is also getting truncated.
Like this
The HTML header has proper Content-Type and charset declaration.
The same code is working fine with EN language
The same code is working fine with zh(chinese) language as well.
When the language is set to japanese in the browser, and we read from HTTP_ACCEPT_LANGUAGE that it is 'ja', than we print the japanese specific data.
We are not using cgid module of apache.
Are we supposed to use some special encoding for japanese language??
Or it is a double encoding issue. I have tried removing the encoding when I am reading the static html file, however, that also did not help.
The same code is working fine with RHEL 5.x (2.6.18-308), and perl-CGI-Session (4.42-2), perl (5.8.8-38) httpd (2.2.3-63), there was no perl-CGI in RHEL-5.x.
I kind of found the solution to the problem. In our code we used to add a data in the CGI::Session object, which had the language. In the following form
$session->param( KEY_SESSION_LANG, $code ).
Where $session is a CGI::Session object and KEY_SESSION_LANG is 'language' and $code is something that we get from HTTP_ACCEPT_LANGUAGE. When we see 'en' we used to set it as perl constant 'en', when we got 'ja' we used to set it as perl constant 'ja'.
When we used to form the session object we used to get a session file as (perl version RHEL 5.x (2.6.18-308) & perl-CGI-Session-4.42-2 ) for JA language we used to get the following in the cgi file
cat cgisess_e31c8d21af82b59fd064babc7ca25c01
$D = {'_SESSION_ID' => 'e31c8d21af82b59fd064babc7ca25c01','_SESSION_ETIME' => 6000,'language' => 'ja','permit' => 'yes','_SESSION_REMOTE_ADDR' => '192.168.101.1','_SESSION_CTIME' => 1441090386,'execute' => 'yes','_SESSION_ATIME' => 1441090387,'_SESSION_EXPIRE_LIST' => {}};*a = \undef;;$D
For perl CGI Session in RHEL 6.6 this is coming out to be
cat cgisess_e31c8d21af82b59fd064babc7ca25c01
$D = {'_SESSION_ID' => 'e31c8d21af82b59fd064babc7ca25c01','_SESSION_ETIME' => 6000,'language' => *a,'permit' => 'yes','_SESSION_REMOTE_ADDR' => '192.168.101.1','_SESSION_CTIME' => 1441090386,'execute' => 'yes','_SESSION_ATIME' => 1441090387,'_SESSION_EXPIRE_LIST' => {}};*a = \undef;;$D
language data for ja is becoming *a. The same is also reflected when we use perl dumper for getting in memory data.
I checked the /usr/share/perl5/vendor_perl/CGI/Session.pm and it had following information in it
=head1 A Warning about UTF8
Trying to use UTF8 in a program which uses CGI::Session has lead to problems. See RT#21981 and RT#28516.
In the first case the user tried "use encoding 'utf8';" in the program, and in the second case the user tried "$dbh->do(qq|set names 'utf8'|);".
Until this problem is understood and corrected, users are advised to avoid UTF8 in conjunction with CGI::Session.
For details, see: http://rt.cpan.org/Public/Bug/Display.html?id=28516 (and ...id=21981).
=head1 TRANSLATIONS
This document is also available in Japanese.
Now when I used the perl dumper following things happened. I quote below from my offical analysis presented on our local development portal
I think the problem is because of perl-CGI-Session OSS package, please see the analysis below.
Some inputs from the CGI session source code.
## Inputs ##
From the file /usr/share/perl5/vendor_perl/CGI/Session.pm
## Following are the status of CGI session, set internally after modification to any of the parameters ##
sub STATUS_NEW () { 1 } # denotes session that's just created
sub STATUS_MODIFIED () { 2 } # denotes session that needs synchronization
sub STATUS_DELETED () { 4 } # denotes session that needs deletion
sub STATUS_EXPIRED () { 8 } # denotes session that was expired.
--snip --
I::Session - persistent session data in CGI applications
=head1 SYNOPSIS
# Object initialization:
use CGI::Session;
$session = new CGI::Session();
$CGISESSID = $session->id();
We are setting the "language" parameter in the session object. (We create a CGI object, set cookie to it, to get sid, and through sid get the session object). For setting up the language parameter we do
$session->param( 'language', ); ---> language_value = en(english) or ja(japanese). When we have completed the printing of the HTML page in /opt/packageManager/pm_gui/cgi/status.cgi file, I checked the cgi session object it is as follows
For EN Language
Before executing session flush
[09/10/2015 16:13:41] [23722] <ERROR> status.cgi : 267 :
$VAR1 = bless( {
'_STATUS' => 2,
'_DATA' => {
'_SESSION_ETIME' => 6000,
'_SESSION_ID' => '995d11334f2c39b95b3fdb86cecd9655',
'permit' => 'yes',
'language' => 'en',
Then after this when I flush the session as $session->flush() and check the session object it is
[09/10/2015 16:13:41] [23722] <ERROR> status.cgi : 270 :
$VAR1 = bless( {
'_STATUS' => 0,
'_DATA' => {
'_SESSION_ETIME' => 6000,
'_SESSION_ID' => '995d11334f2c39b95b3fdb86cecd9655',
'permit' => 'yes',
'language' => 'en',
Inference 1: session status changed after doing flush. This is good, and should be done.
For JP Language
Before executing session flush
[09/10/2015 16:14:54] [31910] status.cgi : 267 :
$VAR1 = bless( {
'_STATUS' => 2,
'_DATA' => {
'_SESSION_ID' => '1cd1b7860af4c71264f3969fe74e7a44',
'_SESSION_ETIME' => 6000,
'language' => *a
Then after this, when I flush the session as $session->flush() and check the session object it is NOT THERE. SCRIPT CRASHES HERE IT SELF
Inference 2 : Doing flush with language JP, is terminating the session, and that is why the session gets destroyed. And that is why, ending data in response is truncated
Due to the wrong value being set in memory, in session object, and then the implicit flush by CGI session is failing on the disk. Which results in termination of the session object, and in between termination of session, and and data loss of HTML.
I checked the actual code in sessions.pm file and it seems to be coming in from here
sub param {
my ($self, #args) = #_;
--snip--
# USAGE: $s->param($name, $value);
# USAGE: $s->param($name1 => $value1, $name2 => $value2 [,...]);
# DESC: updates one or more **public** records using simple syntax
if ((#args % 2) == 0) {
my $modified_cnt = 0;
ARG_PAIR:
while (my ($name, $val) = each %args) {
if ( $name =~ m/^_SESSION_/) {
carp "param(): attempt to write to private parameter";
next ARG_PAIR;
}
$self->{_DATA}->{ $name } = $val; ----> HERE
++$modified_cnt;
}
$self->_set_status(STATUS_MODIFIED);
return $modified_cnt;
}
As a solution, we stopped putting 'ja' value as a perl constant, but now are putting it as a string "ja" and it seems to be working fine now.

Can't send HTTP response as xml using mojolicious

Trying to learn Mojolicious here. For the following request, I get 404 when I try to get to
http://hostname:3000/xml
Here is the simple script:
use Mojolicious::Lite;
use Data::Dumper;
get '/xml' => sub {
my $self = shift;
$self->render(xml => "<employees>
<employee>
<id>1001</id>
<name>John Smith</name>
</employee>
<employee>
<id>1002</id>
<name>Jane Dole</name>
</employee>
</employees>"
);
};
app->start;
This script was adopted from an example for json, which works fine. Not sure why xml doesn't work.
Just need to specify a format
get '/xml' => sub {
my $self = shift;
my $xml = <<'XML';
<employees>
<employee><id>1001</id><name>John Smith</name></employee>
<employee><id>1002</id><name>Jane Dole</name></employee>
</employees>
XML
$self->render(data => $xml, format => 'xml');
};
Response header equals the following:
Connection: keep-alive
Server: Mojolicious (Perl)
Content-Length: 140
Content-Type: application/xml
Date: Wed, 09 Apr 2014 05:36:05 GMT
200 OK
Could also place the data in a template, of course:
get '/xml' => sub {
my $self = shift;
$self->render('employees', format => 'xml');
};
app->start;
__DATA__
## employees.xml.ep
<employees>
<employee><id>1001</id><name>John Smith</name></employee>
<employee><id>1002</id><name>Jane Dole</name></employee>
</employees>

base64-Encoding breaks smime-encrypted emaildata

I'm using Mime::Lite to create and send E-Mails. Now I need to add support for S/Mime-encryption and finally could encrypt my E-Mail (the only Perllib I could install seems broken, so I'm using a systemcall and openssl smime), but when I try to create a mime-object with it, the E-Mail will be broken as soon as I set the Content-Transfer-Encoding to base64. To make it even more curious, it happens only if I set it via $myMessage->attr. If I'm using the constructor ->new everything is fine, besides a little warning which I suppress by using MIME::Lite->quiet(1);
Is it a bug or my fault? Here are the two ways how I create the mime-object.
Setting the Content-Transfer-Encoding via construtor and suppress the warning:
MIME::Lite->quiet(1);
my $msgEncr = MIME::Lite->new(From =>'me#myhost.com',
To => 'you#yourhost.com',
Subject => 'SMIME Test',
Data => $myEncryptedMessage,
'Content-Transfer-Encoding' => 'base64');
$msgEncr->attr('Content-Disposition' => 'attachment');
$msgEncr->attr('Content-Disposition.filename' => 'smime.p7m');
$msgEncr->attr('Content-Type' => 'application/x-pkcs7-mime');
$msgEncr->attr('Content-Type.smime-type' => 'enveloped-data');
$msgEncr->attr('Content-Type.name' => 'smime.p7m');
$msgEncr->send;
MIME::Lite->quiet(0);
Setting the Content-Transfer-Encoding via $myMessage->attr which breaks the encrypted Data, but won't cause a warning:
my $msgEncr = MIME::Lite->new(From => 'me#myhost.com',
To => 'you#yourhost.com',
Subject => 'SMIME Test',
Data => $myEncryptedMessage);
$msgEncr->attr('Content-Disposition' => 'attachment');
$msgEncr->attr('Content-Disposition.filename' => 'smime.p7m');
$msgEncr->attr('Content-Type' => 'application/x-pkcs7-mime');
$msgEncr->attr('Content-Type.smime-type' => 'enveloped-data');
$msgEncr->attr('Content-Type.name' => 'smime.p7m');
$msgEncr->attr('Content-Transfer-Encoding' => 'base64');
$msgEncr->send;
I just don't get why my message is broken when I'm using the attribute-setter. Thanks in advance for your help!
Besides that i'm unable to attach any file to this E-Mail without breaking the encrypted message again.
To debug this
Make a script call showmail.pl
#!/usr/bin/perl
while (<STDIN>) { print $_; }
Test it like
use MIME::Lite;
use Net::SMTP;
use MIME::Base64;
$myEncryptedMessage = encode_base64("This is not valid encrypted message\n");
MIME::Lite->send('sendmail', "./showmail.pl"); ## Add this for debugging.
MIME::Lite->quiet(1); my $msgEncr = MIME::Lite->new(From =>'me#localhost',
To => 'you#localhost',
Subject => 'SMIME Test',
Data => $myEncryptedMessage,
'Content-Transfer-Encoding' => 'base64');
$msgEncr->attr('Content-Disposition' => 'attachment');
$msgEncr->attr('Content-Disposition.filename' => 'smime.p7m');
$msgEncr->attr('Content-Type' => 'application/x-pkcs7-mime');
$msgEncr->attr('Content-Type.smime-type' => 'enveloped-data');
$msgEncr->attr('Content-Type.name' => 'smime.p7m');
$msgEncr->send();
you should see something like.
MIME-Version: 1.0
Content-Disposition: attachment; filename="smime.p7m"
Content-Length: 49
Content-Type: application/x-pkcs7-mime; name="smime.p7m"; smime-type="enveloped-data"
X-Mailer: MIME::Lite 3.028 (F2.74; B3.07; Q3.07)
Date: Mon, 23 Mar 2012 10:40:51 -0400
From: me#localhost
To: you#localhost
Subject: SMIME Test
Content-Transfer-Encoding: base64
VGhpcyBpcyBub3QgdmFsaWQgZW5jcnlwdGVkIG1lc3NhZ2UK
The message is encoded base64, but the real message still needs to be correctly
encypted. You need to make sure that is the case since $myEncryptedMessage is
passed in. With the debug output, you can compare with a known good encrypted mail
and see if the headers are good, as far as I can see the headers are fine, it is probably
the data that is not valid.
I am not able to test this with a real mail client, but this is what I think may work for multi-parts.
use MIME::Lite;
use Net::SMTP;
use MIME::Base64;
MIME::Lite->send('sendmail', "./showmail.pl"); ## <---- for testing only
my $from_address = "nobody#localhost";
my $to_address = "somebody#localhost";
my $mail_host = "localhost";
my $subject = "Subject list";
my $message_body = "Attachment list";
my #files = ("crypt.data1","crypt.data2");
$msg = MIME::Lite->new (
From => $from_address,
To => $to_address,
Subject => $subject,
Type =>'multipart/mixed'
) or die "Error creating multipart container: $!\n";
foreach $c(#files) {
$msg->attach (
Disposition => 'attachment',
Type => "application/x-pkcs7-mime; name=smime.p7m; smime-type=enveloped-data",
Path => $c,
) or die "Error adding $c: $!\n";
}
$msg->send;
As I said in one comment the difference in setting the encoding in the construtor of the mimeobject or with the ->attr-Setter is, that the construtor just sets the encoding in the mimeheader. By using the ->attr-Setter mime encodes the data with base64.
So in my case, my previously generated mimeobject - which is base64-encoded and with s/mime encrypted - read from a file needs to set the encoding in the construtor (and suppress the warning) so no more encoding will be done by mime. Otherwise mime will encode the data again and therefore break the encryption and the email itself.
I finally got attachments to work. To achieve this I create a normal multipart/mixed mimeobject, print this object into a normal file, encrypt this file with openssl smime, read this whole file (except the 6 headerlines) into a variable and use this as the datainput. Additionally I set the Content-Transfer-Encoding to base64 using the construtor (so no encoding is done to my data).
I hope this will help someone else then me ;)
Replace $myEncryptedMessage with encode_base64($myEncryptedMessage)
and use MIME::Base64;

How do I access the HTTP Header of request in a CGI script?

I've used Perl a bit for small applications and test code, but I'm new to networking and CGI.
I get how to make the header of a request (using CGI.pm and printing the results of the header() function), but haven't been able to find any info on how to access the headers being sent to my CGI script. Could someone point me in the right direction?
This could be from a request like this:
curl http://127.0.0.1:80/cgi-bin/headers.cgi -H "HeaderAttribute: value"
The CGI module has a http() function you can use to that purpose:
#!/usr/bin/perl --
use strict;
use warnings;
use CGI;
my $q = CGI->new;
my %headers = map { $_ => $q->http($_) } $q->http();
print $q->header('text/plain');
print "Got the following headers:\n";
for my $header ( keys %headers ) {
print "$header: $headers{$header}\n";
}
Try it out; the above gives me:
$ curl http://localhost/test.cgi -H "HeaderAttribute: value"
Got the following headers:
HTTP_HEADERATTRIBUTE: value
HTTP_ACCEPT: */*
HTTP_HOST: localhost
HTTP_USER_AGENT: curl/7.21.0 (i686-pc-linux-gnu) libcurl/7.21.0 OpenSSL/0.9.8o zlib/1.2.3.4 libidn/1.18
In addition to the CGI.pm http() method you can get HTTP headers information from the environment variables.
So in case you are using something like CGI::Minimal, which doesn't have the http method. you can do something like:
my $header = 'HTTP_X_REQUESTED_WITH';
if (exists $ENV{$header} && lc $ENV{$header} eq 'xmlhttprequest') {
_do_some_ajaxian_stuff();
}
They're supplied as environment variables, such as
HTTP_HEADERATTRIBUTE=value
You may have to do something to configure your web server to supply such a variable, though.