How can I read multiple cookies with Perl's CGI.pm? - perl

I am using CGI.pm to write out cookies. Now during the course of the user using my site, other cookies are added to the "test.com" cookie set, (as shown in the broswer history)
But now I want to log the user out, and "clean" the PC. Since I don't know what scripts the user has used, I can't foresee what cookies would be on the PC.
In short, it there a way to read all the cookies for "test.com" back into a script so I can then print them out again with a 1s duration, (effectively 'deleting' them) ** I know you can read the cookie back in with $xyz=cookie('$name') ... but how can I create the array holding the $name variable so I can loop through it? The script will also run on "test.com", so the cross site policy is not an issue
+++++
brian d foy added a partial answer below. So this how I envisage the code might be strung together.
use CGI::Cookie;
%cookies = CGI::Cookie->fetch;
for (keys %cookies) {
$del_cookie.="cookie(-NAME=>'$cookies[$_]',-PATH=>'/',-EXPIRES=>'+1s');";
}
print header(-cookie=>[$del_cookie]);
I wondered how the script would recognise the domain. Appears the script is intelligent enough to only load the cookies for the domain for which the script is being executed on. (Now I've just got to find out why Firefox doesn't delete expired cookies!! Just found some listed that expired 29th - 31st Jan within my test domain, and at first wondered why they didn't appear in my cookie list!)

If you are trying to do this from your CGI script, you'll only have access to the cookies for that domain. You can get that list and reset them by giving them a time in the past.
It sounds like you aren't asking a cookie question at all. You're asking how to make an array. The CGI::Cookies (which comes with CGI.pm) has an example to deal with all the cookies you have access to under that domain:
%cookies = CGI::Cookie->fetch;
for (keys %cookies) {
do_something($cookies{$_});
}

This is what I ended up with:
use CGI::Cookies;
%cookies = CGI::Cookie->fetch;
#cookie = keys %cookies;
for($x=0; $x<#cookie; $x++){
my $c = CGI::Cookie->new(-name => $cookie[$x],-value => '-',-expires => '+1s');
print "Set-Cookie: $c\n";
}
print "content-type: text/html\n\n";
Firefox still leaves the cookies intact, (apparently that's a "design issue" and not a bug!!) but they are reset to a void value, and set to expire / become redundant in 1 second. Plus, quite why the "print" statement being sent before the "content-type" header doesn't cause a server error I don't know. OK, so purists will probably find a simpler system, and use "foreach" rather than for/next loop ... but I understand how the latter works!

Related

Perl: how can i read a specific cookie value stored by Firefox

I'm currently developing a script in Perl as want to recover a specific cookie that Facebook is currently storing in my browser.
The cookie name is Datr and i've tried multiple Perl modules such as HTTP::Cookies, CGI::Cookie and so forth, without success.
What i wanna do is simple and i need to do it via Perl: storing the Datr value (which changes dynamically) into a new variable in my Perl script.
I decided to set a test cookie and try to read its value, but neither the script returns something nor the browser (Mozilla) seems to store my cookie.
Here is the code i used:
#!bin/perl
use CGI;
$query = new CGI;
##setting a new cookie into the browser
$cookie = $query->cookie(-name=>'MY_COOKIE',
-value=>'HelloWorld',
-domain=>'facebook.com',
-expires=>'+4h');
##retrieving cookie value
$theCookie = $query->cookie('MY_COOKIE');
Please help me with this as i'm going crazy!
thanks
First of all i would like to thanks all of you for the prompt reply.
To Dave: thanks for your very good answer on this. Yes i'm aware that Firefox can read the Datr value via GUI, but actually i need to read it with Perl code, because my original script features a specific SSL request which actually needs the Datr value. Facebook has arealdy answered on Datr ''understanding'' (http://www.adweek.com/digital/datr-cookie-belgium/) and seems an interesting side-topic. Getting back to my original request, i'm quite sure that Firefox stores all cookies value locally in some sort of .sqlite db called cookie.sqlite, and i can get there! If a try to read it ''manually'', i can see the Datr string, but that's not going to fix anything as i do not want to update my PL script each and every time i want to perform any sort of SSL request to FB!! that's the point.
My original question could be reformulated as follows: is there any way to query the .sqlite cookie db created by Firefox in order to retreive the Datr cookie and store it in a Perl variable? Please bear in mind that i don't want to set an absolute ''path to file'' in my PL script, as it is supposed to be executed either in Linux or any other OS (Windows, OSx..).
Thanks in advace for any further reply on this subject.
There are two steps to reading a cookie.
Accessing the value of the cookie.
Understanding that value.
Modules like the ones you mention, only deal with the first step. They allow you to write cookie headers into your web application's responses and read cookie headers from requests that come into your web application. But they will only read or write cookies for the domain that your web application is running on. It's very unlikely that your application is running on facebook.com, therefore these modules are going to be useless to you.
However, all is not lost. Firefox will give you access to any cookies that are stored in the browser. I assume you already know that (as you know the name of the cookie you're interested in) but in case you don't - choose "preferences" from the hamburger menu and then "privacy"; that page has a "remove individual cookies" link.
So you can see the contents of the datr cookie. I'm looking at mine right now. It's string of 24 random-looking characters.
And that's the next problem. How do you interpret that string? Only Facebook can answer that. It's possible that it is a hash containing all sorts of interesting data. But it will be almost impossible to prove that. More likely (because this is, I think, best practice) it's just a random string of characters which is a key into some data store that is held somewhere within Facebook's system.
So it's either a well-encrypted secret or a random string. Either way it's useless to you.
Getting the value of a cookie is easy. Understanding that value is (usually) impossible.
Update: So actually, now you've redefined the question completely. It's not about cookies at all. It's about reading data from an SQLite database. And for that you should look at DBI and DBD::SQLite. If you have any more specific questions about how to do this, then please ask a new question.
This is what i've figured out. It works well. I didn't use absolute path (at least, directly) therefore, in theory, it should work also in Windows (having Perl on it) and Mac OS, but i didn't test it. It simply search the .sqlite db into the system and once found it returns the value of Datr stored by Firefox.
#!/bin/perl
use File::Find::Rule;
use DBI;
use strict;
my #files = File::Find::Rule->file()
->name('firefox', 'cookies.sqlite')
->in( '/home' );
my #matches = grep { /firefox/i && /mozilla/i } #files;
print "DB PATH: #matches\n\n";
####### connect to .sqlite database
my $database = #matches[0];
my $dbfile = "$database";
my $dsn = "dbi:SQLite:dbname=$dbfile"; #set path to db
my $user = "";
my $password = "";
my $dbh = DBI->connect($dsn, $user, $password, {
PrintError => 0,
RaiseError => 1,
});
print "Database opened successfully\n\n";
my $stmt = "SELECT VALUE FROM moz_cookies WHERE NAME='datr'";
my $sth = $dbh->prepare( $stmt );
my $rv = $sth->execute() or die $DBI::errstr;
if($rv < 0) {
print $DBI::errstr;
}
my #row=$sth->fetchrow_array(); #fetching data of sth into a new array
print "COOKIE VALUE: #row"; #print data
print "\n\nOperation done successfully\n";

How to delete a session in cgi-perl?

I have a perl-cgi script through which I am trying to log in.
When the UserName and password are valid, I create a session and redirect a cookie to another page.
However, after the session expires(I have set the expiration time), I do not see it get deleted from the /tmp/sessions folder in this case. I have used the command to delete the session as well.
Can someone help me to delete the session once it expires? Also, does the cookie expire once the session is deleted?
use CGI::Session;
use CGI::Session::Tutorial;
use CGI::Session::Driver::file;
use CGI::Cookie;
my $session = new CGI::Session("driver:File", undef, {Directory=>"/tmp/sessions"});
my $sid = $session->id();
#my $cookie = $query->cookie(CGISESSID => $session->id);
my $cookie = $query->cookie(-name=>"CGISESSID",
-value=>$session->id,
-domain=>'abc.com',
-expires=>"+5m",
-path=>"/");
print $query->redirect(-uri => 'http://abc.cgi', -cookie => $cookie);
$session->param("UserName", $loginUserName);
$query->hidden( 'UserName', $loginUserName );
$session->expire("UserName",'1m');
$session->expire('+5m');
$session->delete();
To avoid confusion with ->delete, I'm going to use the word "remove" instead of "delete" to refer to the removal of the session from storage.
Can someone help me to delete the session once it expires?
The removal doesn't happen when the session expires. That would require having a continually running process. Furthermore, at no point does CGI::Session scan storage for expired sessions; that would take too long since it would require loading each and every session. Instead, CGI::Session only removes expired sessions when you try to load them.
#!/usr/bin/perl
use strict;
use warnings qw( all );
use feature qw( say );
use CGI::Session qw( );
use CGI::Session::Driver::file qw( );
my $session_id; # This represents the browser's cookie.
# These represent requests made the by the browser.
for my $request_num (1..3) {
my $session = CGI::Session->new("driver:file", $session_id, { Directory => "/tmp/sessions" });
$session->expire("1s");
$session_id = $session->id; # This represents setting the browser's cookie.
say "$request_num: ", $session->id;
say "$request_num: ", $session->param("foo") // "[undef]";
$session->param("foo" => "bar");
# This represents time passing by before the third request.
if ($request_num == 2) {
say "Letting session expire...";
sleep(2);
}
}
Output:
$ ./a
1: c57ab28952c6ed422c15f1a223f4b45d
1: [undef]
2: c57ab28952c6ed422c15f1a223f4b45d
2: bar
Letting session expire...
3: df8ba3b66f23a9a2a652520fa6b4c30b
3: [undef]
$ ls -1 /tmp/sessions
cgisess_df8ba3b66f23a9a2a652520fa6b4c30b
If you want to prevent files from accumulating on your drive, create a cron job that deletes old files.
find /tmp/sessions -mindepth 1 -maxdepth 1 -mtime 7 -delete
Also, does the cookie expire once the session is deleted?
No, the cookie expires when you tell it to expire. The thing is, it doesn't matter if the browser's cookie expires or not. For the second argument of new, there's no difference between passing undef, passing the id of a deleted session and passing the id of an expired session; you'll get a new session in all three cases. If anything, it's actually better if it doesn't expire as soon as the session expires because this allows the session to be removed (as demonstrated above).
How to delete a session in cgi-perl?
$session->delete is indeed the way to go, but the actual removal only happens when you would save (flush) the session.
$session->delete();
$session->flush(); # Or let `$session` get destroyed.
As the documentation notes
delete()
Sets the objects status to be "deleted". Subsequent read/write requests on the same object will fail. To physically delete it from the data store you need to call flush(). CGI::Session attempts to do this automatically when the object is being destroyed (usually as the script exits), but see "A Warning about Auto-flushing". (emphases mine)
You go on to ask:
Also, does the cookie expire once the session is deleted?
Of course not. You already sent a cookie to the user's browser with an expiration time of five minutes in the future. The cookie will expire then.
If, in the mean time, you have forced the expiration of the session on the server, the user's browser will still send the previously received cookie. Your application will just not find a session corresponding to the session identifier stored in the cookie.
You really need to understand the HTTP request/response cycle before taking one more step.
Per the CGI::Session documentation, deleteing a session "Sets the objects status to be "deleted". Subsequent read/write requests on the same object will fail. To physically delete it from the data store you need to call flush()." (emphasis mine)
Also, per the CGI::Session::Tutorial, "Expiring a session is the same as deleting it via delete(), but deletion takes place automatically." It is not necessary (or useful) to delete a session after it has expired.

Logging of ip address, transaction id or session id in Mojo::Log

I make use of simple logging in mojolicious application. I want to extend logging by some information. This could be ip address or transaction id or session id.
What I do before is writing for each log level one helper like this:
$self->helper( 'info' => sub {
my $self=shift;
my $msg=shift;
my $ip=$self->tx->remote_address;
$self->app->log->info("[$ip] $msg");
});
...
$self->info("Login failed of user $user.");
I would like to modify format of logging output so I can make use of generic log function which will add any additionally values I need and without lot of helpers for each log level.
Basic call of:
$self->app->log->info("Login failed of user $user.");
should also give log entries like
[Sun Jun 8 11:09:12 2014] [info] [127.0.0.1] Login failed of user Tim.
I try do do it by change log format but anything I do is ignored.
$self->app->log->format(sub {
my ($time, $level, #lines) = #_;
return "[$time] [$level] [$self->tx->remote_address] #lines.\n";
});
I know there is Log4Perl in combination with Mojolicious. But I want to keep it simple as possible.
I got this going pretty quick with using Mojolicious::Lite;
To start off, shift a log to someplace you can find quickly.
use Mojo::Log;
my $log = Mojo::Log->new(path => '~/log/mojo.log');
Then try this, set the remote address variable outside of the sub first.
# $r_ip = remote ip address
my $r_ip = $self->tx->remote_address;
$self->app->log->format(sub {
my ($time, $level, #lines) = #_;
return "[" . localtime(time) . "] [$level] [$r_ip] . join("\n", #lines) . "\n";
});
The format can been seen at:
http://mojolicio.us/perldoc/Mojo/Log
There is a possible to log a unique id per request/response? thread on the Mojolicious mailing list, where Sebastian/sri - the author of Mojolicious - answers:
Simple answer: You can't, Mojolicious is async by design and there may
be thousands of concurrent requests active at any given time.
Complex answer: If you limit your application (through the server) to
handling only one request at a time and don't use any real-time web
features (not that it would make much sense if you can't handle
concurrent requests), you could subclass Mojo::Log to customize the
message format and store a global unique id for every new request.
The other (currently accepted) answer does exactly that: It removes all concurrency and uses a global variable. That'll start breaking down when you start using the real-time Mojolicious features.

How can I see all active sessions in a Mojolicious Lite app?

I'm building an app with Mojolicious Lite and I'm looking to give myself a way to watch any and all data about the active sessions. I'm mostly doing this because this is my first foray into using sessions with Mojolicious Lite, and I want to watch what's going on under the hood.
A couple notes:
I'm pretty new to Mojolicious, as you might guess by the fact that I'm using Mojolicious Lite. Any Mojolicious Lite apps I've written before have been pretty trivial, so my familiarity with it is not deep.
For that matter, I'm still 'early intermediate, at best' with perl, so poking around the inner workings of anything OO in perl is largely foreign territory for me.
That said, I made myself a few little routes like so:
get '/firstpage' => sub{
my $self = shift;
my $usr = $self->session(user => 'first_user');
$self->render(text => $usr);
};
get '/secondpage' => sub{
my $self = shift;
my $usr = $self->session(user => 'second_user');
$self->render(text => $usr);
};
get '/sessions' => sub{
my $self = shift;
$self->render(text => Dumper(app->sessions));
};
I'm working off the assumption that, after I visit the first two urls, Mojolicious will have 'some' data somewhere that would confirm that it knows about first_user and second_user. (I could also be totally off base in my understanding of how to use Mojolicious Lite sessions...honestly, from the documentation, I'm not really sure.)
Sadly, /sessions just shows me the contents of the Mojolicious::Sessions object:
$VAR1 = bless( {
'cookie_path' => '/',
'secure' => 0,
'cookie_name' => 'mojolicious',
'default_expiration' => 3600
}, 'Mojolicious::Sessions' );
But I'm assuming that, somewhere, I can get to a hash of all of the session-related data that Mojolicious has. I've been poking around the documentation for a while but I have yet to find any leads.
Any insight?
I'm working off the assumption that, after I visit the first two urls, Mojolicious will have 'some' data somewhere that would confirm that it knows about first_user and second_user. (I could also be totally off base in my understand of how to use Mojolicious Lite sessions...honestly, from the documentation, I'm not really sure.)
Yeah, I think you're missing the point of sessions. The server/app doesn't remember the state of every user who visits. To allow it to look as if it did, we have cookies. A session is a per-client persistence thing.
Session information is just a hashreference, encoded as JSON and stored in a cookie on the client side. This is useful for remembering that you are logged in, as what username, perhaps an arrayref of things in your shopping cart. When you request a page, this cookie is sent back to the server, which can access the data and prepare the response for you knowing the state of your session.
As such there is no record of "active sessions". All that information is distributed amongst all the clients.
If you would like to get a better idea of what's going on, may I recommend tempire's plugin Mojolicious::Plugin::ConsoleLogger which for the current request shows all of the relevant information (session, stash etc) in your browser's javascript console.
Here is an example.
#!/usr/bin/env perl
use Mojolicious::Lite;
#plugin 'ConsoleLogger'; # if desired
any '/' => sub {
my $self = shift;
my $name = $self->session('name') || 'Unknown'; # get the name from the session
$self->render( text => "Hello $name" );
};
any '/name/:name' => sub {
my $self = shift;
my $name = $self->stash('name'); # get name from path
$self->session( name => $name ); # and store it in the session
$self->redirect_to('/');
};
any '/logout' => sub {
my $self = shift;
$self->session( expires => 1 );
$self->redirect_to('/');
};
app->start;
If you visit /name/ghorahn it will store your name in a cookie. From then on, every time you visit / it will say hello to you until:
Your session expires (default 1 hour from your last visit)
You change your name via /name/whatever
You visit /logout to manually expire the session
You will notice that another user (either on another computer or even a different browser on your same computer) may have a different name, but both are persistent. That is what a session is for. :-)

How do I use and debug WWW::Mechanize?

I am very new to Perl and i am learning on the fly while i try to automate some projects for work. So far its has been a lot of fun.
I am working on generating a report for a customer. I can get this report from a web page i can access.
First i will need to fill a form with my user name, password and choose a server from a drop down list, and log in.
Second i need to click a link for the report section.
Third a need to fill a form to create the report.
Here is what i wrote so far:
my $mech = WWW::Mechanize->new();
my $url = 'http://X.X.X.X/Console/login/login.aspx';
$mech->get( $url );
$mech->submit_form(
form_number => 1,
fields =>{
'ctl00$ctl00$cphVeriCentre$cphLogin$txtUser' => 'someone',
'ctl00$ctl00$cphVeriCentre$cphLogin$txtPW' => '12345',
'ctl00$ctl00$cphVeriCentre$cphLogin$ddlServers' => 'Live',
button => 'Sign-In'
},
);
die unless ($mech->success);
$mech->dump_forms();
I dont understand why, but, after this i look at the what dump outputs and i see the code for the first login page, while i belive i should have reached the next page after my successful login.
Could there be something with a cookie that can effect me and the login attempt?
Anythings else i am doing wrong?
Appreciate you help,
Yaniv
This is several months after the fact, but I resolved the same issue based on a similar questions I asked. See Is it possible to automate postback from the client side? for more info.
I used Python's Mechanize instead or Perl, but the same principle applies.
Summarizing my earlier response:
ASP.NET pages need a hidden parameter called __EVENTTARGET in the form, which won't exist when you use mechanize normally.
When visited by a normal user, there is a __doPostBack('foo') function on these pages that gives the relevant value to __EVENTTARGET via a javascript onclick event on each of the links, but since mechanize doesn't use javascript you'll need to set these values yourself.
The python solution is below, but it shouldn't be too tough to adapt it to perl.
def add_event_target(form, target):
#Creates a new __EVENTTARGET control and adds the value specified
#.NET doesn't generate this in mechanize for some reason -- suspect maybe is
#normally generated by javascript or some useragent thing?
form.new_control('hidden','__EVENTTARGET',attrs = dict(name='__EVENTTARGET'))
form.set_all_readonly(False)
form["__EVENTTARGET"] = target
You can only mechanize stuff that you know. Before you write any more code, I suggest you use a tool like Firebug and inspect what is happening in your browser when you do this manually.
Of course there might be cookies that are used. Or maybe your forgot a hidden form parameter? Only you can tell.
EDIT:
WWW::Mechanize should take care of cookies without any further intervention.
You should always check whether the methods you called were successful. Does the first get() work?
It might be useful to take a look at the server logs to see what is actually requested and what HTTP status code is sent as a response.
If you are on Windows, use Fiddler to see what data is being sent when you perform this process manually, and then use Fiddler to compare it to the data captured when performed by your script.
In my experience, a web debugging proxy like Fiddler is more useful than Firebug when inspecting form posts.
I have found it very helpful to use Wireshark utility when writing web automation with WWW::Mechanize. It will help you in few ways:
Enable you realize whether your HTTP request was successful or not.
See the reason of failure on HTTP level.
Trace the exact data which you pass to the server and see what you receive back.
Just set an HTTP filter for the network traffic and start your Perl script.
The very short gist of aspx pages it that they hold all of the local session information within a couple of variables prefixed by "__" in the general aspxform. Usually this is a top level form and all form elements will be part of it, but I guess that can vary by implementation.
For the particular implementation I was dealing with I needed to worry about 2 of these state variables, specifically:
__VIEWSTATE
__EVENTVALIDATION.
Your goal is to make sure that these variables are submitted into the form you are submitting, since they might be part of that main form aspxform that I mentioned above, and you are probably submitting a different form than that.
When a browser loads up an aspx page a piece of javascript passes this session information along within the asp server/client interaction, but of course we don't have that luxury with perl mechanize, so you will need to manually post these yourself by adding the elements to the current form using mechanize.
In the case that I just solved I basically did this:
my $browser = WWW::Mechanize->new( );
# fetch the login page to get the initial session variables
my $login_page = 'http://www.example.com/login.aspx';
$response = $browser->get( $login_page);
# very short way to find the fields so you can add them to your post
$viewstate = ($browser->find_all_inputs( type => 'hidden', name => '__VIEWSTATE' ))[0]->value;
$validation = ($browser->find_all_inputs( type => 'hidden', name => '__EVENTVALIDATION' ))[0]->value;
# post back the formdata you need along with the session variables
$browser->post( $login_page, [ username => 'user', password => 'password, __VIEWSTATE => $viewstate, __EVENTVALIDATION => $validation ]);
# finally get back the content and make sure it looks right
print $response->content();