File Drop

Computer-to-Computer File Transfer for the Masses

Tagged: Software

Continuing the recent theme of posting-random-scripts-as-blog-entries…

I recently needed a quick and dirty way to send a really large (~1 gigabyte) file to someone. We were both on the same LAN, so it didn’t really make sense for me to upload it to my externally hosted web server. I do not have a web server installed on my laptop and, at the time, it seemed like overkill to install a web server just so I could send him my file. Using a thumb drive or scp would have been an option, but each would require the recipient to be physically at my computer (despite being on the same LAN, he was a 10 minute walk away). Therefore, I gave myself a 10 minute deadline to code my own solution (plus it would be a fun diversion from writing my journal paper due later that day).

Given that I had a whole 10 minutes (an eternity when it comes to Perl hacking), I figured I might as well make my method generalized (i.e., not only should my script be able to send files, but it should also be able to receive).

First, I had to decide on a method. FTP seemed like a logical choice, but, besides really tech savvy people, who has full-blown FTP clients installed these days? In keeping with my generality goal, my solution would ideally be usable by, say, my mom. And moms don’t know ‘bout my FTP. Everyone, my mom (and my mom’s mom) included, has a web browser and knows how to use it. Therefore, good ol’ HTTP it was. And I even had a bunch of old code to hack together!

I ended up with a script that I call filedrop. Here’s the usage:

$ filedrop
Version: filedrop 0.1 2009-07-01 http://www.sultanik.com/
Copyright (C) 2009 Evan A. Sultanik

Usage: filedrop [OPTIONS] FILE_PATH

Options:
  -s           send a file by hosting it on a local web server (default)
  -r           receive a file by accepting it from a local web server.
               FILE_PATH should be a directory to which the files should be
               saved.  FILE_PATH will default to '.' in this mode.
  -n, --num=N  quit after sending/receiving N files.  If N is less than zero
               the program will send/receive files until manually
               terminated.  If N is zero then the program will immediately
               quit.  Default is -1.

And here’s an example of how the file transfer went down:


LeEtH4X0r: Y0 Home Skillet! Can you fry me up some juarez‽
Me: Indubitably!
$ filedrop -s -n1 ./hugefile.tar.gz
Server running at: http://my_ip:47489/
Me: Go to http://my_ip:47489/
LeEtH4X0r: ZOMGKTHXBAI!!!!1ONE

Here’s the code:

#!/usr/bin/perl -w

use HTTP::Daemon;
use HTTP::Status;

my $version = "0.1";
my $date = "2009-07-01";
my $copyright = "2009";

my $port = 80;

sub print_usage {
    print "Version: filedrop $version $date http://www.sultanik.com/\n";
    print "Copyright (C) $copyright Evan A. Sultanik\n\n";
    print "Usage: filedrop [OPTIONS] FILE_PATH\n\n";
    print "Options:\n";
    print "  -s           send a file by hosting it on a local web server (default)\n";
    print "  -r           receive a file by accepting it from a local web server.\n";
    print "               FILE_PATH should be a directory to which the files should be\n";
    print "               saved.  FILE_PATH will default to '.' in this mode.\n";
    print "  -n, --num=N  quit after sending/receiving N files.  If N is less than zero\n";
    print "               the program will send/receive files until manually\n";
    print "               terminated.  If N is zero then the program will immediately\n";
    print "               quit.  Default is -1.\n";
    print "\n";
}

my $mode = "s";
my $num = -1;

my $last = "";
my $nextIsN = 0;
foreach my $arg (@ARGV) {
    if($arg eq "-s") {
        $mode = "s";
    } elsif($arg eq "-r") {
        $mode = "r";
    } elsif($arg eq "-n") {
        $nextIsN = 1;
        next;
    } elsif($arg =~ /-n(\d+)/) {
        $num = $1;
    } elsif($arg =~ m/--num=(\d+)/) {
        $num = $1;
    } elsif($nextIsN) {
        $num = $arg;
    } else {
        if(!($last eq "")) {
            print_usage() && die("Invalid option: " . $last . "\n");
        }
        $last = $arg;
    }
    $nextIsN = 0;
}
if($last eq "" && $mode eq "s") {
    print_usage() && die("Path to a file to host expected!\n");
} elsif($last eq "" && $mode eq "r") {
    $last = ".";
}

my $file = $last;

exit(0) if($num == 0);

my $d = HTTP::Daemon->new(LocalPort => $port) || HTTP::Daemon->new() || die;
print "Server running at: ", $d->url, "\n";
my $servings = 0;
while(my $c = $d->accept) {
    while(my $r = $c->get_request) {
        if($mode eq "s") {
            if($r->method eq 'GET') {
                print "Someone's downloading!\n";
                $c->send_file_response($file);
                print "Download finished!\n";
                $servings++;
            } else {
                $c->send_error(RC_FORBIDDEN)
            }
        } elsif($mode eq "r") {
            if($r->method eq 'POST') {
                print "Someone is uploading!\n";
                $servings++;
                my $url = $r->content;
                while($url =~ m/.*?-+(\d+)\r\nContent-Disposition:.*? filename="([^"]+)".*?\r\n\r\n(.*?)\r\n-+\1-+(.*)$/ism){
                    my $id = $1;
                    my $filename = $2;
                    my $content = $3;
                    $url = $4;
                    my $newName = $filename;
                    my $i = 0;
                    $newName = $filename . "." . ++$i while(-e $file . "/" . $newName);
                    if($i > 0) {
                        print "A file of named $filename already exists in $file!\n";
                        print "Saving to " . $file . "/" . $newName . " instead.\n";
                        $filename = $newName;
                    }
                    open(OUTFILE,">" . $file . "/" . $filename) or die("Error opening $file/$filename for writing!\n");
                    binmode OUTFILE;
                    print OUTFILE $content;
                    close(OUTFILE);
                    print "Received $filename (ID: $id)\n";                    
                }
                $h = HTTP::Headers->new;
                $h->header('Content-Type' => 'text/html');
                my $msg = "Uploaded

Uploaded!

"; $msg .= "

Click here to upload another file.

" if($num < 0 || $servings < $num); $msg .= ""; $r = HTTP::Response->new( HTTP_OK, "", $h, $msg); $c->send_response($r); } elsif($r->method eq 'GET') { print "Someone connected! Sending the upload form...\n"; $h = HTTP::Headers->new; $h->header('Content-Type' => 'text/html'); $r = HTTP::Response->new( HTTP_OK, "", $h, "Upload

Please specify a file, or a set of files:

"); $c->send_response($r); print "Sent!\n"; } else { $c->send_error(RC_FORBIDDEN); } last if($num > 0 && $servings >= $num); } last if($num > 0 && $servings >= $num); } $c->close; undef($c); last if($num > 0 && $servings >= $num); } close($d);
← Older Post Blog Archive Newer Post →