/[suikacvs]/test/cvs
Suika

Contents of /test/cvs

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.11 - (show annotations) (download)
Tue Mar 26 15:19:53 2002 UTC (22 years, 1 month 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
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 field_type => {':DEFAULT' => 'Message::Field::Unstructured'},
51 mail_from => -1,
52 output_bcc => -1,
53 parse_all => -1,
54 );
55 my @field_type_Structured = qw(cancel-lock
56 importance mime-version path precedence x-cite
57 x-face x-mail-count x-msmail-priority x-priority x-uidl xref);
58 for (@field_type_Structured)
59 {$DEFAULT{field_type}->{$_} = 'Message::Field::Structured'}
60 my @field_type_Address = qw(approved bcc cc delivered-to disposition-notification-to
61 envelope-to
62 errors-to fcc from mail-followup-to mail-followup-cc mail-from reply-to resent-bcc
63 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 x-envelope-to x-ml-address x-ml-command x-ml-to x-nfrom x-nto);
67 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 references resent-message-id see-also supersedes);
75 for (@field_type_MsgID)
76 {$DEFAULT{field_type}->{$_} = 'Message::Field::MsgID'}
77 for (qw(received x-received))
78 {$DEFAULT{field_type}->{$_} = 'Message::Field::Received'}
79 $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 for (qw(accept accept-charset accept-encoding accept-language
84 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 {$DEFAULT{field_type}->{$_} = 'Message::Field::CSV'}
88 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 for (qw(list-id))
94 {$DEFAULT{field_type}->{$_} = 'Message::Field::Structured'}
95 for (qw(subject title x-nsubject))
96 {$DEFAULT{field_type}->{$_} = 'Message::Field::Subject'}
97 for (qw(list-software user-agent server))
98 {$DEFAULT{field_type}->{$_} = 'Message::Field::UA'}
99
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 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 } elsif ($field =~ /$REG{M_field}/) {
134 my ($name, $body) = (lc $1, $2);
135 $name =~ s/$REG{WSP}+$//;
136 $body =~ s/$REG{WSP}+$//;
137 $body = $self->_field_body ($body, $name) if $self->{option}->{parse_all}>0;
138 push @{$self->{field}}, {name => $name, body => $body};
139 }
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 $field->{body} = $self->_field_body ($field->{body}, $name);
161 return $field->{body};
162 } else {
163 $field->{body} = $self->_field_body ($field->{body}, $name);
164 push @ret, $field->{body};
165 }
166 }
167 }
168 if ($#ret < 0) {
169 return $self->add ($name);
170 }
171 @ret;
172 }
173
174 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 =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 my $i = shift;
201 $self->{field}->[$i]->{body}
202 = $self->_field_body ($self->{field}->[$i]->{body}, $self->{field}->[$i]->{name});
203 $self->{field}->[$i]->{body};
204 }
205
206 sub _field_body ($$$) {
207 my $self = shift;
208 my ($body, $name) = @_;
209 unless (ref $body) {
210 my $type = $self->{option}->{field_type}->{$name}
211 || $self->{option}->{field_type}->{':DEFAULT'};
212 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 }
219 $body;
220 }
221
222 =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 sub add ($$;$%) {
245 my $self = shift;
246 my ($name, $body) = (lc shift, shift);
247 my %option = @_;
248 return 0 if $name =~ /$REG{UNSAFE_field_name}/;
249 $body = $self->_field_body ($body, $name);
250 if ($option{prepend}) {
251 unshift @{$self->{field}}, {name => $name, body => $body};
252 } else {
253 push @{$self->{field}}, {name => $name, body => $body};
254 }
255 $body;
256 }
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 $body = $self->_field_body ($body, $name);
273 for my $field (@{$self->{field}}) {
274 if ($field->{name} eq $name) {
275 $field->{body} = $body;
276 return $body;
277 }
278 }
279 push @{$self->{field}}, {name => $name, body => $body};
280 $body;
281 }
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 =head2 $self->count ([$field_name])
309
310 Returns the number of times the given C<field> appears.
311 If no $field_name is given, returns the number
312 of fields. (Same as $#$self+1)
313
314 =cut
315
316 sub count ($;$) {
317 my $self = shift;
318 my ($name) = (lc shift);
319 unless ($name) {
320 $self->_delete_empty_field ();
321 return $#{$self->{field}}+1;
322 }
323 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 $OPT{output_bcc} ||= $self->{option}->{output_bcc};
345 push @ret, 'From '.$self->field ('mail-from') if $OPT{mail_from}>0;
346 for my $field (@{$self->{field}}) {
347 my $name = $field->{name};
348 next unless $name;
349 next if $OPT{mail_from}<0 && $name eq 'mail-from';
350 next if $OPT{output_bcc}<0 && ($name eq 'bcc' || $name eq 'resent-bcc');
351 my $fbody = scalar $field->{body};
352 next unless $fbody;
353 $fbody =~ s/\x0D([^\x09\x0A\x20])/\x0D\x20$1/g;
354 $fbody =~ s/\x0A([^\x09\x20])/\x0A\x20$1/g;
355 $name =~ s/((?:^|-)[a-z])/uc($1)/ge if $OPT{capitalize};
356 push @ret, $name.': '.$self->fold ($fbody);
357 }
358 my $ret = join ("\n", @ret);
359 $ret? $ret."\n": "";
360 }
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 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 || $self->{option}->{field_type}->{':DEFAULT'};
393 }
394
395 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 $x .= "$1\n "
424 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 ## 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 }
454
455
456 ## Make simple header
457
458 use Message::Header;
459 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 $Date: 2002/03/26 15:19:53 $
496
497 =cut
498
499 1;

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24