/[suikacvs]/test/cvs
Suika

Contents of /test/cvs

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.8 - (show annotations) (download)
Sat Mar 23 11:43:06 2002 UTC (22 years, 1 month ago) by wakaba
Branch: MAIN
Changes since 1.7: +18 -9 lines
2002-03-23  wakaba <w@suika.fam.cx>

	* Header.pm: Supports Message::Field::CSV,
	Message::Field::ValueParams, Message::Field::ContentType,
	Message::Field::ContentDisposition.

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24