package Mail::Action::Request; use strict; use warnings; use vars '$VERSION'; $VERSION = '0.46'; use Email::MIME; use Email::Address; use Email::MIME::Modifier; sub new { my ($class, $message_text, @args) = @_; my $message = Email::MIME->new( $message_text ); my $self = bless { Message => $message, headers => {}, recipient => '', @args, }, $class; $self->init(); return $self; } sub init { my $self = shift; $self->add_headers(); $self->add_recipient(); $self->remove_recipient( $self->recipient_header(), $self->recipient() ); $self->find_key(); } sub message { my $self = shift; $self->{Message}; } sub headers { my $self = shift; $self->{headers}; } BEGIN { no strict 'refs'; for my $attribute (qw( key recipient recipient_header )) { *{ $attribute } = sub { my $self = shift; $self->{$attribute} = shift if @_; $self->{$attribute}; }; } } sub store_header { my ($self, $header, $value) = @_; my $headers = $self->headers(); $headers->{$header} = $value; } sub recipient_headers { return qw( Delivered-To To Cc ); } sub header { my ($self, $name) = @_; my $headers = $self->headers(); return $self->message->header( $name ) unless exists $headers->{$name}; return wantarray ? @{ $headers->{$name} } : $headers->{$name}[0]; } sub add_headers { my $self = shift; $self->find_headers(qw( Subject )); $self->find_address_headers(); } sub find_headers { my ($self, @headers) = @_; my $message = $self->message(); for my $header (map { ucfirst( lc( $_ ) ) } @headers) { $self->store_header( $header, [ $message->header( $header ) ] ); } } sub find_address_headers { my $self = shift; my $message = $self->message(); for my $header (map { ucfirst(lc($_)) } $self->recipient_headers(), 'From') { my @value = map { Email::Address->parse( $_ ) } $message->header( $header ); $self->store_header( $header, \@value ); } } sub add_recipient { my $self = shift; my $message = $self->message(); my $recipient = $self->recipient(); if ($recipient) { $self->recipient_header( '' ); } else { for my $header (map { ucfirst( lc( $_ ) ) } $self->recipient_headers()) { next unless $recipient = $self->header( $header ); $self->recipient_header( $header ); last; } } $self->recipient( ( Email::Address->parse( $recipient ) )[0] ); } sub remove_recipient { my ($self, $header, $recipient) = @_; use Carp; Carp::cluck( 'no' ) unless $recipient; my $recip_addy = $recipient->address(); for my $remove_header ( 'To', 'Cc' ) { my ($found, @cleaned); my @addresses = $self->header( $remove_header ); while ( my $address = shift @addresses ) { if ( not( $found ) and $address->address() eq $recip_addy ) { push @cleaned, @addresses; $found = 1; last; } else { push @cleaned, $address; } } next unless $found; $self->store_header( $remove_header, \@cleaned ); return; } } sub find_key { my $self = shift; # be paranoid; explicitly copy captured match variables $self->key( "$1" ) if $self->recipient() =~ /\+(\w+)/; } sub process_body { my ($self, $address) = @_; my $attributes = $address->attributes(); my $body = $self->remove_sig(); while (@$body and $body->[0] =~ /^(\w+):\s*(.*)$/) { my ($directive, $value) = (lc( $1 ), $2); $address->$directive( $value ) if exists $attributes->{ $directive }; shift @$body; } return $body; } sub remove_sig { my $self = shift; my $message = $self->message(); my $body = ( $message->parts() )[0]->body(); my @lines; for my $line (split(/\n/, $body)) { last if $line eq '-- '; push @lines, $line; } return \@lines; } sub copy_headers { my $self = shift; my $message = $self->message(); my $headers = $self->headers(); my %copy; for my $header ( $message->headers() ) { next if $header eq 'From '; my @value = exists $headers->{$header} ? $self->header( $header ) : $message->header( $header ); next unless @value; $copy{ ucfirst( lc( $header ) ) } = join(', ', @value); } return \%copy; } 1; __END__ =head1 NAME Mail::Action::Request - base for building modules that represent incoming mail =head1 SYNOPSIS use base 'Mail::Action::Request'; =head1 DESCRIPTION =head1 METHODS Mail::Action::Request objects have the following methods in several categories: =head2 Creation and Initialization =over 4 =item C =item C =back =head2 Accessors =over 4 =item C Returns the raw L object representing the incoming message. =item C Returns a hash reference of known message headers and their values. This can be dangerous, so use it cautiously. =item C If the invocant has a header of the given C<$name>, returns the first or all of the values associated with that header, depending on the context of the call. This will return nothing if the named header does not exist. =item C Returns the key associated with this request, if it exists. (The key of a request is usually, but not always, the extension of an extended e-mail address: C in Eyou+extension@example.comE, for example.) You can use this to store a key, if you must. =item C Returns the e-mail address for which this request exists. It is difficult to determine this reliably and generically across a whole swath of mail servers, but this makes its best guess. Note that this I contain the key, if it exists. You can use this to store a recipient, if you must. =item C Returns the name of the header from which the recipient address came. You'll almost never need this, but when you do need it, you'll really need it. You can use this to store a recipient header, if absolutely necessary. =item C Depending on your mail server, you may need to override this in your own applications to provide a list of headers to check for the e-mail address to which the server delivered a message. With Postfix, at least, it appears that the C header is always correct. This will fall back to C and C as next-best guesses. Ideally, there will be roles to apply for your mail server of choice that handle this for you automatically. =item C Given the C<$name> of a header and an array reference in C<$value>, representing the value of that header, stores both in the invocant's headers structure. =back =head2 Other Methods If you want to subclass this, you might care about the methods: =over 4 =item C Adds the C and address headers to the object. =item C Attempts to find every header in the argument list in the message. Adds every header found to the list of known headers in the object. =item C Adds all recipient headers (see C) and the C header to the object. =item C Attempts to set the recipient for this request, if there's not one set already. Otherwise, it checks all of the headers from C, in order, trying to find a likely recipient. =item C Removes the C<$recipient> from the C<$recipient_header>, leaving the rest of the message headers undisturbed. The idea here is to figure out which address I this message, avoid sending the mail to that address again, and pass it on appropriately otherwise. =item C Attempts to find and set the key for this request. The key is the portion of the recipient address immediately following the C<+> sign before the domain name. That is, for C the key is C. Override this if you have a different way to mark keys. =item C Given the equivalent of an C object, removes the signature of the message, removes and processes all of the directives from the body(using the C<$address>), and returns a reference to an array containing the remaining lines of the body. =item C Attempts to remove the signature from the message by removing everything following a line containing C<-- >. This returns a reference to an array containing the remaining lines. It I to do the right thing with multipart messages, but it looks only in the first part for the signature. This may or may not be correct, depending on how broken the sending MUA was. =item C Copies all headers from the incoming message to a hash reference where the key is the name of the header and the value is a comma-separated list of values for the header. This explicitly I the C header that sometimes C seems to add in some cases. =back =head1 SEE ALSO L and L for example uses. See L, L, and L for related modules. =head1 AUTHOR chromatic, C. =head1 BUGS No known bugs. =head1 COPYRIGHT Copyright (c) 2003 - 2009 chromatic. Some rights reserved. You may use, modify, and distribute this module under the same terms as Perl 5.10 itself.