/[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.5 - (hide annotations) (download)
Sun Mar 31 13:11:55 2002 UTC (23 years ago) by wakaba
Branch: MAIN
Changes since 1.4: +182 -18 lines
2002-03-31  wakaba <w@suika.fam.cx>

	* URI.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 wakaba 1.4 use vars qw(%DEFAULT %REG $VERSION);
17 wakaba 1.1 $VERSION = '1.00';
18 wakaba 1.4 use Message::Util;
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 wakaba 1.5 $REG{mailbox} = qr/(?:(?:$REG{phrase})?<$REG{FWS}(?:(?:$REG{obs_route})?$REG{FWS}$REG{addr_spec}$REG{FWS})?>|$REG{addr_spec}|$REG{atext})/;
35 wakaba 1.1 $REG{mailbox_list} = qr/$REG{mailbox}(?:$REG{FWS},(?:$REG{FWS}$REG{mailbox})?)*/;
36 wakaba 1.5 $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}|$REG{atext})/;
37 wakaba 1.1 $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 wakaba 1.5 $REG{M_mailbox_empty} = qr/(?:($REG{phrase})?<$REG{FWS}>)/;
41 wakaba 1.1 $REG{M_quoted_string} = qr/\x22((?:\x5C[\x00-\xFF]|[\x00-\x0C\x0E-\x21\x23-\x5B\x5D-\xFF])*)\x22/;
42    
43     $REG{NON_atom} = qr/[^\x09\x20\x21\x23-\x27\x2A\x2B\x2D\x2F\x30-\x39\x3D\x3F\x41-\x5A\x5E-\x7E]/;
44    
45 wakaba 1.4 %DEFAULT = (
46 wakaba 1.5 dont_reply => -1, ## See C<$self->dont_reply>
47     dont_reply_display_name => '',
48 wakaba 1.4 encoding_after_encode => '*default',
49     encoding_before_decode => '*default',
50     hook_encode_string => #sub {shift; (value => shift, @_)},
51     \&Message::Util::encode_header_string,
52     hook_decode_string => #sub {shift; (value => shift, @_)},
53     \&Message::Util::decode_header_string,
54 wakaba 1.5 is_mailbox => -1, ## Allow multiple mail addresses?
55     is_mailbox_name => { ## C<is_mailbox> template
56     'complaints-to' => 1,
57     'mail-copies-to' => 1, 'resent-sender' => 1,
58     'return-path' => 1, sender => 1,
59     'x-complaints-to' => 1,
60     },
61 wakaba 1.3 is_return_path => -1,
62     use_display_name => 1,
63 wakaba 1.5 use_dont_reply => -1, ## See C<$self->dont_reply>
64 wakaba 1.3 use_group => 1,
65 wakaba 1.5 use_group_name => { ## C<use_group> template
66     'approved' => -1,
67     'from' => -1, 'resent-from' => -1,
68     },
69     use_keyword => -1, ## See C<$self->keyword>
70     use_keyword_multiple => -1,
71 wakaba 1.3 );
72 wakaba 1.5 ## RFC 2822 C<mailbox> => is_mailbox = +1, (use_group = -1)
73     ## RFC 2822 C<mailbox-list> => is_mailbox = -1, use_group = -1
74     ## RFC 2822 C<address-list> => is_mailbox = -1, use_group = +1
75 wakaba 1.3
76 wakaba 1.5 ## Initialization of option value by C<field-name> and
77     ## version of specification(s).
78 wakaba 1.3 sub _init_option ($$) {
79     my $self = shift;
80 wakaba 1.5 my $name = shift;
81     my $spec = $self->{option}->{format};
82     if ($self->{option}->{is_mailbox_name}->{$name}) {
83 wakaba 1.3 $self->{option}->{is_mailbox} = 1;
84 wakaba 1.5 } elsif ($self->{option}->{use_group_name}->{$name}) {
85     $self->{option}->{use_group} = -1;
86     }
87     if ($spec eq 'usefor') {
88     if ($name eq 'reply-to') {
89     $self->{option}->{use_group} = -1;
90     $self->{option}->{use_dont_reply} = 1;
91     }
92     }
93     if ($name eq 'mail-copies-to') {
94     $self->{option}->{use_keyword} = 1;
95     }
96     if ($name eq 'return-path') {
97 wakaba 1.3 $self->{option}->{is_return_path} = 1;
98     $self->{option}->{use_display_name} = -1;
99     }
100     $self;
101     }
102    
103 wakaba 1.2 =head2 Message::Field::Address->new ()
104 wakaba 1.1
105     Return empty address object.
106    
107     =cut
108    
109 wakaba 1.3 sub new ($;%) {
110     my $self = bless {type => '_ROOT'}, shift;
111     my %option = @_;
112 wakaba 1.4 for (%DEFAULT) {$option{$_} ||= $DEFAULT{$_}}
113 wakaba 1.3 $self->{option} = \%option;
114     $self->_init_option ($self->{option}->{field_name});
115     $self;
116 wakaba 1.1 }
117    
118 wakaba 1.2 =head2 Message::Field::Address->parse ($unfolded_field_body)
119 wakaba 1.1
120     Parse structured C<field-body> contain of C<address-list>.
121    
122     =cut
123    
124 wakaba 1.3 sub parse ($$;%) {
125 wakaba 1.1 my $self = bless {}, shift;
126     my $field_body = shift;
127 wakaba 1.3 my %option = @_;
128 wakaba 1.4 for (%DEFAULT) {$option{$_} ||= $DEFAULT{$_}}
129 wakaba 1.3 $self->{option} = \%option;
130     $self->_init_option ($self->{option}->{field_name});
131 wakaba 1.1 $field_body = $self->delete_comment ($field_body);
132     my %addr = $self->parse_address_list ($field_body);
133     $self->{address} = $addr{address};
134     $self->{type} = $addr{type};
135     $self;
136     }
137    
138     =head2 $self->address ()
139    
140     Return address list in the format described in
141     L<$self-E<gt>parse_address_list ()>.
142    
143     =cut
144    
145     sub address ($) {@{shift->{address}}}
146    
147 wakaba 1.4 =head2 $self->addr_spec ([$index])
148    
149     Returns (C<$index>'th or all) C<addr-spec>.
150 wakaba 1.5 (First C<addr-spec>'s C<$index> is C<1>, not C<0>.)
151 wakaba 1.4
152     =cut
153    
154     sub addr_spec ($;$) {
155     my $self = shift;
156     my $i = shift;
157 wakaba 1.5 #return $self->{address}->[$i]->{addr_spec}
158     # if defined $i && ref $self->{address}->[$i];
159     #map {$_->{addr_spec}} @{$self->{address}};
160     my @r = (); my $j = 0;
161     for my $m (@{$self->{address}}) {
162     if ($m->{type} eq 'group') {
163     for my $n (@{$m->{address}}) {
164     $j++;
165     return $n->{addr_spec} if $j == $i;
166     push @r, $m->{addr_spec};
167     }
168     } else {
169     $j++;
170     return $m->{addr_spec} if $j == $i;
171     push @r, $m->{addr_spec};
172     }
173     }
174     @r;
175 wakaba 1.4 }
176    
177 wakaba 1.1 =head2 $self->add ($addr_spec, [%option])
178    
179     Add an mail address to C<$self> (address object).
180     %option = (name => C<display-name>, route => C<route>,
181     group => C<display-name> of C<group>)
182    
183     Note that this method (and other methods) does not check
184     $addr_spec and $option{route} is valid or not.
185    
186     =cut
187    
188     sub add ($$;%) {
189     my $self = shift;
190     my ($addr, %option) = @_;
191     my $name = $option{name} || $option{display_name};
192     unless ($option{group}) {
193     push @{$self->{address}}, {type => 'mailbox',
194     addr_spec => $addr, display_name => $name, route => $option{route}};
195     } else {
196     for my $i (@{$self->{address}}) {
197     if ($i->{type} eq 'group' && $i->{display_name} eq $option{group}) {
198     push @{$i->{address}}, {type => 'mailbox',
199     addr_spec => $addr, display_name => $name, route => $option{route}};
200     return $self;
201     }
202     }
203     push @{$self->{address}}, {type => 'group', display_name => $option{group},
204     address => [
205     {type => 'mailbox',
206     addr_spec => $addr, display_name => $name, route => $option{route}}
207     ]};
208     }
209     $self;
210     }
211    
212 wakaba 1.3 sub stringify ($;%) {
213 wakaba 1.1 my $self = shift;
214 wakaba 1.3 my %option = @_;
215     $option{is_mailbox} ||= $self->{option}->{is_mailbox};
216     $option{is_return_path} ||= $self->{option}->{is_return_path};
217     $option{use_display_name} ||= $self->{option}->{use_display_name};
218     $option{use_group} ||= $self->{option}->{use_group};
219 wakaba 1.5 $option{use_keyword} ||= $self->{option}->{use_keyword};
220     $option{use_keyword_multiple} ||= $self->{option}->{use_keyword_multiple};
221     $option{use_dont_reply} ||= $self->{option}->{use_dont_reply};
222     $option{dont_reply} ||= $self->{option}->{dont_reply};
223     $option{dont_reply_display_name} = $self->{option}->{dont_reply_display_name}
224     unless defined $option{dont_reply_display_name};
225     if ($option{use_dont_reply}>0 && $option{dont_reply}>0) {
226     ## usefor-article Reply-To:
227     my $g_return = '';
228     if ($option{dont_reply_display_name} && $option{use_display_name}>0) {
229     my %s = &{$self->{option}->{hook_encode_string}} ($self,
230     $option{dont_reply_display_name}, type => 'phrase');
231     $g_return = $self->quote_unsafe_string ($s{value}) .' ';
232     }
233     $g_return .= '<>';
234     return $g_return;
235     }
236 wakaba 1.1 my @return;
237 wakaba 1.5 if ($option{use_keyword}>0) {
238     my @kw = grep {$self->{keyword}->{$_}>0} keys %{$self->{keyword}};
239     return $kw[0] if $kw[0] && $option{use_keyword_multiple}<0;
240     push @return, @kw;
241     }
242 wakaba 1.1 for my $address (@{$self->{address}}) {
243     my $return = '';
244     next if !$address->{addr_spec} && $address->{type} ne 'group';
245 wakaba 1.3 if ($address->{display_name} && $option{use_display_name}>0) {
246 wakaba 1.5 if ($address->{type} eq 'group' && $option{use_group}>0) {
247     my %s = &{$self->{option}->{hook_encode_string}} ($self,
248     $address->{display_name}, type => 'phrase');
249     $return = $self->quote_unsafe_string ($s{value}).': ';
250     #} else {
251     # my %s = &{$self->{option}->{hook_encode_string}} ($self,
252     # $address->{display_name}, type => 'comment');
253     # $s{value} =~ s/[\x28\x29\x5C]/\\$&/g;
254     # $return = '('.$s{value}.') ';
255     } elsif ($address->{type} ne 'group') {
256 wakaba 1.4 my %s = &{$self->{option}->{hook_encode_string}} ($self,
257     $address->{display_name}, type => 'phrase');
258 wakaba 1.5 $return = $self->quote_unsafe_string ($s{value}).' ';
259     }
260 wakaba 1.1 }
261     if ($address->{type} ne 'group') {
262     $return .= '<'.$address->{route}.$address->{addr_spec}.'>';
263     } else {
264     my (@g_return);
265     for my $mailbox (@{$address->{address}}) {
266     next unless $mailbox->{addr_spec};
267     my $g_return = '';
268 wakaba 1.4 if ($mailbox->{display_name} && $option{use_display_name}>0) {
269     my %s = &{$self->{option}->{hook_encode_string}} ($self,
270     $mailbox->{display_name}, type => 'phrase');
271     $g_return = $self->quote_unsafe_string ($s{value}) .' ';
272     }
273 wakaba 1.1 $g_return .= '<'.$mailbox->{route}.$mailbox->{addr_spec}.'>';
274     push @g_return, $g_return;
275 wakaba 1.3 last if $option{is_mailbox}>0;
276 wakaba 1.1 }
277     $return .= join ', ', @g_return;
278 wakaba 1.3 $return .= ';' if $address->{type} eq 'group' && $option{use_group}>0;
279 wakaba 1.1 }
280     push @return, $return;
281 wakaba 1.3 last if $option{is_mailbox}>0;
282     }
283 wakaba 1.5 if ($#return == -1) {
284     if ($option{is_return_path}>0) {
285     push @return, '<>';
286     #} elsif ($option{use_dont_reply}>0) { ## usefor-article Reply-To:
287     # my $g_return = '';
288     # if ($option{dont_reply_display_name} && $option{use_display_name}>0) {
289     # my %s = &{$self->{option}->{hook_encode_string}} ($self,
290     # $option{dont_reply_display_name}, type => 'phrase');
291     # $g_return = $self->quote_unsafe_string ($s{value}) .' ';
292     # }
293     # $g_return .= '<>';
294     # push @return, $g_return;
295     }
296 wakaba 1.1 }
297     join ', ', @return;
298     }
299    
300 wakaba 1.5 =head2 $self->dont_reply ([$don't_reply?, [$display_name]])
301    
302     Set/gets whether C<field-body> content is "don't reply!"
303     or not. Second argument is short string used as C<display-name>.
304    
305     This function returns an array of C<($don't_reply?, $display_name)>.
306     <$don't_reply> takes value C<1> (true) or C<-1> (false).
307    
308     Note: This don't reply convention is defined by draft-usefor-article.
309     You should not use this in RFC 2822 mail message.
310    
311     Enable this, set C<use_dont_reply> option to C<1>. (Default is C<-1>.
312    
313     Note: Set this value does not mean clear address list that
314     current C<Message::Field::Address> has. You can get it
315     as well as before setting new value. But C<stringify> method
316     does not output any of addresses if <$don't_reply> is C<1>.
317    
318     =head3 example
319    
320     my $a = Message::Field::Address->new (use_reply => 1);
321     $a->dont_reply (1, 'Please do not reply');
322     print $a; # "Please do not reply <>"
323    
324     =cut
325    
326     sub dont_reply ($;$$) {
327     my $self = shift;
328     my $dr = shift;
329     my $dname = shift;
330     $self->{option}->{dont_reply} = $dr if $dr;
331     $self->{option}->{dont_reply_display_name} = $dname if defined $dname;
332     ($self->{dont_reply}, $self->{dont_reply_display_name});
333     }
334    
335     =head2 $self->keyword ($keyword, [$true_of_false])
336    
337     Set/gets whether keyword is specified. C<$true_or_false>
338     takes values C<1> and C<-1>.
339    
340     This function is intended to be used for keyword(s) allowed
341     mail addresses field, such as C<Mail-Copies-To:> defined by
342     draft-usefor-article.
343    
344     There are two related options, C<use_keyword> and C<use_keyword_multiple>.
345     Former is on/off switch of this function. Later allows
346     multiple options/addresses, such as C<keyworda, E<lt>foo@bar.exampleE<gt>>.
347    
348     =cut
349    
350     sub keyword ($$;$) {
351     my $self = shift;
352     my $key = shift;
353     my $tf = shift;
354     $self->{keyword}->{$key} = $tf if $tf;
355     $self->{keyword}->{$key} || -1;
356     }
357    
358 wakaba 1.1 sub quote_unsafe_string ($$) {
359     my $self = shift;
360     my $string = shift;
361     if ($string =~ /$REG{NON_atom}/ || $string =~ /$REG{WSP}$REG{WSP}+/) {
362     $string =~ s/([\x22\x5C])/\x5C$1/g;
363     $string = '"'.$string.'"';
364     }
365     $string;
366     }
367    
368     =head2 $self->unquote_quoted_string ($string)
369    
370     Unquote C<quoted-string>. Get rid of C<DQUOTE>s and
371     C<REVERSED SOLIDUS> included in C<quoted-pair>.
372     This method is intended for internal use.
373    
374     =cut
375    
376     sub unquote_quoted_string ($$) {
377     my $self = shift;
378     my $quoted_string = shift;
379     $quoted_string =~ s{$REG{M_quoted_string}}{
380     my $qtext = $1;
381     $qtext =~ s/\x5C([\x00-\xFF])/$1/g;
382     $qtext;
383     }goex;
384     $quoted_string;
385     }
386    
387 wakaba 1.4 sub _decode_quoted_string ($$) {
388     my $self = shift;
389     my $quoted_string = shift;
390     $quoted_string =~ s{$REG{M_quoted_string}|([^\x22]+)}{
391     my ($qtext,$t) = ($1, $2);
392     if ($t) {
393     my %s = &{$self->{option}->{hook_decode_string}} ($self, $t,
394     type => 'value');
395     $s{value};
396     } else {
397     $qtext =~ s/\x5C([\x00-\xFF])/$1/g;
398     my %s = &{$self->{option}->{hook_decode_string}} ($self, $qtext,
399     type => 'value/quoted');
400     $s{value};
401     }
402     }goex;
403     $quoted_string;
404     }
405    
406 wakaba 1.1 =head2 $self->parse_mailbox ($mailbox)
407    
408     Parse C<mailbox> and return array of C<addr-spec>,
409     C<display-name> and C<route> (aka C<obs-route> of RFC 2822).
410     This method is intended for internal use.
411    
412     =cut
413    
414     sub parse_mailbox ($$) {
415     my $self = shift;
416     my $mailbox = shift;
417     if ($mailbox =~ /$REG{M_mailbox}/) {
418     my ($display_name, $route, $addr_spec) = ($1, $2, $3 || $4);
419     $display_name =~ s/$REG{WSP}+$//;
420 wakaba 1.4 $display_name = $self->_decode_quoted_string ($display_name);
421 wakaba 1.1 $addr_spec =~ s{($REG{quoted_string}|$REG{domain_literal})|$REG{WSP}}{$1}go;
422     $route =~ s{($REG{quoted_string}|$REG{domain_literal})|$REG{WSP}}{$1}go;
423     return ($addr_spec, $display_name, $route);
424 wakaba 1.5 } elsif ($mailbox =~ /$REG{M_mailbox_empty}/) {
425     my ($display_name) = ($1);
426     $display_name =~ s/$REG{WSP}+$//;
427     $display_name = $self->_decode_quoted_string ($display_name);
428     return ('', $display_name, 'dummy');
429     } elsif ($mailbox =~ /^$REG{atext}$/) {
430     $self->{keyword}->{$mailbox} = 1;
431 wakaba 1.1 }
432 wakaba 1.5 (undef, undef, undef);
433 wakaba 1.1 }
434    
435     =head2 $self->parse_address_list ($address_list)
436    
437     Parse C<address-list> and return hash.
438     This method is intended for internal use.
439    
440     =head3 Structure of hash returned by parse_address_list
441    
442     %address = (
443    
444     type => '_ROOT',
445     address => [
446    
447     ## mailbox
448     {
449     type => 'mailbox',
450     display_name => 'Foo H. Bar',
451     addr_spec => 'foo@bar.example',
452     route => '@hoge.example:',
453     },
454    
455     ## group
456     {
457     type => 'group',
458     display_name => 'The committee',
459     address => [
460    
461     ## mailbox
462     {
463     type => 'mailbox',
464     display_name => 'Tom (Director)',
465     addr_spec => 'tom@committee.example',
466     route => '',
467     }
468    
469     ],
470     },
471    
472     ],
473    
474     );
475    
476     =cut
477    
478     sub parse_address_list ($$) {
479     my $self = shift;
480     my $address_list = shift;
481     my %r_addr = (type => '_ROOT');
482     $address_list =~ s{($REG{address})}{
483     my $address = $1;
484     if ($address =~ /^$REG{M_group}/) {
485     my %r_group = (type => 'group', display_name => $1);
486     $r_group{display_name} =~ s/$REG{WSP}+$//;
487     $r_group{display_name} = $self->unquote_quoted_string ($r_group{display_name});
488     $address =~ s{($REG{mailbox})}{
489     my ($addr, $name, $route) = $self->parse_mailbox ($1);
490     push @{$r_group{address}}, {type => 'mailbox',
491 wakaba 1.5 display_name => $name, route => $route, addr_spec => $addr}
492     if $addr;
493 wakaba 1.1 }goex;
494     push @{$r_addr{address}}, \%r_group;
495     } else {
496     my ($addr, $name, $route) = $self->parse_mailbox ($address);
497 wakaba 1.5 if ($addr) {
498     push @{$r_addr{address}}, {type => 'mailbox',
499     display_name => $name, route => $route, addr_spec => $addr};
500     } elsif ($route) { # dummy
501     $self->{option}->{dont_reply} = 1;
502     $self->{option}->{dont_reply_display_name} = $name;
503     }
504 wakaba 1.1 }
505     }goex;
506     %r_addr;
507     }
508    
509     =head2 $self->delete_comment ($field_body)
510    
511     Remove all C<comment> in given strictured C<field-body>.
512     This method is intended for internal use.
513    
514     =cut
515    
516     sub delete_comment ($$) {
517     my $self = shift;
518     my $body = shift;
519     $body =~ s{($REG{quoted_string}|$REG{domain_literal})|$REG{comment}}{
520     my $o = $1; $o? $o : ' ';
521     }gex;
522     $body;
523     }
524    
525     =head1 EXAMPLE
526    
527     ## Compose field-body for To: field.
528    
529 wakaba 1.2 use Message::Field::Address;
530 wakaba 1.1 my $addr = new Message::Field::Address;
531     $addr->add ('foo@example.org', name => 'Mr. foo bar');
532     $addr->add ('webmaster@example.org', group => 'administrators');
533     $addr->add ('postmaster@example.org', group => 'administrators');
534    
535     my $field_body = $addr->stringify ();
536    
537    
538     ## Output parsed address-list tree.
539    
540     use Message::Field::Address;
541     my $addr = Message::Field::Address->parse ($field_body);
542    
543     for my $i (@$addr) {
544     if ($i->{type} eq 'group') {
545     print "\x40 $i->{display_name}: \n";
546     for my $j (@{$i->{address}}) {
547     print "\t- $j->{display_name} <$j->{route}$j->{addr_spec}>\n";
548     }
549     } else {
550     print "- $i->{display_name} <$i->{route}$i->{addr_spec}>\n";
551     }
552     }
553    
554     =head1 LICENSE
555    
556     Copyright 2002 wakaba E<lt>w@suika.fam.cxE<gt>.
557    
558     This program is free software; you can redistribute it and/or modify
559     it under the terms of the GNU General Public License as published by
560     the Free Software Foundation; either version 2 of the License, or
561     (at your option) any later version.
562    
563     This program is distributed in the hope that it will be useful,
564     but WITHOUT ANY WARRANTY; without even the implied warranty of
565     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
566     GNU General Public License for more details.
567    
568     You should have received a copy of the GNU General Public License
569     along with this program; see the file COPYING. If not, write to
570     the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
571     Boston, MA 02111-1307, USA.
572    
573     =head1 CHANGE
574    
575     See F<ChangeLog>.
576 wakaba 1.5 $Date: 2002/03/25 10:15:26 $
577 wakaba 1.1
578     =cut
579    
580     1;

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24