/[suikacvs]/test/cvs
Suika

Contents of /test/cvs

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.18 - (show annotations) (download)
Tue May 14 13:50:11 2002 UTC (21 years, 11 months ago) by wakaba
Branch: MAIN
Changes since 1.17: +198 -49 lines
2002-05-14  wakaba <w@suika.fam.cx>

	* Entity.pm (pod:uri-url-mailto-*): New list-items.
	(stringify): Output mailto: URL when format =~ url-mailto.
	* Header.pm (stringify): Ditto.
	* Util.pm: Bugs are fixed.
	(remove_meaningless_wsp): New function.

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);
15 $VERSION = '1.00';
16 use Carp ();
17 use overload '@{}' => sub { shift->_delete_empty_field->{field} },
18 '""' => sub { shift->stringify },
19 fallback => 1;
20
21 $REG{WSP} = qr/[\x09\x20]/;
22 $REG{FWS} = qr/[\x09\x20]*/;
23 $REG{M_field} = qr/^([^\x3A]+):$REG{FWS}([\x00-\xFF]*)$/;
24 $REG{M_fromline} = qr/^\x3E?From$REG{WSP}+([\x00-\xFF]*)$/;
25 $REG{UNSAFE_field_name} = qr/[\x00-\x20\x3A\x7F-\xFF]/;
26
27 =head2 options
28
29 These options can be getten/set by C<get_option>/C<set_option>
30 method.
31
32 =head3 capitalize = 0/1
33
34 (First character of) C<field-name> is capitalized
35 when C<stringify>. (Default = 1)
36
37 =head3 fold_length = numeric value
38
39 Length of line used to fold. (Default = 70)
40
41 =head3 mail_from = 0/1
42
43 Outputs "From " line (known as Un*x From, Mail-From, and so on)
44 when C<stringify>. (Default = 0)
45
46 =cut
47
48 =head1 CONSTRUCTORS
49
50 The following methods construct new C<Message::Header> objects:
51
52 =over 4
53
54 =cut
55
56 ## Initialize
57 my %DEFAULT = (
58 capitalize => 1,
59 fold => 1,
60 fold_length => 70,
61 field_format_pattern => '%s: %s',
62 #field_type => {},
63 format => 'mail-rfc2822',
64 linebreak_strict => 0,
65 mail_from => 0,
66 output_bcc => 0,
67 parse_all => 0,
68 sort => 'none',
69 translate_underscore => 1,
70 uri_mailto_safe => {
71 ## 1 all (no check) 2 no trace & bcc & from
72 ## 3 no sender's info 4 (default) (currently not used)
73 ## 5 only a few
74 ':default' => 4,
75 'cc' => 4,
76 'bcc' => 1,
77 'body' => 1,
78 'comment' => 5,
79 'content-id' => 1,
80 'date' => 1,
81 'from' => 1,
82 'keywords' => 5,
83 'list-id' => 1,
84 'mail-from' => 1,
85 'message-id' => 1,
86 'received' => 1,
87 'resent-bcc' => 1,
88 'resent-date' => 1,
89 'resent-from' => 1,
90 'resent-sender' => 1,
91 'return-path' => 1,
92 'sender' => 1,
93 'subject' => 5,
94 'summary' => 5,
95 'to' => 4,
96 'user-agent' => 3,
97 'x-face' => 2,
98 'x-mailer' => 3,
99 'x-nsubject' => 5,
100 'x-received' => 1,
101 'x400-received' => 1,
102 },
103 uri_mailto_safe_level => 4,
104 validate => 1,
105 );
106 $DEFAULT{field_type} = {
107 ':DEFAULT' => 'Message::Field::Unstructured',
108
109 received => 'Message::Field::Received',
110 'x-received' => 'Message::Field::Received',
111
112 'content-type' => 'Message::Field::ContentType',
113 p3p => 'Message::Field::Params',
114 'auto-submitted' => 'Message::Field::ValueParams',
115 'content-disposition' => 'Message::Field::ValueParams',
116 link => 'Message::Field::ValueParams',
117 archive => 'Message::Field::ValueParams',
118 'x-face-type' => 'Message::Field::ValueParams',
119 'x-mozilla-draft-info' => 'Message::Field::ValueParams',
120
121 subject => 'Message::Field::Subject',
122 'x-nsubject' => 'Message::Field::Subject',
123
124 'list-software' => 'Message::Field::UA',
125 'user-agent' => 'Message::Field::UA',
126 server => 'Message::Field::UA',
127
128 ## A message id
129 'content-id' => 'Message::Field::MsgID',
130 'message-id' => 'Message::Field::MsgID',
131 'resent-message-id' => 'Message::Field::MsgID',
132
133 ## Numeric value
134 'content-length' => 'Message::Field::Numval',
135 lines => 'Message::Field::Numval',
136 'max-forwards' => 'Message::Field::Numval',
137 'mime-version' => 'Message::Field::Numval',
138 'x-jsmail-priority' => 'Message::Field::Numval',
139 'x-mail-count' => 'Message::Field::Numval',
140 'x-ml-count' => 'Message::Field::Numval',
141 'x-priority' => 'Message::Field::Numval',
142
143 path => 'Message::Field::Path',
144 };
145 for (qw(archive cancel-lock content-features content-md5
146 disposition-notification-options encoding
147 importance injector-info
148 pics-label posted-and-mailed precedence list-id message-type
149 original-recipient priority x-list-id
150 sensitivity status x-face x-msmail-priority xref))
151 {$DEFAULT{field_type}->{$_} = 'Message::Field::Structured'}
152 ## Not supported yet, but to be supported...
153 # x-list: unstructured, ml name
154 for (qw(abuse-reports-to apparently-to approved approved-by bcc cc complaints-to
155 delivered-to disposition-notification-to envelope-to
156 errors-to from mail-copies-to mail-followup-to mail-reply-to
157 notice-requested-upon-delivery-to read-receipt-to register-mail-reply-requested-by
158 reply-to resent-bcc
159 resent-cc resent-to resent-from resent-sender return-path
160 return-receipt-to return-receipt-requested-to sender to x-abuse-reports-to
161 x-admin x-approved
162 x-beenthere
163 x-confirm-reading-to
164 x-complaints-to x-envelope-from x-envelope-sender
165 x-envelope-to x-ml-address x-ml-command x-ml-to x-nfrom x-nto
166 x-rcpt-to x-sender x-x-sender))
167 {$DEFAULT{field_type}->{$_} = 'Message::Field::Address'}
168 for (qw(client-date date date-received delivery-date expires
169 expire-date nntp-posting-date posted posted-date received-date
170 reply-by resent-date
171 x-originalarrivaltime x-tcup-date))
172 {$DEFAULT{field_type}->{$_} = 'Message::Field::Date'}
173 for (qw(article-updates in-reply-to
174 obsoletes references replaces see-also supersedes))
175 {$DEFAULT{field_type}->{$_} = 'Message::Field::MsgIDs'}
176 for (qw(accept accept-charset accept-encoding accept-language
177 content-language
178 content-transfer-encoding encrypted followup-to keywords
179 list-archive list-digest list-help list-owner
180 list-post list-subscribe list-unsubscribe list-url uri newsgroups
181 posted-to))
182 {$DEFAULT{field_type}->{$_} = 'Message::Field::CSV'}
183 for (qw(x-brother x-boss x-classmate x-daughter x-dearfriend x-favoritesong
184 x-friend x-me
185 x-moe x-respect
186 x-sublimate x-son x-sister x-wife))
187 {$DEFAULT{field_type}->{$_} = 'Message::Field::CSV'} ## NOT M::F::XMOE!
188 for (qw(content-alias content-base content-location location referer
189 url x-home-page x-http_referer
190 x-info x-pgp-key x-ml-url x-uri x-url x-web))
191 {$DEFAULT{field_type}->{$_} = 'Message::Field::URI'}
192
193 my %header_goodcase = (
194 'article-i.d.' => 'Article-I.D.',
195 'content-id' => 'Content-ID',
196 'content-md5' => 'Content-MD5',
197 'content-sgml-entity' => 'Content-SGML-Entity',
198 etag => 'ETag',
199 fax => 'FAX',
200 'pics-label' => 'PICS-Label',
201 'list-url' => 'List-URL',
202 'list-id' => 'List-ID',
203 'message-id' => 'Message-ID',
204 'mime-version' => 'MIME-Version',
205 'nic' => 'NIC',
206 'nntp-posting-date' => 'NNTP-Posting-Date',
207 'nntp-posting-host' => 'NNTP-Posting-Host',
208 'resent-message-id' => 'Resent-Message-ID',
209 te => 'TE',
210 url => 'URL',
211 'www-authenticate' => 'WWW-Authenticate',
212 'x-dearfriend' => 'X-DearFriend',
213 'x-mime-autoconverted' => 'X-MIME-Autoconverted',
214 'x-nntp-posting-date' => 'X-NNTP-Posting-Date',
215 'x-nntp-posting-host' => 'X-NNTP-Posting-Host',
216 'x-uri' => 'X-URI',
217 'x-url' => 'X-URL',
218 );
219 $DEFAULT{capitalize} = sub {
220 my $self = shift;
221 my $name = shift;
222 if ($header_goodcase{$name}) {
223 return $header_goodcase{$name};
224 }
225 $name =~ s/(?:^|-)cgi-/uc $&/ge;
226 $name =~ s/(?:^|-)[a-z]/uc $&/ge;
227 $name;
228 };
229
230 ## taken from L<HTTP::Header>
231 # "Good Practice" order of HTTP message headers:
232 # - General-Headers
233 # - Request-Headers
234 # - Response-Headers
235 # - Entity-Headers
236 # (From draft-ietf-http-v11-spec-rev-01, Nov 21, 1997)
237 my @header_order = qw(
238 mail-from x-envelope-from relay-version path status
239
240 cache-control connection date pragma transfer-encoding upgrade trailer via
241
242 accept accept-charset accept-encoding accept-language
243 authorization expect from host
244 if-modified-since if-match if-none-match if-range if-unmodified-since
245 max-forwards proxy-authorization range referer te user-agent
246
247 accept-ranges age location proxy-authenticate retry-after server vary
248 warning www-authenticate
249
250 mime-version
251 allow content-base content-encoding content-language content-length
252 content-location content-md5 content-range content-type
253 etag expires last-modified content-style-type content-script-type
254 link
255
256 xref
257 );
258 my %header_order;
259
260 sub _init ($;%) {
261 my $self = shift;
262 my %options = @_;
263 $self->{field} = [];
264 $self->{option} = \%DEFAULT;
265 my @new_fields = ();
266 for my $name (keys %options) {
267 if (substr ($name, 0, 1) eq '-') {
268 $self->{option}->{substr ($name, 1)} = $options{$name};
269 } else {
270 push @new_fields, ($name => $options{$name});
271 }
272 }
273 $self->_init_by_format ($self->{option}->{format}, $self->{option});
274 # Make alternative representations of @header_order. This is used
275 # for sorting.
276 my $i = 1;
277 for (@header_order) {
278 $header_order{$_} = $i++ unless $header_order{$_};
279 }
280
281 $self->add (@new_fields, -parse => $self->{option}->{parse_all})
282 if $#new_fields > -1;
283 }
284
285 sub _init_by_format ($$\%) {
286 my $self = shift;
287 my ($format, $option) = @_;
288 if ($format =~ /rfc822/) {
289 $header_goodcase{bcc} = 'bcc';
290 $header_goodcase{cc} = 'cc';
291 $header_goodcase{'resent-bcc'} = 'Resent-bcc';
292 $header_goodcase{'resent-cc'} = 'Resent-cc';
293 } elsif ($format =~ /cgi/) {
294 unshift @header_order, qw(content-type location);
295 $option->{sort} = 'good-practice';
296 $option->{fold} = 0;
297 } elsif ($format =~ /http/) {
298 $option->{sort} = 'good-practice';
299 }
300 if ($format =~ /uri-url-mailto/) {
301 $option->{output_bcc} = 0;
302 $option->{capitalize} = 0;
303 $option->{field_format_pattern} = '%s=%s';
304 $option->{fold} = sub {
305 $_[1] =~ s/([^:@+\$A-Za-z0-9\-_.!~*])/sprintf('%%%02X', ord $1)/ge;
306 $_[1];
307 };
308 }
309 }
310
311 =item Message::Header->new ([%initial-fields/options])
312
313 Constructs a new C<Message::Headers> object. You might pass some initial
314 C<field-name>-C<field-body> pairs and/or options as parameters to the constructor.
315
316 Example:
317
318 $hdr = new Message::Headers
319 Date => 'Thu, 03 Feb 1994 00:00:00 +0000',
320 Content_Type => 'text/html',
321 Content_Location => 'http://www.foo.example/',
322 -format => 'mail-rfc2822' ## not to be header field
323 ;
324
325 =cut
326
327 sub new ($;%) {
328 my $class = shift;
329 my $self = bless {}, $class;
330 $self->_init (@_);
331 $self;
332 }
333
334 =item Message::Header->parse ($header, [%initial-fields/options])
335
336 Parses given C<header> and constructs a new C<Message::Headers>
337 object. You might pass some additional C<field-name>-C<field-body> pairs
338 or/and initial options as parameters to the constructor.
339
340 =cut
341
342 sub parse ($$;%) {
343 my $class = shift;
344 my $header = shift;
345 my $self = bless {}, $class;
346 $self->_init (@_); ## BUG: don't check linebreak_strict
347 $header =~ s/\x0D?\x0A$REG{WSP}/\x20/gos; ## unfold
348 for my $field (split /\x0D?\x0A/, $header) {
349 if ($field =~ /$REG{M_fromline}/) {
350 my $body = $1;
351 $body = $self->_field_body ($body, 'mail-from')
352 if $self->{option}->{parse_all};
353 push @{$self->{field}}, {name => 'mail-from', body => $body};
354 } elsif ($field =~ /$REG{M_field}/) {
355 my ($name, $body) = (lc $1, $2);
356 $name =~ s/$REG{WSP}+$//;
357 $body =~ s/$REG{WSP}+$//;
358 $body = $self->_field_body ($body, $name) if $self->{option}->{parse_all};
359 push @{$self->{field}}, {name => $name, body => $body};
360 }
361 }
362 $self;
363 }
364
365 =item Message::Header->parse_array (\@header, [%initial-fields/options])
366
367 Parses given C<header> and constructs a new C<Message::Headers>
368 object. Same as C<Message::Header-E<lt>parse> but this method
369 is given an array reference. You might pass some additional
370 C<field-name>-C<field-body> pairs or/and initial options
371 as parameters to the constructor.
372
373 =cut
374
375 sub parse_array ($\@;%) {
376 my $class = shift;
377 my $header = shift;
378 Carp::croak "parse_array: first argument is not an array reference"
379 unless ref $header eq 'ARRAY';
380 my $self = bless {}, $class;
381 $self->_init (@_);
382 while (1) {
383 my $field = shift @$header;
384 while (1) {
385 if ($$header[0] =~ /^$REG{WSP}/) {
386 $field .= shift @$header;
387 } else {last}
388 }
389 if ($self->{option}->{linebreak_strict}) {
390 $field =~ s/\x0D\x0A//g;
391 } else {
392 $field =~ tr/\x0D\x0A//d;
393 }
394 if ($field =~ /$REG{M_fromline}/) {
395 my $body = $1;
396 $body = $self->_field_body ($body, 'mail-from')
397 if $self->{option}->{parse_all};
398 push @{$self->{field}}, {name => 'mail-from', body => $body};
399 } elsif ($field =~ /$REG{M_field}/) {
400 my ($name, $body) = (lc $1, $2);
401 $name =~ s/$REG{WSP}+$//;
402 $body =~ s/$REG{WSP}+$//;
403 $body = $self->_field_body ($body, $name) if $self->{option}->{parse_all};
404 push @{$self->{field}}, {name => $name, body => $body};
405 }
406 last if $#$header < 0;
407 }
408 $self;
409 }
410
411 =back
412
413 =head1 METHODS
414
415 =head2 $self->field ($field_name)
416
417 Returns C<field-body> of given C<field-name>.
418 When there are two or more C<field>s whose name is C<field-name>,
419 this method return all C<field-body>s as array. (On scalar
420 context, only first one is returned.)
421
422 =cut
423
424 sub field ($$) {
425 my $self = shift;
426 my $name = lc shift;
427 my @ret;
428 for my $field (@{$self->{field}}) {
429 if ($field->{name} eq $name) {
430 unless (wantarray) {
431 $field->{body} = $self->_field_body ($field->{body}, $name);
432 return $field->{body};
433 } else {
434 $field->{body} = $self->_field_body ($field->{body}, $name);
435 push @ret, $field->{body};
436 }
437 }
438 }
439 if ($#ret < 0) {
440 return $self->add ($name);
441 }
442 @ret;
443 }
444
445 sub field_exist ($$) {
446 my $self = shift;
447 my $name = lc shift;
448 my @ret;
449 for my $field (@{$self->{field}}) {
450 return 1 if ($field->{name} eq $name);
451 }
452 0;
453 }
454
455 =head2 $self->field_name ($index)
456
457 Returns C<field-name> of $index'th C<field>.
458
459 =head2 $self->field_body ($index)
460
461 Returns C<field-body> of $index'th C<field>.
462
463 =cut
464
465 sub field_name ($$) {
466 my $self = shift;
467 $self->{field}->[shift]->{name};
468 }
469 sub field_body ($$) {
470 my $self = shift;
471 my $i = shift;
472 $self->{field}->[$i]->{body}
473 = $self->_field_body ($self->{field}->[$i]->{body}, $self->{field}->[$i]->{name});
474 $self->{field}->[$i]->{body};
475 }
476
477 sub _field_body ($$$) {
478 my $self = shift;
479 my ($body, $name) = @_;
480 unless (ref $body) {
481 my $type = $self->{option}->{field_type}->{$name}
482 || $self->{option}->{field_type}->{':DEFAULT'};
483 eval "require $type" or Carp::croak ("_field_body: $type: $@");
484 unless ($body) {
485 $body = $type->new (-field_name => $name,
486 -format => $self->{option}->{format},
487 -parse_all => $self->{option}->{parse_all},
488 field_name => $name, format => $self->{option}->{format});
489 } else {
490 $body = $type->parse ($body, -field_name => $name,
491 -format => $self->{option}->{format},
492 -parse_all => $self->{option}->{parse_all},
493 field_name => $name,format => $self->{option}->{format});
494 }
495 }
496 $body;
497 }
498
499 =head2 $self->field_name_list ()
500
501 Returns list of all C<field-name>s. (Even if there are two
502 or more C<field>s which have same C<field-name>, this method
503 returns ALL names.)
504
505 =cut
506
507 sub field_name_list ($) {
508 my $self = shift;
509 $self->_delete_empty_field ();
510 map {$_->{name}} @{$self->{field}};
511 }
512
513 =item $hdr->add ($field-name, $field-body, [$name, $body, ...])
514
515 Adds some field name/body pairs. Even if there are
516 one or more fields named given C<$field-name>,
517 given name/body pairs are ADDed. Use C<replace>
518 to remove same-name-fields.
519
520 Instead of field name-body pair, you might pass some options.
521 Four options are available for this method.
522
523 C<-parse>: Parses and validates C<field-body>, and returns
524 C<field-body> object. (When multiple C<field-body>s are
525 added, returns only last one.) (Default: C<defined wantarray>)
526
527 C<-prepend>: New fields are not appended,
528 but prepended to current fields. (Default: C<0>)
529
530 C<-translate-underscore>: Do C<field-name> =~ tr/_/-/. (Default: C<1>)
531
532 C<-validate>: Checks whether C<field-name> is valid or not.
533
534 =cut
535
536 sub add ($%) {
537 my $self = shift;
538 my %fields = @_;
539 my %option = %{$self->{option}};
540 $option{parse} = 1 if defined wantarray;
541 for (grep {/^-/} keys %fields) {$option{substr ($_, 1)} = $fields{$_}}
542 my $body;
543 for (grep {/^[^-]/} keys %fields) {
544 my $name = lc $_; $body = $fields{$_};
545 $name =~ tr/_/-/ if $option{translate_underscore};
546 Carp::croak "add: $name: invalid field-name"
547 if $option{validate} && $name =~ /$REG{UNSAFE_field_name}/;
548 $body = $self->_field_body ($body, $name) if $option{parse};
549 if ($option{prepend}) {
550 unshift @{$self->{field}}, {name => $name, body => $body};
551 } else {
552 push @{$self->{field}}, {name => $name, body => $body};
553 }
554 }
555 $body if $option{parse};
556 }
557
558 =head2 $self->relace ($field_name, $field_body)
559
560 Set the C<field-body> named C<field-name> as $field_body.
561 If $field_name C<field> is already exists, it is replaced
562 by new $field_body value. If not, new C<field> is inserted.
563 (If there are some C<field> named as $field_name,
564 first one is used and the others are not changed.)
565
566 =cut
567
568 sub replace ($%) {
569 my $self = shift;
570 my %params = @_;
571 my %option = %{$self->{option}};
572 $option{parse} = defined wantarray unless defined $option{parse};
573 for (grep {/^-/} keys %params) {$option{substr ($_, 1)} = $params{$_}}
574 my (%new_field);
575 for (grep {/^[^-]/} keys %params) {
576 my $name = lc $_;
577 $name =~ tr/_/-/ if $option{translate_underscore};
578 Carp::croak "replace: $name: invalid field-name"
579 if $option{validate} && $name =~ /$REG{UNSAFE_field_name}/;
580 $params{$_} = $self->_field_body ($params{$_}, $name) if $option{parse};
581 $new_field{$name} = $params{$_};
582 }
583 my $body = (%new_field)[-1];
584 for my $field (@{$self->{field}}) {
585 if (defined $new_field{$field->{name}}) {
586 $field->{body} = $new_field {$field->{name}};
587 $new_field{$field->{name}} = undef;
588 }
589 }
590 for (keys %new_field) {
591 push @{$self->{field}}, {name => $_, body => $new_field{$_}};
592 }
593 $body if $option{parse};
594 }
595
596 =head2 $self->delete ($field-name, [$name, ...])
597
598 Deletes C<field> named as $field_name.
599
600 =cut
601
602 sub delete ($@) {
603 my $self = shift;
604 my %delete; for (@_) {$delete{lc $_} = 1}
605 for my $field (@{$self->{field}}) {
606 undef $field if $delete{$field->{name}};
607 }
608 }
609
610 =head2 $self->count ([$field_name])
611
612 Returns the number of times the given C<field> appears.
613 If no $field_name is given, returns the number
614 of fields. (Same as $#$self+1)
615
616 =cut
617
618 sub count ($;$) {
619 my $self = shift;
620 my ($name) = (lc shift);
621 unless ($name) {
622 $self->_delete_empty_field ();
623 return $#{$self->{field}}+1;
624 }
625 my $count = 0;
626 for my $field (@{$self->{field}}) {
627 if ($field->{name} eq $name) {
628 $count++;
629 }
630 }
631 $count;
632 }
633
634 =head2 $self->rename ($field-name, $new-name, [$old, $new,...])
635
636 Renames C<$field-name> as C<$new-name>.
637
638 =cut
639
640 sub rename ($%) {
641 my $self = shift;
642 my %params = @_;
643 my %option = %{$self->{option}};
644 for (grep {/^-/} keys %params) {$option{substr ($_, 1)} = $params{$_}}
645 my %new_name;
646 for (grep {/^[^-]/} keys %params) {
647 my ($old => $new) = (lc $_ => lc $params{$_});
648 $new =~ tr/_/-/ if $option{translate_underscore};
649 Carp::croak "rename: $new: invalid field-name"
650 if $option{validate} && $new =~ /$REG{UNSAFE_field_name}/;
651 $new_name{$old} = $new;
652 }
653 for my $field (@{$self->{field}}) {
654 if (length $new_name{$field->{name}}) {
655 $field->{name} = $new_name{$field->{name}};
656 }
657 }
658 $self if defined wantarray;
659 }
660
661
662 =item $self->scan(\&doit)
663
664 Apply a subroutine to each header field in turn. The callback routine is
665 called with two parameters; the name of the field and a single value.
666 If the header has more than one value, then the routine is called once
667 for each value.
668
669 =cut
670
671 sub scan ($&) {
672 my ($self, $sub) = @_;
673 my $sort;
674 $sort = \&_header_cmp if $self->{option}->{sort} eq 'good-practice';
675 $sort = {$a cmp $b} if $self->{option}->{sort} eq 'alphabetic';
676 my @field = @{$self->{field}};
677 if (ref $sort) {
678 @field = sort $sort @{$self->{field}};
679 }
680 for my $field (@field) {
681 next if $field->{name} =~ /^_/;
682 &$sub($field->{name} => $field->{body});
683 }
684 }
685
686 # Compare function which makes it easy to sort headers in the
687 # recommended "Good Practice" order.
688 ## taken from HTTP::Header
689 sub _header_cmp
690 {
691 my ($na, $nb) = ($a->{name}, $b->{name});
692 # Unknown headers are assign a large value so that they are
693 # sorted last. This also helps avoiding a warning from -w
694 # about comparing undefined values.
695 $header_order{$na} = 999 unless defined $header_order{$na};
696 $header_order{$nb} = 999 unless defined $header_order{$nb};
697
698 $header_order{$na} <=> $header_order{$nb} || $na cmp $nb;
699 }
700
701 =head2 $self->stringify ([%option])
702
703 Returns the C<header> as a string.
704
705 =cut
706
707 sub stringify ($;%) {
708 my $self = shift;
709 my %params = @_;
710 my %option = %{$self->{option}};
711 $option{format} = $params{-format} if $params{-format};
712 $self->_init_by_format ($option{format}, \%option);
713 for (grep {/^-/} keys %params) {$option{substr ($_, 1)} = $params{$_}}
714 my @ret;
715 my $_stringify = sub {
716 my ($name, $body) = (@_);
717 return unless length $name;
718 return if $option{mail_from} && $name eq 'mail-from';
719 return if !$option{output_bcc} && ($name eq 'bcc' || $name eq 'resent-bcc');
720 if ($option{format} =~ /uri-url-mailto/) {
721 return if (( $option{uri_mailto_safe}->{$name}
722 || $option{uri_mailto_safe}->{':default'})
723 < $option{uri_mailto_safe_level});
724 if ($name eq 'to') {
725 $body = $self->field ('to');
726 return unless ref $body && $body->have_group;
727 }
728 }
729 my $fbody;
730 if (ref $body) {
731 $fbody = $body->stringify (-format => $option{format});
732 } else {
733 $fbody = $body;
734 }
735 return unless length $fbody;
736 unless ($option{linebreak_strict}) {
737 ## bare \x0D and bare \x0A are unsafe
738 $fbody =~ s/\x0D(?=[^\x09\x0A\x20])/\x0D\x20/g;
739 $fbody =~ s/\x0A(?=[^\x09\x20])/\x0A\x20/g;
740 } else {
741 $fbody =~ s/\x0D\x0A(?=[^\x09\x20])/\x0D\x0A\x20/g;
742 }
743 if (ref $option{capitalize}) {
744 $name = &{$option{capitalize}} ($self, $name);
745 } elsif ($option{capitalize}) {
746 $name =~ s/((?:^|-)[a-z])/uc($1)/ge;
747 }
748 if (ref $option{fold}) {
749 $fbody = &{$option{fold}} ($self, $fbody);
750 } elsif ($option{fold}) {
751 $fbody = $self->_fold ($fbody);
752 }
753 push @ret, sprintf $option{field_format_pattern}, $name, $fbody;
754 };
755 if ($option{format} =~ /uri-url-mailto-to/) {
756 if ($self->field_exist ('to')) {
757 my $to = $self->field ('to');
758 unless ($to->have_group) {
759 my $fbody = $to->stringify (-format => $option{format});
760 return &{$option{fold}} ($self, $fbody);
761 }
762 }
763 '';
764 } elsif ($option{format} =~ /uri-url-mailto/) {
765 $self->scan ($_stringify);
766 my $ret = join ('&', @ret);
767 $ret;
768 } else {
769 if ($option{mail_from}) {
770 my $fromline = $self->field ('mail-from');
771 push @ret, 'From '.$fromline if $fromline;
772 }
773 $self->scan ($_stringify);
774 my $ret = join ("\n", @ret);
775 $ret? $ret."\n": '';
776 }
777 }
778 *as_string = \&stringify;
779
780 =head2 $self->option ($option_name, [$option_value])
781
782 Set/gets new value of the option.
783
784 =cut
785
786 sub option ($@) {
787 my $self = shift;
788 if (@_ == 1) {
789 return $self->{option}->{ shift (@_) };
790 }
791 while (my ($name, $value) = splice (@_, 0, 2)) {
792 $name =~ s/^-//;
793 $self->{option}->{$name} = $value;
794 if ($name eq 'format') {
795 for my $f (@{$self->{field}}) {
796 if (ref $f->{body}) {
797 $f->{body}->option (-format => $value);
798 }
799 }
800 }
801 }
802 }
803
804 sub field_type ($$;$) {
805 my $self = shift;
806 my $field_name = shift;
807 my $new_field_type = shift;
808 if ($new_field_type) {
809 $self->{option}->{field_type}->{$field_name} = $new_field_type;
810 }
811 $self->{option}->{field_type}->{$field_name}
812 || $self->{option}->{field_type}->{':DEFAULT'};
813 }
814
815 sub _delete_empty_field ($) {
816 my $self = shift;
817 my @ret;
818 for my $field (@{$self->{field}}) {
819 push @ret, $field if $field->{name};
820 }
821 $self->{field} = \@ret;
822 $self;
823 }
824
825 sub _fold ($$;$) {
826 my $self = shift;
827 my $string = shift;
828 my $len = shift || $self->{option}->{fold_length};
829 $len = 60 if $len < 60;
830
831 ## This code is taken from Mail::Header 1.43 in MailTools,
832 ## by Graham Barr (Maintained by Mark Overmeer <mailtools@overmeer.net>).
833 my $max = int($len - 5); # 4 for leading spcs + 1 for [\,\;]
834 my $min = int($len * 4 / 5) - 4;
835 my $ml = $len;
836
837 if (length($string) > $ml) {
838 #Split the line up
839 # first bias towards splitting at a , or a ; >4/5 along the line
840 # next split a whitespace
841 # else we are looking at a single word and probably don't want to split
842 my $x = "";
843 $x .= "$1\n "
844 while($string =~ s/^$REG{WSP}*(
845 [^"]{$min,$max}?[\,\;]
846 |[^"]{1,$max}$REG{WSP}
847 |[^\s"]*(?:"[^"]*"[^\s"]*)+$REG{WSP}
848 |[^\s"]+$REG{WSP}
849 )
850 //x);
851 $x .= $string;
852 $string = $x;
853 $string =~ s/(\A$REG{WSP}+|$REG{WSP}+\Z)//sog;
854 $string =~ s/\s+\n/\n/sog;
855 }
856 $string;
857 }
858
859 =head2 $self->clone ()
860
861 Returns a copy of Message::Header object.
862
863 =cut
864
865 sub clone ($) {
866 my $self = shift;
867 my $clone = new Message::Header;
868 for my $name (%{$self->{option}}) {
869 if (ref $self->{option}->{$name} eq 'HASH') {
870 $clone->{option}->{$name} = {%{$self->{option}->{$name}}};
871 } elsif (ref $self->{option}->{$name} eq 'ARRAY') {
872 $clone->{option}->{$name} = [@{$self->{option}->{$name}}];
873 } else {
874 $clone->{option}->{$name} = $self->{option}->{$name};
875 }
876 }
877 for (@{$self->{field}}) {
878 $clone->add ($_->{name}, scalar $_->{body});
879 }
880 $clone;
881 }
882
883 =head1 NOTE
884
885 =head2 C<field-name>
886
887 The header field name is not case sensitive. To make the life
888 easier for perl users who wants to avoid quoting before the => operator,
889 you can use '_' as a synonym for '-' in header field names
890 (this behaviour can be suppressed by setting
891 C<translate_underscore> option to C<0> value).
892
893 =head1 EXAMPLE
894
895 ## Print field list
896
897 use Message::Header;
898 my $header = Message::Header->parse ($header);
899
900 for my $i (0..$#$header) {
901 print $header->field_name ($i), "\t=> ", $header->field_body ($i), "\n";
902 }
903
904
905 ## Make simple header
906
907 use Message::Header;
908 use Message::Field::Address;
909 my $header = new Message::Header;
910
911 my $from = new Message::Field::Address;
912 $from->add ('foo@foo.example', name => 'F. Foo');
913 my $to = new Message::Field::Address;
914 $to->add ('bar@bar.example', name => 'Mr. Bar');
915 $to->add ('hoge@foo.example', name => 'Hoge-san');
916 $header->add ('From' => $from);
917 $header->add ('To' => $to);
918 $header->add ('Subject' => 'Re: Meeting');
919 $header->add ('References' => '<hoge.msgid%foo@foo.example>');
920 print $header;
921
922 =head1 ACKNOWLEDGEMENTS
923
924 Some of codes are taken from other modules such as
925 HTTP::Header, Mail::Header.
926
927 =head1 LICENSE
928
929 Copyright 2002 wakaba E<lt>w@suika.fam.cxE<gt>.
930
931 This program is free software; you can redistribute it and/or modify
932 it under the terms of the GNU General Public License as published by
933 the Free Software Foundation; either version 2 of the License, or
934 (at your option) any later version.
935
936 This program is distributed in the hope that it will be useful,
937 but WITHOUT ANY WARRANTY; without even the implied warranty of
938 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
939 GNU General Public License for more details.
940
941 You should have received a copy of the GNU General Public License
942 along with this program; see the file COPYING. If not, write to
943 the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
944 Boston, MA 02111-1307, USA.
945
946 =head1 CHANGE
947
948 See F<ChangeLog>.
949 $Date: 2002/05/14 13:50:11 $
950
951 =cut
952
953 1;

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24