/[suikacvs]/test/cvs
Suika

Contents of /test/cvs

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.8 - (hide 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 wakaba 1.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 wakaba 1.7 field_type => {':DEFAULT' => 'Message::Field::Unstructured'},
52 wakaba 1.1 );
53 wakaba 1.7 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 wakaba 1.4 for (@field_type_Structured)
57     {$DEFAULT{field_type}->{$_} = 'Message::Field::Structured'}
58 wakaba 1.8 my @field_type_Address = qw(approved bcc cc delivered-to disposition-notification-to
59     envelope-to
60 wakaba 1.7 errors-to fcc from mail-followup-to mail-followup-cc mail-from reply-to resent-bcc
61 wakaba 1.4 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 wakaba 1.7 x-envelope-to x-ml-address x-ml-command x-ml-to x-nfrom x-nto);
65 wakaba 1.4 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 wakaba 1.6 references resent-message-id see-also supersedes);
73 wakaba 1.4 for (@field_type_MsgID)
74 wakaba 1.6 {$DEFAULT{field_type}->{$_} = 'Message::Field::MsgID'}
75 wakaba 1.7 for (qw(received x-received))
76 wakaba 1.5 {$DEFAULT{field_type}->{$_} = 'Message::Field::Received'}
77 wakaba 1.8 $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 wakaba 1.7 for (qw(accept accept-charset accept-encoding accept-language
82 wakaba 1.8 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 wakaba 1.7 {$DEFAULT{field_type}->{$_} = 'Message::Field::CSV'}
86 wakaba 1.4 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 wakaba 1.7 for (qw(list-id))
92 wakaba 1.4 {$DEFAULT{field_type}->{$_} = 'Message::Field::Structured'}
93 wakaba 1.7 for (qw(content-description subject title x-nsubject))
94 wakaba 1.5 {$DEFAULT{field_type}->{$_} = 'Message::Field::Subject'}
95 wakaba 1.1
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 wakaba 1.5 $field->{body} = $self->_field_body ($field->{body}, $name);
153     return $field->{body};
154 wakaba 1.1 } else {
155 wakaba 1.5 $field->{body} = $self->_field_body ($field->{body}, $name);
156     push @ret, $field->{body};
157 wakaba 1.1 }
158     }
159     }
160     @ret;
161     }
162    
163 wakaba 1.2 =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 wakaba 1.4 my $i = shift;
180 wakaba 1.5 $self->{field}->[$i]->{body}
181     = $self->_field_body ($self->{field}->[$i]->{body}, $self->{field}->[$i]->{name});
182     $self->{field}->[$i]->{body};
183 wakaba 1.4 }
184    
185     sub _field_body ($$$) {
186     my $self = shift;
187     my ($body, $name) = @_;
188 wakaba 1.5 unless (ref $body) {
189 wakaba 1.4 my $type = $self->{option}->{field_type}->{$name}
190 wakaba 1.7 || $self->{option}->{field_type}->{':DEFAULT'};
191 wakaba 1.5 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 wakaba 1.4 }
198 wakaba 1.5 $body;
199 wakaba 1.2 }
200    
201 wakaba 1.1 =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 wakaba 1.8 sub add ($$$;%) {
224 wakaba 1.1 my $self = shift;
225     my ($name, $body) = (lc shift, shift);
226 wakaba 1.8 my %option = @_;
227 wakaba 1.1 return 0 if $name =~ /$REG{UNSAFE_field_name}/;
228 wakaba 1.5 $body = $self->_field_body ($body, $name);
229 wakaba 1.8 if ($option{prepend}) {
230     unshift @{$self->{field}}, {name => $name, body => $body};
231     } else {
232     push @{$self->{field}}, {name => $name, body => $body};
233     }
234 wakaba 1.5 $body;
235 wakaba 1.1 }
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 wakaba 1.8 return $body;
255 wakaba 1.1 }
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 wakaba 1.2 =head2 $self->count ([$field_name])
287 wakaba 1.1
288     Returns the number of times the given C<field> appears.
289 wakaba 1.2 If no $field_name is given, returns the number
290     of fields. (Same as $#$self+1)
291 wakaba 1.1
292     =cut
293    
294 wakaba 1.2 sub count ($;$) {
295 wakaba 1.1 my $self = shift;
296     my ($name) = (lc shift);
297 wakaba 1.2 unless ($name) {
298     $self->_delete_empty_field ();
299     return $#{$self->{field}}+1;
300     }
301 wakaba 1.1 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 wakaba 1.5 my $fbody = scalar $field->{body};
328     next unless $fbody;
329 wakaba 1.1 $name =~ s/((?:^|-)[a-z])/uc($1)/ge if $OPT{capitalize};
330 wakaba 1.5 push @ret, $name.': '.$self->fold ($fbody);
331 wakaba 1.1 }
332 wakaba 1.3 my $ret = join ("\n", @ret);
333     $ret? $ret."\n": "";
334 wakaba 1.1 }
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 wakaba 1.4 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 wakaba 1.7 || $self->{option}->{field_type}->{':DEFAULT'};
367 wakaba 1.4 }
368    
369 wakaba 1.1 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 wakaba 1.2 ## 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 wakaba 1.1 }
428    
429    
430     ## Make simple header
431    
432 wakaba 1.2 use Message::Header;
433 wakaba 1.1 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 wakaba 1.8 $Date: 2002/03/23 11:43:06 $
470 wakaba 1.1
471     =cut
472    
473     1;

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24