#!/usr/bin/perl -Tw

# Copyright (c) 2005 Holt Sorenson <hso at nosneros dot net>
# This code is made available under the Artistic License
# (http://www.opensource.org/licenses/artistic-license.php)

# This script, in it's current state, isn't recommended
# on win32 hosts because it doesn't use binmode().

# This script requires CGI.pm >= 2.47

use strict;

use MIME::Base64;
use CGI qw/:standard -private_tempfiles/;


# get the current date and stuff it in a string
sub dstr()
{
    my ($d, $i, $e) = ();

    $i = 5;
    foreach $e ( (localtime(time()))[5,4,3,2,1,0] )
    {
	if ($i == 5)
	{
	    $e += 1900;
	}
	elsif ($i == 4)
	{
	    $e++;
	}

	if ($e < 10)
	{
	    $e = "0" . $e;
	}
	$d .= $e;

	$i--;
    }

    return($d);
}


# acquire some random bits and return them as hex
sub rnd()
{
    my ($r1, $r2, $i) = ();

    open(RND, "</dev/urandom") || return(-1);

    for($i=4;$i>0;$i--)
    {
        $r1 .= getc(RND) ^ getc(RND);
    }

    if (defined($r1) && length($r1) == 4)
    {
        foreach $i (split(/ */, $r1))
        {       
            $r2 .= sprintf("%02x", ord($i));
        }       
        return($r2);
    }
    else    
    {
        return(-1);
    }

    return(-1);
}


# get lines from an array or a filehandle
sub getline()
{
    my ($b, $n) = ($_[0], $_[1]);

    # it's an array, return line $n unless $n exceeds array size
    if (ref($b) eq 'ARRAY')
    {
	if ($n >= scalar @{$b})
	{
	    return ();
	}
	else
	{
	    return(${$b}[$n]);
	}
    }
    # it's a file handle, use readline
    elsif (ref($b) eq 'Fh')
    {
	return(readline($b));
    }
    else
    {
	return ();
    }
}


# output base64 decode error page
sub decode_error()
{

    my ($mode, $ft) = ($_[0], $_[1]);

    if ($mode eq "html")
    {
	print "\n<html><head><title>lbbdecode cgi - Error</title><head>" .
	"<body bgcolor=\"#ffffff\">\n<table border=\"0\"><tr><td>" .
	"<span style=\"font-family: Arial, sans-serif; font-size: 16pt;\">" .
	"Error decoding base64 encoded data.</span></tr></td><tr><td>&nbsp;</td></tr>" .
	"<tr><td align=\"center\">${ft}</tr></td></table>\n</body></html>\n";
    }
    elsif ($mode eq "text")
    {
	print "\n===> lbbdecode cgi error ${ft} -- Error decoding base64 encoded data.\n";
    }
    else
    {
	print STDERR "\n===> lbbdecode cgi error ${ft}: Error mode ${mode} not supported at line __LINE__.\n";
    }
}


# begin main
{

    my ($b64blob, $decoded, $dstr, $rnd, $e, $q, $blob, $i, $l) = ();

    my $version = "0.2";
    my $html_ft =
	"<span style=\"font-family: Arial, sans-serif; font-size: 8pt;\">" .
	"low budget base64 decoder cgi (v${version}) - Holt Sorenson" .
	" &lt;hso at nosneros dot net&gt;<br>Copyright (c) 2005. " .
	"see: <a target=\"nonlocal\" href=\"http://www.nosneros.net/hso/code/\">" .
	"http://www.nosneros.net/hso/code/</a><br>" .
	"lbbdecode cgi licensed under the <a target=\"nonlocal\"" .
        "href=\"http://www.opensource.org/licenses/artistic-license.php\">" .
	"Artistic License</a>.</span>";
    my $txt_ft = "v${version}";

    $dstr = &dstr();
    $rnd = &rnd();

    $q = new CGI;


    # a form isn't being submitted, so create one
    if (!$q->param('b64blob') && !$q->param('b64file'))
    {
	print $q->header, $q->start_html('lbbdecode cgi'), "\n",
	# file upload form
	$q->start_multipart_form, "<table border=\"0\">\n" . "<tr><td align=\"left\">",
	"<span style=\"font-family: Arial, sans-serif; font-size: 16pt;\">" .
	"Browse to base64 encoded file:</span>\n", "</td></tr>\n<tr><td align=\"left\">", 
	$q->filefield(-name=> 'b64file', -size=>45, -maxlength=>255), "</td><td>&nbsp;", $q->submit(-name=>'Submit'),
	$q->reset(-name=>'Reset'), "</td></tr>\n</table>\n", $q->end_form, 

	# paste form
	$q->start_form, "<table border=\"0\">\n" .  "<tr><td colspan=\"2\">" .
	"<span style=\"font-family: Arial, sans-serif; font-size: 16pt;\">Or</span></td></tr>\n" .
	"<tr><td align=\"left\">", "<span style=\"font-family: Arial, sans-serif; font-size: 16pt;\">" .
	"Enter (paste) base64 encoded blob:</span>", "</td><td align=\"right\">", 
        $q->submit(name=>'Submit'), $q->reset(name=>'Reset'), "</td></tr>\n<tr><td colspan=\"2\">",
	$q->textarea(-name=>'b64blob',-rows=>32,-columns=>77),
	"</td></tr>\n<tr><td colspan=\"2\" align=\"center\">${html_ft}\n</tr></td></table>\n", 
	$q->end_form;

	# file upload failed
	if ($q->upload('uploaded_file') && $q->cgi_error())
	{
	    print $q->header(-status=>cgi_error);
	}
    }
    # try to decode and output
    elsif (param('b64file') || $q->param('b64blob'))
    {
	if ($rnd ne "-1" && length($rnd) == 8)
	{
	    print "Content-type: application/octet-stream name=\"lbbdecode_output_${dstr}_$rnd.raw\"\n";
	    print "Content-disposition: attachment; filename=\"lbbdecode_output_${dstr}_$rnd.raw\"\n\n";
	}
	else
	{
	    print "Content-type: application/octet-stream name=\"lbbdecode_output_${dstr}.raw\"\n";
	    print "Content-disposition: attachment; filename=\"lbbdecode_output_${dstr}.raw\"\n\n";
	}

	if (param('b64file'))
	{
	    # make $blob the filehandle to the uploaded file
	    $blob = $q->upload('b64file'); 
	}
	elsif (param('b64blob'))
	{
	    # make $blob an anonymous array reference to the base64
	    # that's split on linefeed and/or carriage return
	    $blob = [split(/[\r\n]+/, $q->param('b64blob'))];
	}
	else
	{
	    &decode_error("text", $txt_ft);
	}

	$i = 0;
	while (defined($l = &getline($blob, $i)))
	{
	    $decoded = MIME::Base64::decode_base64($l);

	    # decode failed, print error
	    if (!defined($decoded) || length($decoded) == 0)
	    {
		&decode_error("text", $txt_ft);
		last;
	    }
	    else
	    {
		print "$decoded";
	    }
	    $decoded = ();

	    $i++;
	}

    }

}
# end main
