/usr/local/CPAN/Matts-Message-Parser/Matts/Message.pm
package Matts::Message;
use strict;
use MIME::Base64 qw(encode_base64);
sub new {
bless {
encodings => [],
bin_headers => {},
headers => {},
body_parts => [],
attachments => [],
raw => '',
}, shift;
}
sub raw_headers {
my $self = shift;
return $self->{raw};
}
sub header {
my $self = shift;
my $k = shift;
my $key = lc($k);
if (@_) {
$self->{raw} .= "$k: @_";
if (exists $self->{headers}{$key}) {
push @{$self->{headers}{$key}}, @_;
}
else {
$self->{headers}{$key} = [@_];
}
return $self->{headers}{$key}[-1];
}
if (wantarray) {
return unless exists $self->{headers}{$key};
return @{$self->{headers}{$key}};
}
else {
return '' unless exists $self->{headers}{$key};
return $self->{headers}{$key}[-1];
}
}
sub binary_header {
my $self = shift;
my $key = lc(shift);
if (@_) {
if (exists $self->{bin_headers}{$key}) {
push @{$self->{bin_headers}{$key}}, @_;
}
else {
$self->{bin_headers}{$key} = [@_];
}
return $self->{bin_headers}{$key}[-1];
}
if (wantarray) {
return unless exists $self->{bin_headers}{$key};
return @{$self->{bin_headers}{$key}};
}
else {
return '' unless exists $self->{bin_headers}{$key};
return $self->{bin_headers}{$key}[-1];
}
}
sub header_del {
my $self = shift;
my $header = lc(shift);
$self->{raw} =~ s/^$header:.*?^(\S)/$1/ism;
delete $self->{headers}{$header};
}
sub headers {
my $self = shift;
return keys %{$self->{headers}};
}
sub binary_headers {
my $self = shift;
return keys %{$self->{bin_headers}};
}
sub add_body_part {
my $self = shift;
my ($type, $fh) = @_;
$type ||= 'text/plain';
my $enc = 'null';
if ($type =~ s/;(.*$)//) { # strip everything after first semi-colon
my $cs = $1;
if ($cs =~ /charset="?([\w-]+)/) {
$enc = $1;
}
}
$type =~ s/[^a-zA-Z\/]//g; # strip inappropriate chars
push @{ $self->{encodings} }, $enc;
push @{ $self->{body_parts} }, [ $type => $fh ];
}
sub add_attachment {
my $self = shift;
my ($type, $fh, $name) = @_;
push @{ $self->{attachments} }, {
filename => $name,
type => $type,
fh => $fh,
};
}
sub body {
my $self = shift;
my $type = shift;
return unless @{ $self->{body_parts} };
if ($type) {
# warn("body has ", scalar(@{ $self->{body_parts} }), " [$type]\n");
foreach my $body ( @{ $self->{body_parts} } ) {
# warn("type: $body->[0]\n");
if (lc($type) eq lc($body->[0])) {
return wantarray ? @$body : $body->[1];
}
}
}
return wantarray ? @{ $self->{body_parts}[0] } : $self->{body_parts}[0][1];
}
sub bodies {
my $self = shift;
my @ret;
foreach my $body ( @{ $self->{body_parts} } ) {
push @ret, lc($body->[0]), $body->[1];
}
return @ret;
}
sub body_enc {
my $self = shift;
my ($id) = @_;
return $self->{encodings}[$id];
}
sub attachment {
my $self = shift;
return $self->{attachments}[shift];
}
sub attachments {
my $self = shift;
return @{ $self->{attachments} };
}
sub num_attachments {
my $self = shift;
return scalar @{ $self->{attachments} };
}
sub to_string {
my $self = shift;
my $output = '';
my $sub = sub { $output .= join('', @_) };
$self->_walk_tree($sub);
return $output;
}
sub size {
my $self = shift;
@_ and $self->{size} = shift;
$self->{size};
}
sub mtime {
my $self = shift;
@_ and $self->{mtime} = shift;
$self->{mtime};
}
sub dump {
my $self = shift;
my ($fh) = @_;
$fh ||= \*STDOUT;
my $sub = sub { print $fh @_ };
$self->_walk_tree($sub);
}
sub _walk_tree {
my $msg = shift;
my ($sub) = @_;
# Munge the whole thing into a big old multipart/mixed thingy.
$msg->header_del('content-type');
my $boundary = "----=_NextPart_000_" . $$ . time;
$msg->header('Content-Type', "multipart/mixed; boundary=\"$boundary\"\n");
$sub->($msg->raw_headers);
# Output Received headers first.
#foreach my $value ($msg->header('Received')) {
# $sub->("Received: $value\n");
#}
# Output remaining headers in random order
#foreach my $header ($msg->headers) {
# next if lc($header) eq 'received';
#foreach my $value ($msg->header($header)) {
# $header =~ s/(^|-)(\w)/$1 . uc($2)/eg;
#$sub->("$header: $value\n");
#}
#}
$sub->("\n");
$sub->("This is a dump of a parsed message. Ignore this bit.\n\n");
$sub->("--$boundary\n");
my $body_boundary = "----=_NextPart_111_" . $$ . time;
$sub->("Content-Type: multipart/alternate; boundary=\"$body_boundary\"\n");
$sub->("\n");
my @body_parts = $msg->bodies;
while (@body_parts) {
my ($type, $fh) = splice(@body_parts, 0, 2);
$sub->("--$body_boundary\n");
$sub->("Content-Type: $type\n");
if ($type !~ /^text\//) {
$sub->("Content-Transfer-Encoding: base64\n");
$sub->("\n");
local $/;
$sub->(encode_base64(<$fh>));
$sub->("\n");
}
else {
$sub->("Content-Transfer-Encoding: 8bit\n");
$sub->("\n");
local $/;
$sub->(<$fh>);
$sub->("\n");
}
}
$sub->("--$body_boundary--\n\n");
foreach my $att ($msg->attachments) {
$sub->("--$boundary\n");
$sub->("Content-Type: $att->{type}\n");
$sub->("Content-Disposition: attachment; filename=$att->{filename}\n");
$sub->("Content-Transfer-Encoding: base64\n");
$sub->("\n");
my $fh = $att->{fh};
local $/;
$sub->(encode_base64(<$fh>));
$sub->("\n");
}
$sub->("--$boundary--\n");
}
1;
__END__