ghenry has asked for the wisdom of the Perl Monks concerning the following question:

Dear Master Monks,

What ways have people implemented e-mails with links that expire?

What I mean is, that I want to send an e-mail which has a link to a download, but the link is only valid for say 1 day?

I think this is much like mailing list confirmation e-mails?

I have seen sessions on CPAN, but I am not sure if that the right thing?

Should this be done via a RDBMS etc.?

I will be implementing this idea in a few weeks, but wanted to get things going now, so I had an idea which things to look at and read up on.

Any pointers are much appreciated.

Walking the road to enlightenment... I found a penguin and a camel on the way.....
Fancy a yourname@perl.me.uk? Just ask!!!
  • Comment on Ideas for implementing download links that expire, via e-mails

Replies are listed 'Best First'.
Re: Ideas for implementing download links that expire, via e-mails
by dorward (Curate) on Apr 22, 2005 at 08:39 UTC

    I'd use this technique:

    1. User requests that link be generated
    2. A random string (such as 1234567890abcdef) is generated and stored with its time of creation. A database is the logical tool to use for this storage.
    3. The link is generated using that random string, probably in the query string. e.g. http://example.com/foo.cgi?user=dorward;token=1234567890abcdef
    4. Link is emailed to user

    Then, when the link is activated, the CGI script would look up the string (I'd add a WHERE clause in the SQL query to say "where time created is greater then now - 24 hours"). If its the query returns a result, then the link is OK, otherwise the user gets an "invalid or expired link" message that suggests they check they copied it correctly (watch for word wrap) and offers them a chance to generate a new one.

    A regular process (probably triggered by cron) would then delete old entries from the database.

    This is the type of thing that would be used for mailing list conformation emails. I wrote something along those lines for a "Forgotten your password" section of a website.

    The technique generates randomish tokens to identify users, but that is about as similar as it gets to sessions.

      A regular process (probably triggered by cron) would then delete old entries from the database.
      You can avoid this by calling the purging mechanism each time you generate or go looking for a link. Moreover, I'd put this purging call before the actual search for the required item, so that you can get rid of the WHERE clause.
      $dbh->do('DELETE FROM tmp_downloads WHERE expiration < NOW()'); $dbh->prepare('SELECT * FROM tmp_downloads WHERE token = ?', undef, + $token); # ... and so on...
      I think you're willing to accept the slight race between the DELETE and the SELECT - it would mean that a particularly lucky guy could download stuff in the very few instants between the two queries if the expiration happens right in the middle :)

      Flavio (perl -e 'print(scalar(reverse("\nti.xittelop\@oivalf")))')

      Don't fool yourself.

      This is how I imagined the process.

      To be fair, I should of actually shared my thoughts on how I saw this problem being sovled, instead of being lazy and waiting for someone to give me the answer (which I find myself doing more and more these days for some reason :( ).

      Walking the road to enlightenment... I found a penguin and a camel on the way.....
      Fancy a yourname@perl.me.uk? Just ask!!!
Re: Ideas for implementing download links that expire, via e-mails
by TedPride (Priest) on Apr 22, 2005 at 09:52 UTC
    That's overly complicated. Just include the timestamp the link expires on plus a hash code based on the timestamp, like follows:
    use strict; use warnings; my ($url, $key, $exp, $hash, $link); $url = 'http://www.domain.com/folder/page.html'; $key = 'm239VdSn'; $exp = time() + 60*60*24; $hash = crypt(substr($exp,-8,8),$key); $hash = substr($hash, 2); $hash =~ s/[^a-zA-Z0-9]//g; $hash = uc($hash); $link = "$url?$exp-$hash"; print $link;
    Note that crypt only works on the first 8 characters of the given text, so you want to feed it the last 8 characters of the timestamp. The modifications to the resulting hash were to make the link look prettier.

    EDIT: Added a line to remove the first two characters of the hash, which when using crypt, contain the salt.

      $hash =~ s/[^a-zA-Z0-9]//g;

      ITYM $hash =~ y/a-zA-Z0-9//cd; HTH. HAND.

      $ perl -MBenchmark=cmpthese -le 'cmpthese( 500_000, { s => sub { $_ = +q{123#$%abc^&*DEF}; s/[^a-zA-Z0-9]//g }, y => sub { $_ = q{123#$%abc^ +&*DEF}; y/a-zA-Z0-9//cd; } } )' Rate s y s 174825/s -- -80% y 862069/s 393% --
Re: Ideas for implementing download links that expire, via e-mails
by tlm (Prior) on Apr 22, 2005 at 12:20 UTC

    I'd do something similar to what TedPride suggests (I agree that there's no need to involve a DBMS in this), except that I'd make a subroutine for the purpose of computing $hash, since the code will need this computation at least at two different places in the program: 1) to create the $hash, and 2) to test that the $hash and $exp parts of a requested URL match. Also, instead of having $url point to a regular HTML page, I'd make it point to a CGI script that served the page if requested before the URL-encoded expiration date.

    Alternatively, you could devise a simple encoding/decoding scheme to encrypt or at least obfuscate the expiration time. Here's one.

    This particular encoding/decoding scheme is not necessarily the best (on the one hand, it may be overkill as far as obfuscating the expiration time goes, and on the other, you may be able to find a enc/dec module in CPAN that provides both adequate encryption/obfuscation and nice alphanumeric output in one swoop), but it gives you an idea of a general approach.

    the lowliest monk

Re: Ideas for implementing download links that expire, via e-mails
by pboin (Deacon) on Apr 22, 2005 at 13:33 UTC

    Well, if it were me, I'd K.I.S.S. as much as possible.

    The first thing that I thought of was kicking off find -mtime 1 --exec rm {} \;. This releives you from any DB work or tracking. It all happens at the filesystem metadata level.

    (Of course, you'll probably need a *nix system to get this to work right.

      I like that idea.

      But what if the download on offer has to be kept?

      I suppose you could make a copy and then delete that one after 24 hours.

      Walking the road to enlightenment... I found a penguin and a camel on the way.....
      Fancy a yourname@perl.me.uk? Just ask!!!
Re: Ideas for implementing download links that expire, via e-mails
by tweetiepooh (Hermit) on Apr 22, 2005 at 13:22 UTC
    Assume that you are a little generous and you allow the link to be valid until midnight the following day.

    Use that known point in time to encode something in the URL and send it out.

    When server receives URL with coding decode using midnight tomorrow. If OK then download proceeds. If not retry with midnight today and allow if that is OK. If both fail then time has expired and you can tell user so.

    You will need to expand on this a little but this way you don't pass the expiry info over.