/[suikacvs]/messaging/manakai/lib/Message/Body/Multipart.pm
Suika

Contents of /messaging/manakai/lib/Message/Body/Multipart.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.4 - (hide annotations) (download)
Tue Jul 2 06:30:49 2002 UTC (22 years, 4 months ago) by wakaba
Branch: MAIN
Changes since 1.3: +63 -17 lines
2002-07-02  Wakaba <w@suika.fam.cx>

	* Multipart.pm:
	- (parse): Call Message::Util::decide_newline.
	- (data_part, control_part): New methods.
	- (stringify): Set 'protocol' parameter of parent entity
	if media type is message/signed or message/encrypted.

1 wakaba 1.1
2     =head1 NAME
3    
4     Message::Body::Multipart --- Perl module
5     for "multipart/*" Internet Media Types
6    
7     =cut
8    
9     package Message::Body::Multipart;
10     use strict;
11     use vars qw(%DEFAULT @ISA $VERSION);
12 wakaba 1.4 $VERSION=do{my @r=(q$Revision: 1.3 $=~/\d+/g);sprintf "%d."."%02d" x $#r,@r};
13 wakaba 1.1
14     require Message::Body::Text;
15     push @ISA, qw(Message::Body::Text);
16    
17     my @BCHARS = ('0'..'9', 'A'..'Z', 'a'..'z', qw#+ _ , - . / : =#);
18     #my @BCHARS = ('0'..'9', 'A'..'Z', 'a'..'z', qw#' ( ) + _ , - . / : = ?#, ' '); ## RFC 2046
19     my %REG;
20     $REG{NON_bchars} = qr#[^0-9A-Za-z'()+_,-./:=?\x20]#;
21    
22     %DEFAULT = (
23     ## "#i" : only inherited from parent Entity and inherits to child Entity
24     -_ARRAY_NAME => 'value',
25 wakaba 1.4 -_METHODS => [qw|data_part control_part entity_header add delete count item preamble epilogue|],
26 wakaba 1.2 -_MEMBERS => [qw|boundary preamble epilogue|],
27 wakaba 1.1 #i accept_cte
28     #i body_default_charset
29     #i body_default_charset_input
30     #i cte_default
31     -default_media_type => 'text',
32     -default_media_subtype => 'plain',
33 wakaba 1.4 -linebreak_strict => 0,
34 wakaba 1.1 -media_type => 'multipart',
35     -media_subtype => 'mixed',
36     #output_epilogue
37     -parse_all => 0,
38     #i text_coderange
39     #use_normalization => 0,
40     #use_param_charset => 0,
41     -value_type => {},
42     );
43    
44     =head1 CONSTRUCTORS
45    
46     The following methods construct new objects:
47    
48     =over 4
49    
50     =cut
51    
52     ## Initialize of this class -- called by constructors
53     sub _init ($;%) {
54     my $self = shift;
55     my $DEFAULT = Message::Util::make_clone (\%DEFAULT);
56     my %option = @_;
57     $self->SUPER::_init (%$DEFAULT, %option);
58    
59     unless (defined $self->{option}->{output_epilogue}) {
60     $self->{option}->{output_epilogue} = $self->{option}->{format} !~ /http/;
61     }
62     $self->{option}->{value_type}->{body_part}->[1]->{-format}
63     =
64     my @ilist = qw/accept_coderange body_default_charset body_default_charset_input cte_default text_coderange/;
65     $self->{option}->{value_type}->{preamble} = ['Message::Body::TextPlain',
66     {-media_type => 'text', -media_subtype => '/multipart-preamble'},
67     \@ilist];
68     $self->{option}->{value_type}->{body_part} = sub {['Message::Entity',
69     {-format => $_[0]->{option}->{format} . '/' . 'mime-entity',
70     -body_default_media_type => $_[0]->{option}->{default_media_type},
71     -body_default_media_subtype => $_[0]->{option}->{default_media_subtype}},
72     \@ilist]};
73     $self->{option}->{value_type}->{epilogue} = ['Message::Body::TextPlain',
74     {-media_type => 'text', -media_subtype => '/multipart-epilogue'},
75     \@ilist];
76    
77     $self->{boundary} = $option{boundary};
78     if (!length $self->{boundary} && ref $self->{header}) {
79     my $ct = $self->{header}->field ('content-type', -new_item_unless_exist => 0);
80     $self->{boundary} = $ct->parameter ('boundary') if ref $ct;
81     }
82     }
83    
84     =item $body = Message::Body::Multipart->new ([%options])
85    
86     Constructs a new object. You might pass some options as parameters
87     to the constructor.
88    
89     =cut
90    
91     ## Inherited
92    
93     =item $body = Message::Body::Multipart->parse ($body, [%options])
94    
95     Constructs a new object with given field body. You might pass
96     some options as parameters to the constructor.
97    
98     =cut
99    
100     sub parse ($$;%) {
101     my $class = shift;
102     my $self = bless {}, $class;
103     my $body = shift;
104     $self->_init (@_);
105     my $b = $self->{boundary};
106 wakaba 1.4 my $nl = "\x0D\x0A";
107     unless ($self->{option}->{strict_linebreak}) {
108     $nl = Message::Util::decide_newline ($body);
109     }
110 wakaba 1.1 if (length $b) {
111 wakaba 1.4 $body = $nl . $body if $body =~ /^--\Q$b\E[\x09\x20]*$nl/s;
112     $self->{value} = [ split /$nl--\Q$b\E[\x09\x20]*$nl/s, $body ];
113     $self->{preamble} = shift (@{ $self->{value} });
114 wakaba 1.1 if (length $self->{value}->[-1]) {
115 wakaba 1.4 my @p = split /$nl--\Q$b\E--[\x09\x20]*(?:$nl)?/s, $self->{value}->[-1], 2;
116 wakaba 1.1 $self->{value}->[-1] = $p[0];
117     $self->{epilogue} = $p[1];
118     }
119     } else {
120 wakaba 1.4 $self->{preamble} = $body;
121 wakaba 1.1 }
122     if ($self->{option}->{parse_all}) {
123 wakaba 1.4 $self->{value} = [ map {
124 wakaba 1.1 $self->_parse_value (body_part => $_);
125 wakaba 1.4 } @{ $self->{value} } ];
126 wakaba 1.1 $self->{preamble} = $self->_parse_value (preamble => $self->{preamble});
127     $self->{epilogue} = $self->_parse_value (epilogue => $self->{epilogue});
128     }
129     $self;
130     }
131    
132     =back
133    
134     =cut
135    
136 wakaba 1.2 ## add, item, delete, count
137    
138     ## item-by?, \$checked-item, {item-key => 1}, \%option
139     sub _item_match ($$\$\%\%) {
140     my $self = shift;
141     my ($by, $i, $list, $option) = @_;
142     return 0 unless ref $$i; ## Already removed
143     if ($by eq 'content-type') {
144     $$i = $self->_parse_value (body_part => $$i);
145     return 1 if ref $$i && $$list{$$i->content_type};
146     } elsif ($by eq 'content-id') {
147     $$i = $self->_parse_value (body_part => $$i);
148     return 1 if ref $$i && ( $$list{$$i->id} || $$list{'<'.$$i->id.'>'} );
149     }
150     0;
151     }
152     *_delete_match = \&_item_match;
153    
154     ## Returns returned item value \$item-value, \%option
155     sub _item_return_value ($\$\%) {
156     unless (ref ${$_[1]}) {
157 wakaba 1.4 ${$_[1]} = $_[0]->_parse_value (body_part => ${$_[1]})
158     if $_[2]->{parse};
159 wakaba 1.2 }
160     ${$_[1]};
161     }
162     *_add_return_value = \&_item_return_value;
163    
164     ## Returns returned (new created) item value $name, \%option
165     sub _item_new_value ($$\%) {
166     my $v = shift->_parse_value (body_part => '');
167     my ($key, $option) = @_;
168     if ($option->{by} eq 'content-type') {
169     $v->header->field ('content-type')->media_type ($key);
170     } elsif ($option->{by} eq 'content-id') {
171     $v->header->add ('content-id' => $key);
172     }
173     $v;
174     }
175    
176 wakaba 1.4 sub data_part ($;%) {
177     my $self = shift;
178     my %option = @_;
179     $option{-by} = 'index';
180     my $st = $self->{option}->{media_subtype};
181     if ($st eq 'signed' || $st eq 'appledouble') {
182     $self->item (0, @_);
183     } elsif ($st eq 'encrypted') {
184     $self->item (1, @_);
185     } else {
186     my $msg = qq{data_part: This method is not supported for $self->{option}->{media_type}/$st};
187     if ($option{-dont_croak}) {
188     Carp::carp $msg;
189     } else {
190     Carp::croak $msg;
191     }
192     }
193     }
194    
195     sub control_part ($;%) {
196     my $self = shift;
197     my %option = @_;
198     $option{-by} = 'index';
199     my $st = $self->{option}->{media_subtype};
200     if ($st eq 'signed' || $st eq 'appledouble') {
201     $self->item (1, @_);
202     } elsif ($st eq 'encrypted') {
203     $self->item (0, @_);
204     } else {
205     my $msg = qq{control_part: This method is not supported for $self->{option}->{media_type}/$st};
206     if ($option{-dont_croak}) {
207     Carp::carp $msg;
208     } else {
209     Carp::croak $msg;
210     }
211     }
212     }
213    
214 wakaba 1.2 sub _add_array_check ($$\%) {
215     my $self = shift;
216     my ($value, $option) = @_;
217     my $value_option = {};
218     if (ref $value eq 'ARRAY') {
219     ($value, %$value_option) = @$value;
220     }
221     $value = $self->_parse_value (body_part => $value) if $$option{parse};
222     $$option{parse} = 0;
223     (1, value => $value);
224     }
225    
226 wakaba 1.1 ## entity_header: Inherited
227    
228 wakaba 1.2 sub preamble ($;$) {
229     my $self = shift;
230     my $np = shift;
231     if (defined $np) {
232     $np = $self->_parse_value (preamble => $np) if $self->{option}->{parse_all};
233     $self->{preamble} = $np;
234     }
235     $self->{preamble};
236     }
237     sub epilogue ($;$) {
238     my $self = shift;
239     my $np = shift;
240     if (defined $np) {
241     $np = $self->_parse_value (epilogue => $np) if $self->{option}->{parse_all};
242     $self->{epilogue} = $np;
243     }
244     $self->{epilogue};
245     }
246    
247 wakaba 1.1 =head2 $self->stringify ([%option])
248    
249     Returns the C<body> as a string.
250    
251     =cut
252    
253     sub stringify ($;%) {
254     my $self = shift;
255     my %o = @_; my %option = %{$self->{option}};
256     for (grep {/^-/} keys %o) {$option{substr ($_, 1)} = $o{$_}}
257     my $max = $option{max} || $#{$self->{value}}+1; $max--;
258     $max = $#{$self->{value}} if $max > $#{$self->{value}};
259     my @parts = map { ''. $_ } @{$self->{value}}[0..$max];
260     my $b = $self->{boundary};
261     if ($b =~ $REG{NON_bchars} || length ($b) > 70) {
262     undef $b;
263     } elsif (substr ($b, -1, 1) eq "\x20") {
264     $b .= 'B';
265     }
266 wakaba 1.3 my $blength = 35;
267 wakaba 1.1 $b ||= $self->_generate_boundary ($blength);
268     my $i = 1; while ($i++) {
269     my @t = grep {/\Q--$b\E/} @parts;
270     last if @t == 0;
271     $b = $self->_generate_boundary ($blength);
272     if ($i > @BCHARS ** $blength) {
273     $blength++; $i = 1;
274     }
275     }
276     if (ref $self->{header}) {
277 wakaba 1.4 my $ct = $self->{header}->field ('content-type');
278     my $mt = $ct->media_type;
279     $ct->replace (boundary => $b);
280     if ($mt eq 'multipart/signed') {
281     $ct->replace (protocol => scalar $self->{value}->[1]->content_type);
282     } elsif ($mt eq 'multipart/encrypted') {
283     $ct->replace (protocol => scalar $self->{value}->[0]->content_type);
284     }
285 wakaba 1.1 }
286     $self->{preamble}."\x0D\x0A--".$b."\x0D\x0A".
287     join ("\x0D\x0A--".$b."\x0D\x0A", @parts)
288     ."\x0D\x0A--$b--\x0D\x0A".
289     ($option{output_epilogue}? $self->{epilogue}: '');
290     }
291     *as_string = \&stringify;
292    
293     ## Inherited: option, clone
294    
295     ## $self->_option_recursive (\%argv)
296     sub _option_recursive ($\%) {
297     my $self = shift;
298     my $o = shift;
299     for (@{$self->{value}}) {
300     $_->option (%$o) if ref $_;
301     }
302     $self->{preamble}->option (%$o) if ref $self->{preamble};
303     $self->{epilogue}->option (%$o) if ref $self->{epilogue};
304     }
305    
306     sub _generate_boundary ($$) {
307     my $self = shift;
308     my $blength = shift || 45; ## Length of boundary
309     join('', map($BCHARS[rand @BCHARS], 1..$blength));
310     }
311    
312     =head1 SEE ALSO
313    
314     RFC 2046 <urn:ietf:rfc:2046>
315    
316     =head1 LICENSE
317    
318     Copyright 2002 wakaba E<lt>w@suika.fam.cxE<gt>.
319    
320     This program is free software; you can redistribute it and/or modify
321     it under the terms of the GNU General Public License as published by
322     the Free Software Foundation; either version 2 of the License, or
323     (at your option) any later version.
324    
325     This program is distributed in the hope that it will be useful,
326     but WITHOUT ANY WARRANTY; without even the implied warranty of
327     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
328     GNU General Public License for more details.
329    
330     You should have received a copy of the GNU General Public License
331     along with this program; see the file COPYING. If not, write to
332     the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
333     Boston, MA 02111-1307, USA.
334    
335     =head1 CHANGE
336    
337     See F<ChangeLog>.
338 wakaba 1.4 $Date: 2002/06/16 10:44:08 $
339 wakaba 1.1
340     =cut
341    
342     1;

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24