#!/usr/bin/perl
###########################################
# Mike Schilli, 2003 (m@perlmeister.com)
###########################################
use warnings;
use strict;
use Log::Log4perl qw(:easy);
use Cache::FileCache;

my $DB_FILE     = "/tmp/shrinky.dat";
my $DB_MAX_SIZE = 10_000_000;
my $MAX_URL_LEN = 256;
my $REQS_PER_IP = 200;

Log::Log4perl->init(\ <<"EOT");
log4perl.logger = DEBUG, Rot
log4perl.appender.Rot=\\
  Log::Dispatch::FileRotate
log4perl.appender.Rot.filename=\\
  /tmp/shrink.log
log4perl.appender.Rot.layout=\\
  PatternLayout
log4perl.appender.Rot.layout.\\
ConversionPattern=%d %m%n
log4perl.appender.Rot.mode=append
log4perl.appender.Rot.size=1
log4perl.appender.Rot.max=1
EOT

use CGI qw(:all);
use CGI::Carp qw(fatalsToBrowser);
use DB_File;

tie my %URLS, 'DB_File', $DB_FILE, 
    O_RDWR|O_CREAT, 0755 or 
        LOGDIE "tie failed: $!";

    # First time initialization
$URLS{"next/"} ||= 1;

my $redir = "";

if(exists $ENV{PATH_INFO}) {
        # Redirect requested
    my $num = substr($ENV{PATH_INFO}, 1);
    $redir = $URLS{"by_shrink/$num"} if 
        $num ne "_" 
        and exists $URLS{"by_shrink/$num"};
}

if($redir) {
    print redirect($redir);
    goto END;
}

print header();

if(my $url = param('url')) {

    if(length $url > $MAX_URL_LEN) {
      print "Sorry, URL too long.\n";
      goto END;
    }

    my $surl;

    # Does it already exist?
    if(exists $URLS{"by_url/$url"}) {
      DEBUG "$url exists already";
      $surl = $URLS{"by_url/$url"};

    } else {
      if(-s $DB_FILE > $DB_MAX_SIZE) {
        DEBUG "DB File maxed out " . 
             (-s $DB_FILE) . " > $DB_FILE";
        print "Sorry, no more URLs.\n";
        goto END;
      }

      if(rate_limit($ENV{REMOTE_ADDR})) {
        print "Sorry, too many requests " .
              "from this IP\n";
        goto END;
      }

      # Register new URL
      my $n = base36($URLS{"next/"}++);
      INFO "$url: New shortcut: $n";
      $surl = url() . "/$n";
        $URLS{"by_shrink/$n"} = $url;
        $URLS{"by_url/$url"}  = $surl;
    }
    print a({href => $surl}, $surl);
  }

      # Accept user input
  print h1("Add a URL"), 
        start_form(), 
        textfield(-size    => 60, 
                  -name    => "url", 
                  -default => "http://"), 
        submit(), end_form();
    
END:

untie %URLS;

###########################################
sub base36 {
###########################################
    my ($num) = @_;

    use integer;

    my @chars  = ('0'..'9', 'a'..'z');
    my $result = "";

    for(my $b=@chars; $num; $num/=$b) {
        $result .= $chars[$num % $b];
    }

    return scalar reverse $result;
}

###########################################
sub rate_limit {
###########################################
    my ($ip) = @_;

    $ip = 'NO_IP' unless defined $ip;

    INFO "Request from IP $ip";

    my $cache = Cache::FileCache->new(
        { default_expires_in  => 3600*24,
          auto_purge_on_get   => 1,
        }
    );

    my $count = $cache->get($ip);

    if(defined $count and
       $count >= $REQS_PER_IP) {
        INFO "Rate-limiting IP $ip";
        return 1;
    }

    $cache->set($ip, ++$count);

    return 0;
}
