#!/usr/bin/perl
=head1 NAME

email2pdf - Render email as pdf

=head1 SYNOPSIS

B<email2pdf> [options] filein fileout

=head1 ARGUMENTS

=over 8

=item I<filein>

RFC2822 email. Use - for stdin.

=item I<fileout>

PDF file. Use - for stdout.

=back

=head1 OPTIONS

=over 8

=item B<--help>

Print a brief help message and exits.

=item B<--man>

Prints the manual page and exits.

=item B<--header>=header.html

Set header file. This file will be processed using Template-Toolkit.

=back

=head1 COPYRIGHT AND AUTHORS

Copyright (c) 2009 Mathieu Parent <math.parent@gmail.com>

Latest version can be grabbed from:
http://svn.freeswitch.org/svn/freeswitch/trunk/contrib/sathieu/email2pdf/

=head1 LICENSE

This  program is free  software; you can redistribute  it and/or
modify it  under the terms of the GNU  General Public License as
published by the  Free Software Foundation; either version 2, or
(at your option) any later version.

This program is  distributed in the hope that it will be useful,
but WITHOUT  ANY WARRANTY; without even the  implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.

You can view the  GNU General Public License, online, at the GNU
Project's homepage; see <http://www.gnu.org/licenses/gpl.html>.

=cut


use strict;
use warnings;

use Getopt::Long;
use Pod::Usage;

use File::Temp qw/tempdir tempfile/;
use Email::MIME;
use Email::MIME::ContentType;
use Template;

# =========================================================================== #
# CONFIG

my $mime_types = {
	#plain and html
	'text/plain' => 'plain',
	'text/html' => 'html',
	
	#images
	'image/gif' => 'gif',
	'image/jpeg' => 'jpeg',
	'image/png' => 'png',
	'image/tiff' => 'tiff',

	#OpenDocument
	'application/vnd.oasis.opendocument.text' => 'opendoctext',
	'application/vnd.oasis.opendocument.spreadsheet' => 'opendocspreadsheet',
	'application/vnd.oasis.opendocument.presentation' => 'opendocpresentation',
	'application/vnd.oasis.opendocument.graphics' => 'opendocgraphics',
	                                                        
	#MS office
	'application/vnd.ms-excel' => 'msexcel',
	'application/msexcel' => 'msexcel',
	'application/x-msexcel' => 'msexcel',
	'application/vnd.ms-powerpoint' => 'mspowerpoint',
	'application/vnd.ms-word' => 'msword',
	'application/msword' => 'msword',
	
	'application/pdf' => 'pdf',
	
};
my $mime_drivers = {
	#plain and html
	'plain' => ['.txt', 'html2ps FILE | ps2pdf - -'],
	'html' => ['.html', 'html2ps FILE | ps2pdf - -'],
	
	#images
	'gif' => ['.gif', 'convert FILE pdf:-'],
	'jpeg' => ['.jpeg', 'convert FILE pdf:-'],
	'png' => ['.png', 'convert FILE pdf:-'],
	'tiff' => ['.tiff', 'convert FILE pdf:-'],
	
	#OpenDocument
	'opendoctext' => ['.odt', 'ooextract --pdf --stdout FILE'],
	'opendocspreadsheet' => ['.ods', 'ooextract --pdf --stdout FILE'],
	'opendocpresentation' => ['.odp', 'ooextract --pdf --stdout FILE'],
	'opendocgraphics' => ['.odg', 'ooextract --pdf --stdout FILE'],

	#MS office
	'msexcel' => ['.xls', 'ooextract --pdf --stdout FILE'],
	'mspowerpoint' => ['.ppt', 'ooextract --pdf --stdout FILE'],
	'msword' => ['.doc', 'ooextract --pdf --stdout FILE'],
	'pdf' => ['.pdf', 'cat FILE'],
};
# Template config
my $tt_config = {
	'ABSOLUTE' => 1,
};

# =========================================================================== #
# vars
my @files;
push @files, (0);

my $email;
my $output_dir;

# options
my $o_help = 0;
my $o_man = 0;
my $o_header = 'header.html';
# =========================================================================== #
sub process_raw
{
	my $filename = shift || 'noname';
	my $content_type = shift;
	my $raw = shift;
	my $is_header = shift || 0;
	if(defined($mime_types->{$content_type})) {
		if(defined($mime_drivers->{$mime_types->{$content_type}})) {
			my @mime_driver = $mime_drivers->{$mime_types->{$content_type}};
			my $suffix = $mime_driver[0][0];
			my $command = $mime_driver[0][1];
			my ($fh_in, $filename_in) = tempfile( DIR => $output_dir, SUFFIX => $suffix );
			system("chmod ugo+r $filename_in");
			print { $fh_in } $raw;
			close $fh_in or warn $! ? "Error closing sort pipe: $!" : "Exit status $? from sort";
			$command =~ s/FILE/$filename_in/;
			print STDERR "Processing ";
			print STDERR "header " if $is_header;
			print STDERR "'$filename' ($content_type). From '$filename_in'. ";
			my $out = `$command`;
			if(!$out) {
				print STDERR "Empty generated PDF.\n";
				return;
			}
			my ($fh_out, $filename_out) = tempfile( DIR => $output_dir, SUFFIX => '.pdf' );
			print STDERR "To '$filename_out'. ";
			print { $fh_out } $out;
			close $fh_out or warn $! ? "Error closing sort pipe: $!" : "Exit status $? from sort";
			if($is_header) {
				shift @files;
				unshift @files, ($filename_out);
			} else {
				push @files, ($filename_out);
			}
			print STDERR "\n";
		} else {
			print STDERR "Undefined mime driver: $mime_types->{$content_type}\n";
		}
	} else {
		print STDERR "Undefined mime type: $content_type\n";
	}
}

sub process_header
{
	my $parsed = shift;
	unshift @files, (0) if $files[0];
	# define template variables for replacement
	my $vars = {
		body => '', #$parsed->body,
		body_raw => $parsed->body_raw,
		content_type => $parsed->content_type,
		mail => $parsed,
	};

	# specify input filename, or file handle, text reference, etc.
	my $content_type = `file --mime-type --brief $o_header`;
	$content_type =~ s/\s+$//; # trim end whitespace
	my $header = '';

	# create Template object
	my $template = Template->new($tt_config);

	# process input template, substituting variables
	$template->process($o_header, $vars, \$header)
		|| die $template->error();
	process_raw $parsed->filename, $content_type, $header, 1;
}

sub process_part
{
	my $part = shift;
	my $parsed_content_type = parse_content_type($part->content_type);
	my $content_type = $parsed_content_type->{'discrete'}.'/'.$parsed_content_type->{'composite'};
	if($content_type eq 'multipart/alternative') {
		my @subparts = $part->subparts;
		foreach my $subpart (@subparts) {
			return if $subpart->parts > 1; # multipart
			my $parsed_subcontent_type = parse_content_type($subpart->content_type);
			my $subcontent_type = $parsed_subcontent_type->{'discrete'}.'/'.$parsed_subcontent_type->{'composite'};
			process_raw $subpart->filename, $subcontent_type, $subpart->body, 1;
		}
	} else {
		process_raw $part->filename, $content_type, $part->body;
	}
}

# =========================================================================== #
my $result = GetOptions(
    'help|?' => \$o_help,
    'man' => \$o_man,
    'header=s' => \$o_header,
    );

pod2usage(-exitstatus => 0, -verbose => 2) if $o_man;
pod2usage(-exitstatus => 0, -verbose => 1) if $o_help;

my $file_in = shift @ARGV;
if(!defined($file_in)) {
	print "Missing argument: filein\n";
	pod2usage(-exitstatus => 0, -verbose => 1);
	exit 1;
}
my $file_out = shift @ARGV;
if(!defined($file_out)) {
	print "Missing argument: fileout\n";
	pod2usage(-exitstatus => 0, -verbose => 1);
	exit 1;
}

# =========================================================================== #
# setup temp dir
$output_dir = tempdir( CLEANUP => 1 );
system("chmod ugo+rwx $output_dir");

# load mail
if($file_in eq "-") {
	$email = Email::MIME->new(join('',<>));
} else {
	open(FILEIN, '<', $file_in);
	$email = Email::MIME->new(join('',<FILEIN>));
	close(FILEIN);
}

my $email_parsed_content_type = parse_content_type($email->content_type);
my $email_content_type = $email_parsed_content_type->{'discrete'}.'/'.$email_parsed_content_type->{'composite'};
if($email_content_type eq 'multipart/alternative') {
	process_part $email;
} else {
	my @parts = $email->subparts;
	foreach my $part (@parts) {
		process_part $part;
	}
}
process_header($email);
print "Merging files: ".join(' ', @files)."\n";
system('pdftk '.join(' ', @files)." cat output $file_out");
