#!/usr/bin/perl

use Getopt::Std qw(getopts);
use MIME::Base64 qw(decode_base64 encode_base64);

my $VERSION = '1.0';

my (%opt, $bufsz, $in, $out);
getopts('do:v', \%opt) or usage();
if (scalar(@ARGV) > 1) {
    warn "$0 too many arguments\n";
    usage();
}
if ($opt{'v'}) {
    die "$0 version $VERSION\n";
}
$bufsz = $opt{'d'} ? 76 : 57;
$bufsz *= 20;
if (defined $opt{'o'}) {
    unless (open $out, '>', $opt{'o'}) {
        die "$0: $opt{o}: $!\n";
    }
} else {
    $out = *STDOUT;
}
if (defined $ARGV[0] && $ARGV[0] ne '-') {
    unless (open $in, '<', $ARGV[0]) {
        die "$0: $ARGV[0]: $!\n";
    }
} else {
    $in = *STDIN;
}

$opt{'d'} ? decode() : encode();
close $in;
close $out;

sub decode {
    my $buf = '';
    while (readline $in) {
        s/\s//g;
        die("$0: bad input\n") if m/[^A-Za-z0-9\+\/\=]/;
        $buf .= $_;
        if (length($buf) >= $bufsz) {
            my $chunk = substr($buf, 0, $bufsz);
            print {$out} decode_base64($chunk);
            $buf = substr($buf, $bufsz);
        }
    }
    if (length $buf) {
        print {$out} decode_base64($buf); # end chunk
    }
}

sub encode {
    my $buf;
    while (read $in, $buf, $bufsz) {
        print {$out} encode_base64($buf);
    }
}

sub usage {
    die "usage: $0 [-dv] [-o FILE] [FILE]\n";
}

__END__

=pod

=head1 NAME

base64 - encode and decode base64 data

=head1 SYNOPSIS

base64 [-dv] [-o FILE] [FILE]

=head1 DESCRIPTION

When encoding (the default mode), a binary file
is read and a base64 format file is created.
If no input file argument is provided, or file is '-',
stdin will be used.
The base64 output contains 76 characters per line.
Output is written to stdout by default.

When decoding, the input file is expected to contain
only valid base64 characters (alphanumeric, '+', '/' and '=').
Spaces are ignored when decoding. Selecting a binary file
as input will result in an error.

=head2 OPTIONS

The following options are available:

=over 4

=item -d

Decode data

=item -o FILE

Write output to the specified FILE

=item -v

Print version number and exit

=back

=head1 BUGS

No option exists for wrapping encoded base64 output at different
column widths.

It might be desirable to ignore unrecognised input characters when
decoding. This version of base64 has no option for relaxing the
input validation.

=head1 AUTHOR

Written by Michael Mikonos.

=head1 COPYRIGHT

Copyright (c) 2023 Michael Mikonos.

This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.

