File Coverage

blib/lib/MMS/Mail/Parser.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package MMS::Mail::Parser;
2              
3 5     5   112400 use warnings;
  5         13  
  5         191  
4 5     5   34 use strict;
  5         9  
  5         220  
5              
6 5     5   28 use base "Class::Accessor";
  5         14  
  5         5113  
7              
8 5     5   17912 use IO::Wrap;
  0            
  0            
9             use IO::File;
10             use MIME::Parser;
11              
12             use MMS::Mail::Message;
13             use MMS::Mail::Parser;
14             use MMS::Mail::Provider;
15              
16             # These are eval'd so the user doesn't have to install all Providers
17             eval {
18             require MMS::Mail::Provider::UKVodafone;
19             require MMS::Mail::Provider::UK02;
20             require MMS::Mail::Provider::UKOrange;
21             require MMS::Mail::Provider::UKTMobile;
22             require MMS::Mail::Provider::UKVirgin;
23             require MMS::Mail::Provider::UK3;
24             };
25              
26             =head1 NAME
27              
28             MMS::Mail::Parser - A class for parsing MMS (or picture) messages via email.
29              
30             =head1 VERSION
31              
32             Version 0.14
33              
34             =cut
35              
36             our $VERSION = '0.14';
37              
38             =head1 SYNOPSIS
39              
40             This class takes an MMS message and parses it into two 'standard' formats (an MMS::Mail::Message and MMS::Mail::Message::Parsed) for further use. It is intended to make parsing MMS messages network/provider agnostic such that a 'standard' object results from parsing, independant of the network/provider it was sent through.
41              
42             =head2 Code usage example
43              
44             This example demonstrates the use of the two stage parse. The first pass provides an MMS::Mail::Message instance that is then passed through to the C method that attempts to determine the Network provider the message was sent through and extracts the relevant information and parses it into an MMS::Mail::Message::Parsed instance.
45              
46             use MMS::Mail::Parser;
47             my $mms = MMS::Mail::Parser->new();
48             my $message = $mms->parse(\*STDIN);
49             if (defined($message)) {
50             my $parsed = $mms->provider_parse;
51             print $parsed->header_subject."\n";
52             }
53              
54             =head2 Examples of input
55              
56             MMS::Mail::Parser has the same input methods as L.
57              
58             # Parse from a filehandle:
59             $entity = $parser->parse(\*STDIN);
60              
61             # Parse an in-memory MIME message:
62             $entity = $parser->parse_data($message);
63              
64             # Parse a file based MIME message:
65             $entity = $parser->parse_open("/some/file.msg");
66              
67             # Parse already-split input (as "deliver" would give it to you):
68             $entity = $parser->parse_two("msg.head", "msg.body");
69              
70             =head2 Examples of parser modification
71              
72             MMS::Mail::Parser uses MIME::Parser as it's parsing engine. The MMS::Mail::Parser class creates it's own MIME::Parser instance if one is not passed in via the C or C methods. There are a number of reasons for providing your own parser, such as forcing all attachment storage to be done in memory than on disk (providing a speed increase to your application at the cost of memory usage).
73              
74             my $parser = new MIME::Parser;
75             $parser->output_to_core(1);
76             my $mmsparser = new MMS::Mail::Parser;
77             $mmsparser->mime_parser($parser);
78             my $message = $mmsparser->parse(\*STDIN);
79             if (defined($message)) {
80             my $parsed = $mms->provider_parse;
81             }
82              
83             =head2 Examples of error handling
84              
85             The parser contains an error stack and will ultimately return an undef value from any of the main parse methods if an error occurs. The last error message can be retreived by calling C method.
86              
87             my $message = $mmsparser->parse(\*STDIN);
88             unless (defined($message)) {
89             print STDERR $mmsparser->last_error."\n";
90             exit(0);
91             }
92              
93             =head2 Miscellaneous methods
94              
95             There are a small set of miscellaneous methods available. The C method is provided so that a new MIME::Parser instance does not have to be created to supply a separate storage directory for parsed attachments (however any attachments created as part of the process are removed when the message is destroyed so the lack of specification of a storage location is not a requirement for small scale message parsing ).
96              
97             # Provide debug ouput to STDERR
98             $mmsparser->debug(1);
99              
100             # Set an output directory for MIME::Parser
101             $mmsparser->output_dir('/tmp');
102              
103             # Get/set an array reference to the error stack
104             my $errors = $mmsparser->errors;
105              
106             # Get/set the MIME::Parser instance used by MMS::Parser
107             $mmsparser->mime_parser($parser);
108              
109             # Set the characters to be stripped from the returned
110             # MMS::Mail::Message and MMS::Mail::Message::Parsed instances
111             $mmsparser->strip_characters("\r\n");
112              
113             # Set the regular expression map for accessors
114             # Removes trailing EOL chars from subject and body accessors
115             my $map = { header_subject => 's/\n$//g',
116             header_datetime => 's/\n$//g'
117             };
118             $mmsparser->cleanse_map($map);
119              
120             =head2 Tutorial
121              
122             A tutorial can be accessed at http://www.monkeyhelper.com/2006/02/roll_your_own_flickrpoddr_or_v.html
123              
124             =head1 METHODS
125              
126             The following are the top-level methods of MMS::Mail::Parser class.
127              
128             =head2 Constructor
129              
130             =over
131              
132             =item C
133              
134             Return a new MMS::Mail::Parser instance. Valid attributes are:
135              
136             =over
137              
138             =item C MIME::Parser
139              
140             Passed as a hash reference, C specifies the MIME::Parser instance to use instead of MMS::Mail::Parser creating it's own.
141              
142             =item C INTEGER
143              
144             Passed as a hash reference, C determines whether debuging information is outputted to standard error (defaults to 0 - no debug output).
145              
146             =item C STRING
147              
148             Passed as a hash reference, C defines the characters to strip from the MMS::Mail::Message (and MMS::Mail::Message::Parsed) class C and C properties.
149              
150             =item C HASH REF
151              
152             Passed as a hash reference, C defines regexes (or function references) to apply to instance properties from the MMS::Mail::Message (and MMS::Mail::Message::Parsed) classes.
153              
154             =back
155              
156             =back
157              
158             =head2 Regular Methods
159              
160             =over
161              
162             =item C INSTREAM
163              
164             Instance method - Returns an MMS::Mail::Message instance by parsing the input stream INSTREAM
165              
166             =item C DATA
167              
168             Instance method - Returns an MMS::Mail::Message instance by parsing the in memory string DATA
169              
170             =item C EXPR
171              
172             Instance method - Returns an MMS::Mail::Message instance by parsing the file specified in EXPR
173              
174             =item C HEADFILE, BODYFILE
175              
176             Instance method - Returns an MMS::Mail::Message instance by parsing the header and body file specified in HEADFILE and BODYFILE filenames
177              
178             =item C MMS::MailMessage
179              
180             Instance method - Returns an MMS::Mail::Message::Parsed instance by attempting to discover the network provider the message was sent through and parsing with the appropriate MMS::Mail::Provider. If an MMS::Mail::Message instance is supplied as an argument then the C method will parse the supplied MMS::Mail::Message instance. If a provider has been set via the provider method then that parser will be used by the C method instead of attempting to discover the network provider from the MMS::Mail::Message attributes.
181              
182             =item C DIRECTORY
183              
184             Instance method - Returns the C parameter used with the MIME::Parser instance when invoked with no argument supplied. When an argument is supplied it sets the C property used by the MIME::Parser to the value of the argument supplied.
185              
186             =item C MIME::Parser
187              
188             Instance method - Returns the MIME::Parser instance used by MMS::Mail::Parser (if created) when invoked with no argument supplied. When an argument is supplied it sets the MIME::Parser instance used by the MMS::Mail::Parser instance to parse messages.
189              
190             =item C MMS::Mail::Provider
191              
192             Instance method - Returns an instance for the currently set provider property when invoked with no argument supplied. When an argument is supplied it sets the provider to the supplied instance.
193              
194             =item C STRING
195              
196             Instance method - Returns the characters to be stripped from the returned MMS::Mail::Message and MMS::Mail::Message::Parsed instances. When an argument is supplied it sets the strip characters to the supplied string.
197              
198             =item C HASHREF
199              
200             Instance method - This method allows a regular expression or subroutine reference to be applied when an accessor sets a value, allowing message values to be cleansed or modified. These accessors are C, C, C, C and C.
201              
202             The method expects a hash reference with key values as one of the above public accessor method names and values as a scalar in the form of a regular expression or as a subroutine reference.
203              
204             =item C
205              
206             Instance method - Returns the error stack used by the MMS::Mail::Parser instance as an array reference.
207              
208             =item C
209              
210             Instance method - Returns the last error from the stack.
211              
212             =item C INTEGER
213              
214             Instance method - Returns a number indicating whether STDERR debugging output is active (1) or not (0). When an argument is supplied it sets the debug property to that value.
215              
216             =back
217              
218             =head1 AUTHOR
219              
220             Rob Lee, C<< >>
221              
222             =head1 BUGS
223              
224             Please report any bugs or feature requests to
225             C, or through the web interface at
226             L.
227             I will be notified, and then you'll automatically be notified of progress on
228             your bug as I make changes.
229              
230             =head1 NOTES
231              
232             Please read the Perl artistic license ('perldoc perlartistic') :
233              
234             10. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
235             WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES
236             OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
237              
238             =head1 ACKNOWLEDGEMENTS
239              
240             As per usual this module is sprinkled with a little Deb magic.
241              
242             =head1 COPYRIGHT & LICENSE
243              
244             Copyright 2005 Rob Lee, all rights reserved.
245              
246             This program is free software; you can redistribute it and/or modify it
247             under the same terms as Perl itself.
248              
249             =head1 SEE ALSO
250              
251             L, L, L
252              
253             =cut
254              
255             my @Accessors=( "message",
256             "mime_parser",
257             "debug",
258             "errors",
259             "output_dir",
260             "provider",
261             "strip_characters",
262             "cleanse_map"
263             );
264              
265             # Class data retrieval
266             sub _Accessors {
267             return \@Accessors;
268             }
269              
270             __PACKAGE__->mk_accessors(@{__PACKAGE__->_Accessors});
271              
272              
273             sub new {
274              
275             my $type = shift;
276             my $self = SUPER::new $type( {@_} );
277              
278             # Set defaults
279             unless (defined $self->get('debug')) {
280             $self->set('debug',0);
281             }
282             unless (defined $self->get('mime_parser')) {
283             $self->set('mime_parser',undef);
284             }
285             unless (defined $self->get('strip_characters')) {
286             $self->set('strip_characters',undef);
287             }
288             unless (defined $self->get('cleanse_map')) {
289             $self->set('cleanse_map',undef);
290             }
291             unless (defined $self->get('message')) {
292             $self->set('message',undef);
293             }
294             $self->set('errors',[]);
295              
296             return $self;
297              
298             }
299              
300             sub parse {
301              
302             my $self = shift;
303             my $in = wraphandle(shift);
304              
305             print STDERR "Starting to parse\n" if ($self->debug);
306             return $self->_parse($in);
307             }
308              
309             sub parse_data {
310              
311             my $self = shift;
312             my $in = shift;
313              
314             print STDERR "Starting to parse string\n" if ($self->debug);
315             return $self->_parse($in);
316             }
317              
318             sub parse_open {
319             my $self = shift;
320             my $opendata = shift;
321              
322             my $in = IO::File->new($opendata) || $self->_add_error("Could not open file - $opendata");
323             return $self->_parse($in);
324             }
325              
326             sub parse_two {
327             my $self = shift;
328             my $headfile = shift;
329             my $bodyfile = shift;
330              
331             my @lines;
332             foreach ($headfile, $bodyfile) {
333             open IN, "<$_" || $self->_add_error("Could not open file - $_");
334             push @lines, ;
335             close IN;
336             }
337             return $self->parse_data(\@lines);
338             }
339              
340             sub _parse {
341              
342             my $self = shift;
343             my $in = shift;
344              
345             # Set up a default parser
346             unless (defined $self->mime_parser) {
347             my $parser = new MIME::Parser;
348             $parser->ignore_errors(1);
349             $self->mime_parser($parser);
350             }
351              
352             if (defined $self->output_dir) {
353             $self->mime_parser->output_dir($self->output_dir);
354             }
355              
356             unless (defined $self->mime_parser) {
357             $self->_add_error("Failed to create parser");
358             return undef;
359             }
360              
361             print STDERR "Created MIME::Parser\n" if ($self->debug);
362              
363             my $message = new MMS::Mail::Message;
364             if (defined $self->strip_characters) {
365             $message->strip_characters($self->strip_characters);
366             }
367             if (defined $self->cleanse_map) {
368             $message->cleanse_map($self->cleanse_map);
369             }
370             $self->message($message);
371              
372             print STDERR "Created MMS::Mail::Message\n" if ($self->debug);
373              
374             my $parsed = eval { $self->mime_parser->parse($in) };
375             if (defined $@ && $@) {
376             $self->_add_error($@);
377             }
378             unless ($self->_recurse_message($parsed)) {
379             $self->_add_error("Failed to parse message");
380             return undef;
381             }
382              
383             print STDERR "Parsed message\n" if ($self->debug);
384              
385             unless ($self->message->is_valid) {
386             $self->_add_error("Parsed message is not valid");
387             print STDERR "Parsed message is not valid\n" if ($self->debug);
388             return undef;
389             }
390              
391             print STDERR "Parsed message is valid\n" if ($self->debug);
392              
393             return $self->message;
394              
395             }
396              
397             sub _recurse_message {
398              
399             my $self = shift;
400             my $mime = shift;
401              
402             unless (defined($mime)) {
403             $self->_add_error("No mime message supplied");
404             return 0;
405             }
406              
407             print STDERR "Parsing MIME Message\n" if ($self->debug);
408              
409             my $header = $mime->head;
410             unless (defined($self->message->header_from)) {
411             $self->message->header_datetime($header->get('Date'));
412             $self->message->header_from($header->get('From'));
413             $self->message->header_to($header->get('To'));
414             $self->message->header_subject($header->get('Subject'));
415             my $received = $header->get('Received', 0);
416             if ($received=~m/\[(.+)\.(.+)\.(.+)\.(.+)\]/) {
417             $self->message->header_received_from(join(".",$1,$2,$3,$4));
418             }
419             print STDERR "Parsed Headers\n" if ($self->debug);
420             }
421              
422             my @multiparts;
423              
424             if($mime->parts == 0) {
425             $self->message->body_text($mime->bodyhandle->as_string);
426             print STDERR "No parts to MIME mail - grabbing header text\n" if ($self->debug);
427             $mime->bodyhandle->purge;
428             }
429              
430             print STDERR "Recursing through message parts\n" if ($self->debug);
431             foreach my $part ($mime->parts) {
432             my $bh = $part->bodyhandle;
433              
434             print STDERR "Message contains ".$part->mime_type."\n" if ($self->debug);
435              
436             if ($part->mime_type eq 'text/plain') {
437             # Compile a complete body text and add to attachments for later
438             # parsing by Provider class
439             if (defined($self->message->body_text())) {
440             $self->message->body_text(($self->message->body_text()) . $bh->as_string);
441             } else {
442             $self->message->body_text($bh->as_string);
443             }
444             print STDERR "Adding attachment to stack\n" if ($self->debug);
445             $self->message->add_attachment($part);
446             next;
447             }
448              
449             if ($part->mime_type =~ /multipart/) {
450             print STDERR "Adding multipart to stack for later processing\n" if ($self->debug);
451             push @multiparts, $part;
452             next;
453             } else {
454             print STDERR "Adding attachment to stack\n" if ($self->debug);
455             $self->message->add_attachment($part);
456             }
457              
458             }
459             # Loop through multiparts
460             print STDERR "Preparing to loop through multipart stack\n" if ($self->debug);
461             foreach my $multi (@multiparts) {
462             return $self->_recurse_message($multi);
463             }
464              
465             return 1;
466              
467             }
468              
469             sub _decipher {
470              
471             my $self = shift;
472              
473             unless (defined($self->message)) {
474             $self->_add_error("No MMS mail message supplied");
475             return undef;
476             }
477              
478             if (defined($self->provider)) {
479             my $message;
480             #eval( 'require '.$self->provider.';'.'$message='.$self->provider.'::parse($self->{message})');
481             $message = $self->provider->parse($self->message);
482              
483             unless (defined $message) {
484             print STDERR "Failed to parse message with custom Provider Object\n" if ($self->debug);
485             if (defined($@) && $@) {
486             $self->_add_error($@);
487             }
488             }
489              
490             return $message;
491             }
492              
493             # NOTE : This section could be replaced by config file and dispatcher
494             # TODO : Add more error and debug output
495             #
496             # We eval here as it is possible the Provider classes are not installed
497             #
498              
499             if ($self->message->header_from =~ /vodafone.co.uk$/) {
500             print STDERR "UKVodafone message type detected\n" if ($self->debug);
501             my $provider = eval { new MMS::Mail::Provider::UKVodafone };
502             if (defined($@) && $@) { return undef; }
503             $self->provider($provider);
504             return $provider->parse($self->message);
505             } elsif ($self->message->header_from =~ /mediamessaging.o2.co.uk/) {
506             print STDERR "UK02 message type detected\n" if ($self->debug);
507             my $provider = eval { new MMS::Mail::Provider::UK02 };
508             if (defined($@) && $@) { return undef; }
509             $self->provider($provider);
510             return $provider->parse($self->message);
511             } elsif ($self->message->header_from =~ /orangemms.net$/ || $self->message->header_from =~ /orange.net$/) {
512             print STDERR "UKOrange message type detected\n" if ($self->debug);
513             my $provider = eval { new MMS::Mail::Provider::UKOrange };
514             if (defined($@) && $@) { return undef; }
515             $self->provider($provider);
516             return $provider->parse($self->message);
517             } elsif ($self->message->header_from =~ /t-mobile.co.uk/) {
518             print STDERR "T-Mobile message type detected\n" if ($self->debug);
519             my $provider = eval { new MMS::Mail::Provider::UKTMobile };
520             if (defined($@) && $@) { return undef; }
521             $self->provider($provider);
522             return $provider->parse($self->message);
523             } elsif ($self->message->header_from =~ /virginmobilemessaging.co.uk/) {
524             print STDERR "Virgin message type detected\n" if ($self->debug);
525             my $provider = eval { new MMS::Mail::Provider::UKVirgin };
526             if (defined($@) && $@) { return undef; }
527             $self->provider($provider);
528             return $provider->parse($self->message);
529             } elsif ($self->message->header_from =~ /mms.three.co.uk/) {
530             print STDERR "3 message type detected\n" if ($self->debug);
531             my $provider = eval { new MMS::Mail::Provider::UK3 };
532             if (defined($@) && $@) { return undef; }
533             $self->provider($provider);
534             return $provider->parse($self->message);
535             } else {
536             print STDERR "No message type detected using base provider\n" if ($self->debug);
537             my $provider = new MMS::Mail::Provider;
538             $self->provider($provider);
539             return $provider->parse($self->message);
540             }
541              
542             }
543              
544             sub provider_parse {
545              
546             my $self = shift;
547             my $message = shift;
548            
549             if (defined($message)) {
550             $self->message($message);
551             }
552              
553             unless (defined($self->message)) {
554             $self->_add_error("No MMS::Message available to parse");
555             print STDERR "No MMS::Message available to parse\n" if ($self->debug);
556             return undef;
557             }
558              
559             my $mms = $self->_decipher;
560              
561             unless (defined $mms) {
562             $self->_add_error("Could not parse");
563             print STDERR "No MMS::Message::Parsed was returned by Provider\n" if ($self->debug);
564             return undef;
565             }
566              
567             print STDERR "Returning MMS::Mail::Message::Parsed\n" if ($self->debug);
568              
569             return $mms;
570             }
571              
572             sub _add_error {
573              
574             my $self = shift;
575             my $error = shift;
576              
577             unless (defined $error) {
578             return 0;
579             }
580             push @{$self->errors}, $error;
581              
582             return 1;
583             }
584              
585             sub last_error {
586              
587             my $self = shift;
588              
589             if (@{$self->errors} > 0) {
590             return ((pop @{$self->errors})."\n");
591             } else {
592             return undef;
593             }
594              
595             }
596              
597             1; # End of MMS::Mail::Parser