/[suikacvs]/test/cvs
Suika

Contents of /test/cvs

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.9 - (show annotations) (download)
Mon Mar 25 10:18:35 2002 UTC (22 years, 1 month ago) by wakaba
Branch: MAIN
Changes since 1.8: +31 -10 lines
2002-03-25  wakaba <w@suika.fam.cx>

	* MIME/: New directory.
	* Util.pm: New module.
	* Entity.pm, Header.pm (parse_all): New option.
	(hook_encode_string, hook_decode_string): Likewise.

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24