/[suikacvs]/test/cvs
Suika

Contents of /test/cvs

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.4 - (show annotations) (download)
Sat Mar 16 08:54:39 2002 UTC (22 years, 1 month ago) by wakaba
Branch: MAIN
Changes since 1.3: +69 -4 lines
2002-03-16  wakaba <w@suika.fam.cx>

	* Header.pm: Use Message::Field::Date.

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24