/[suikacvs]/test/cvs
Suika

Contents of /test/cvs

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.7 - (show annotations) (download)
Thu Mar 21 04:21:28 2002 UTC (22 years, 1 month ago) by wakaba
Branch: MAIN
Changes since 1.6: +17 -22 lines
2002-03-21  wakaba <w@suika.fam.cx>

	* Body/: New directory.

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24