/[suikacvs]/test/cvs
Suika

Diff of /test/cvs

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 1.3 by wakaba, Wed Mar 13 14:47:07 2002 UTC revision 1.18 by wakaba, Tue May 14 13:50:11 2002 UTC
# Line 11  Perl module for RFC 822/2822 message C<h Line 11  Perl module for RFC 822/2822 message C<h
11    
12  package Message::Header;  package Message::Header;
13  use strict;  use strict;
14  use vars qw($VERSION %REG %DEFAULT);  use vars qw($VERSION %REG);
15  $VERSION = '1.00';  $VERSION = '1.00';
16    use Carp ();
17  use overload '@{}' => sub {shift->_delete_empty_field()->{field}},  use overload '@{}' => sub { shift->_delete_empty_field->{field} },
18               '""' => sub {shift->stringify};               '""' => sub { shift->stringify },
19                 fallback => 1;
20    
21  $REG{WSP}     = qr/[\x09\x20]/;  $REG{WSP}     = qr/[\x09\x20]/;
22  $REG{FWS}     = qr/[\x09\x20]*/;  $REG{FWS}     = qr/[\x09\x20]*/;
# Line 44  when C<stringify>.  (Default = 0) Line 45  when C<stringify>.  (Default = 0)
45    
46  =cut  =cut
47    
48  %DEFAULT = (  =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,    capitalize    => 1,
59      fold  => 1,
60    fold_length   => 70,    fold_length   => 70,
61      field_format_pattern  => '%s: %s',
62      #field_type   => {},
63      format        => 'mail-rfc2822',
64      linebreak_strict      => 0,
65    mail_from     => 0,    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  =head2 Message::Header->new ([%option])  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  Returns new Message::Header instance.  Some options can be  Example:
317  specified as hash.  
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  =cut
326    
327  sub new ($;%) {  sub new ($;%) {
328    my $class = shift;    my $class = shift;
329    my $self = bless {option => {@_}}, $class;    my $self = bless {}, $class;
330    for (keys %DEFAULT) {$self->{option}->{$_} ||= $DEFAULT{$_}}    $self->_init (@_);
331    $self;    $self;
332  }  }
333    
334  =head2 Message::Header->parse ($header, [%option])  =item Message::Header->parse ($header, [%initial-fields/options])
335    
336  Parses given C<header> and return a new Message::Header  Parses given C<header> and constructs a new C<Message::Headers>
337  object.  Some options can be specified as hash.  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  =cut
341    
342  sub parse ($$;%) {  sub parse ($$;%) {
343    my $class = shift;    my $class = shift;
344    my $header = shift;    my $header = shift;
345    my $self = bless {option => {@_}}, $class;    my $self = bless {}, $class;
346    for (keys %DEFAULT) {$self->{option}->{$_} ||= $DEFAULT{$_}}    $self->_init (@_);    ## BUG: don't check linebreak_strict
347    $header =~ s/\x0D?\x0A$REG{WSP}+/\x20/gos;    ## unfold    $header =~ s/\x0D?\x0A$REG{WSP}/\x20/gos;     ## unfold
348    for my $field (split /\x0D?\x0A/, $header) {    for my $field (split /\x0D?\x0A/, $header) {
349      if ($field =~ /$REG{M_fromline}/) {      if ($field =~ /$REG{M_fromline}/) {
350        push @{$self->{field}}, {name => 'mail-from', body => $1};        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}/) {      } elsif ($field =~ /$REG{M_field}/) {
355        my ($name, $body) = ($1, $2);        my ($name, $body) = (lc $1, $2);
356        $name =~ s/$REG{WSP}+$//;        $name =~ s/$REG{WSP}+$//;
357        $body =~ s/$REG{WSP}+$//;        $body =~ s/$REG{WSP}+$//;
358        push @{$self->{field}}, {name => lc $name, body => $body};        $body = $self->_field_body ($body, $name) if $self->{option}->{parse_all};
359          push @{$self->{field}}, {name => $name, body => $body};
360      }      }
361    }    }
362    $self;    $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)  =head2 $self->field ($field_name)
416    
417  Returns C<field-body> of given C<field-name>.  Returns C<field-body> of given C<field-name>.
# Line 106  sub field ($$) { Line 428  sub field ($$) {
428    for my $field (@{$self->{field}}) {    for my $field (@{$self->{field}}) {
429      if ($field->{name} eq $name) {      if ($field->{name} eq $name) {
430        unless (wantarray) {        unless (wantarray) {
431            $field->{body} = $self->_field_body ($field->{body}, $name);
432          return $field->{body};          return $field->{body};
433        } else {        } else {
434            $field->{body} = $self->_field_body ($field->{body}, $name);
435          push @ret, $field->{body};          push @ret, $field->{body};
436        }        }
437      }      }
438    }    }
439      if ($#ret < 0) {
440        return $self->add ($name);
441      }
442    @ret;    @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)  =head2 $self->field_name ($index)
456    
457  Returns C<field-name> of $index'th C<field>.  Returns C<field-name> of $index'th C<field>.
# Line 131  sub field_name ($$) { Line 468  sub field_name ($$) {
468  }  }
469  sub field_body ($$) {  sub field_body ($$) {
470    my $self = shift;    my $self = shift;
471    $self->{field}->[shift]->{body};    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 ()  =head2 $self->field_name_list ()
# Line 148  sub field_name_list ($) { Line 510  sub field_name_list ($) {
510    map {$_->{name}} @{$self->{field}};    map {$_->{name}} @{$self->{field}};
511  }  }
512    
513  =head2 $self->add ($field_name, $field_body)  =item $hdr->add ($field-name, $field-body, [$name, $body, ...])
514    
515  Adds an new C<field>.  It is not checked whether  Adds some field name/body pairs.  Even if there are
516  the field which named $field_body is already exist or not.  one or more fields named given C<$field-name>,
517  If you don't want duplicated C<field>s, use C<replace> method.  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  =cut
535    
536  sub add ($$$) {  sub add ($%) {
537    my $self = shift;    my $self = shift;
538    my ($name, $body) = (lc shift, shift);    my %fields = @_;
539    return 0 if $name =~ /$REG{UNSAFE_field_name}/;    my %option = %{$self->{option}};
540    push @{$self->{field}}, {name => $name, body => $body};    $option{parse} = 1 if defined wantarray;
541    $self;    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)  =head2 $self->relace ($field_name, $field_body)
# Line 174  first one is used and the others are not Line 565  first one is used and the others are not
565    
566  =cut  =cut
567    
568  sub replace ($$$) {  sub replace ($%) {
569    my $self = shift;    my $self = shift;
570    my ($name, $body) = (lc shift, shift);    my %params = @_;
571    return 0 if $name =~ /$REG{UNSAFE_field_name}/;    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}}) {    for my $field (@{$self->{field}}) {
585      if ($field->{name} eq $name) {      if (defined $new_field{$field->{name}}) {
586        $field->{body} = $body;        $field->{body} = $new_field {$field->{name}};
587        return $self;        $new_field{$field->{name}} = undef;
588      }      }
589    }    }
590    push @{$self->{field}}, {name => $name, body => $body};    for (keys %new_field) {
591    $self;      push @{$self->{field}}, {name => $_, body => $new_field{$_}};
592      }
593      $body if $option{parse};
594  }  }
595    
596  =head2 $self->delete ($field_name, [$index])  =head2 $self->delete ($field-name, [$name, ...])
597    
598  Deletes C<field> named as $field_name.  Deletes C<field> named as $field_name.
 If $index is specified, only $index'th C<field> is deleted.  
 If not, ($index == 0), all C<field>s that have the C<field-name>  
 $field_name are deleted.  
599    
600  =cut  =cut
601    
602  sub delete ($$;$) {  sub delete ($@) {
603    my $self = shift;    my $self = shift;
604    my ($name, $index) = (lc shift, shift);    my %delete;  for (@_) {$delete{lc $_} = 1}
   my $i = 0;  
605    for my $field (@{$self->{field}}) {    for my $field (@{$self->{field}}) {
606      if ($field->{name} eq $name) {      undef $field if $delete{$field->{name}};
       $i++;  
       if ($index == 0 || $i == $index) {  
         undef $field;  
         return $self if $i == $index;  
       }  
     }  
607    }    }
   $self;  
608  }  }
609    
610  =head2 $self->count ([$field_name])  =head2 $self->count ([$field_name])
# Line 237  sub count ($;$) { Line 631  sub count ($;$) {
631    $count;    $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])  =head2 $self->stringify ([%option])
702    
703  Returns the C<header> as a string.  Returns the C<header> as a string.
# Line 245  Returns the C<header> as a string. Line 706  Returns the C<header> as a string.
706    
707  sub stringify ($;%) {  sub stringify ($;%) {
708    my $self = shift;    my $self = shift;
709    my %OPT = @_;    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;    my @ret;
715    $OPT{capitalize} ||= $self->{option}->{capitalize};    my $_stringify = sub {
716    $OPT{mail_from} ||= $self->{option}->{mail_from};        my ($name, $body) = (@_);
717    push @ret, 'From '.$self->field ('mail-from') if $OPT{mail_from};        return unless length $name;
718    for my $field (@{$self->{field}}) {        return if $option{mail_from} && $name eq 'mail-from';
719      my $name = $field->{name};        return if !$option{output_bcc} && ($name eq 'bcc' || $name eq 'resent-bcc');
720      next unless $field->{name};        if ($option{format} =~ /uri-url-mailto/) {
721      next if !$OPT{mail_from} && $name eq 'mail-from';          return if ((   $option{uri_mailto_safe}->{$name}
722      $name =~ s/((?:^|-)[a-z])/uc($1)/ge if $OPT{capitalize};               || $option{uri_mailto_safe}->{':default'})
723      push @ret, $name.': '.$self->fold ($field->{body});                < $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    }    }
   my $ret = join ("\n", @ret);  
   $ret? $ret."\n": "";  
777  }  }
778    *as_string = \&stringify;
779    
780  =head2 $self->get_option ($option_name)  =head2 $self->option ($option_name, [$option_value])
   
 Returns value of the option.  
   
 =head2 $self->set_option ($option_name, $option_value)  
781    
782  Set new value of the option.  Set/gets new value of the option.
783    
784  =cut  =cut
785    
786  sub get_option ($$) {  sub option ($@) {
787    my $self = shift;    my $self = shift;
788    my ($name) = @_;    if (@_ == 1) {
789    $self->{option}->{$name};      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  sub set_option ($$$) {  
804    sub field_type ($$;$) {
805    my $self = shift;    my $self = shift;
806    my ($name, $value) = @_;    my $field_name = shift;
807    $self->{option}->{$name} = $value;    my $new_field_type = shift;
808    $self;    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 ($) {  sub _delete_empty_field ($) {
# Line 293  sub _delete_empty_field ($) { Line 822  sub _delete_empty_field ($) {
822    $self;    $self;
823  }  }
824    
825  sub fold ($$;$) {  sub _fold ($$;$) {
826    my $self = shift;    my $self = shift;
827    my $string = shift;    my $string = shift;
828    my $len = shift || $self->{option}->{fold_length};    my $len = shift || $self->{option}->{fold_length};
# Line 311  sub fold ($$;$) { Line 840  sub fold ($$;$) {
840       # next split a whitespace       # next split a whitespace
841       # else we are looking at a single word and probably don't want to split       # else we are looking at a single word and probably don't want to split
842       my $x = "";       my $x = "";
843       $x .= "$1\n    "       $x .= "$1\n "
844         while($string =~ s/^$REG{WSP}*(         while($string =~ s/^$REG{WSP}*(
845                            [^"]{$min,$max}?[\,\;]                            [^"]{$min,$max}?[\,\;]
846                            |[^"]{1,$max}$REG{WSP}                            |[^"]{1,$max}$REG{WSP}
# Line 327  sub fold ($$;$) { Line 856  sub fold ($$;$) {
856    $string;    $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  =head1 EXAMPLE
894    
895    ## Print field list    ## Print field list
# Line 334  sub fold ($$;$) { Line 897  sub fold ($$;$) {
897    use Message::Header;    use Message::Header;
898    my $header = Message::Header->parse ($header);    my $header = Message::Header->parse ($header);
899        
   ## Next sample is better.  
   #for my $field (@$header) {  
   #  print $field->{name}, "\t=> ", $field->{body}, "\n";  
   #}  
     
900    for my $i (0..$#$header) {    for my $i (0..$#$header) {
901      print $header->field_name ($i), "\t=> ", $header->field_body ($i), "\n";      print $header->field_name ($i), "\t=> ", $header->field_body ($i), "\n";
902    }    }
# Line 361  sub fold ($$;$) { Line 919  sub fold ($$;$) {
919    $header->add ('References' => '<hoge.msgid%foo@foo.example>');    $header->add ('References' => '<hoge.msgid%foo@foo.example>');
920    print $header;    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  =head1 LICENSE
928    
929  Copyright 2002 wakaba E<lt>w@suika.fam.cxE<gt>.  Copyright 2002 wakaba E<lt>w@suika.fam.cxE<gt>.

Legend:
Removed from v.1.3  
changed lines
  Added in v.1.18

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24