Add content-length header; increase default block size; add timings; add IP address
[ambdownload.git] / download.pl
index af11039..feadfa6 100755 (executable)
@@ -1,5 +1,6 @@
 #!/usr/bin/perl
 
+use File::stat;
 use strict;
 use warnings;
 
@@ -7,29 +8,35 @@ use POSIX qw(strftime);
 use URI::Escape;
 use File::Copy qw( copy );
 use File::Basename;
+use File::stat;
 use Digest::SHA qw(sha256_hex sha1);
 use MIME::Base64;
 use File::Spec;
+use File::Spec::Functions qw(rel2abs);
 use CGI;
 use HTML::Entities;
 use IO::Handle;
+use Time::HiRes qw( usleep ualarm gettimeofday tv_interval );
 
 my $log;
 my $transaction="unknown";
 
 my $logfile = "/var/log/download.log";
-my $datadir = "/var/www/server.example.com/public_html/download/";
+my $datadir = dirname(rel2abs($0))."/";
 my $secretfile="/etc/apache2/download.secret";
 my $secret;
 my $sentheader = 0;
 my $maxdrift = 60;
+my $remoteip;
+my $t0 = [gettimeofday];
+my $filesize;
 
 sub lprintf
 {
     if (defined($log))
     {
        my $now = strftime "%a, %d %b %Y %T %z", localtime;
-       print $log "$now: [$$][$transaction]: ";
+       printf $log "$now: [$$:%s][$transaction]: ", defined($remoteip)?$remoteip:"unknown";
        printf $log @_;
     }
 }
@@ -50,7 +57,8 @@ sub openlog
 sub qdie
 {
     my $err = shift @_;
-    lprintf "ERROR: $err\n";
+    my $elapsed = tv_interval ( $t0, [gettimeofday]);
+    lprintf "ERROR: $err after %.3f secs\n", $elapsed;
     if (!$sentheader)
     {
        my $error = encode_entities( $err );
@@ -79,12 +87,13 @@ sub sendfile {
     $sentheader = 1;
     print CGI::header(
         -type => 'application/octet-stream',
+       -Content_length=> $filesize,
         -attachment => $name,
        );
 
     binmode STDOUT, ':raw';
 
-    unless (copy $fh => \*STDOUT, 8192)
+    unless (copy $fh => \*STDOUT, 65536)
     {
        qdie "Cannot write to STDOUT";
     }
@@ -100,6 +109,14 @@ sub gethash
     return sha256_hex(shift @_);
 }
 
+sub getfile
+{
+    my $fn = $datadir.(shift @_);
+    $fn = File::Spec->rel2abs( readlink($fn) ) if (-l $fn);
+    qdie ("File not found") unless ( -f $fn);
+    return $fn;
+}
+
 sub decodeparams
 {
     my $query = CGI::url(-absolute=>1);
@@ -109,22 +126,49 @@ sub decodeparams
     my $clientfile = CGI::url_param('file');
     $clientfile = "default" unless(defined($clientfile));
     qdie ("Bad parameters") unless (defined($clienttime) && defined($clientid) && defined($clienthash) && ($clienttime=~/^[0-9]+$/));
-    my $drift = time()-$clienttime;
-    qdie ("Client time has drifted - we have ".time()) if (($drift < -$maxdrift) || ($drift > $maxdrift));
+    my $now = time();
+    my $drift = $now-$clienttime;
+    qdie ("Client time has drifted - $now - $clienttime = $drift") if (($drift < -$maxdrift) || ($drift > $maxdrift));
     qdie ("Bad ID") unless ($clientid=~/^[-+._\@a-zA-Z0-9]+$/);
     qdie ("Bad filename") unless ($clientfile=~/^[-+._a-zA-Z0-9]+$/);
     qdie ("Bad filename") if ($clientfile=~/^\./);
 
     my $hash = gethash($clienttime.":".$clientid.":".$clientfile.":".$secret);
     qdie ("Bad hash") unless ($hash eq $clienthash);
-    my $fn = $datadir.$clientfile;
-    $fn = File::Spec->rel2abs( readlink($fn) ) if (-l $fn);
-    qdie ("File not found") unless ( -f $fn);
+    my $fn = getfile($clientfile);
     $clientfile = basename ($fn);
     $transaction=$hash." ".$clientfile." ".$clientid;
     return $fn;
 }
 
+sub doinfo
+{
+    my $clientfile = shift @_;
+    my $fn = getfile($clientfile);
+    $clientfile = basename ($fn);
+    my $size = "unknown";
+    my $sb = stat($fn);
+    $size = $sb->size if (defined($sb) && defined($sb->size));
+    my $md5sum = "unknown";
+    my $md5fn = $fn.".md5sum";
+    if ( -r $md5fn )
+    {
+       my $md5;
+       open $md5, "<", $md5fn || qdie ("Can't read md5sum");
+       while (<$md5>)
+       {
+           chomp;
+           $md5sum = $1 if (/^([a-f0-9]+)\b/);
+       }
+       close $md5;
+    }
+    $sentheader = 1;
+    print CGI::header(
+       -type => 'text/plain' );
+
+    print "$clientfile $size $md5sum\n";
+}
+
 open (my $sfh, "<", $secretfile) || qdie("Can't open secret file $secretfile: $!");
 chomp($secret=join("",<$sfh>));
 close ($sfh);
@@ -138,8 +182,18 @@ if (!defined($ENV{DOCUMENT_ROOT}) && !defined($ENV{SERVER_NAME}))
 }
 else
 {
+    my $info = CGI::url_param('info');
+    if (defined($info))
+    {
+       doinfo($info);
+       exit 0;
+    }
+    $remoteip = CGI::remote_addr();
+
     openlog;
     my $file = decodeparams;
+    my $sb = stat($file);
+    $filesize = $sb->size;
     lprintf("STARTING\n");
     $SIG{INT} = \&caughtsignal;
     $SIG{QUIT} = \&caughtsignal;
@@ -148,7 +202,9 @@ else
     $SIG{KILL} = \&caughtsignal;
     $SIG{TERM} = \&caughtsignal;
     sendfile($file);
-    lprintf("SUCCESS\n");
+    my $elapsed = tv_interval ( $t0, [gettimeofday]);
+    my $rate = $filesize/(1000000.0*(($elapsed<0.001)?0.001:$elapsed));
+    lprintf("SUCCESS %d bytes %.3f MB/s %.3f secs\n", $filesize, $rate, $elapsed);
     closelog;
 
     exit(0);