File Coverage

blib/lib/Email/Address/XS.pm
Criterion Covered Total %
statement 137 145 94.4
branch 80 90 88.8
condition 26 36 72.2
subroutine 22 25 88.0
pod 14 18 77.7
total 279 314 88.8


line stmt bran cond sub pod time code
1             # Copyright (c) 2015-2017 by Pali
2              
3             package Email::Address::XS;
4              
5 2     2   107628 use 5.006;
  2         21  
6 2     2   12 use strict;
  2         4  
  2         46  
7 2     2   12 use warnings;
  2         5  
  2         94  
8              
9             our $VERSION = '1.01';
10              
11 2     2   13 use Carp;
  2         5  
  2         130  
12              
13 2     2   13 use base 'Exporter';
  2         7  
  2         301  
14             our @EXPORT_OK = qw(parse_email_addresses parse_email_groups format_email_addresses format_email_groups compose_address split_address);
15              
16 2     2   19 use XSLoader;
  2         5  
  2         3285  
17             XSLoader::load(__PACKAGE__, $VERSION);
18              
19             =head1 NAME
20              
21             Email::Address::XS - Parse and format RFC 2822 email addresses and groups
22              
23             =head1 SYNOPSIS
24              
25             use Email::Address::XS;
26              
27             my $winstons_address = Email::Address::XS->new(phrase => 'Winston Smith', user => 'winston.smith', host => 'recdep.minitrue', comment => 'Records Department');
28             print $winstons_address->address();
29             # winston.smith@recdep.minitrue
30              
31             my $julias_address = Email::Address::XS->new('Julia', 'julia@ficdep.minitrue');
32             print $julias_address->format();
33             # Julia
34              
35             my $users_address = Email::Address::XS->parse('user ');
36             print $users_address->host();
37             # oceania
38              
39             my $goldsteins_address = Email::Address::XS->parse_bare_address('goldstein@brotherhood.oceania');
40             print $goldsteins_address->user();
41             # goldstein
42              
43             my @addresses = Email::Address::XS->parse('"Winston Smith" (Records Department), Julia ');
44             # ($winstons_address, $julias_address)
45              
46              
47             use Email::Address::XS qw(format_email_addresses format_email_groups parse_email_addresses parse_email_groups);
48              
49             my $addresses_string = format_email_addresses($winstons_address, $julias_address, $users_address);
50             # "Winston Smith" (Records Department), Julia , user
51              
52             my @addresses = map { $_->address() } parse_email_addresses($addresses_string);
53             # ('winston.smith@recdep.minitrue', 'julia@ficdep.minitrue', 'user@oceania')
54              
55             my $groups_string = format_email_groups('Brotherhood' => [ $winstons_address, $julias_address ], undef() => [ $users_address ]);
56             # Brotherhood: "Winston Smith" (Records Department), Julia ;, user
57              
58             my @groups = parse_email_groups($groups_string);
59             # ('Brotherhood' => [ $winstons_address, $julias_address ], undef() => [ $users_address ])
60              
61              
62             use Email::Address::XS qw(compose_address split_address);
63              
64             my ($user, $host) = split_address('julia(outer party)@ficdep.minitrue');
65             # ('julia', 'ficdep.minitrue')
66              
67             my $string = compose_address('charrington"@"shop', 'thought.police.oceania');
68             # "charrington\"@\"shop"@thought.police.oceania
69              
70             =head1 DESCRIPTION
71              
72             This module implements L
73             parser and formatter of email addresses and groups. It parses an input
74             string from email headers which contain a list of email addresses or
75             a groups of email addresses (like From, To, Cc, Bcc, Reply-To, Sender,
76             ...). Also it can generate a string value for those headers from a
77             list of email addresses objects.
78              
79             Parser and formatter functionality is implemented in XS and uses
80             shared code from Dovecot IMAP server.
81              
82             It is a drop-in replacement for L
83             which has several security issues. E.g. issue L,
84             which allows remote attackers to cause denial of service, is still
85             present in L version 1.908.
86              
87             Email::Address::XS module was created to finally fix CVE-2015-7686.
88              
89             Existing applications that use Email::Address module could be easily
90             switched to Email::Address::XS module. In most cases only changing
91             C to C and replacing every
92             C occurrence with C is sufficient.
93              
94             So unlike L, this module does not use
95             regular expressions for parsing but instead native XS implementation
96             parses input string sequentially according to RFC 2822 grammar.
97              
98             Additionally it has support also for named groups and so can be use
99             instead of L.
100              
101             If you are looking for the module which provides object representation
102             for the list of email addresses suitable for the MIME email headers,
103             see L.
104              
105             =head2 EXPORT
106              
107             None by default. Exportable functions are:
108             L|/parse_email_addresses>,
109             L|/parse_email_groups>,
110             L|/format_email_addresses>,
111             L|/format_email_groups>,
112             L|/compose_address>,
113             L|/split_address>.
114              
115             =head2 Exportable Functions
116              
117             =over 4
118              
119             =item format_email_addresses
120              
121             use Email::Address::XS qw(format_email_addresses);
122              
123             my $winstons_address = Email::Address::XS->new(phrase => 'Winston Smith', address => 'winston@recdep.minitrue');
124             my $julias_address = Email::Address::XS->new(phrase => 'Julia', address => 'julia@ficdep.minitrue');
125             my @addresses = ($winstons_address, $julias_address);
126             my $string = format_email_addresses(@addresses);
127             print $string;
128             # "Winston Smith" , Julia
129              
130             Takes a list of email address objects and returns one formatted string
131             of those email addresses.
132              
133             =cut
134              
135             sub format_email_addresses {
136 39     39 1 1175 my (@args) = @_;
137 39         3036 return format_email_groups(undef, \@args);
138             }
139              
140             =item format_email_groups
141              
142             use Email::Address::XS qw(format_email_groups);
143             my $undef = undef;
144              
145             my $winstons_address = Email::Address::XS->new(phrase => 'Winston Smith', user => 'winston.smith', host => 'recdep.minitrue');
146             my $julias_address = Email::Address::XS->new('Julia', 'julia@ficdep.minitrue');
147             my $users_address = Email::Address::XS->new(address => 'user@oceania');
148              
149             my $groups_string = format_email_groups('Brotherhood' => [ $winstons_address, $julias_address ], $undef => [ $users_address ]);
150             print $groups_string;
151             # Brotherhood: "Winston Smith" , Julia ;, user@oceania
152              
153             my $undisclosed_string = format_email_groups('undisclosed-recipients' => []);
154             print $undisclosed_string;
155             # undisclosed-recipients:;
156              
157             Like L|/format_email_addresses> but this
158             method takes pairs which consist of a group display name and a
159             reference to address list. If a group is not undef then address
160             list is formatted inside named group.
161              
162             =item parse_email_addresses
163              
164             use Email::Address::XS qw(parse_email_addresses);
165              
166             my $string = '"Winston Smith" , Julia , user@oceania';
167             my @addresses = parse_email_addresses($string);
168             # @addresses now contains three Email::Address::XS objects, one for each address
169              
170             Parses an input string and returns a list of Email::Address::XS
171             objects. Optional second string argument specifies class name for
172             blessing new objects.
173              
174             =cut
175              
176             sub parse_email_addresses {
177 33     33 1 1181 my (@args) = @_;
178 33         46 my $t = 1;
179 33         880 return map { @{$_} } grep { $t ^= 1 } parse_email_groups(@args);
  26         31  
  26         122  
  52         107  
180             }
181              
182             =item parse_email_groups
183              
184             use Email::Address::XS qw(parse_email_groups);
185             my $undef = undef;
186              
187             my $string = 'Brotherhood: "Winston Smith" , Julia ;, user@oceania, undisclosed-recipients:;';
188             my @groups = parse_email_groups($string);
189             # @groups now contains list ('Brotherhood' => [ $winstons_object, $julias_object ], $undef => [ $users_object ], 'undisclosed-recipients' => [])
190              
191             Like L|/parse_email_addresses> but this
192             function returns a list of pairs: a group display name and a
193             reference to a list of addresses which belongs to that named group.
194             An undef value for a group means that a following list of addresses
195             is not inside any named group. An output is in a same format as a
196             input for the function L|/format_email_groups>.
197             This function preserves order of groups and does not do any
198             de-duplication or merging.
199              
200             =item compose_address
201              
202             use Email::Address::XS qw(compose_address);
203             my $string_address = compose_address($user, $host);
204              
205             Takes an unescaped user part and unescaped host part of an address
206             and returns escaped address.
207              
208             Available since version 1.01.
209              
210             =item split_address
211              
212             use Email::Address::XS qw(split_address);
213             my ($user, $host) = split_address($string_address);
214              
215             Takes an escaped address and split it into pair of unescaped user
216             part and unescaped host part of address. If splitting input address
217             into these two parts is not possible then this function returns
218             pair of undefs.
219              
220             Available since version 1.01.
221              
222             =back
223              
224             =head2 Class Methods
225              
226             =over 4
227              
228             =item new
229              
230             my $empty_address = Email::Address::XS->new();
231             my $winstons_address = Email::Address::XS->new(phrase => 'Winston Smith', user => 'winston.smith', host => 'recdep.minitrue', comment => 'Records Department');
232             my $julias_address = Email::Address::XS->new('Julia', 'julia@ficdep.minitrue');
233             my $users_address = Email::Address::XS->new(address => 'user@oceania');
234             my $only_name = Email::Address::XS->new(phrase => 'Name');
235             my $copy_of_winstons_address = Email::Address::XS->new(copy => $winstons_address);
236              
237             Constructs and returns a new C object. Takes named
238             list of arguments: phrase, address, user, host, comment and copy.
239             An argument address takes precedence over user and host.
240              
241             When an argument copy is specified then it is expected an
242             Email::Address::XS object and a cloned copy of that object is
243             returned. All other parameters are ignored.
244              
245             Old syntax L is
246             supported too. Takes one to four positional arguments: phrase, address
247             comment, and original string. Passing an argument original is
248             deprecated, ignored and throws a warning.
249              
250             =cut
251              
252             sub new {
253 132     132 1 68681 my ($class, @args) = @_;
254              
255 132         452 my %hash_keys = (phrase => 1, address => 1, user => 1, host => 1, comment => 1, copy => 1);
256 132         199 my $is_hash;
257 132 100 100     962 if ( scalar @args == 2 and defined $args[0] ) {
    100 66        
    100 66        
258 21 100       68 $is_hash = 1 if exists $hash_keys{$args[0]};
259             } elsif ( scalar @args == 4 and defined $args[0] and defined $args[2] ) {
260 51 100 66     203 $is_hash = 1 if exists $hash_keys{$args[0]} and exists $hash_keys{$args[2]};
261             } elsif ( scalar @args > 4 ) {
262 23         37 $is_hash = 1;
263             }
264              
265 132         223 my %args;
266 132 100       231 if ( $is_hash ) {
267 91         213 %args = @args;
268             } else {
269 41 100       546 carp 'Argument original is deprecated and ignored' if scalar @args > 3;
270 41 100       110 $args{comment} = $args[2] if scalar @args > 2;
271 41 100       100 $args{address} = $args[1] if scalar @args > 1;
272 41 100       103 $args{phrase} = $args[0] if scalar @args > 0;
273             }
274              
275 132         209 my $invalid;
276             my $original;
277 132 100       333 if ( exists $args{copy} ) {
278 2 50       25 if ( $class->is_obj($args{copy}) ) {
279 2         11 $args{phrase} = $args{copy}->phrase();
280 2         9 $args{comment} = $args{copy}->comment();
281 2         9 $args{user} = $args{copy}->user();
282 2         9 $args{host} = $args{copy}->host();
283 2         7 $invalid = $args{copy}->{invalid};
284 2         7 $original = $args{copy}->{original};
285 2         6 delete $args{address};
286             } else {
287 0         0 carp 'Named argument copy does not contain a valid object';
288             }
289             }
290              
291 132         263 my $self = bless {}, $class;
292              
293 132         431 $self->phrase($args{phrase});
294 132         433 $self->comment($args{comment});
295              
296 132 100       299 if ( exists $args{address} ) {
297 66         145 $self->address($args{address});
298             } else {
299 66         196 $self->user($args{user});
300 66         180 $self->host($args{host});
301             }
302              
303 132 50       309 $self->{invalid} = 1 if $invalid;
304 132         223 $self->{original} = $original;
305              
306 132         1104 return $self;
307             }
308              
309             =item parse
310              
311             my $winstons_address = Email::Address::XS->parse('"Winston Smith" (Records Department)');
312             my @users_addresses = Email::Address::XS->parse('user1@oceania, user2@oceania');
313              
314             Parses an input string and returns a list of an Email::Address::XS
315             objects. Same as the function L|/parse_email_addresses>
316             but this one is class method.
317              
318             In scalar context this function returns just first parsed object.
319             If more then one object was parsed then L|/is_valid>
320             method on returned object returns false. If no object was parsed
321             then empty Email::Address::XS object is returned.
322              
323             Prior to version 1.01 return value in scalar context is undef when
324             no object was parsed.
325              
326             =cut
327              
328             sub parse {
329 10     10 1 5686 my ($class, $string) = @_;
330 10         29 my @addresses = parse_email_addresses($string, $class);
331 10 100       59 return @addresses if wantarray;
332 5 100       15 my $self = @addresses ? $addresses[0] : Email::Address::XS->new();
333 5 100       16 $self->{invalid} = 1 if scalar @addresses != 1;
334 5 100       16 $self->{original} = $string unless defined $self->{original};
335 5         17 return $self;
336             }
337              
338             =item parse_bare_address
339              
340             my $winstons_address = Email::Address::XS->parse_bare_address('winston.smith@recdep.minitrue');
341              
342             Parses an input string as one bare email address (addr spec) which
343             does not allow phrase part or angle brackets around email address and
344             returns an Email::Address::XS object. It is just a wrapper around
345             L|/address> method. Method L|/is_valid> can be
346             used to check if parsing was successful.
347              
348             Available since version 1.01.
349              
350             =cut
351              
352             sub parse_bare_address {
353 18     18 1 6740 my ($class, $string) = @_;
354 18         42 my $self = $class->new();
355 18 100       36 if ( defined $string ) {
356 16         37 $self->address($string);
357 16         29 $self->{original} = $string;
358             } else {
359 2         227 carp 'Use of uninitialized value for string';
360             }
361 18         66 return $self;
362             }
363              
364             =back
365              
366             =head2 Object Methods
367              
368             =over 4
369              
370             =item format
371              
372             my $string = $address->format();
373              
374             Returns formatted Email::Address::XS object as a string.
375              
376             =cut
377              
378             sub format {
379 33     33 1 99 my ($self) = @_;
380 33         110 return format_email_addresses($self);
381             }
382              
383             =item is_valid
384              
385             my $is_valid = $address->is_valid();
386              
387             Returns true if the parse function or method which created this
388             Email::Address::XS object had not received any syntax error on input
389             string and also that L|/user> and L|/host> part of
390             the email address are not empty strings.
391              
392             Thus this function can be used for checking if Email::Address::XS
393             object is valid before calling L|/format> method on it.
394              
395             Available since version 1.01.
396              
397             =cut
398              
399             sub is_valid {
400 40     40 1 994 my ($self) = @_;
401 40         89 my $user = $self->user();
402 40         87 my $host = $self->host();
403 40   66     398 return (defined $user and length $user and defined $host and length $host and not $self->{invalid});
404             }
405              
406             =item phrase
407              
408             my $phrase = $address->phrase();
409             $address->phrase('Winston Smith');
410              
411             Accessor and mutator for the phrase (display name).
412              
413             =cut
414              
415             sub phrase {
416 215     215 1 1805 my ($self, @args) = @_;
417 215 100       655 return $self->{phrase} unless @args;
418 147 50       642 delete $self->{invalid} if exists $self->{invalid};
419 147         375 return $self->{phrase} = $args[0];
420             }
421              
422             =item user
423              
424             my $user = $address->user();
425             $address->user('winston.smith');
426              
427             Accessor and mutator for the unescaped user (local/mailbox) part of
428             an address.
429              
430             =cut
431              
432             sub user {
433 181     181 1 1118 my ($self, @args) = @_;
434 181 100       669 return $self->{user} unless @args;
435 75 100       171 delete $self->{cached_address} if exists $self->{cached_address};
436 75 50       179 delete $self->{invalid} if exists $self->{invalid};
437 75         172 return $self->{user} = $args[0];
438             }
439              
440             =item host
441              
442             my $host = $address->host();
443             $address->host('recdep.minitrue');
444              
445             Accessor and mutator for the unescaped host (domain) part of an address.
446              
447             =cut
448              
449             sub host {
450 166     166 1 1050 my ($self, @args) = @_;
451 166 100       604 return $self->{host} unless @args;
452 72 100       157 delete $self->{cached_address} if exists $self->{cached_address};
453 72 50       162 delete $self->{invalid} if exists $self->{invalid};
454 72         161 return $self->{host} = $args[0];
455             }
456              
457             =item address
458              
459             my $string_address = $address->address();
460             $address->address('winston.smith@recdep.minitrue');
461              
462             Accessor and mutator for the escaped address (addr spec).
463              
464             Internally this module stores a user and a host part of an address
465             separately. Function L|/compose_address> is used
466             for composing full address and function L|/split_address>
467             for splitting into a user and a host parts. If splitting new address
468             into these two parts is not possible then this method returns undef
469             and sets both parts to undef.
470              
471             =cut
472              
473             sub address {
474 149     149 1 1044 my ($self, @args) = @_;
475 149         265 my $user;
476             my $host;
477 149 100       318 if ( @args ) {
478 97 50       214 delete $self->{invalid} if exists $self->{invalid};
479 97 100       706 ($user, $host) = split_address($args[0]) if defined $args[0];
480 97 100 66     401 if ( not defined $user or not defined $host ) {
481 14         30 $user = undef;
482 14         24 $host = undef;
483             }
484 97         212 $self->{user} = $user;
485 97         172 $self->{host} = $host;
486             } else {
487 52 100       287 return $self->{cached_address} if exists $self->{cached_address};
488 17         52 $user = $self->user();
489 17         50 $host = $self->host();
490             }
491 114 100 66     749 if ( defined $user and defined $host and length $user and length $host ) {
      100        
      66        
492 93         606 return $self->{cached_address} = compose_address($user, $host);
493             } else {
494 21         96 return $self->{cached_address} = undef;
495             }
496             }
497              
498             =item comment
499              
500             my $comment = $address->comment();
501             $address->comment('Records Department');
502              
503             Accessor and mutator for the comment which is formatted after an
504             address. A comment can contain another nested comments in round
505             brackets. When setting new comment this method check if brackets are
506             balanced. If not undef is set and returned.
507              
508             =cut
509              
510             sub comment {
511 207     207 1 565 my ($self, @args) = @_;
512 207 100       703 return $self->{comment} unless @args;
513 152 50       359 delete $self->{invalid} if exists $self->{invalid};
514 152 100       419 return $self->{comment} = undef unless defined $args[0];
515 31         68 my $count = 0;
516 31         71 my $cleaned = $args[0];
517 31         403 $cleaned =~ s/(?:\\.|[^\(\)])//g;
518 31         127 foreach ( split //, $cleaned ) {
519 33 100       97 $count++ if $_ eq '(';
520 33 100       83 $count-- if $_ eq ')';
521 33 100       93 last if $count < 0;
522             }
523 31 100       125 return $self->{comment} = undef if $count != 0;
524 26         109 return $self->{comment} = $args[0];
525             }
526              
527             =item name
528              
529             my $name = $address->name();
530              
531             This method tries to return a name which belongs to the address. It
532             returns either L|/phrase> or L|/comment> or
533             L|/user> part of the address or empty string (first defined
534             value in this order). But it never returns undef.
535              
536             =cut
537              
538             sub name {
539 34     34 1 107 my ($self) = @_;
540 34         106 my $phrase = $self->phrase();
541 34 100 66     257 return $phrase if defined $phrase and length $phrase;
542 14         41 my $comment = $self->comment();
543 14 100 66     61 return $comment if defined $comment and length $comment;
544 12         31 my $user = $self->user();
545 12 100 66     102 return $user if defined $user and length $user;
546 4         24 return '';
547             }
548              
549             =item original
550              
551             my $address = Email::Address::XS->parse('(Winston) "Smith" (Minitrue)');
552             my $original = $address->original();
553             # (Winston) "Smith" (Minitrue)
554             my $format = $address->format();
555             # Smith (Minitrue)
556              
557             This method returns original part of the string which was used for
558             parsing current Email::Address::XS object. If object was not created
559             by parsing input string, then this method returns undef.
560              
561             Note that L|/format> method does not have to return same
562             original string.
563              
564             Available since version 1.01.
565              
566             =cut
567              
568             sub original {
569 27     27 1 51 my ($self) = @_;
570 27         96 return $self->{original};
571             }
572              
573             =back
574              
575             =head2 Overloaded Operators
576              
577             =over 4
578              
579             =item stringify
580              
581             my $address = Email::Address::XS->new(phrase => 'Winston Smith', address => 'winston.smith@recdep.minitrue');
582             print "Winston's address is $address.";
583             # Winston's address is "Winston Smith" .
584              
585             Objects stringify to L|/format>. For stringification purpose
586             is defined method C.
587              
588             =cut
589              
590             our $STRINGIFY; # deprecated
591              
592             sub as_string {
593 5     5 0 21 my ($self) = @_;
594 5 50       24 return $self->format() unless defined $STRINGIFY;
595 0           carp 'Variable $Email::Address::XS::STRINGIFY is deprecated; subclass instead';
596 0           my $method = $self->can($STRINGIFY);
597 0 0         croak 'Stringify method ' . $STRINGIFY . ' does not exist' unless defined $method;
598 0           return $method->($self);
599             }
600              
601 2     2   2267 use overload '""' => \&as_string;
  2         2181  
  2         18  
602              
603             =back
604              
605             =head2 Deprecated Functions and Variables
606              
607             For compatibility with L
608             there are defined some deprecated functions and variables.
609             Do not use them in new code. Their usage throws warnings.
610              
611             Altering deprecated variable C<$Email::Address::XS::STRINGIFY> changes
612             method which is called for objects stringification.
613              
614             Deprecated cache functions C, C and
615             C are noop and do nothing.
616              
617             =cut
618              
619             sub purge_cache {
620 0     0 0   carp 'Function purge_cache is deprecated and does nothing';
621             }
622              
623             sub disable_cache {
624 0     0 0   carp 'Function disable_cache is deprecated and does nothing';
625             }
626              
627             sub enable_cache {
628 0     0 0   carp 'Function enable_cache is deprecated and does nothing';
629             }
630              
631             =head1 SEE ALSO
632              
633             L,
634             L,
635             L,
636             L,
637             L,
638             L
639              
640             =head1 AUTHOR
641              
642             Pali Epali@cpan.orgE
643              
644             =head1 COPYRIGHT AND LICENSE
645              
646             Copyright (C) 2015-2017 by Pali Epali@cpan.orgE
647              
648             This library is free software; you can redistribute it and/or modify
649             it under the same terms as Perl itself, either Perl version 5.6.0 or,
650             at your option, any later version of Perl 5 you may have available.
651              
652             Dovecot parser is licensed under The MIT License and copyrighted by
653             Dovecot authors.
654              
655             =cut
656              
657             1;