diff options
Diffstat (limited to 'cpan/lib/Mail/Header.pm')
| -rw-r--r-- | cpan/lib/Mail/Header.pm | 1257 |
1 files changed, 437 insertions, 820 deletions
diff --git a/cpan/lib/Mail/Header.pm b/cpan/lib/Mail/Header.pm index ca864212..a999f656 100644 --- a/cpan/lib/Mail/Header.pm +++ b/cpan/lib/Mail/Header.pm @@ -1,166 +1,126 @@ -# Mail::Header.pm -# -# Copyright (c) 1995-2001 Graham Barr <gbarr@pobox.com>. All rights reserved. -# This program is free software; you can redistribute it and/or -# modify it under the same terms as Perl itself. - -# -# The internals of this package are implemented in terms of a list of lines -# and a hash indexed by the tags. The hash contains a list of references to -# the actual SV's in the list. We therefore do our upmost to preserve this. -# anyone who delves into these structures deserve all they get. -# - +# Copyrights 1995-2017 by [Mark Overmeer <perl@overmeer.net>]. +# For other contributors see ChangeLog. +# See the manual pages for details on the licensing terms. +# Pod stripped from pm file by OODoc 2.02. package Mail::Header; +use vars '$VERSION'; +$VERSION = '2.19'; -require 5.002; use strict; use Carp; -use vars qw($VERSION $FIELD_NAME); - -$VERSION = "1.52"; my $MAIL_FROM = 'KEEP'; my %HDR_LENGTHS = (); -# -# Pattern to match a RFC822 Field name ( Extract from RFC #822) -# -# field = field-name ":" [ field-body ] CRLF -# -# field-name = 1*<any CHAR, excluding CTLs, SPACE, and ":"> -# -# CHAR = <any ASCII character> ; ( 0-177, 0.-127.) -# CTL = <any ASCII control ; ( 0- 37, 0.- 31.) -# character and DEL> ; ( 177, 127.) -# I have included the trailing ':' in the field-name -# -$FIELD_NAME = '[^\x00-\x1f\x7f-\xff :]+:'; +our $FIELD_NAME = '[^\x00-\x1f\x7f-\xff :]+:'; + ## ## Private functions ## -sub _error { warn @_; return (wantarray ? () : undef) } +sub _error { warn @_; () } # tidy up internal hash table and list sub _tidy_header -{ - my $me = shift; - my($ref,$key); - my $i; - my $d = 0; - - for($i = 0 ; $i < scalar(@{$me->{'mail_hdr_list'}}) ; $i++) - { - unless(defined $me->{'mail_hdr_list'}[$i]) - { - splice(@{$me->{'mail_hdr_list'}},$i,1); - $d++; - $i--; - } - } +{ my $self = shift; + my $deleted = 0; - if($d) - { - local $_; - my @del = (); + for(my $i = 0 ; $i < @{$self->{mail_hdr_list}}; $i++) + { next if defined $self->{mail_hdr_list}[$i]; - while(($key,$ref) = each %{$me->{'mail_hdr_hash'}} ) - { - push(@del, $key) - unless @$ref = grep { ref($_) && defined $$_ } @$ref; + splice @{$self->{mail_hdr_list}}, $i, 1; + $deleted++; + $i--; } - map { delete $me->{'mail_hdr_hash'}{$_} } @del; - } + if($deleted) + { local $_; + my @del; + + while(my ($key,$ref) = each %{$self->{mail_hdr_hash}} ) + { push @del, $key + unless @$ref = grep { ref $_ && defined $$_ } @$ref; + } + + delete $self->{'mail_hdr_hash'}{$_} for @del; + } } # fold the line to the given length -my %STRUCTURE; -@STRUCTURE{ map { lc } qw{ - To Cc Bcc From Date Reply-To Sender - Resent-Date Resent-From Resent-Sender Resent-To Return-Path - list-help list-post list-unsubscribe Mailing-List - Received References Message-ID In-Reply-To - Content-Length Content-Type Content-Disposition - Delivered-To - Lines - MIME-Version - Precedence - Status -}} = (); +my %STRUCTURE = map { (lc $_ => undef) } + qw{ To Cc Bcc From Date Reply-To Sender + Resent-Date Resent-From Resent-Sender Resent-To Return-Path + list-help list-post list-unsubscribe Mailing-List + Received References Message-ID In-Reply-To + Content-Length Content-Type Content-Disposition + Delivered-To + Lines + MIME-Version + Precedence + Status + }; sub _fold_line -{ - my($ln,$maxlen) = @_; - - $maxlen = 20 - if($maxlen < 20); - - my $max = int($maxlen - 5); # 4 for leading spcs + 1 for [\,\;] - my $min = int($maxlen * 4 / 5) - 4; - my $ml = $maxlen; - - $_[0] =~ s/\s*[\r\n]+\s*/ /og; # Compress any white space around a newline - $_[0] =~ s/\s*\Z/\n/so; # End line with a EOLN - - return if $_[0] =~ /^From\s/io; - - if(length($_[0]) > $ml) - { - if ($_[0] =~ /^([-\w]+)/ && exists $STRUCTURE{ lc $1 } ) - { - #Split the line up - # first bias towards splitting at a , or a ; >4/5 along the line - # next split a whitespace - # else we are looking at a single word and probably don't want to split - my $x = ""; - - $x .= "$1\n " - while($_[0] =~ s/^\s*( - [^"]{$min,$max}?[\,\;] - |[^"]{1,$max}\s - |[^\s"]*(?:"[^"]*"[^\s"]*)+\s - |[^\s"]+\s - ) - //x); - $x .= $_[0]; - $_[0] = $x; - $_[0] =~ s/(\A\s+|[\t ]+\Z)//sog; - $_[0] =~ s/\s+\n/\n/sog; - } - else - { - $_[0] =~ s/(.{$min,$max})\s+/$+\n /g; - $_[0] =~ s/\s*$/\n/s; +{ my($ln,$maxlen) = @_; + + $maxlen = 20 + if $maxlen < 20; + + my $max = int($maxlen - 5); # 4 for leading spcs + 1 for [\,\;] + my $min = int($maxlen * 4 / 5) - 4; + + $_[0] =~ s/[\r\n]+//og; # Remove new-lines + $_[0] =~ s/\s*\Z/\n/so; # End line with a EOLN + + return if $_[0] =~ /^From\s/io; + + if(length($_[0]) > $maxlen) + { if($_[0] =~ /^([-\w]+)/ && exists $STRUCTURE{ lc $1 } ) + { #Split the line up + # first bias towards splitting at a , or a ; >4/5 along the line + # next split a whitespace + # else we are looking at a single word and probably don't want to split + my $x = ""; + $x .= "$1\n " while $_[0] =~ + s/^\s* + ( [^"]{$min,$max} [,;] + | [^"]{1,$max} [,;\s] + | [^\s"]*(?:"[^"]*"[ \t]?[^\s"]*)+\s + ) //x; + + $x .= $_[0]; + $_[0] = $x; + $_[0] =~ s/(\A\s+|[\t ]+\Z)//sog; + $_[0] =~ s/\s+\n/\n/sog; + } + else + { $_[0] =~ s/(.{$min,$max})(\s)/$1\n$2/g; + $_[0] =~ s/\s*$/\n/s; + } } - } - $_[0] =~ s/\A(\S+)\n\s*(?=\S)/$1 /so; + $_[0] =~ s/\A(\S+)\n\s*(?=\S)/$1 /so; } -# attempt to change the case of a tag to that required by RFC822. That +# Tags are case-insensitive, but there is a (slightly) preferred construction # being all characters are lowercase except the first of each word. Also # if the word is an `acronym' then all characters are uppercase. We decide # a word is an acronym if it does not contain a vowel. +# In general, this change of capitalization is a bad idea, but it is in +# the code for ages, and therefore probably crucial for existing +# applications. sub _tag_case -{ - my $tag = shift; - - $tag =~ s/:\Z//o; - - # Change the casing of the tag, eg "Message-Id" - # Bug in unicode \U, perl 5.8.0 requires an extra \u - $tag =~ s/\b([a-z]+)/\L\u\u$1/gio; - $tag =~ s/\b([b-df-hj-np-tv-z]+|MIME)\b/\U\u$1/gio - if $tag =~ /-/; - - $tag; +{ my $tag = shift; + $tag =~ s/\:$//; + join '-' + , map { /^[b-df-hj-np-tv-z]+$|^(?:MIME|SWE|SOAP|LDAP|ID)$/i + ? uc($_) : ucfirst(lc($_)) + } split m/\-/, $tag, -1; } # format a complete line @@ -170,846 +130,503 @@ sub _tag_case # fold the line sub _fmt_line -{ - my $me = shift; - my $tag = shift; - my $line = shift; - my $modify = shift || $me->{'mail_hdr_modify'}; - my $ctag = undef; - - ($tag) = $line =~ /\A($FIELD_NAME|From )/oi - unless(defined $tag); - - if($tag =~ /\AFrom /io && $me->{'mail_hdr_mail_from'} ne 'KEEP') - { - if ($me->{'mail_hdr_mail_from'} eq 'COERCE') - { - $line =~ s/^From /Mail-From: /o; - $tag = "Mail-From:"; - } - elsif ($me->{'mail_hdr_mail_from'} eq 'IGNORE') - { - return (); - } - elsif ($me->{'mail_hdr_mail_from'} eq 'ERROR') - { - return _error "unadorned 'From ' ignored: <$line>" +{ my ($self, $tag, $line, $modify) = @_; + $modify ||= $self->{mail_hdr_modify}; + my $ctag = undef; + + ($tag) = $line =~ /^($FIELD_NAME|From )/oi + unless defined $tag; + + if(defined $tag && $tag =~ /^From /io && $self->{mail_hdr_mail_from} ne 'KEEP') + { if($self->{mail_hdr_mail_from} eq 'COERCE') + { $line =~ s/^From /Mail-From: /o; + $tag = "Mail-From:"; + } + elsif($self->{mail_hdr_mail_from} eq 'IGNORE') + { return (); + } + elsif($self->{mail_hdr_mail_from} eq 'ERROR') + { return _error "unadorned 'From ' ignored: <$line>"; + } } - } - - if(defined $tag) - { - $tag = _tag_case($ctag = $tag); - - $ctag = $tag - if($modify); - $ctag =~ s/([^ :])\Z/$1:/o if defined $ctag; - } + if(defined $tag) + { $tag = _tag_case($ctag = $tag); + $ctag = $tag if $modify; + $ctag =~ s/([^ :])$/$1:/o if defined $ctag; + } - croak( "Bad RFC822 field name '$tag'\n") - unless(defined $ctag && $ctag =~ /\A($FIELD_NAME|From )/oi); + defined $ctag && $ctag =~ /^($FIELD_NAME|From )/oi + or croak "Bad RFC822 field name '$tag'\n"; - # Ensure the line starts with tag - if(defined($ctag) && ($modify || $line !~ /\A\Q$ctag\E/i)) - { - my $xtag; - ($xtag = $ctag) =~ s/\s*\Z//o; - $line =~ s/\A(\Q$ctag\E)?\s*/$xtag /i; - } + # Ensure the line starts with tag + if(defined $ctag && ($modify || $line !~ /^\Q$ctag\E/i)) + { (my $xtag = $ctag) =~ s/\s*\Z//o; + $line =~ s/^(\Q$ctag\E)?\s*/$xtag /i; + } - my $maxlen = $me->{'mail_hdr_lengths'}{$tag} - || $HDR_LENGTHS{$tag} - || $me->fold_length; + my $maxlen = $self->{mail_hdr_lengths}{$tag} + || $HDR_LENGTHS{$tag} + || $self->fold_length; - _fold_line($line,$maxlen) - if $modify && defined $maxlen; + if ($modify && defined $maxlen) + { # folding will fix bad header continuations for us + _fold_line $line, $maxlen; + } + elsif($line =~ /\r?\n\S/) + { return _error "Bad header continuation, skipping '$tag': ", + "no space after newline in '$line'\n"; + } - $line =~ s/\n*\Z/\n/so; - ($tag, $line); + $line =~ s/\n*$/\n/so; + ($tag, $line); } sub _insert -{ - my($me,$tag,$line,$where) = @_; - - if($where < 0) - { - $where = @{$me->{'mail_hdr_list'}} + $where + 1; +{ my ($self, $tag, $line, $where) = @_; - $where = 0 - if($where < 0); - } - elsif($where >= @{$me->{'mail_hdr_list'}}) - { - $where = @{$me->{'mail_hdr_list'}}; - } - - my $atend = $where == @{$me->{'mail_hdr_list'}}; - - splice(@{$me->{'mail_hdr_list'}},$where,0,$line); - - $me->{'mail_hdr_hash'}{$tag} ||= []; - my $ref = \${$me->{'mail_hdr_list'}}[$where]; + if($where < 0) + { $where = @{$self->{mail_hdr_list}} + $where + 1; + $where = 0 if $where < 0; + } + elsif($where >= @{$self->{mail_hdr_list}}) + { $where = @{$self->{mail_hdr_list}}; + } - if($me->{'mail_hdr_hash'}{$tag} && $where) - { - if($atend) - { - push(@{$me->{'mail_hdr_hash'}{$tag}}, $ref); + my $atend = $where == @{$self->{mail_hdr_list}}; + splice @{$self->{mail_hdr_list}}, $where, 0, $line; + + $self->{mail_hdr_hash}{$tag} ||= []; + my $ref = \${$self->{mail_hdr_list}}[$where]; + + my $def = $self->{mail_hdr_hash}{$tag}; + if($def && $where) + { if($atend) { push @$def, $ref } + else + { my $i = 0; + foreach my $ln (@{$self->{mail_hdr_list}}) + { my $r = \$ln; + last if $r == $ref; + $i++ if $r == $def->[$i]; + } + splice @$def, $i, 0, $ref; + } } - else - { - my $i = 0; - foreach my $ln (@{$me->{'mail_hdr_list'}}) - { - my $r = \$ln; - last if($r == $ref); - $i++ if($r == $me->{'mail_hdr_hash'}{$tag}[$i]); - } - splice(@{$me->{'mail_hdr_hash'}{$tag}},$i,0,$ref); + else + { unshift @$def, $ref; } - } - else - { - unshift(@{$me->{'mail_hdr_hash'}{$tag}}, $ref); - } } -## -## Constructor -## +#------------ sub new -{ - my $self = shift; - my $type = ref($self) || $self; - my $arg = @_ % 2 ? shift : undef; - my %arg = @_; - - $arg{Modify} = delete $arg{Reformat} unless exists $arg{Modify}; - - my %hash = ( - mail_hdr_list => [], - mail_hdr_hash => {}, - mail_hdr_modify => delete $arg{Modify} || 0, - mail_hdr_foldlen => 79, - mail_hdr_lengths => {} - ); - - my $me = bless \%hash, $type; - - $me->mail_from( uc($arg{'MailFrom'} || $MAIL_FROM) ); - - $me->fold_length($arg{FoldLength}) - if exists $arg{FoldLength}; - - if(ref $arg) - { - if(ref($arg) eq 'ARRAY') - { - $me->extract([ @{$arg} ]); - } - elsif(defined fileno($arg)) - { - $me->read($arg); - } - } +{ my $call = shift; + my $class = ref($call) || $call; + my $arg = @_ % 2 ? shift : undef; + my %opt = @_; - $me; -} + $opt{Modify} = delete $opt{Reformat} + unless exists $opt{Modify}; -sub modify -{ - my $me = shift; - my $old = $me->{'mail_hdr_modify'}; + my $self = bless + { mail_hdr_list => [] + , mail_hdr_hash => {} + , mail_hdr_modify => (delete $opt{Modify} || 0) + , mail_hdr_foldlen => 79 + , mail_hdr_lengths => {} + }, $class; - $me->{'mail_hdr_modify'} = 0 + shift - if @_; + $self->mail_from( uc($opt{MailFrom} || $MAIL_FROM) ); + + $self->fold_length($opt{FoldLength}) + if exists $opt{FoldLength}; - $old; + if(!ref $arg) {} + elsif(ref($arg) eq 'ARRAY') { $self->extract( [ @$arg ] ) } + elsif(defined fileno($arg)) { $self->read($arg) } + + $self; } -sub mail_from -{ - my $me = shift; - my $choice = uc(shift); - $choice =~ /^(IGNORE|ERROR|COERCE|KEEP)$/ - or die "bad Mail-From choice: '$choice'"; +sub dup +{ my $self = shift; + my $dup = ref($self)->new; - if(ref($me)) - { - $me->{'mail_hdr_mail_from'} = $choice; - } - else - { - $MAIL_FROM = $choice; - } + %$dup = %$self; + $dup->empty; # rebuild tables - $me; -} + $dup->{mail_hdr_list} = [ @{$self->{mail_hdr_list}} ]; -sub fold -{ - my $me = shift; - my $maxlen = shift; - my($tag,$list,$ln); - - while(($tag,$list) = each %{$me->{'mail_hdr_hash'}}) - { - my $len = $maxlen - || $me->{'mail_hdr_lengths'}{$tag} - || $HDR_LENGTHS{$tag} - || $me->fold_length; - - foreach $ln (@$list) - { - _fold_line($$ln,$len) - if defined $ln; + foreach my $ln ( @{$dup->{mail_hdr_list}} ) + { my $tag = _tag_case +($ln =~ /^($FIELD_NAME|From )/oi)[0]; + push @{$dup->{mail_hdr_hash}{$tag}}, \$ln; } - } - $me; + $dup; } -sub unfold -{ - my $me = shift; - my($tag,$list,$ln); - - if(@_) - { - $tag = _tag_case(shift); - return $me unless exists $me->{'mail_hdr_hash'}{$tag}; - $list = $me->{'mail_hdr_hash'}{$tag}; - foreach $ln (@$list) - { - $$ln =~ s/\r?\n\s+/ /sog - if defined $ln && defined $$ln; - } - } - else - { - while(($tag,$list) = each %{$me->{'mail_hdr_hash'}}) - { - foreach $ln (@$list) - { - $$ln =~ s/\r?\n\s+/ /sog - if defined $ln && defined $$ln; - } - } - } - $me; -} +#------------ sub extract -{ - my $me = shift; - my $arr = shift; - my $line; - - $me->empty; +{ my ($self, $lines) = @_; + $self->empty; - while(scalar(@{$arr}) && $arr->[0] =~ /\A($FIELD_NAME|From )/o) - { - my $tag = $1; + while(@$lines) + { my $line = shift @$lines; + last if $line =~ /^\r?$/; - $line = shift @{$arr}; - $line .= shift @{$arr} - while(scalar(@{$arr}) && $arr->[0] =~ /\A[ \t]+/o); + $line =~ /^($FIELD_NAME|From )/o or next; + my $tag = $1; - ($tag,$line) = _fmt_line($me,$tag,$line); + $line .= shift @$lines + while @$lines && $lines->[0] =~ /^[ \t]+/; - _insert($me,$tag,$line,-1) - if defined $line; - } + ($tag, $line) = _fmt_line $self, $tag, $line; - shift @{$arr} - if(scalar(@{$arr}) && $arr->[0] =~ /\A\s*\Z/o); + _insert $self, $tag, $line, -1 + if defined $line; + } - $me; + $self; } + sub read -{ - my $me = shift; - my $fd = shift; +{ my ($self, $fd) = @_; + $self->empty; - $me->empty; + my ($ln, $tag, $line); + while(1) + { $ln = <$fd>; - my $line = undef; - my $ln = ""; - my $tag = undef; + if(defined $ln && defined $line && $ln =~ /^[ \t]+/) + { $line .= $ln; # folded line + next; + } - while(1) - { - $ln = <$fd>; + if(defined $line) + { ($tag, $line) = _fmt_line $self, $tag, $line; + _insert $self, $tag, $line, -1 + if defined $line; + ($tag, $line) = (); + } - if(defined $ln && defined $line && $ln =~ /\A[ \t]+/o) - { - $line .= $ln; - next; - } + last if !defined $ln || $ln =~ m/^\r?$/; - if(defined $line) - { - ($tag,$line) = _fmt_line($me,$tag,$line); - _insert($me,$tag,$line,-1) - if defined $line; + $ln =~ /^($FIELD_NAME|From )/o or next; + ($tag, $line) = ($1, $ln); } - last - unless(defined $ln && $ln =~ /\A($FIELD_NAME|From )/o); - - $tag = $1; - $line = $ln; - } - - $me; + $self; } -sub empty -{ - my $me = shift; - - $me->{'mail_hdr_list'} = []; - $me->{'mail_hdr_hash'} = {}; - $me; +sub empty +{ my $self = shift; + $self->{mail_hdr_list} = []; + $self->{mail_hdr_hash} = {}; + $self; } -sub header -{ - my $me = shift; - $me->extract(@_) - if(@_); +sub header +{ my $self = shift; - $me->fold - if $me->{'mail_hdr_modify'}; + $self->extract(@_) + if @_; - # Must protect ourself against corruption as the hash contains refs to the - # SV's in the list, if the user modifies this list we are really screwed :- + $self->fold + if $self->{mail_hdr_modify}; - [ @{$me->{'mail_hdr_list'}} ]; + [ @{$self->{mail_hdr_list}} ]; } -# Return/set headers by hash reference. This can probably be -# optimized. I didn't want to mess much around with the internal -# implementation as for now... -# -- Tobias Brox <tobix@cpan.org> -sub header_hashref { - my $me = shift; - my $hashref = shift; +sub header_hashref +{ my ($self, $hashref) = @_; - # Extract the input data - for my $hdrkey (keys %$hashref) { - for (ref $hashref->{$hdrkey} - ? @{$hashref->{$hdrkey}} - : $hashref->{$hdrkey}) { - $me->add($hdrkey, $_); - } - } + while(my ($key, $value) = each %$hashref) + { $self->add($key, $_) for ref $value ? @$value : $value; + } - $me->fold - if $me->{'mail_hdr_modify'}; + $self->fold + if $self->{mail_hdr_modify}; - # Build a hash - my $hash={ map { $_ => [ $me->get($_) ] } keys %{$me->{'mail_hdr_hash'}} }; + defined wantarray # MO, added minimal optimization + or return; - return $hash; + +{ map { ($_ => [$self->get($_)] ) } # MO: Eh? + keys %{$self->{mail_hdr_hash}} + }; } -sub add -{ - my $me = shift; - my($tag,$text,$where) = @_; - my $line; - ($tag,$line) = _fmt_line($me,$tag,$text); - - # Must have a tag and text to add - return undef - unless(defined $tag && defined $line); - - $where = -1 - unless defined $where; +#------------ - _insert($me,$tag,$line,$where); - - $line =~ /^\S+\s(.*)/os; - return $1; -} +sub modify +{ my $self = shift; + my $old = $self->{mail_hdr_modify}; -sub replace -{ - my $me = shift; - my $idx = 0; - my($tag,$line); - - $idx = pop @_ - if(@_ % 2); - -TAG: - while(@_) - { - ($tag,$line) = _fmt_line($me,splice(@_,0,2)); - - return undef - unless(defined $tag && defined $line); - - if(exists $me->{'mail_hdr_hash'}{$tag} && - defined $me->{'mail_hdr_hash'}{$tag}[$idx]) - { - ${$me->{'mail_hdr_hash'}{$tag}[$idx]} = $line; - } - else - { - _insert($me,$tag,$line,-1); - } - } + $self->{mail_hdr_modify} = 0 + shift + if @_; - $line =~ /^\S+\s*(.*)/os; - return $1; + $old; } -sub combine -{ - my $me = shift; - my $tag = _tag_case(shift); - my $with = shift || ' '; - my $line; - - return _error "unadorned 'From ' ignored" - if($tag =~ /^From /io && $me->{'mail_hdr_mail_from'} ne 'KEEP'); - - return undef - unless exists $me->{'mail_hdr_hash'}{$tag}; - if(scalar(@{$me->{'mail_hdr_hash'}{$tag}}) > 1) - { - my @lines = $me->get($tag); - - chomp(@lines); - - map { $$_ = undef } @{$me->{'mail_hdr_hash'}{$tag}}; +sub mail_from +{ my $thing = shift; + my $choice = uc shift; - $line = ${$me->{'mail_hdr_hash'}{$tag}[0]} = - (_fmt_line($me,$tag, join($with,@lines),1))[1]; + $choice =~ /^(IGNORE|ERROR|COERCE|KEEP)$/ + or die "bad Mail-From choice: '$choice'"; - _tidy_header($me); - } - else - { - return $me->{'mail_hdr_hash'}{$tag}[0]; - } + if(ref $thing) { $thing->{mail_hdr_mail_from} = $choice } + else { $MAIL_FROM = $choice } - return $line; # post-match + $thing; } -sub get -{ - my $me = shift; - my $tag = _tag_case(shift); - my $idx = shift; - - return wantarray ? () : undef - unless exists $me->{'mail_hdr_hash'}{$tag}; - - my $l = length($tag); - $l += 1 unless $tag =~ / \Z/o; - - $idx = 0 - unless defined $idx || wantarray; - - if(defined $idx) - { - return defined $me->{'mail_hdr_hash'}{$tag}[$idx] - ? eval { # why won't do work here ?? - my $tmp = substr(${$me->{'mail_hdr_hash'}{$tag}[$idx]}, $l); - $tmp =~ s/^\s+//; - $tmp; - } - : undef; - } - - return map { - my $tmp = substr($$_,$l); - $tmp =~ s/^\s+//; - $tmp - } @{$me->{'mail_hdr_hash'}{$tag}}; -} - -sub count -{ - my $me = shift; - my $tag = _tag_case(shift); - exists $me->{'mail_hdr_hash'}{$tag} - ? scalar(@{$me->{'mail_hdr_hash'}{$tag}}) - : 0; -} +sub fold_length +{ my $thing = shift; + my $old; -sub exists -{ - carp "Depriciated use of Mail::Header::exists, use count" if $^W; - count(@_); -} + if(@_ == 2) + { my $tag = _tag_case shift; + my $len = shift; -sub delete -{ - my $me = shift; - my $tag = _tag_case(shift); - my $idx = shift; - my @val = (); - - if(defined $me->{'mail_hdr_hash'}{$tag}) - { - my $l = length($tag); - $l += 2 unless $tag =~ / \Z/o; - - if(defined $idx) - { - if(defined $me->{'mail_hdr_hash'}{$tag}[$idx]) - { - push(@val, substr(${$me->{'mail_hdr_hash'}{$tag}[$idx]},$l)); - undef ${$me->{'mail_hdr_hash'}{$tag}[$idx]}; - } + my $hash = ref $thing ? $thing->{mail_hdr_lengths} : \%HDR_LENGTHS; + $old = $hash->{$tag}; + $hash->{$tag} = $len > 20 ? $len : 20; } - else - { - local $_; - @val = map { - my $x = substr($$_,$l); - undef $$_; - $x - } @{$me->{'mail_hdr_hash'}{$tag}}; + else + { my $self = $thing; + my $len = shift; + $old = $self->{mail_hdr_foldlen}; + + if(defined $len) + { $self->{mail_hdr_foldlen} = $len > 20 ? $len : 20; + $self->fold if $self->{mail_hdr_modify}; + } } - _tidy_header($me); - } - - return @val; -} - -sub print -{ - my $me = shift; - my $fd = shift || \*STDOUT; - my $ln; - - foreach $ln (@{$me->{'mail_hdr_list'}}) - { - next - unless defined $ln; - print $fd $ln or - return 0; - } - - 1; -} - -sub as_string -{ - my $me = shift; - - join('', grep { defined } @{$me->{'mail_hdr_list'}}); + $old; } -sub fold_length -{ - my $me = shift; - my $old; - - if(@_ == 2) - { - my($tag,$len) = @_; - - my $hash = ref($me) ? $me->{'mail_hdr_lengths'} : \%HDR_LENGTHS; - - $tag = _tag_case($tag); - - $old = $hash->{$tag} || undef; - $hash->{$tag} = $len > 20 ? $len : 20; - } - else - { - my $len = shift; +#------------ - $old = $me->{'mail_hdr_foldlen'}; - - if(defined $len) - { - $me->{'mail_hdr_foldlen'} = $len > 20 ? $len : 20; - $me->fold if $me->{'mail_hdr_modify'}; +sub fold +{ my ($self, $maxlen) = @_; + + while(my ($tag, $list) = each %{$self->{mail_hdr_hash}}) + { my $len = $maxlen + || $self->{mail_hdr_lengths}{$tag} + || $HDR_LENGTHS{$tag} + || $self->fold_length; + + foreach my $ln (@$list) + { _fold_line $$ln, $len + if defined $ln; + } } - } - $old; + $self; } -sub tags -{ - my $me = shift; - keys %{$me->{'mail_hdr_hash'}}; -} - -sub dup -{ - my $me = shift; - my $type = ref($me) || croak "Cannot dup without an object"; - my $dup = new $type; - - %$dup = %$me; - $dup->empty; - - $dup->{'mail_hdr_list'} = [ @{$me->{'mail_hdr_list'}} ]; - - my $ln; - foreach $ln ( @{$dup->{'mail_hdr_list'}} ) - { - my $tag = _tag_case(($ln =~ /\A($FIELD_NAME|From )/oi)[0]); +sub unfold +{ my $self = shift; - $dup->{'mail_hdr_hash'}{$tag} ||= []; - push(@{$dup->{'mail_hdr_hash'}{$tag}}, \$ln); - } + if(@_) + { my $tag = _tag_case shift; + my $list = $self->{mail_hdr_hash}{$tag} + or return $self; - $dup; -} + foreach my $ln (@$list) + { $$ln =~ s/\r?\n\s+/ /sog + if defined $ln && defined $$ln; + } -sub cleanup -{ - my $me = shift; - my $d = 0; - my $key; - - foreach $key (@_ ? @_ : keys %{$me->{'mail_hdr_hash'}}) - { - my $arr = $me->{'mail_hdr_hash'}{$key}; - my $ref; - foreach $ref (@$arr) - { - unless($$ref =~ /\A\S+\s+\S/soi) - { - $$ref = undef; - $d++; - } + return $self; } - } - _tidy_header($me) - if $d; + while( my ($tag, $list) = each %{$self->{mail_hdr_hash}}) + { foreach my $ln (@$list) + { $$ln =~ s/\r?\n\s+/ /sog + if defined $ln && defined $$ln; + } + } - $me; + $self; } -1; # keep require happy - - -=head1 NAME - -Mail::Header - manipulate mail RFC822 compliant headers - -=head1 SYNOPSIS - - use Mail::Header; - - $head = new Mail::Header; - $head = new Mail::Header \*STDIN; - $head = new Mail::Header [<>], Modify => 0; - -=head1 DESCRIPTION - -This package provides a class object which can be used for reading, creating, -manipulating and writing RFC822 compliant headers. - -=head1 CONSTRUCTOR - -=over 4 - -=item new ( [ ARG ], [ OPTIONS ] ) - -C<ARG> may be either a file descriptor (reference to a GLOB) -or a reference to an array. If given the new object will be -initialized with headers either from the array of read from -the file descriptor. - -C<OPTIONS> is a list of options given in the form of key-value -pairs, just like a hash table. Valid options are -=over 8 - -=item B<Modify> - -If this value is I<true> then the headers will be re-formatted, -otherwise the format of the header lines will remain unchanged. - -=item B<MailFrom> - -This option specifies what to do when a header in the form `From ' -is encountered. Valid values are C<IGNORE> - ignore and discard the header, -C<ERROR> - invoke an error (call die), C<COERCE> - rename them as Mail-From -and C<KEEP> - keep them. - -=item B<FoldLength> - -The default length of line to be used when folding header lines - -=back - -=back - -=head1 METHODS - -=over 4 - -=item modify ( [ VALUE ] ) - -If C<VALUE> is I<false> then C<Mail::Header> will not do any automatic -reformatting of the headers, other than to ensure that the line -starts with the tags given. - -=item mail_from ( OPTION ) - -C<OPTION> specifies what to do when a C<`From '> line is encountered. -Valid values are C<IGNORE> - ignore and discard the header, -C<ERROR> - invoke an error (call die), C<COERCE> - rename them as Mail-From -and C<KEEP> - keep them. - -=item fold ( [ LENGTH ] ) - -Fold the header. If C<LENGTH> is not given then C<Mail::Header> uses the -following rules to determine what length to fold a line. - -The fold length for the tag that is begin processed - -The default fold length for the tag that is being processed +sub add +{ my ($self, $tag, $text, $where) = @_; + ($tag, my $line) = _fmt_line $self, $tag, $text; -The default fold length for the object + defined $tag && defined $line + or return undef; -=item extract ( ARRAY_REF ) + defined $where + or $where = -1; -Extract a header from the given array. C<extract> B<will modify> this array. -Returns the object that the method was called on. + _insert $self, $tag, $line, $where; -=item read ( FD ) + $line =~ /^\S+\s(.*)/os; + $1; +} -Read a header from the given file descriptor. -=item empty () +sub replace +{ my $self = shift; + my $idx = @_ % 2 ? pop @_ : 0; -Empty the C<Mail::Header> object of all lines. + my ($tag, $line); + TAG: + while(@_) + { ($tag,$line) = _fmt_line $self, splice(@_,0,2); -=item header ( [ ARRAY_REF ] ) + defined $tag && defined $line + or return undef; -C<header> does multiple operations. First it will extract a header from -the array, if given. It will the reformat the header, if reformatting -is permitted, and finally return a reference to an array which -contains the header in a printable form. + my $field = $self->{mail_hdr_hash}{$tag}; + if($field && defined $field->[$idx]) + { ${$field->[$idx]} = $line } + else { _insert $self, $tag, $line, -1 } + } -=item header_hashref ( [ HASH_REF ] ) + $line =~ /^\S+\s*(.*)/os; + $1; +} -As C<header>, but it will eventually set headers from a hash -reference, and it will return the headers as a hash reference. -The values in the hash might either be a scalar or an array reference, -as an example: +sub combine +{ my $self = shift; + my $tag = _tag_case shift; + my $with = shift || ' '; - $hashref->{From}='Tobias Brox <tobix@cpan.org>'; - $hashref->{To}=['you@somewhere', 'me@localhost']; + $tag =~ /^From /io && $self->{mail_hdr_mail_from} ne 'KEEP' + and return _error "unadorned 'From ' ignored"; -=item add ( TAG, LINE [, INDEX ] ) + my $def = $self->{mail_hdr_hash}{$tag} + or return undef; -Add a new line to the header. If C<TAG> is I<undef> the the tag will be -extracted from the beginning of the given line. If C<INDEX> is given -the new line will be inserted into the header at the given point, otherwise -the new line will be appended to the end of the header. + return $def->[0] + if @$def <= 1; -=item replace ( TAG, LINE [, INDEX ] ) + my @lines = $self->get($tag); + chomp @lines; -Replace a line in the header. If C<TAG> is I<undef> the the tag will be -extracted from the beginning of the given line. If C<INDEX> is given -the new line will replace the Nth instance of that tag, otherwise the -first instance of the tag is replaced. If the tag does not appear in the -header then a new line will be appended to the header. + my $line = (_fmt_line $self, $tag, join($with,@lines), 1)[1]; -=item combine ( TAG [, WITH ] ) + $self->{mail_hdr_hash}{$tag} = [ \$line ]; + $line; +} -Combine all instances of C<TAG> into one. The lines will be -joined togther with C<WITH>, or a single space if not given. The new -item will be positioned in the header where the first instance was, all -other instances of <TAG> will be removed. -=item get ( TAG [, INDEX ] ) +sub get +{ my $self = shift; + my $tag = _tag_case shift; + my $idx = shift; -Get the text form a line. If C<INDEX> is given then the text of the Nth -instance will be returned. If it is not given the return value depends on the -context in which C<get> was called. In an array context a list of all the -text from all the instances of C<TAG> will be returned. In a scalar context -the text for the first instance will be returned. + my $def = $self->{mail_hdr_hash}{$tag} + or return (); -=item delete ( TAG [, INDEX ] ) + my $l = length $tag; + $l += 1 if $tag !~ / $/o; -Delete a tag from the header. If C<INDEX> id given then the Nth instance -of the tag will be removed. If C<INDEX> is not given all instances -of tag will be removed. + if(defined $idx || !wantarray) + { $idx ||= 0; + defined $def->[$idx] or return undef; + my $val = ${$def->[$idx]}; + defined $val or return undef; -=item count ( TAG ) + $val = substr $val, $l; + $val =~ s/^\s+//; + return $val; + } -Returns the number of times the given atg appears in the header + map { my $tmp = substr $$_,$l; $tmp =~ s/^\s+//; $tmp } @$def; +} -=item print ( [ FD ] ) -Print the header to the given file descriptor, or C<STDOUT> if no -file descriptor is given. -=item as_string () +sub count +{ my $self = shift; + my $tag = _tag_case shift; + my $def = $self->{mail_hdr_hash}{$tag}; + defined $def ? scalar(@$def) : 0; +} -Returns the header as a single string. -=item fold_length ( [ TAG ], [ LENGTH ] ) -Set the default fold length for all tags or just one. With no arguments -the default fold length is returned. With two arguments it sets the fold -length for the given tag and returns the previous value. If only C<LENGTH> -is given it sets the default fold length for the current object. +sub delete +{ my $self = shift; + my $tag = _tag_case shift; + my $idx = shift; + my @val; + + if(my $def = $self->{mail_hdr_hash}{$tag}) + { my $l = length $tag; + $l += 2 if $tag !~ / $/; + + if(defined $idx) + { if(defined $def->[$idx]) + { push @val, substr ${$def->[$idx]}, $l; + undef ${$def->[$idx]}; + } + } + else + { @val = map {my $x = substr $$_,$l; undef $$_; $x } @$def; + } + + _tidy_header($self); + } -In the two argument form C<fold_length> may be called as a static method, -setting default fold lengths for tags that will be used by B<all> -C<Mail::Header> objects. See the C<fold> method for -a description on how C<Mail::Header> uses these values. + @val; +} -=item tags () -Retruns an array of all the tags that exist in the header. Each tag will -only appear in the list once. The order of the tags is not specified. -=item dup () +sub print +{ my $self = shift; + my $fd = shift || \*STDOUT; -Create a duplicate of the current object. + foreach my $ln (@{$self->{mail_hdr_list}}) + { defined $ln or next; + print $fd $ln or return 0; + } -=item cleanup () + 1; +} -Remove any header line that, other than the tag, only contains whitespace -=item unfold ( [ TAG ] ) +sub as_string { join '', grep {defined} @{shift->{mail_hdr_list}} } -Unfold all instances of the given tag so that they do not spread across -multiple lines. IF C<TAG> is not given then all lines are unfolded. -=back +sub tags { keys %{shift->{mail_hdr_hash}} } -=head1 AUTHOR -Graham Barr. Maintained by Mark Overmeer <mailtools@overmeer.net> +sub cleanup +{ my $self = shift; + my $deleted = 0; + + foreach my $key (@_ ? @_ : keys %{$self->{mail_hdr_hash}}) + { my $fields = $self->{mail_hdr_hash}{$key}; + foreach my $field (@$fields) + { next if $$field =~ /^\S+\s+\S/s; + undef $$field; + $deleted++; + } + } -=head1 COPYRIGHT + _tidy_header $self + if $deleted; -Copyright (c) 1995-2001 Graham Barr. All rights reserved. This program is free -software; you can redistribute it and/or modify it under the same terms -as Perl itself. + $self; +} -=cut +1; |
