#!/usr/local/bin/perl
#	b64 -- Base 64 decoder/encoder
#	Brian Katzung	8 February 1996
#
# This software is provided on a strictly as-is basis.
# Use it at your own risk.

$True = 1;
$False = 0;

######################################################################
# Decode base 64 input
sub Decode
{
	local($Data1);
	local($Data2);
	local($Length);

	while (defined($Data1 = <>))
	{
		# Zap anything outside of the character set
		$Data1 =~ s/[^A-Za-z0-9+\/=]//g;
		$Data2 .= $Data1;			# Left over + new

		# Grab the largest group of 24 bits and leave the
		# rest for the next iteration
		while ($Length = length($Data2) & ~3)
		{
			$Length = 64 if ($Length > 64);

			# For this iteration
			$Data1 = substr($Data2, 0, $Length);
			# For the next one
			$Data2 = substr($Data2, $Length);

			# Figure out how many characters to decode
			$Data1 =~ /=*$/;
			$Length = $Length / 4 * 3 - length($&);

			# Convert base 64 to uuencode so that we can just
			# unpack('u') (which is fast) instead of manipulating
			# the bits explicitly (which is slow).
			$Data1 =~ tr/A-Za-z0-9+\/=/ -_ /;
			print unpack('u', pack('c', $Length + 32) . $Data1);
		}
	}
}

######################################################################
# Return the next 45 characters of input
$I45'Data = '';
$I45'EOF = $False;

sub Input45
{
	local($Data);
	local($Length);

	while (length($I45'Data) < 45 && $I45'EOF != $True)
	{
		# Try to refill the input buffer
		$Length = sysread(STDIN, $Data, 8192);
		if ($Length < 8192)
		{
			if (scalar(@ARGV) == 0)
			{
				# No more files to read
				$I45'EOF = $True;
			}
			else
			{
				# Start reading the next file
				open(STDIN, '< ' . shift(@ARGV));
			}
		}

		# Perform NL -> CRLF translation if requested
		$Data =~ s/\n/\r\n/g if $CRLF == $True;
		$I45'Data .= $Data;
	}

	# Return the next 45 characters
	$Data = substr($I45'Data, 0, 45);
	$I45'Data = substr($I45'Data, 45);
	$Data;
}

######################################################################
# Encode input in base 64
sub Encode
{
	local(*FILE) = $_[0];
	local($Output);
	local($Data);
	local($Length);

	# Grab the input in chunks of 45 characters
	while (($Data = &Input45) ne '')
	{
		$Length = length($Data);

		# Instead of directly manipulating bits to generate base 64
		# (which is slow), use pack('u') first (which is fast)...
		$Data = substr(pack('u', $Data), 1, int(($Length * 8 + 5) / 6));

		# And then translate from uuencode to base 64
		$Data =~ tr/` -_/AA-Za-z0-9+\//;

		# Reformat the results as lines of 76 characters
		$Output .= $Data;
		if (length($Output) > 76)
		{
			print substr($Output, 0, 76), "\n";
			$Output = substr($Output, 76);
		}
	}

	# Handle any remainder
	if ($Output ne '')
	{
		# Pad encoding to 32 bits
		$Length = length($Output) & 3;
		$Output .= '==' if $Length == 2;
		$Output .= '=' if $Length == 3;

		print "$Output\n";
	}
}

######################################################################
# Main program

$Mode = 'decode';
$CRLF = $False;

while ($ARGV[0] =~ /^-/)
{
	$Arg = shift;
	last if $Arg eq '--';
	$CRLF = $True if $Arg =~ /c/;		# NL -> CRLF mapping
	$Mode = 'decode' if $Arg =~ /d/;
	$Mode = 'encode' if $Arg =~ /e/;
}

if ($Mode eq 'encode')
{
	open(STDIN, '< ' . shift) if scalar(@ARGV) > 0;
	&Encode;
}
else
{
	&Decode;
}
