How do you get CGI::Session to expire after a certain interval? - perl

It seems like CGI::Session expire() function only expires after a user is idle for a specified interval. I'm assuming by idle they mean a user hasnt refreshed the page or accessed any others.
While I see that you can force the session to expire by using delete(), what I dont see is a way to automatically force the session to expire whether the use has been idle or not.
Maybe this is not a desired user experience, but for sake of understanding is there a way to do this without having to track the time interval or using any additional libraries?
Also what is the point of CGI::Session::is_expired if session returns a new one when it expires anyway? At least it seems I can't get to an expired state with my script
sub build_sss{
my($this) = shift;
$cgi = $this->cgi_obj();
my $sid = $this->sid();
my $sss = CGI::Session->load(undef, $cgi, {Directory=>'tmp'}) or die CGI::Session->errstr();
#check expired session
if ( $sss->is_expired() ) {
my $expired_url = $ENV{'SCRIPT_URI'}.'?expired='.$sid;
$this->session_status("SESSION_EXPIRED");
print $cgi->redirect($expired_url); #when expired param is set shows a msg
}
#check if empty create new
if ( $sss->is_empty() ) {
$sss = $sss->new() or $sss->errstr;
$this->session_status("SESSION_NEW");
$sss->expire('30s');
$sss->expire(_TEST_SUB_SESSION => '15s');
}
return $sss;
}

update: so yeah, if you want a session to expire based on creation time, you have to subclass CGI::Session
1) make sure you have latest version of CGI::Session
2) read CGI::Session::Tutorial
3) write programs that prove your claims, like this CGI::Session expire demo
#!/usr/bin/perl --
use strict;
use warnings;
use CGI();
use CGI::Session();
my ($oneid);
{
my $one = CGI::Session->new or die CGI::Session->errstr;
$one->expire('3s');
$one->param(qw' var value ');
$oneid = $one->id;
print "set exire to 3 seconds\n";
}
for my $loop ( 1 .. 4 ) {
sleep 1;
my $bob = CGI::Session->load($oneid) or die CGI::Session->errstr;
print "one second later $bob / $oneid load\n";
}
for my $loop ( 1 .. 4 ) {
sleep 2;
my $bob = CGI::Session->load($oneid) or die CGI::Session->errstr;
print "two seconds later ";
if ( $bob->is_expired ) {
print "$bob / $oneid is_expired\n";
} else {
print "var=", $bob->param('var'), "\n";
}
} ## end for my $loop ( 1 .. 4 )
{
sleep 3;
my $bob = CGI::Session->load($oneid) or die CGI::Session->errstr;
print "three seconds later ";
if ( $bob->is_expired ) {
print "$bob / $oneid is_expired\n";
} else {
print "var=", $bob->param('var'), "\n";
}
}
__END__
set exire to 3 seconds
one second later CGI::Session=HASH(0xa965fc) / cf27e3ec9ff5a06a5bef4491e830c8b6 load
one second later CGI::Session=HASH(0x97a164) / cf27e3ec9ff5a06a5bef4491e830c8b6 load
one second later CGI::Session=HASH(0xbef68c) / cf27e3ec9ff5a06a5bef4491e830c8b6 load
one second later CGI::Session=HASH(0xbef56c) / cf27e3ec9ff5a06a5bef4491e830c8b6 load
two seconds later var=value
two seconds later var=value
two seconds later var=value
two seconds later var=value
three seconds later CGI::Session=HASH(0xa965ec) / cf27e3ec9ff5a06a5bef4491e830c8b6 is_expired

From the documentation:
"is_empty() - Returns true for sessions that are empty. It's preferred way of testing whether requested session was loaded successfully or not"
I don't understand how you are trying to expire a session on the condition that it is empty?
#check if empty create new
if ( $sss->is_empty() ) {
$sss = $sss->new() or $sss->errstr;
$this->session_status("SESSION_NEW");
$sss->expire('30s');
$sss->expire(_TEST_SUB_SESSION => '15s');
}
Did you perhaps mean:
if ( !$sss->is_empty() ) { #...Not empty?

Related

How can I get these Perl scripts to delay?

I'm making a simple IRC bot in Perl that can be used to "hunt ducks" in response to this IRC game bot. I'm doing this on a private scripting channel, irc.freenode.net ##duckhunt2 so as not to interfere with real people playing the game.
So far I've tried making a Perl bot using Net::IRC and a plugin for XChat, with my code here. The duck source bot sends a message like
・゜゜・。。・゜゜\_O< quack!
a random amount of time in between 8-60 minutes since the last duck was shot to let you know that a duck has arrived. You can then reply with .bang to shoot the duck and get one point added to your score. However, if you reply too quickly (within one second), it puts you in a 2 hour cooldown mode where you can't shoot any ducks. Sometimes it also throws in 7 second cooldowns because of "jammed guns" and such, as shown in line 272 of the game bot code.
Perl code
use Net::IRC;
use Time::HiRes qw(usleep nanosleep);
$ducksource = 'DUCK_SOURCE';
$server = 'IRC_SERVER';
$channel = 'IRC_CHANNEL';
$botnick = 'BOT_NICKNAME';
$botnick2 = 'BOT_BACKUP_NICKNAME';
$password = 'BOT_PASSWORD';
$botadmin = 'BOT_ADMIN_NICKNAME';
$irc = new Net::IRC;
$conn = $irc->newconn(
Nick => $botnick,
Server => $server,
Port => IRC_SERVER_PORT,
Username => $botnick
);
$conn->add_global_handler('376', \&on_connect);
$conn->add_global_handler('disconnect', \&on_disconnect);
$conn->add_global_handler('kick', \&on_kick);
$conn->add_global_handler('msg', \&on_msg);
$conn->add_global_handler('public', \&on_public);
$irc->start;
sub on_connect {
$self = shift;
$self->privmsg('nickserv', "identify $password");
$self->join($channel);
print "Connected\n";
}
sub on_disconnect {
$self = shift;
print "Disconnected, attempting to reconnect\n";
$self->connect();
}
sub on_kick {
$self = shift;
$self->join($channel);
$self->privmsg('nickserv', "/nick $botnick");
}
sub on_msg {
$self = shift;
$event = shift;
if ($event->nick eq $botadmin) {
foreach $arg ($event->args) {
if ($arg =~ m/uptime/) {
$self->privmsg($botadmin, `uptime`);
}
}
}
}
sub on_public {
$self = shift;
$event = shift;
if ($event->nick eq $ducksource) {
foreach $arg ($event->args) {
if (($arg =~ m/</) && ($arg !~ m/>/)) {
usleep(250000);
$self->privmsg($channel, ".bang");
}
if ( ($arg =~ m/missed/)
|| ($arg =~ m/jammed/)
|| ($arg =~ m/luck/)
|| ($arg =~ m/WTF/)) {
$self->privmsg('nickserv', "/nick $botnick2");
$self->privmsg($channel, ".bang");
$self->privmsg('nickserv', "/nick $botnick");
}
if (($arg =~ m/script/) || ($arg =~ m/period/)) {
$self->privmsg('nickserv', "/nick $botnick2");
$self->privmsg($channel, ".bang");
}
}
}
}
The Perl bot connects to the server, joins the chat room, and responds to a duck appearing, but I can't get it to delay the sending of the command .bang so that the game bot receives it after 1 second has passed and I don't go into the two-hour cooldown mode.
I know that the Perl sleep command only accept multiples of one second. I need to delay 0.25 seconds because it takes about 0.75 seconds for the message to reach the game bot, so I've tried using Time::HiRes and the usleep command, which uses microseconds (1,000 microseconds = 1 millisecond).
On line 61 of my code, I added usleep(250000) which should make the script pause for 0.25s before sending the message on the next line
$self->privmsg($channel, ".bang")
But the script does not wait -- it just sends the message as normal. It acts like it is ignoring the usleep command.
How can I fix this and make the bot wait before it sends the message?
Secondly, I'm confused over how to change nicknames. If the game bot gives me a 7 second cooldown, I'd like to quickly change my nick to another nick (e.g. HunterBot6000 to HunterBot6000_) shoot the duck (.bang), and change my nick back before another bot gets the duck. Typically you accomplish a nick change through the /nick NEWNICK command. However, I've tried sending this command to the channel and NickServ, and this doesn't change my nickname. How should I accomplish this?
I also tried writing an XChat plugin for the script to see if that would get rid of the timing issue, but that doesn't work either. After connecting to the server and joining the chat room in XChat, I load the plugin, and I have the same issue -- it responds to ducks with .bang but I cannot get it to wait before sending.
You can see the documentation Writing a simple XChat Perl Script. What am I doing wrong?
You're asking multiple questions, but I can only answer one from my phone
You can change nicknames by sending
NICK newnick
Further information can be found in the RFC 2812.
However, Net::IRC might have more appropriate means for that.
I have also had trouble from usleep from Time::HiRes. This should effect a sleep of 250ms:
select(undef, undef, undef, 0.25);
Thank you for everyone's help. I was able to get the usleep command working and verify that it was delaying properly by changing the delay to a larger amount of seconds (e.g. usleep(25000000), 25 seconds) and then changing back to 0.25 seconds by removing one 0 at a time. I also added print Time::HiRes::time; before and after to verify that the delay was working. I also found that the proper command to change nicks is $self->nick($botnick2);, even though it is nowhere to be found in any Net::IRC documentation. Once again, thank you all for the help and advice.

How can I check if a user enters my page the first time?

I wrote the following subroutine to set cookie for new users who come to my website for the first time
Sub setcookie {
$cookie_id = localtime();
$exp = "Fri, 31-Sep-2015 24:00:00 GMT";
if($ENV{'HTTP_COOKIE'} eq "") {
print "Set-Cookie: first_access=$cookie_id; expires=$exp; PATH=/; domain=mysite.com\n";
}
}
Whenever user hits "purchase" to buy any items from my shop, the script will check if user already has cookie. If not, display the login form. This is the step that I've been stuck on.
&setcookie;
if ($form{submit} eq "Purchase")
if (!= cookieForuser)
&displayloginform;
else
&checkoutform;
How do I write another sub called cookieForuser to implement the said idea?
Please do you a favor and use at least the CGI module, it will make your life much easier and safer.
use CGI qw/:standard/;
use CGI::Cookie;
%cookies = CGI::Cookie->fetch;
if ( exists $cookies{VISITED} ) {
checkoutform();
}
else {
my $cookie = CGI::Cookie->new(-name=>'VISITED',-value=>1,
-expires='Fri, 31-Sep-2015 24:00:00 GMT');
print header(-cookie=>[$cookie]);
displayloginform();
}

How do I make 25 requests at a time with HTTP::Async in Perl?

I'm doing a lot of HTTP requests and I chose HTTP::Async to do the job. I've over 1000 requests to make, and if I simply do the following (see code below), a lot of requests time out by the time they get processed because it can take tens of minutes before processing gets to them:
for my $url (#urls) {
$async->add(HTTP::Request->new(GET => $url));
}
while (my $resp = $async->wait_for_next_response) {
# use $resp
}
So I decided to do 25 requests per time, but I can't think of a way to express it in code.
I tried the following:
while (1) {
L25:
for (1..25) {
my $url = shift #urls;
if (!defined($url)) {
last L25;
}
$async->add(HTTP::Request->new(GET => $url));
}
while (my $resp = $async->wait_for_next_response) {
# use $resp
}
}
This however doesn't work well as because it's too slow now. Now it waits until all 25 requests have been processed until it adds another 25. So if it has 2 requests left, it does nothing. I've to wait for all requests to be processed to add the next batch of 25.
How could I improve this logic to make $async do something while I process records, but also make sure they don't time out.
You're close, you just need to combine the two approaches! :-)
Untested, so think of it as pseudo code. In particular I am not sure if total_count is the right method to use, the documentation doesn't say. You could also just have an $active_requests counter that you ++ when adding a request and -- when you get a response.
while (1) {
# if there aren't already 25 requests "active", then add more
while (#urls and $async->total_count < 25) {
my $url = shift #urls;
$async->add( ... );
}
# deal with any finished requests right away, we wait for a
# second just so we don't spin in the main loop too fast.
while (my $response = $async->wait_for_next_response(1)) {
# use $response
}
# finish the main loop when there's no more work
last unless ($async->total_count or #urls);
}
If you can't call wait_for_next_response fast enough because you're in the middle of executing other code, the simplest solution is to make the code interruptable by moving it to a separate thread of execution. But if you're going to start using threads, why use HTTP::Async?
use threads;
use Thread::Queue::Any 1.03;
use constant NUM_WORKERS => 25;
my $req_q = Thread::Queue::Any->new();
my $res_q = Thread::Queue::Any->new();
my #workers;
for (1..NUM_WORKERS) {
push #workers, async {
my $ua = LWP::UserAgent->new();
while (my $req = $req_q->dequeue()) {
$res_q->enqueue( $ua->request($req) );
}
};
}
for my $url (#urls) {
$req_q->enqueue( HTTP::Request->new( GET => $url ) );
}
$req_q->enqueue(undef) for #workers;
for (1..#urls) {
my $res = $res_q->dequeue();
...
}
$_->join() for #workers;

Perl - How to get the email address from the FROM part of header?

I am trying to set up this script for my local bands newsletter.
Currently, someone sends an email with a request to be added, we manually add it to newsletter mailer I set up.
(Which works great thanks to help I found here!)
The intent now is to have my script below log into the email account I set up for the list on our server, grab the info to add the email automatically.
I know there are a bunch of apps that do this but, I want to learn myself.
I already have the "add to list" working when there is an email address returned from the header(from) below BUT, sometimes the header(from) is a name and not the email address (eg "persons name" is returned from persons name<email#address> but, not the <email#address>.)
Now, I am not set in stone on the below method but, it works famously... to a point.
I read all the docs on these modules and there was nothing I could find to get the darn email in there all the time.
Can someone help me here? Verbose examples are greatly appreciated since I am struggling learning Perl.
#!/usr/bin/perl -w
##########
use CGI;
use Net::IMAP::Simple;
use Email::Simple;
use IO::Socket::SSL; #optional i think if no ssl is needed
use strict;
use CGI::Carp qw(fatalsToBrowser warningsToBrowser);
######################################################
# fill in your details here
my $username = '#########';
my $password = '#############';
my $mailhost = '##############';
#######################################################
print CGI::header();
# Connect
my $imap = Net::IMAP::Simple->new($mailhost, port=> 143, use_ssl => 0, ) || die "Unable to connect to IMAP: $Net::IMAP::Simple::errstr\n";
# Log in
if ( !$imap->login( $username, $password ) ) {
print STDERR "Login failed: " . $imap->errstr . "\n";
exit(64);
}
# Look in the INBOX
my $nm = $imap->select('INBOX');
# How many messages are there?
my ($unseen, $recent, $num_messages) = $imap->status();
print "unseen: $unseen, <br />recent: $recent, <br />total: $num_messages<br />\n\n";
## Iterate through unseen messages
for ( my $i = 1 ; $i <= $nm ; $i++ ) {
if ( $imap->seen($i) ) {
my $es = Email::Simple->new( join '', #{ $imap->top($i) } );
printf( "[%03d] %s\n\t%s\n", $i, $es->header('From'), $es->header('Subject'));
print "<br />";
next;
}## in the long version these are pushed into different arrays for experimenting purposes
else {
my $es = Email::Simple->new( join '', #{ $imap->top($i) } );
printf( "[%03d] %s\n\t%s\n", $i, $es->header('From'), $es->header('Subject'));
print "<br />";
}
}
# Disconnect
$imap->quit;
exit;
use Email::Address;
my #addresses = Email::Address->parse('persons name <email#address>');
print $addresses[0]->address;
The parse method returns an array, so the above way works for me.
I'm making this a separate answer because even though this information is hidden in the comments of the accepted answer, it took me all day to figure that out.
First you need to get the From header using something like Email::Simple. THEN you need to extract the address portion with Email::Address.
use Email::Simple;
use Email::Address;
my $email = Email::Simple->new($input);
my $from = $email->header('From');
my #addrs = Email::Address->parse($from);
my $from_address = $addrs[0]->address; # finally, the naked From address.
Those 4 steps in that order.
The final step is made confusing by the fact that Email::Address uses some voodoo where if you print the parts that Email::Address->parse returns, they will look like simple strings, but they are actually objects. For example if you print the result of Email::Address->parse like so,
my #addrs = Email::Address->parse($from);
foreach my $addr (#addrs) { say $addr; }
You will get the complete address as output:
"Some Name" <address#example.com>
This was highly confusing when working on this. Granted, I caused the confusion by printing the results in the first place, but I do that out of habit when debugging.

How can I get the date of an email using Perl's Mail::MboxParser::Mail?

This is a simple question. I have a little program here that reads
a list of emails in a specific inbox of a user account specified by the program.
I can access an account using its username, password and host. The only problem is I don't know how to get the date on each of these mails.
Here's some part of my code:
my $pop = new Mail::POP3Client(
USER => $user, #some user,password & host assigned
PASSWORD => $pass,
HOST => $host );
for( $i = 1; $i <= $pop->Count(); $i++ ) {
#header = $pop->Head($i);
#body = $pop->Body($i);
$mail = new Mail::MboxParser::Mail(\#header, \#body);
$user_email = $mail->from()->{email
print "Email:".$user_email; #this prints out right
foreach( $pop->Head( $i ) ) {
/^(Date):\s+/i && print $_, "\n";
$date = $_;
}
}
Now what i need is to get the only one date for each email,
but that loop gives me all.. but when remove the loop, it
returns an error. I'm using Perl.
Kindly help me? :)
According to MboxParser::Email doc, you should be able to do:
$date = $mail->header->{'date'}; #Keys are all lowercase
If you have more than one date returned, $date will be an array ref and you can access the first occurence of the Date with:
$date->[0];
So you shouldn't need to loop through the header and use a regular expression.