/[suikacvs]/messaging/manakai/lib/Message/Header.pm
Suika

Contents of /messaging/manakai/lib/Message/Header.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.11 - (hide annotations) (download)
Tue Mar 26 15:19:53 2002 UTC (22 years, 8 months ago) by wakaba
Branch: MAIN
Changes since 1.10: +2 -2 lines
2002-03-27  wakaba <w@suika.fam.cx>

	* Fix bug of treatment of FWS sorounding encoded-word.
	(MIME/EncodedWord.pm and related modules.)

1 wakaba 1.1
2     =head1 NAME
3    
4     Message::Header Perl module
5    
6     =head1 DESCRIPTION
7    
8     Perl module for RFC 822/2822 message C<header>.
9    
10     =cut
11    
12     package Message::Header;
13     use strict;
14     use vars qw($VERSION %REG %DEFAULT);
15     $VERSION = '1.00';
16    
17     use overload '@{}' => sub {shift->_delete_empty_field()->{field}},
18     '""' => sub {shift->stringify};
19    
20     $REG{WSP} = qr/[\x09\x20]/;
21     $REG{FWS} = qr/[\x09\x20]*/;
22     $REG{M_field} = qr/^([^\x3A]+):$REG{FWS}([\x00-\xFF]*)$/;
23     $REG{M_fromline} = qr/^\x3E?From$REG{WSP}+([\x00-\xFF]*)$/;
24     $REG{UNSAFE_field_name} = qr/[\x00-\x20\x3A\x7F-\xFF]/;
25    
26     =head2 options
27    
28     These options can be getten/set by C<get_option>/C<set_option>
29     method.
30    
31     =head3 capitalize = 0/1
32    
33     (First character of) C<field-name> is capitalized
34     when C<stringify>. (Default = 1)
35    
36     =head3 fold_length = numeric value
37    
38     Length of line used to fold. (Default = 70)
39    
40     =head3 mail_from = 0/1
41    
42     Outputs "From " line (known as Un*x From, Mail-From, and so on)
43     when C<stringify>. (Default = 0)
44    
45     =cut
46    
47     %DEFAULT = (
48     capitalize => 1,
49     fold_length => 70,
50 wakaba 1.7 field_type => {':DEFAULT' => 'Message::Field::Unstructured'},
51 wakaba 1.9 mail_from => -1,
52 wakaba 1.10 output_bcc => -1,
53 wakaba 1.9 parse_all => -1,
54 wakaba 1.1 );
55 wakaba 1.7 my @field_type_Structured = qw(cancel-lock
56 wakaba 1.10 importance mime-version path precedence x-cite
57 wakaba 1.7 x-face x-mail-count x-msmail-priority x-priority x-uidl xref);
58 wakaba 1.4 for (@field_type_Structured)
59     {$DEFAULT{field_type}->{$_} = 'Message::Field::Structured'}
60 wakaba 1.8 my @field_type_Address = qw(approved bcc cc delivered-to disposition-notification-to
61     envelope-to
62 wakaba 1.7 errors-to fcc from mail-followup-to mail-followup-cc mail-from reply-to resent-bcc
63 wakaba 1.4 resent-cc resent-to resent-from resent-sender return-path
64     return-receipt-to sender to x-approved x-beenthere
65     x-complaints-to x-envelope-from x-envelope-sender
66 wakaba 1.7 x-envelope-to x-ml-address x-ml-command x-ml-to x-nfrom x-nto);
67 wakaba 1.4 for (@field_type_Address)
68     {$DEFAULT{field_type}->{$_} = 'Message::Field::Address'}
69     my @field_type_Date = qw(date date-received delivery-date expires
70     expire-date nntp-posting-date posted reply-by resent-date x-tcup-date);
71     for (@field_type_Date)
72     {$DEFAULT{field_type}->{$_} = 'Message::Field::Date'}
73     my @field_type_MsgID = qw(content-id in-reply-to message-id
74 wakaba 1.6 references resent-message-id see-also supersedes);
75 wakaba 1.4 for (@field_type_MsgID)
76 wakaba 1.6 {$DEFAULT{field_type}->{$_} = 'Message::Field::MsgID'}
77 wakaba 1.7 for (qw(received x-received))
78 wakaba 1.5 {$DEFAULT{field_type}->{$_} = 'Message::Field::Received'}
79 wakaba 1.8 $DEFAULT{field_type}->{'content-type'} = 'Message::Field::ContentType';
80     $DEFAULT{field_type}->{'content-disposition'} = 'Message::Field::ContentDisposition';
81     for (qw(x-face-type))
82     {$DEFAULT{field_type}->{$_} = 'Message::Field::ValueParams'}
83 wakaba 1.7 for (qw(accept accept-charset accept-encoding accept-language
84 wakaba 1.8 content-language
85     content-transfer-encoding encrypted followup-to keywords newsgroups
86     x-brother x-daughter x-respect x-moe x-syster x-wife))
87 wakaba 1.7 {$DEFAULT{field_type}->{$_} = 'Message::Field::CSV'}
88 wakaba 1.4 my @field_type_URI = qw(list-archive list-help list-owner
89     list-post list-subscribe list-unsubscribe uri url x-home-page x-http_referer
90     x-info x-pgp-key x-ml-url x-uri x-url x-web);
91     for (@field_type_URI)
92     {$DEFAULT{field_type}->{$_} = 'Message::Field::Structured'}
93 wakaba 1.7 for (qw(list-id))
94 wakaba 1.4 {$DEFAULT{field_type}->{$_} = 'Message::Field::Structured'}
95 wakaba 1.10 for (qw(subject title x-nsubject))
96 wakaba 1.5 {$DEFAULT{field_type}->{$_} = 'Message::Field::Subject'}
97 wakaba 1.10 for (qw(list-software user-agent server))
98     {$DEFAULT{field_type}->{$_} = 'Message::Field::UA'}
99 wakaba 1.1
100     =head2 Message::Header->new ([%option])
101    
102     Returns new Message::Header instance. Some options can be
103     specified as hash.
104    
105     =cut
106    
107     sub new ($;%) {
108     my $class = shift;
109     my $self = bless {option => {@_}}, $class;
110     for (keys %DEFAULT) {$self->{option}->{$_} ||= $DEFAULT{$_}}
111     $self;
112     }
113    
114     =head2 Message::Header->parse ($header, [%option])
115    
116     Parses given C<header> and return a new Message::Header
117     object. Some options can be specified as hash.
118    
119     =cut
120    
121     sub parse ($$;%) {
122     my $class = shift;
123     my $header = shift;
124     my $self = bless {option => {@_}}, $class;
125     for (keys %DEFAULT) {$self->{option}->{$_} ||= $DEFAULT{$_}}
126     $header =~ s/\x0D?\x0A$REG{WSP}+/\x20/gos; ## unfold
127     for my $field (split /\x0D?\x0A/, $header) {
128     if ($field =~ /$REG{M_fromline}/) {
129 wakaba 1.9 my $body = $1;
130     $body = $self->_field_body ($body, 'mail-from')
131     if $self->{option}->{parse_all}>0;
132     push @{$self->{field}}, {name => 'mail-from', body => $body};
133 wakaba 1.1 } elsif ($field =~ /$REG{M_field}/) {
134 wakaba 1.9 my ($name, $body) = (lc $1, $2);
135 wakaba 1.1 $name =~ s/$REG{WSP}+$//;
136     $body =~ s/$REG{WSP}+$//;
137 wakaba 1.9 $body = $self->_field_body ($body, $name) if $self->{option}->{parse_all}>0;
138     push @{$self->{field}}, {name => $name, body => $body};
139 wakaba 1.1 }
140     }
141     $self;
142     }
143    
144     =head2 $self->field ($field_name)
145    
146     Returns C<field-body> of given C<field-name>.
147     When there are two or more C<field>s whose name is C<field-name>,
148     this method return all C<field-body>s as array. (On scalar
149     context, only first one is returned.)
150    
151     =cut
152    
153     sub field ($$) {
154     my $self = shift;
155     my $name = lc shift;
156     my @ret;
157     for my $field (@{$self->{field}}) {
158     if ($field->{name} eq $name) {
159     unless (wantarray) {
160 wakaba 1.5 $field->{body} = $self->_field_body ($field->{body}, $name);
161     return $field->{body};
162 wakaba 1.1 } else {
163 wakaba 1.5 $field->{body} = $self->_field_body ($field->{body}, $name);
164     push @ret, $field->{body};
165 wakaba 1.1 }
166     }
167     }
168 wakaba 1.9 if ($#ret < 0) {
169     return $self->add ($name);
170     }
171 wakaba 1.1 @ret;
172     }
173    
174 wakaba 1.9 sub field_exist ($$) {
175     my $self = shift;
176     my $name = lc shift;
177     my @ret;
178     for my $field (@{$self->{field}}) {
179     return 1 if ($field->{name} eq $name);
180     }
181     0;
182     }
183    
184 wakaba 1.2 =head2 $self->field_name ($index)
185    
186     Returns C<field-name> of $index'th C<field>.
187    
188     =head2 $self->field_body ($index)
189    
190     Returns C<field-body> of $index'th C<field>.
191    
192     =cut
193    
194     sub field_name ($$) {
195     my $self = shift;
196     $self->{field}->[shift]->{name};
197     }
198     sub field_body ($$) {
199     my $self = shift;
200 wakaba 1.4 my $i = shift;
201 wakaba 1.5 $self->{field}->[$i]->{body}
202     = $self->_field_body ($self->{field}->[$i]->{body}, $self->{field}->[$i]->{name});
203     $self->{field}->[$i]->{body};
204 wakaba 1.4 }
205    
206     sub _field_body ($$$) {
207     my $self = shift;
208     my ($body, $name) = @_;
209 wakaba 1.5 unless (ref $body) {
210 wakaba 1.4 my $type = $self->{option}->{field_type}->{$name}
211 wakaba 1.7 || $self->{option}->{field_type}->{':DEFAULT'};
212 wakaba 1.5 eval "require $type";
213     unless ($body) {
214     $body = $type->new (field_name => $name);
215     } else {
216     $body = $type->parse ($body, field_name => $name);
217     }
218 wakaba 1.4 }
219 wakaba 1.5 $body;
220 wakaba 1.2 }
221    
222 wakaba 1.1 =head2 $self->field_name_list ()
223    
224     Returns list of all C<field-name>s. (Even if there are two
225     or more C<field>s which have same C<field-name>, this method
226     returns ALL names.)
227    
228     =cut
229    
230     sub field_name_list ($) {
231     my $self = shift;
232     $self->_delete_empty_field ();
233     map {$_->{name}} @{$self->{field}};
234     }
235    
236     =head2 $self->add ($field_name, $field_body)
237    
238     Adds an new C<field>. It is not checked whether
239     the field which named $field_body is already exist or not.
240     If you don't want duplicated C<field>s, use C<replace> method.
241    
242     =cut
243    
244 wakaba 1.9 sub add ($$;$%) {
245 wakaba 1.1 my $self = shift;
246     my ($name, $body) = (lc shift, shift);
247 wakaba 1.8 my %option = @_;
248 wakaba 1.1 return 0 if $name =~ /$REG{UNSAFE_field_name}/;
249 wakaba 1.5 $body = $self->_field_body ($body, $name);
250 wakaba 1.8 if ($option{prepend}) {
251 wakaba 1.9 unshift @{$self->{field}}, {name => $name, body => $body};
252 wakaba 1.8 } else {
253     push @{$self->{field}}, {name => $name, body => $body};
254     }
255 wakaba 1.5 $body;
256 wakaba 1.1 }
257    
258     =head2 $self->relace ($field_name, $field_body)
259    
260     Set the C<field-body> named C<field-name> as $field_body.
261     If $field_name C<field> is already exists, it is replaced
262     by new $field_body value. If not, new C<field> is inserted.
263     (If there are some C<field> named as $field_name,
264     first one is used and the others are not changed.)
265    
266     =cut
267    
268     sub replace ($$$) {
269     my $self = shift;
270     my ($name, $body) = (lc shift, shift);
271     return 0 if $name =~ /$REG{UNSAFE_field_name}/;
272 wakaba 1.9 $body = $self->_field_body ($body, $name);
273 wakaba 1.1 for my $field (@{$self->{field}}) {
274     if ($field->{name} eq $name) {
275     $field->{body} = $body;
276 wakaba 1.8 return $body;
277 wakaba 1.1 }
278     }
279     push @{$self->{field}}, {name => $name, body => $body};
280 wakaba 1.9 $body;
281 wakaba 1.1 }
282    
283     =head2 $self->delete ($field_name, [$index])
284    
285     Deletes C<field> named as $field_name.
286     If $index is specified, only $index'th C<field> is deleted.
287     If not, ($index == 0), all C<field>s that have the C<field-name>
288     $field_name are deleted.
289    
290     =cut
291    
292     sub delete ($$;$) {
293     my $self = shift;
294     my ($name, $index) = (lc shift, shift);
295     my $i = 0;
296     for my $field (@{$self->{field}}) {
297     if ($field->{name} eq $name) {
298     $i++;
299     if ($index == 0 || $i == $index) {
300     undef $field;
301     return $self if $i == $index;
302     }
303     }
304     }
305     $self;
306     }
307    
308 wakaba 1.2 =head2 $self->count ([$field_name])
309 wakaba 1.1
310     Returns the number of times the given C<field> appears.
311 wakaba 1.2 If no $field_name is given, returns the number
312     of fields. (Same as $#$self+1)
313 wakaba 1.1
314     =cut
315    
316 wakaba 1.2 sub count ($;$) {
317 wakaba 1.1 my $self = shift;
318     my ($name) = (lc shift);
319 wakaba 1.2 unless ($name) {
320     $self->_delete_empty_field ();
321     return $#{$self->{field}}+1;
322     }
323 wakaba 1.1 my $count = 0;
324     for my $field (@{$self->{field}}) {
325     if ($field->{name} eq $name) {
326     $count++;
327     }
328     }
329     $count;
330     }
331    
332     =head2 $self->stringify ([%option])
333    
334     Returns the C<header> as a string.
335    
336     =cut
337    
338     sub stringify ($;%) {
339     my $self = shift;
340     my %OPT = @_;
341     my @ret;
342     $OPT{capitalize} ||= $self->{option}->{capitalize};
343     $OPT{mail_from} ||= $self->{option}->{mail_from};
344 wakaba 1.10 $OPT{output_bcc} ||= $self->{option}->{output_bcc};
345 wakaba 1.9 push @ret, 'From '.$self->field ('mail-from') if $OPT{mail_from}>0;
346 wakaba 1.1 for my $field (@{$self->{field}}) {
347     my $name = $field->{name};
348 wakaba 1.10 next unless $name;
349 wakaba 1.9 next if $OPT{mail_from}<0 && $name eq 'mail-from';
350 wakaba 1.10 next if $OPT{output_bcc}<0 && ($name eq 'bcc' || $name eq 'resent-bcc');
351 wakaba 1.5 my $fbody = scalar $field->{body};
352     next unless $fbody;
353 wakaba 1.9 $fbody =~ s/\x0D([^\x09\x0A\x20])/\x0D\x20$1/g;
354     $fbody =~ s/\x0A([^\x09\x20])/\x0A\x20$1/g;
355 wakaba 1.1 $name =~ s/((?:^|-)[a-z])/uc($1)/ge if $OPT{capitalize};
356 wakaba 1.5 push @ret, $name.': '.$self->fold ($fbody);
357 wakaba 1.1 }
358 wakaba 1.3 my $ret = join ("\n", @ret);
359     $ret? $ret."\n": "";
360 wakaba 1.1 }
361    
362     =head2 $self->get_option ($option_name)
363    
364     Returns value of the option.
365    
366     =head2 $self->set_option ($option_name, $option_value)
367    
368     Set new value of the option.
369    
370     =cut
371    
372     sub get_option ($$) {
373     my $self = shift;
374     my ($name) = @_;
375     $self->{option}->{$name};
376     }
377     sub set_option ($$$) {
378     my $self = shift;
379     my ($name, $value) = @_;
380     $self->{option}->{$name} = $value;
381     $self;
382     }
383    
384 wakaba 1.4 sub field_type ($$;$) {
385     my $self = shift;
386     my $field_name = shift;
387     my $new_field_type = shift;
388     if ($new_field_type) {
389     $self->{option}->{field_type}->{$field_name} = $new_field_type;
390     }
391     $self->{option}->{field_type}->{$field_name}
392 wakaba 1.7 || $self->{option}->{field_type}->{':DEFAULT'};
393 wakaba 1.4 }
394    
395 wakaba 1.1 sub _delete_empty_field ($) {
396     my $self = shift;
397     my @ret;
398     for my $field (@{$self->{field}}) {
399     push @ret, $field if $field->{name};
400     }
401     $self->{field} = \@ret;
402     $self;
403     }
404    
405     sub fold ($$;$) {
406     my $self = shift;
407     my $string = shift;
408     my $len = shift || $self->{option}->{fold_length};
409     $len = 60 if $len < 60;
410    
411     ## This code is taken from Mail::Header 1.43 in MailTools,
412     ## by Graham Barr (Maintained by Mark Overmeer <mailtools@overmeer.net>).
413     my $max = int($len - 5); # 4 for leading spcs + 1 for [\,\;]
414     my $min = int($len * 4 / 5) - 4;
415     my $ml = $len;
416    
417     if (length($string) > $ml) {
418     #Split the line up
419     # first bias towards splitting at a , or a ; >4/5 along the line
420     # next split a whitespace
421     # else we are looking at a single word and probably don't want to split
422     my $x = "";
423 wakaba 1.11 $x .= "$1\n "
424 wakaba 1.1 while($string =~ s/^$REG{WSP}*(
425     [^"]{$min,$max}?[\,\;]
426     |[^"]{1,$max}$REG{WSP}
427     |[^\s"]*(?:"[^"]*"[^\s"]*)+$REG{WSP}
428     |[^\s"]+$REG{WSP}
429     )
430     //x);
431     $x .= $string;
432     $string = $x;
433     $string =~ s/(\A$REG{WSP}+|$REG{WSP}+\Z)//sog;
434     $string =~ s/\s+\n/\n/sog;
435     }
436     $string;
437     }
438    
439     =head1 EXAMPLE
440    
441     ## Print field list
442    
443     use Message::Header;
444     my $header = Message::Header->parse ($header);
445    
446 wakaba 1.2 ## Next sample is better.
447     #for my $field (@$header) {
448     # print $field->{name}, "\t=> ", $field->{body}, "\n";
449     #}
450    
451     for my $i (0..$#$header) {
452     print $header->field_name ($i), "\t=> ", $header->field_body ($i), "\n";
453 wakaba 1.1 }
454    
455    
456     ## Make simple header
457    
458 wakaba 1.2 use Message::Header;
459 wakaba 1.1 use Message::Field::Address;
460     my $header = new Message::Header;
461    
462     my $from = new Message::Field::Address;
463     $from->add ('foo@foo.example', name => 'F. Foo');
464     my $to = new Message::Field::Address;
465     $to->add ('bar@bar.example', name => 'Mr. Bar');
466     $to->add ('hoge@foo.example', name => 'Hoge-san');
467     $header->add ('From' => $from);
468     $header->add ('To' => $to);
469     $header->add ('Subject' => 'Re: Meeting');
470     $header->add ('References' => '<hoge.msgid%foo@foo.example>');
471     print $header;
472    
473     =head1 LICENSE
474    
475     Copyright 2002 wakaba E<lt>w@suika.fam.cxE<gt>.
476    
477     This program is free software; you can redistribute it and/or modify
478     it under the terms of the GNU General Public License as published by
479     the Free Software Foundation; either version 2 of the License, or
480     (at your option) any later version.
481    
482     This program is distributed in the hope that it will be useful,
483     but WITHOUT ANY WARRANTY; without even the implied warranty of
484     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
485     GNU General Public License for more details.
486    
487     You should have received a copy of the GNU General Public License
488     along with this program; see the file COPYING. If not, write to
489     the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
490     Boston, MA 02111-1307, USA.
491    
492     =head1 CHANGE
493    
494     See F<ChangeLog>.
495 wakaba 1.11 $Date: 2002/03/26 05:41:16 $
496 wakaba 1.1
497     =cut
498    
499     1;

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24