/[suikacvs]/messaging/manakai/lib/Message/Field/Address.pm
Suika

Contents of /messaging/manakai/lib/Message/Field/Address.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.2 - (hide annotations) (download)
Sat Mar 16 01:26:30 2002 UTC (23 years, 1 month ago) by wakaba
Branch: MAIN
Changes since 1.1: +6 -3 lines
2002-03-15  wakaba <w@suika.fam.cx>

	* Date.pm: New module.

1 wakaba 1.1
2     =head1 NAME
3    
4     Message::Field::Address Perl module
5    
6     =head1 DESCRIPTION
7    
8     Perl module for RFC 822/2822 address related C<field>s.
9    
10     =cut
11    
12     package Message::Field::Address;
13     require 5.6.0;
14     use strict;
15     use re 'eval';
16     use vars qw(%REG $VERSION);
17     $VERSION = '1.00';
18    
19 wakaba 1.2 use overload '@{}' => sub {shift->{address}},
20     '""' => sub {shift->stringify};
21 wakaba 1.1
22     $REG{comment} = qr/\x28(?:\x5C[\x00-\xFF]|[\x00-\x0C\x0E-\x27\x2A-\x5B\x5D-\xFF]+|(??{$REG{comment}}))*\x29/;
23     $REG{quoted_string} = qr/\x22(?:\x5C[\x00-\xFF]|[\x00-\x0C\x0E-\x21\x23-\x5B\x5D-\xFF])*\x22/;
24     $REG{domain_literal} = qr/\x5B(?:\x5C[\x00-\xFF]|[\x00-\x0C\x0E-\x5A\x5E-\xFF])*\x5D/;
25    
26     $REG{WSP} = qr/[\x20\x09]+/;
27     $REG{FWS} = qr/[\x20\x09]*/;
28     $REG{atext} = qr/[\x21\x23-\x27\x2A\x2B\x2D\x2F\x30-\x39\x3D\x3F\x41-\x5A\x5E-\x7E]+/;
29     $REG{dot_atom} = qr/$REG{atext}(?:$REG{FWS}\x2E$REG{FWS}$REG{atext})*/;
30     $REG{dot_word} = qr/(?:$REG{atext}|$REG{quoted_string})(?:$REG{FWS}\x2E$REG{FWS}(?:$REG{atext}|$REG{quoted_string}))*/;
31     $REG{phrase} = qr/(?:$REG{atext}|$REG{quoted_string})(?:$REG{atext}|$REG{quoted_string}|\.|$REG{FWS})*/;
32     $REG{obs_route} = qr/(?:\x40$REG{FWS}(?:$REG{dot_word}|$REG{domain_literal})(?:$REG{FWS},?$REG{FWS}\x40$REG{FWS}(?:$REG{dot_word}|$REG{domain_literal}))*):/;
33     $REG{addr_spec} = qr/$REG{dot_word}$REG{FWS}\x40$REG{FWS}(?:$REG{dot_atom}|$REG{domain_literal})/;
34     $REG{mailbox} = qr/(?:(?:$REG{phrase})?<$REG{FWS}(?:$REG{obs_route})?$REG{FWS}$REG{addr_spec}$REG{FWS}>|$REG{addr_spec})/;
35     $REG{mailbox_list} = qr/$REG{mailbox}(?:$REG{FWS},(?:$REG{FWS}$REG{mailbox})?)*/;
36     $REG{address} = qr/(?:(?:$REG{phrase})?(?:<$REG{FWS}(?:$REG{obs_route})?$REG{FWS}$REG{addr_spec}$REG{FWS}>|:$REG{FWS}(?:$REG{mailbox_list}$REG{FWS})?;)|$REG{addr_spec})/;
37     $REG{address_list} = qr/$REG{address}(?:$REG{FWS},(?:$REG{FWS}$REG{address})?)*/;
38     $REG{M_group} = qr/($REG{phrase}):/;
39     $REG{M_mailbox} = qr/(?:($REG{phrase})?<$REG{FWS}($REG{obs_route})?$REG{FWS}($REG{addr_spec})$REG{FWS}>|($REG{addr_spec}))/;
40     $REG{M_quoted_string} = qr/\x22((?:\x5C[\x00-\xFF]|[\x00-\x0C\x0E-\x21\x23-\x5B\x5D-\xFF])*)\x22/;
41    
42     $REG{NON_atom} = qr/[^\x09\x20\x21\x23-\x27\x2A\x2B\x2D\x2F\x30-\x39\x3D\x3F\x41-\x5A\x5E-\x7E]/;
43    
44 wakaba 1.2 =head2 Message::Field::Address->new ()
45 wakaba 1.1
46     Return empty address object.
47    
48     =cut
49    
50     sub new ($) {
51     bless {type => '_ROOT'}, shift;
52     }
53    
54 wakaba 1.2 =head2 Message::Field::Address->parse ($unfolded_field_body)
55 wakaba 1.1
56     Parse structured C<field-body> contain of C<address-list>.
57    
58     =cut
59    
60     sub parse ($$) {
61     my $self = bless {}, shift;
62     my $field_body = shift;
63     $field_body = $self->delete_comment ($field_body);
64     my %addr = $self->parse_address_list ($field_body);
65     $self->{address} = $addr{address};
66     $self->{type} = $addr{type};
67     $self;
68     }
69    
70     =head2 $self->address ()
71    
72     Return address list in the format described in
73     L<$self-E<gt>parse_address_list ()>.
74    
75     =cut
76    
77     sub address ($) {@{shift->{address}}}
78    
79     =head2 $self->add ($addr_spec, [%option])
80    
81     Add an mail address to C<$self> (address object).
82     %option = (name => C<display-name>, route => C<route>,
83     group => C<display-name> of C<group>)
84    
85     Note that this method (and other methods) does not check
86     $addr_spec and $option{route} is valid or not.
87    
88     =cut
89    
90     sub add ($$;%) {
91     my $self = shift;
92     my ($addr, %option) = @_;
93     my $name = $option{name} || $option{display_name};
94     unless ($option{group}) {
95     push @{$self->{address}}, {type => 'mailbox',
96     addr_spec => $addr, display_name => $name, route => $option{route}};
97     } else {
98     for my $i (@{$self->{address}}) {
99     if ($i->{type} eq 'group' && $i->{display_name} eq $option{group}) {
100     push @{$i->{address}}, {type => 'mailbox',
101     addr_spec => $addr, display_name => $name, route => $option{route}};
102     return $self;
103     }
104     }
105     push @{$self->{address}}, {type => 'group', display_name => $option{group},
106     address => [
107     {type => 'mailbox',
108     addr_spec => $addr, display_name => $name, route => $option{route}}
109     ]};
110     }
111     $self;
112     }
113    
114     sub stringify ($) {
115     my $self = shift;
116     my @return;
117     for my $address (@{$self->{address}}) {
118     my $return = '';
119     next if !$address->{addr_spec} && $address->{type} ne 'group';
120     if ($address->{display_name}) {
121     $return = $self->quote_unsafe_string ($address->{display_name})
122     .($address->{type} eq 'group'? ': ': ' ');
123     }
124     if ($address->{type} ne 'group') {
125     $return .= '<'.$address->{route}.$address->{addr_spec}.'>';
126     } else {
127     my (@g_return);
128     for my $mailbox (@{$address->{address}}) {
129     next unless $mailbox->{addr_spec};
130     my $g_return = '';
131     $g_return = $self->quote_unsafe_string ($mailbox->{display_name}) .' '
132     if $mailbox->{display_name};
133     $g_return .= '<'.$mailbox->{route}.$mailbox->{addr_spec}.'>';
134     push @g_return, $g_return;
135     }
136     $return .= join ', ', @g_return;
137     $return .= ';' if $address->{type} eq 'group';
138     }
139     push @return, $return;
140     }
141     join ', ', @return;
142     }
143    
144     sub quote_unsafe_string ($$) {
145     my $self = shift;
146     my $string = shift;
147     if ($string =~ /$REG{NON_atom}/ || $string =~ /$REG{WSP}$REG{WSP}+/) {
148     $string =~ s/([\x22\x5C])/\x5C$1/g;
149     $string = '"'.$string.'"';
150     }
151     $string;
152     }
153    
154     =head2 $self->unquote_quoted_string ($string)
155    
156     Unquote C<quoted-string>. Get rid of C<DQUOTE>s and
157     C<REVERSED SOLIDUS> included in C<quoted-pair>.
158     This method is intended for internal use.
159    
160     =cut
161    
162     sub unquote_quoted_string ($$) {
163     my $self = shift;
164     my $quoted_string = shift;
165     $quoted_string =~ s{$REG{M_quoted_string}}{
166     my $qtext = $1;
167     $qtext =~ s/\x5C([\x00-\xFF])/$1/g;
168     $qtext;
169     }goex;
170     $quoted_string;
171     }
172    
173     =head2 $self->parse_mailbox ($mailbox)
174    
175     Parse C<mailbox> and return array of C<addr-spec>,
176     C<display-name> and C<route> (aka C<obs-route> of RFC 2822).
177     This method is intended for internal use.
178    
179     =cut
180    
181     sub parse_mailbox ($$) {
182     my $self = shift;
183     my $mailbox = shift;
184     if ($mailbox =~ /$REG{M_mailbox}/) {
185     my ($display_name, $route, $addr_spec) = ($1, $2, $3 || $4);
186     $display_name =~ s/$REG{WSP}+$//;
187     $display_name = $self->unquote_quoted_string ($display_name);
188     $addr_spec =~ s{($REG{quoted_string}|$REG{domain_literal})|$REG{WSP}}{$1}go;
189     $route =~ s{($REG{quoted_string}|$REG{domain_literal})|$REG{WSP}}{$1}go;
190     return ($addr_spec, $display_name, $route);
191     }
192     }
193    
194     =head2 $self->parse_address_list ($address_list)
195    
196     Parse C<address-list> and return hash.
197     This method is intended for internal use.
198    
199     =head3 Structure of hash returned by parse_address_list
200    
201     %address = (
202    
203     type => '_ROOT',
204     address => [
205    
206     ## mailbox
207     {
208     type => 'mailbox',
209     display_name => 'Foo H. Bar',
210     addr_spec => 'foo@bar.example',
211     route => '@hoge.example:',
212     },
213    
214     ## group
215     {
216     type => 'group',
217     display_name => 'The committee',
218     address => [
219    
220     ## mailbox
221     {
222     type => 'mailbox',
223     display_name => 'Tom (Director)',
224     addr_spec => 'tom@committee.example',
225     route => '',
226     }
227    
228     ],
229     },
230    
231     ],
232    
233     );
234    
235     =cut
236    
237     sub parse_address_list ($$) {
238     my $self = shift;
239     my $address_list = shift;
240     my %r_addr = (type => '_ROOT');
241     $address_list =~ s{($REG{address})}{
242     my $address = $1;
243     if ($address =~ /^$REG{M_group}/) {
244     my %r_group = (type => 'group', display_name => $1);
245     $r_group{display_name} =~ s/$REG{WSP}+$//;
246     $r_group{display_name} = $self->unquote_quoted_string ($r_group{display_name});
247     $address =~ s{($REG{mailbox})}{
248     my ($addr, $name, $route) = $self->parse_mailbox ($1);
249     push @{$r_group{address}}, {type => 'mailbox',
250     display_name => $name, route => $route, addr_spec => $addr};
251     }goex;
252     push @{$r_addr{address}}, \%r_group;
253     } else {
254     my ($addr, $name, $route) = $self->parse_mailbox ($address);
255     push @{$r_addr{address}}, {type => 'mailbox',
256     display_name => $name, route => $route, addr_spec => $addr};
257     }
258     }goex;
259     %r_addr;
260     }
261    
262     =head2 $self->delete_comment ($field_body)
263    
264     Remove all C<comment> in given strictured C<field-body>.
265     This method is intended for internal use.
266    
267     =cut
268    
269     sub delete_comment ($$) {
270     my $self = shift;
271     my $body = shift;
272     $body =~ s{($REG{quoted_string}|$REG{domain_literal})|$REG{comment}}{
273     my $o = $1; $o? $o : ' ';
274     }gex;
275     $body;
276     }
277    
278     =head1 EXAMPLE
279    
280     ## Compose field-body for To: field.
281    
282 wakaba 1.2 use Message::Field::Address;
283 wakaba 1.1 my $addr = new Message::Field::Address;
284     $addr->add ('foo@example.org', name => 'Mr. foo bar');
285     $addr->add ('webmaster@example.org', group => 'administrators');
286     $addr->add ('postmaster@example.org', group => 'administrators');
287    
288     my $field_body = $addr->stringify ();
289    
290    
291     ## Output parsed address-list tree.
292    
293     use Message::Field::Address;
294     my $addr = Message::Field::Address->parse ($field_body);
295    
296     for my $i (@$addr) {
297     if ($i->{type} eq 'group') {
298     print "\x40 $i->{display_name}: \n";
299     for my $j (@{$i->{address}}) {
300     print "\t- $j->{display_name} <$j->{route}$j->{addr_spec}>\n";
301     }
302     } else {
303     print "- $i->{display_name} <$i->{route}$i->{addr_spec}>\n";
304     }
305     }
306    
307     =head1 LICENSE
308    
309     Copyright 2002 wakaba E<lt>w@suika.fam.cxE<gt>.
310    
311     This program is free software; you can redistribute it and/or modify
312     it under the terms of the GNU General Public License as published by
313     the Free Software Foundation; either version 2 of the License, or
314     (at your option) any later version.
315    
316     This program is distributed in the hope that it will be useful,
317     but WITHOUT ANY WARRANTY; without even the implied warranty of
318     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
319     GNU General Public License for more details.
320    
321     You should have received a copy of the GNU General Public License
322     along with this program; see the file COPYING. If not, write to
323     the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
324     Boston, MA 02111-1307, USA.
325    
326     =head1 CHANGE
327    
328     See F<ChangeLog>.
329 wakaba 1.2 $Date: $
330 wakaba 1.1
331     =cut
332    
333     1;

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24