/[suikacvs]/messaging/manakai/lib/Message/Entity.pm
Suika

Contents of /messaging/manakai/lib/Message/Entity.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.35 - (show annotations) (download)
Sun Jul 28 00:31:38 2002 UTC (24 years, 1 month ago) by wakaba
Branch: MAIN
Changes since 1.34: +59 -22 lines
2002-07-28  Wakaba <w@suika.fam.cx>

	* Entity.pm (destination, sender): New methods.

1
2 =head1 NAME
3
4 Message::Entity Perl module
5
6 =head1 DESCRIPTION
7
8 Perl module for RFC 822/2822 C<message>.
9 MIME multipart will be also supported (but not implemented yet).
10
11 =cut
12
13 package Message::Entity;
14 use strict;
15 use vars qw(%DEFAULT $VERSION);
16 $VERSION=do{my @r=(q$Revision: 1.34 $=~/\d+/g);sprintf "%d."."%02d" x $#r,@r};
17
18 require Message::Util;
19 require Message::Header;
20 require Message::MIME::MediaType;
21 require Message::MIME::Encoding;
22 use overload '""' => sub { $_[0]->stringify },
23 fallback => 1;
24
25 ## Initialize of this class -- called by constructors
26 %DEFAULT = (
27 -_METHODS => [qw|header body content_type id|],
28 -_MEMBERS => [qw|header body _cte|],
29 ## entity_header -- Don't clone.
30 -accept_coderange => '7bit', ## 7bit / 8bit / binary
31 -body_default_charset => 'iso-2022-int-1',
32 -body_default_charset_input => 'iso-2022-int-1',
33 -body_default_media_type => 'text',
34 -body_default_media_subtype => 'plain',
35 -cte_default => '7bit',
36 -fill_missing_fields => 1,
37 #add_ua => 1,
38 #fill_date => 1,
39 -fill_date_name => 'date',
40 -fill_destination => 0,
41 #fill_destination_ns
42 #fill_destination_resent_ns
43 #fill_from_ns
44 -fill_md5 => 0,
45 -fill_md5_name => 'md5',
46 #fill_msgid => 1,
47 -fill_msgid_name => 'message-id',
48 #fill_sender_ns
49 -fill_source => 1,
50 -recalc_md5 => 1,
51 -force_mime_entity => 0,
52 -format => 'mail-rfc2822',
53 -guess_media_type => 1,
54 #internal_charset_name
55 -header_default_charset => 'iso-2022-int-1',
56 -header_default_charset_input => 'iso-2022-int-1',
57 -hook_init_fill_options => sub {},
58 -linebreak_strict => 0, ## BUG: not work perfectly
59 -parse_all => 0,
60 -text_coderange => 'binary',
61 ## '8bit' (MIME text/*) / 'binary' (HTTP text/*)
62 #ua_field_name => 'user-agent',
63 -ua_use_Config => 1,
64 -ua_use_Win32 => 1,
65 -uri_mailto_safe_level => 4,
66 );
67 sub _init ($;%) {
68 my $self = shift;
69 my %options = @_;
70 $self->{option} = {};
71 my $o = Message::Util::make_clone (\%DEFAULT);
72 for my $name (keys %$o) {
73 if (substr ($name, 0, 1) eq '-') {
74 $self->{option}->{substr ($name, 1)} = $$o{$name};
75 }
76 }
77
78 my @new_fields = ();
79 for my $name (keys %options) {
80 if (substr ($name, 0, 1) eq '-') {
81 $self->{option}->{substr ($name, 1)} = $options{$name};
82 } elsif ($name eq 'entity_header') {
83 $self->{entity_header} = $options{entity_header};
84 } else {
85 push @new_fields, ($name => $options{$name});
86 }
87 }
88
89 my $format = $self->{option}->{format};
90 if ($format =~ /http/) {
91 $self->{option}->{fill_date_ns} = $Message::Header::NS_phname2uri{'x-http'};
92 $self->{option}->{fill_from_ns} = $Message::Header::NS_phname2uri{'x-http'};
93 $self->{option}->{fill_msgid_from_ns} = $Message::Header::NS_phname2uri{'x-http'};
94 $self->{option}->{fill_ua_ns} = $Message::Header::NS_phname2uri{'x-http'};
95 $self->{option}->{accept_coderange} = 'binary';
96 $self->{option}->{text_coderange} = 'binary';
97 $self->{option}->{cte_default} = 'binary';
98 } else {
99 if ($format =~ /mail-rfc822|mail-rfc2822/) {
100 $self->{option}->{fill_destination} = 1;
101 }
102 $self->{option}->{fill_date_ns} = $Message::Header::NS_phname2uri{'x-rfc822'};
103 $self->{option}->{fill_from_ns} = $Message::Header::NS_phname2uri{'x-rfc822'};
104 $self->{option}->{fill_msgid_from_ns} = $Message::Header::NS_phname2uri{'x-rfc822'};
105 $self->{option}->{fill_ua_ns} = $Message::Header::NS_phname2uri{'x-rfc822'};
106 $self->{option}->{text_coderange} = '8bit';
107 if ($format =~ /news-usefor|smtp-8bitmime/) {
108 $self->{option}->{accept_coderange} = '8bit';
109 #$self->{option}->{cte_default} = '8bit';
110 } else {
111 $self->{option}->{accept_coderange} = '7bit';
112 }
113 }
114 for (qw/fill_msgid_ns fill_mimever_ns fill_destination_ns fill_sender_ns/) {
115 $self->{option}->{$_} = $Message::Header::NS_phname2uri{'x-rfc822'};
116 }
117 $self->{option}->{fill_destination_resent_ns} = $Message::Header::NS_phname2uri{'x-rfc822-resent'};
118 unless (defined $self->{option}->{fill_date}) {
119 $self->{option}->{fill_date} = $format !~ /mime-entity|cgi|uri-url-mailto/;
120 }
121 unless (defined $self->{option}->{fill_msgid}) {
122 $self->{option}->{fill_msgid} = $format !~ /mime-entity|http|uri-url-mailto/;
123 }
124 unless (defined $self->{option}->{fill_ct}) {
125 $self->{option}->{fill_ct} = $format !~ /http/;
126 }
127 unless (defined $self->{option}->{fill_mimever}) {
128 $self->{option}->{fill_mimever} = $format !~ /http|mime-entity/;
129 }
130 unless (defined $self->{option}->{add_ua}) {
131 $self->{option}->{add_ua} = $format !~ /mime-entity/;
132 }
133 unless ($self->{option}->{fill_ua_name}) {
134 $self->{option}->{fill_ua_name} = $format =~ /response|cgi|uri-url-mailto/?
135 'server': 'user-agent';
136 }
137 &{ $self->{option}->{hook_init_fill_options} } ($self, $self->{option});
138 @new_fields;
139 }
140
141 =head1 CONSTRUCTORS
142
143 The following methods construct new C<Message::Entity> objects:
144
145 =over 4
146
147 =item Message::Entity->new ([%initial-fields/options])
148
149 Constructs a new C<Message::Entity> object. You might pass some initial
150 C<field-name>-C<field-body> pairs and/or options as parameters to the constructor.
151
152 Example:
153
154 $msg = new Message::Entity
155 Date => 'Thu, 03 Feb 1994 00:00:00 +0000',
156 Content_Type => 'text/html',
157 X_URI => '<http://www.foo.example/>',
158 -format => 'mail-rfc2822' ## not to be header field
159 ;
160
161 =cut
162
163 sub new ($;%) {
164 my $class = shift;
165 my $self = bless {}, $class;
166 my %new_field = $self->_init (@_);
167 if (defined $new_field{body}) {
168 $self->{body} = $new_field{body}; $new_field{body} = undef;
169 $self->{body} = $self->_parse_value ([$self->content_type] => $self->{body})
170 if $self->{option}->{parse_all};
171 }
172 $self->{header} = new Message::Header
173 -format => $self->{option}->{format},
174 -header_default_charset => $self->{option}->{header_default_charset},
175 -header_default_charset_input => $self->{option}->{header_default_charset_input},
176 -parse_all => $self->{option}->{parse_all}, %new_field;
177 $self;
178 }
179
180 =item Message::Entity->parse ($message, [%options])
181
182 Parses given C<message> (a message entity) and constructs a new C<Message::Entity>
183 object. You might pass some additional C<field-name>-C<field-body> pairs
184 or/and initial options as parameters to the constructor.
185
186 =cut
187
188 sub parse ($$;%) {
189 my $class = shift;
190 my $message = shift;
191 my $self = bless {}, $class;
192 my %new_field = $self->_init (@_);
193 my $nl = "\x0D\x0A";
194 unless ($self->{option}->{linebreak_strict}) {
195 $nl = Message::Util::decide_newline ($message);
196 }
197 ## BUG: binary unsafe yet!
198 my @header = ();
199 my @body = split /$nl/, $message;
200 while (1) {
201 my $line = shift @body;
202 unless (length($line)) {
203 last;
204 } else {
205 push @header, $line;
206 }
207 }
208 $new_field{body} = undef if $new_field{body};
209 $self->{header} = parse_array Message::Header \@header,
210 -header_default_charset => $self->{option}->{header_default_charset},
211 -header_default_charset_input => $self->{option}->{header_default_charset_input},
212 -parse_all => $self->{option}->{parse_all},
213 -format => $self->{option}->{format}, %new_field;
214 $self->{body} = join ($nl, @body) . $nl;
215 $self->{body} = $self->_parse_value ([$self->content_type] => $self->{body})
216 if $self->{option}->{parse_all};
217 $self;
218 }
219
220 =back
221
222 =head1 METHODS
223
224 =head2 $self->header ([$new_header])
225
226 Returns Message::Header unless $new_header.
227 Set $new_header instead of current C<header>.
228 If !ref $new_header, Message::Header->parse is automatically
229 called.
230
231 =cut
232
233 ## TODO: to be compatible with HTTP::Message
234 sub header ($;$) {
235 my $self = shift;
236 my $new_header = shift;
237 if (ref $new_header) {
238 $self->{header} = $new_header;
239 } elsif ($new_header) {
240 $self->{header} = Message::Header->parse ($new_header,
241 -header_default_charset => $self->{option}->{header_default_charset},
242 -header_default_charset_input => $self->{option}->{header_default_charset_input},
243 -parse_all => $self->{option}->{parse_all},
244 -format => $self->{option}->{format});
245 }
246 unless (ref $self->{header} || length $self->{header}) {
247 $self->{header} = new Message::Header (
248 -header_default_charset => $self->{option}->{header_default_charset},
249 -header_default_charset_input => $self->{option}->{header_default_charset_input},
250 -parse_all => $self->{option}->{parse_all},
251 -format => $self->{option}->{format});
252 }
253 $self->{header};
254 }
255
256 =head2 $self->body ([$new_body])
257
258 Returns C<body> as string unless $new_body.
259 Set $new_body instead of current C<body>.
260
261 =cut
262
263 sub body ($;$) {
264 my $self = shift;
265 my $new_body = shift;
266 if ($new_body) {
267 $self->{body} = $new_body;
268 }
269 $self->{body} = $self->_parse_value ([$self->content_type] => $self->{body})
270 unless ref $self->{body};
271 $self->{body};
272 }
273
274 ## [SG]et its entity header. This method is or can be used
275 ## when Message::Entity is used as a body (such as message/rfc822).
276 sub entity_header ($;$) {
277 my $self = shift;
278 my $new_header = shift;
279 if (ref $new_header) {
280 $self->{entity_header} = $new_header;
281 }
282 $self->{entity_header};
283 }
284
285 ## Note: If you once parse body (including parse_all => 1 option),
286 ## it might make validation failed.
287 sub md5_check ($) {
288 my $self = shift;
289 my $md5f = $self->{header}->field ('content-md5', -new_item_unless_exist => 0);
290 my $md5; $md5 = $md5f->value if ref $md5f;
291 unless ($md5) {
292 Carp::carp "md5_check: MD5 checksum not found";
293 return undef;
294 }
295 my $MD5;
296 eval q{
297 require Digest::MD5;
298 $MD5 = ($self->Message::MIME::Encoding::encode_base64 (Digest::MD5::md5 ($self->{body})))[0];
299 $MD5 =~ tr/\x09\x0A\x0D\x20//d;
300 1} or Carp::croak $@;
301 return $MD5 eq $md5? 1 : 0;
302 }
303
304 ## $self->_parse_value ($type, $value);
305 sub _parse_value ($$$) {
306 my $self = shift;
307 my ($mt,$mst) = @{ shift(@_) };
308 my $value = shift;
309 return $value if ref $value;
310
311 ## decode
312 $value = $self->_decode_body ($value);
313
314 my $mt_def = $Message::MIME::MediaType::type{$mt}->{$mst};
315 $mt_def = $Message::MIME::MediaType::type{$mt}->{'/default'} unless ref $mt_def;
316 $mt_def = $Message::MIME::MediaType::type{'/default'}->{'/default'}
317 unless ref $mt_def;
318 my $handler = $mt_def->{handler}
319 || $Message::MIME::MediaType::type{$mt}->{'/default'}->{handler}
320 || $Message::MIME::MediaType::type{'/default'}->{'/default'}->{handler};
321 ## Ummmmmm....
322 if (ref $handler eq 'CODE') {
323 $handler = &$handler ($self, $mt, $mst);
324 }
325 my $vtype = $handler->[0];
326 my %vopt = (
327 -format => $self->{option}->{format},
328 -linebreak_strict => $self->{option}->{linebreak_strict},
329 -media_type => $mt,
330 -media_subtype => $mst,
331 -parse_all => $self->{option}->{parse_all},
332 -body_default_charset => $self->{option}->{body_default_charset},
333 -body_default_charset_input => $self->{option}->{body_default_charset_input},
334 -internal_charset_name => $self->{option}->{internal_charset_name},
335 entity_header => $self->{header},
336 );
337 ## Media type specified option/parameters
338 if (ref $handler->[1] eq 'HASH') {
339 for (keys %{$handler->[1]}) {
340 $vopt{$_} = ${$handler->[1]}{$_};
341 }
342 }
343 ## Inherited options
344 if (ref $handler->[2] eq 'ARRAY') {
345 for (@{$handler->[2]}) {
346 $vopt{'-'.$_} = $self->{option}->{$_};
347 }
348 }
349
350 if ($vtype eq ':none:') {
351 return $value;
352 } elsif (defined $value) {
353 eval "require $vtype" or Carp::croak qq{<parse>: $vtype: Can't load package: $@};
354 return $vtype->parse ($value, %vopt);
355 } else {
356 eval "require $vtype" or Carp::croak qq{<parse>: $vtype: Can't load package: $@};
357 return $vtype->new (%vopt);
358 }
359 }
360
361 sub _decode_body ($$) {
362 my $self = shift;
363 my $value = shift;
364 ## MIME CTE
365 my $cte = $self->{_cte} || '';
366 my $ctef = $self->header->field ('content-transfer-encoding',
367 -new_item_unless_exist => 0);
368 $cte = $ctef->value if ref $ctef;
369 my $f = $Message::MIME::Encoding::DECODER{$cte};
370 if (ref $f) {
371 ($value, $cte) = &$f ($self, $value);
372 }
373 $self->{_cte} = $cte;
374 $value;
375 }
376
377 sub _encode_body ($$\%) {
378 my $self = shift;
379 my $value = shift;
380 my $option = shift;
381 ## MIME CTE
382 my $current_cte = $self->{_cte} || 'binary';
383 my $ctef = $self->{header}->field ('content-transfer-encoding',
384 -new_item_unless_exist => 0);
385 my $cte = ''; $cte = lc $ctef->value if ref $ctef;
386 my %enoption;
387 ## Get media type of entity body and its accept CTE list
388 my ($mt,$mst) = $self->content_type;
389 my $mt_def = $Message::MIME::MediaType::type{$mt}->{$mst};
390 $mt_def = $Message::MIME::MediaType::type{$mt}->{'/default'}
391 unless ref $mt_def;
392 $mt_def = $Message::MIME::MediaType::type{'/default'}->{'/default'}
393 unless ref $mt_def;
394 $enoption{mt_is_text} = 1
395 if $mt eq 'text' || $mt eq 'multipart' || $mt eq 'message';
396 $enoption{mt_is_text} = 1 if $mt_def->{text_content};
397 my ($charset, $charset_def) = '';
398 if ($mt_def->{mime_charset}) {
399 ## If CT is able to have its charset parameter,
400 my $ct = $self->{header}->field ('content-type',
401 -new_item_unless_exist => 0);
402 $charset = $ct->parameter ('charset') if ref $ct;
403 if ($charset) {
404 $charset_def = $Message::MIME::Charset::CHARSET{$charset};
405 } else {
406 $charset_def = $Message::MIME::Charset::CHARSET{'*default'};
407 ## Note: 'encoding_after_encode' option's value is hardcoded.
408 }
409 } else { ## Don't have mime style "charset" parameter
410 $charset_def = {mime_text => 1};
411 }
412 $charset_def = {} unless ref $charset_def; ## dummy
413 #if ($charset_def->{mime_text} != 1) { ## See also Note above
414 if (Message::MIME::Charset::is_mime_text ($charset || '*default') != 1) {
415 $enoption{mt_is_text} = 0 if $mt eq 'text';
416 my $ct = $self->{header}->field ('content-type');
417 $ct->not_mime_text ($option->{text_coderange} eq 'binary'? 0:1);
418 }
419 ## If accept CTE list is defined,
420 for my $def ($charset_def, $mt_def) {
421 if (ref $def->{accept_cte} eq 'ARRAY') {
422 my $f = 1; for (@{$def->{accept_cte}}) {
423 if ($cte eq $_) {$f = 0; last}
424 }
425 if ($f) { ## If CTE is not accepted,
426 $cte = $def->{accept_cte}->[0];
427 }
428 }
429 }
430 if ($current_cte eq 'binary' || ($current_cte && $current_cte ne $cte)) {
431 my $de = $Message::MIME::Encoding::DECODER{$current_cte};
432 my $en = $Message::MIME::Encoding::ENCODER{$cte || 'binary'};
433 if (ref $de && ref $en) {
434 my ($e, $decoded);
435 ($decoded, $e) = &$de ($self, $value);
436 ## Check transparent coderange
437 my $cr = $self->Message::MIME::Encoding::decide_coderange
438 ($decoded, \%enoption);
439 if ($option->{accept_coderange} eq '8bit') {
440 if ($cr eq 'binary') {
441 $cte = $charset_def->{cte_7bit_preferred}
442 || $mt_def->{cte_7bit_preferred} || 'base64';
443 $en = $Message::MIME::Encoding::ENCODER{$cte};
444 }
445 } elsif ($option->{accept_coderange} eq '7bit') {
446 if ($cr eq 'binary' || $cr eq '8bit') {
447 $cte = $charset_def->{cte_7bit_preferred}
448 || $mt_def->{cte_7bit_preferred} || 'base64';
449 $en = $Message::MIME::Encoding::ENCODER{$cte};
450 if ($mt eq 'message') {
451 my $ct = $self->{header}->field ('content-type');
452 $ct->not_mime_text ($option->{text_coderange} eq 'binary'? 0:1);
453 }
454 }
455 }
456 if ($e eq 'binary') {
457 ($value, $e) = &$en ($self, $decoded, \%enoption);
458 $e = '' if ($e eq $option->{cte_default});
459 $e = '' if $e eq '7bit'
460 && ( $option->{cte_default} eq '8bit'
461 || $option->{cte_default} eq 'binary');
462 $e = '' if $e eq '8bit' && $option->{cte_default} eq 'binary';
463 if ($e) {
464 $ctef = $self->{header}->field ('content-transfer-encoding')
465 unless ref $ctef;
466 $ctef->value ($e);
467 } elsif (ref $ctef) {
468 $ctef->value ('');
469 }
470 } else {
471 $ctef = $self->{header}->field ('content-transfer-encoding')
472 unless ref $ctef;
473 $ctef->value ($current_cte);
474 }
475 } else { ## Can't encode by given CTE
476 $ctef = $self->{header}->field ('content-transfer-encoding')
477 unless ref $ctef;
478 $ctef->value ($current_cte);
479 }
480 }
481 if (ref $ctef && $ctef->value eq '') {
482 $self->{header}->delete ('content-transfer-encoding');
483 }
484 $value;
485 }
486
487 =head2 $self->stringify ([%option])
488
489 Returns the C<message> as a string.
490
491 =cut
492
493 sub stringify ($;%) {
494 my $self = shift;
495 my %params = @_;
496 my %option = %{$self->{option}};
497 for (grep {/^-/} keys %params) {$option{substr ($_, 1)} = $params{$_}}
498 my ($header, $body, $body0);
499 if (ref $self->{body}) {
500 $self->{body}->entity_header ($self->{header});
501 $body0 = $self->{body}->stringify (-parent_format => $option{format},
502 -linebreak_strict => $option{linebreak_strict});
503 } else {
504 $body0 = $self->{body};
505 }
506 $body = $self->_encode_body ($body0, \%option);
507 if (ref $self->{header}) {
508 my $ns_content = $Message::Header::NS_phname2uri{content};
509 my $filler;
510 $filler = sub {
511 my ($hdr, $exist, $hdr_option) = @_;
512 ## Date: (RFC 822, HTTP)
513 if ($option{fill_date}
514 && !$exist->{$option{fill_date_name}.':'.$option{fill_date_ns}}) {
515 $hdr->field
516 ($option{fill_date_name}, -ns => $option{fill_date_ns})->unix_time (time);
517 }
518 ## Message-ID: (RFC 822)
519 if ($option{fill_msgid}
520 && !$exist->{$option{fill_msgid_name}.':'.$option{fill_msgid_ns}}) {
521 my $from = $hdr->field
522 ('from', -ns => $option{fill_msgid_from_ns}, -new_item_unless_exist => 0);
523 $from = $from->addr_spec if ref $from;
524 $hdr->field ($option{fill_msgid_name}, -ns => $option{fill_msgid_ns})
525 ->generate (addr_spec => $from) if $from;
526 } # fill_msgid
527 ## To:, CC:, BCC:, Resent-To:, Resent-Cc:, Resent-Bcc: (RFC 822)
528 if ($option{fill_destination}) {
529 if ( !$exist->{ 'to:'.$option{fill_destination_ns} }
530 && !$exist->{ 'cc:'.$option{fill_destination_ns} }
531 && !$exist->{ 'bcc:'.$option{fill_destination_ns} }
532 && !$exist->{ 'to:'.$option{fill_destination_resent_ns} }
533 && !$exist->{ 'cc:'.$option{fill_destination_resent_ns} }
534 && !$exist->{ 'bcc:'.$option{fill_destination_resent_ns} } ) {
535 $hdr->add (bcc => '');
536 }
537 }
538 ## From:, Sender:
539 if ($option{fill_source}) {
540 ## From:
541 if (!$exist->{ 'from:'.$option{fill_from_ns} }) {
542 $hdr->add (from => 'Unknown source <[email protected]>',
543 -ns => $option{fill_from_ns});
544 ## From: exists, Sender: not exist
545 } elsif (!$exist->{ 'sender:'.$option{fill_sender_ns} }) {
546 my $from = $hdr->field ('from', -ns => $option{fill_from_ns});
547 if ($from->count > 1) {
548 $hdr->field ('sender', -ns => $option{fill_sender_ns})
549 ->add ($from->item (0, -by => 'index'));
550 }
551 }
552 }
553 ## Content-MD5:
554 if (($option{fill_md5} && !$exist->{ $option{fill_md5_name} .':'. $ns_content})
555 || ($option{recalc_md5} && $exist->{ $option{fill_md5_name} .':'. $ns_content})) {
556 my $md5;
557 eval q{
558 require Digest::MD5;
559 $md5 = ($self->Message::MIME::Encoding::encode_base64 (Digest::MD5::md5 ($body0)))[0];
560 $md5 =~ tr/\x09\x0A\x0D\x20//d;
561 1} or Carp::carp $@;
562 if ($md5) {
563 my $md5f = $hdr->field ($option{fill_md5_name}, -ns => $ns_content);
564 $md5f->value ($md5);
565 }
566 }
567 my $ismime = 0;
568 for (keys %$exist) {if (/:$ns_content$/) { $ismime = 1; last }}
569 unless ($ismime) {
570 $ismime = 1 if $option{force_mime_entity}
571 || $option{fill_md5}
572 || $option{body_default_media_type} ne 'text'
573 || $option{body_default_media_subtype} ne 'plain';
574 }
575 if ($ismime) {
576 ## Content-Type: (MIME, HTTP)
577 if ($option{fill_ct} && !$exist->{'type:'.$ns_content}) {
578 my $ct = $hdr->field ('type', -ns => $ns_content);
579 $ct->media_type ($option{body_default_media_type}.'/'
580 .$option{body_default_media_subtype});
581 $ct->replace (Message::MIME::Charset::name_minimumize ($option{body_default_charset} => $body0));
582 }
583 ## MIME-Version: (MIME)
584 if ($option{fill_mimever}
585 && !$exist->{'mime-version:'.$option{fill_mimever_ns}}) {
586 ## BUG: doesn't support rfc1049, HTTP (ie. non-MIME) content-*: fields
587 $hdr->add ('mime-version' => '1.0',
588 -parse => 0, -ns => $option{fill_mimever_ns});
589 }
590 } # $ismime
591 ## User-Agent: (USEFOR, HTTP)
592 if ($option{add_ua}) {
593 $hdr->field ($option{fill_ua_name})->add_our_name (
594 -use_Config => $option{ua_use_Config},
595 -use_Win32 => $option{ua_use_Win32},
596 -date => q$Date: 2002/07/27 04:44:25 $,
597 );
598 }
599 } if $option{fill_missing_fields};
600
601 if ($option{format} =~ /uri-url-mailto/
602 && $self->{header}->field_exist ('type', -ns => $ns_content)
603 && $option{uri_mailto_safe_level} > 1) {
604 $self->{header}->field ('type', -ns => $ns_content)->media_type ('text/plain');
605 }
606 $header = $self->{header}->stringify (
607 -format => $option{format},
608 -linebreak_strict => $option{linebreak_strict},
609 -uri_mailto_safe_level => $option{uri_mailto_safe_level},
610 ($filler? (-hook_stringify_fill_fields => $filler) :()),
611 );
612 } else {
613 $header = $self->{header};
614 unless ($option{linebreak_strict}) {
615 ## bare \x0D and bare \x0A are unsafe
616 $header =~ s/\x0D(?=[^\x09\x0A\x20])/\x0D\x20/g;
617 $header =~ s/\x0A(?=[^\x09\x20])/\x0A\x20/g;
618 }
619 }
620 if ($option{format} =~ /uri-url-mailto/) {
621 if ($option{format} =~ /rfc1738/) {
622 my $to = $self->{header}->stringify (-format => $option{format},
623 -uri_mailto_safe_level => $option{uri_mailto_safe_level});
624 $to? 'mailto:'.$to: '';
625 } else {
626 my $f = $option{format}; $f =~ s/-mailto/-mailto-to/;
627 my $to = $self->{header}->stringify (-format => $f,
628 -uri_mailto_safe_level => $option{uri_mailto_safe_level});
629 $body =~ s/([^:@+\$A-Za-z0-9\-_.!~*])/sprintf('%%%02X', ord $1)/ge;
630 if (length $body) {
631 $header .= '&' if $header;
632 $header .= 'body='.$body;
633 }
634 $header = '?'.$header if $header;
635 $to||$header? 'mailto:'.$to.$header: '';
636 }
637 } else {
638 $header .= "\x0D\x0A" if $header && $header !~ /\x0D\x0A$/;
639 $header."\x0D\x0A".$body;
640 }
641 }
642 *as_string = \&stringify;
643
644
645 =head1 SHORTCUT METHOD FOR MESSAGE PROPERTIES
646
647 =over 4
648
649 =item $self->content_type ([%options])
650
651 Returns Internet media type of message body
652 (aka MIME type, content type). Only media type
653 (type/subtype pair) is returned, i.e. no parameter
654 is returned, if any. To get such value, or to set
655 new value, use C<field> method.
656
657 Default is C<text/plain>.
658
659 Example:
660
661 $msg->field ('Content-Type')->media_type ('text/html');
662 print $msg->content_type; ## text/html
663
664 =cut
665
666 sub content_type ($;%) {
667 my $self = shift;
668 my $ct = $self->{header}->field ('content-type', -new_item_unless_exist => 0);
669 my ($mt, $mst);
670 unless (ref $ct) {
671 $mt = $self->{option}->{body_default_media_type};
672 $mst = $self->{option}->{body_default_media_subtype};
673 if ($mt ne 'text' || $mst ne 'plain') {
674 $ct = $self->{header}->field ('content-type');
675 $ct->media_type_major ($mt);
676 $ct->media_type_minor ($mst);
677 }
678 if ($self->{option}->{guess_media_type} && $self->{body} && !ref $self->{body}) {
679 if ($self->{body} =~ /^-----BEGIN PGP SIGNED MESSAGE-----\x0D?$/m
680 && $self->{body} =~ /^-----BEGIN PGP SIGNATURE-----\x0D?$/m
681 && $self->{body} =~ /^-----END PGP SIGNATURE-----\x0D?$/m) {
682 $ct = $self->{header}->field ('content-type') unless ref $ct;
683 $mt = $ct->media_type_major ('text');
684 $mst = $ct->media_type_minor ('x-pgp-cleartext-signed');
685 } elsif ($self->{body} =~ /^-----BEGIN PGP [A-Z\x20]+-----\x0D?$/m
686 && $self->{body} =~ /^-----END PGP [A-Z\x20]+-----\x0D?$/m) {
687 $ct = $self->{header}->field ('content-type') unless ref $ct;
688 $mt = $ct->media_type_major ('application');
689 $mst = $ct->media_type_minor ('pgp');
690 $ct->parameter (format => 'text');
691 } elsif ($self->{body} =~ /^-+ start of forwarded message \(RFC 934 encapsulation\) -+\x0D?$/m) {
692 $ct = $self->{header}->field ('content-type') unless ref $ct;
693 $mt = $ct->media_type_major ('text');
694 $mst = $ct->media_type_minor ('x-message-rfc934');
695 } elsif ($self->{body} =~ /^-{70,70}\x0D?$/m
696 && $self->{body} =~ /^-{30,30}\x0D?$/m
697 && $self->{body} =~ /\x0D?\x0A-{30,30}\x0D?\x0A\x0D?\x0AEnd of.+?Digest.*?\x0D?\x0A\*+(?:\x0D?\x0A)*$/s) {
698 $ct = $self->{header}->field ('content-type') unless ref $ct;
699 $mt = $ct->media_type_major ('text');
700 $mst = $ct->media_type_minor ('x-message-rfc1153');
701 } elsif ($self->{body} =~ /^-----PRIVACY-ENHANCED MESSAGE BOUNDARY-----\x0D?$/m) {
702 $ct = $self->{header}->field ('content-type') unless ref $ct;
703 $mt = $ct->media_type_major ('text');
704 $mst = $ct->media_type_minor ('x-message-pem');
705 }
706 }
707 } else {
708 ($mt, $mst) = ($ct->media_type_major, $ct->media_type_minor);
709 }
710 if ($self->{option}->{guess_media_type}) {
711 if ($mt eq 'text' && $mst eq 'plain') {
712 my $mls = $self->{header}->field ('x-mlserver', -new_item_unless_exist => 0);
713 if (ref $mls && $mls =~ /fml/) {
714 my $s = $self->{header}->field ('subject', -new_item_unless_exist => 0);
715 if (index ($s, 'RFC934(mh-burst)') >= 0) {
716 $ct = $self->{header}->field ('content-type') unless ref $ct;
717 $mt = $ct->media_type_major ('text');
718 $mst = $ct->media_type_minor ('x-message-rfc934');
719 $ct->delete ('charset');
720 } elsif (index ($s, 'Digest (RFC1153)') >= 0) {
721 $ct = $self->{header}->field ('content-type') unless ref $ct;
722 $mt = $ct->media_type_major ('text');
723 $mst = $ct->media_type_minor ('x-message-rfc1153');
724 $ct->delete ('charset');
725 }
726 }
727 }
728 }
729 if (wantarray) {
730 ($mt, $mst);
731 } else {
732 $ct->media_type;
733 }
734 }
735 *media_type = \&content_type;
736
737 =item $self->id
738
739 Returns ID of message entity. If there are C<Message-ID:>
740 field, its value is returned. Unless, but there are
741 C<Content-ID:> field, it is returned. Without both of
742 fields, C<""> is returned.
743
744 =cut
745
746 sub id ($) {
747 my $self = shift;
748 return scalar $self->{header}->field ('message-id')->id
749 if $self->{header}->field_exist ('message-id');
750 return scalar $self->{header}->field ('content-id')->id
751 if $self->{header}->field_exist ('content-id');
752 '';
753 }
754
755 sub sender ($;%) {
756 my $self = shift;
757 my %params = @_;
758 my %option = %{$self->{option}};
759 $option{use_x_envelope_from} = 1; ## X-Envelope-From:
760 $option{use_normal} = 1; ## From:, Sender:
761 $option{use_resent} = 1; ## Resent-From:, Resent-Sender:
762 my $hdr = $self->{header};
763 if ($option{use_x_envelope_from} && $hdr->field_exist ('x-envelope-from')) {
764 return $hdr->field ('x-envelope-from')->addr_spec;
765 } elsif ($option{use_resent} && $hdr->field_exist ('resent-sender')) {
766 ## TODO: Resent block support
767 return $hdr->field ('resent-sender')->addr_spec;
768 } elsif ($option{use_resent} && $hdr->field_exist ('resent-from')) {
769 ## TODO: Resent block support
770 return $hdr->field ('resent-from')->addr_spec;
771 } elsif ($option{use_normal} && $hdr->field_exist ('sender')) {
772 return $hdr->field ('sender')->addr_spec;
773 } elsif ($option{use_normal} && $hdr->field_exist ('from')) {
774 return $hdr->field ('from')->addr_spec;
775 }
776 undef;
777 }
778
779 sub destination ($;%) {
780 my $self = shift;
781 my %params = @_;
782 my %option = %{$self->{option}};
783 $option{use_x_envelope_to} = 1; ## X-Envelope-To:
784 $option{use_normal} = 1; ## To:, Cc:, Bcc:
785 $option{use_resent} = 1; ## Resent-To:, Resent-Cc:, Resent-Bcc:
786 for (grep {/^-/} keys %params) {$option{substr ($_, 1)} = $params{$_}}
787 my $hdr = $self->{header};
788 my @to;
789 if ($option{use_x_envelope_to} && $hdr->field_exist ('x-envelope-to')) {
790 @to = $hdr->field ('x-envelope-to')->addr_spec;
791 } elsif ($option{use_resent} && $hdr->field_exist ('resent-from')) {
792 ## TODO: Resent block support
793 @to = ($hdr->field ('resent-to')->addr_spec,
794 $hdr->field ('resent-cc')->addr_spec,
795 $hdr->field ('resent-bcc')->addr_spec);
796 } elsif ($option{use_normal}) {
797 @to = ($hdr->field ('to')->addr_spec,
798 $hdr->field ('cc')->addr_spec,
799 $hdr->field ('bcc')->addr_spec);
800 }
801 @to;
802 }
803
804 =back
805
806 =head1 MISC. METHODS
807
808 =over 4
809
810 =item $self->option ( $option-name / $option-name, $option-value, ...)
811
812 If @_ == 1, returns option value. Else...
813
814 Set option value. You can pass multiple option name-value pair
815 as parameter. Example:
816
817 $msg->option (format => 'mail-rfc822',
818 capitalize => 0);
819 print $msg->option ('format'); ## mail-rfc822
820
821 =cut
822
823 sub option ($@) {
824 my $self = shift;
825 if (@_ == 1) {
826 return $self->{option}->{ $_[0] };
827 }
828 my %option = @_;
829 while (my ($name, $value) = splice (@_, 0, 2)) {
830 $self->{option}->{$name} = $value;
831 }
832 if ($option{-recursive} && ($self->content_type)[0] ne 'message') {
833 $self->{header}->option (%option);
834 $self->{body}->option (%option) if ref $self->{body};
835 }
836 }
837
838 =item $self->clone ()
839
840 Returns a copy of Message::Entity object.
841
842 =cut
843
844 sub clone ($) {
845 my $self = shift;
846 my $clone = new Message::Entity;
847 $clone->{option} = Message::Util::make_clone ($self->{option});
848 for (@{$self->{option}->{_MEMBERS}}) {
849 $clone->{$_} = Message::Util::make_clone ($self->{$_});
850 }
851 $clone;
852 }
853
854 my %_method_default_list = qw(new 1 parse 1 stringify 1 option 1 clone 1 method_available 1);
855 sub method_available ($$) {
856 my $self = shift;
857 my $name = shift;
858 return 1 if $_method_default_list{$name};
859 for (@{$self->{option}->{_METHODS}}) {
860 return 1 if $_ eq $name;
861 }
862 0;
863 }
864
865 sub import ($;%) {
866 my $self = shift;
867 my %option = @_;
868 for (keys %option) {
869 $DEFAULT{$_} = $option{$_};
870 }
871 if ($option{-body_default_charset} && !$option{-body_default_charset_input}) {
872 $DEFAULT{-body_default_charset_input} = $option{-body_default_charset};
873 }
874 if ($option{-header_default_charset} && !$option{-header_default_charset_input}) {
875 $DEFAULT{-header_default_charset_input} = $option{-header_default_charset};
876 }
877 }
878
879 =back
880
881 =head1 C<format>
882
883 =over 2
884
885 =item mail-rfc650
886
887 Internet mail message, defined by IETF RFC 650
888
889 =item mail-rfc724
890
891 Internet mail message, defined by IETF RFC 724
892
893 =item mail-rfc733
894
895 Internet mail message, defined by IETF RFC 733
896
897 =item mail-rfc822
898
899 Internet mail message, defined by IETF RFC 822
900
901 =item mail-rfc822+rfc1123
902
903 Internet mail message, defined by IETF RFC 822, ammended by RFC 1123
904
905 =item mail-rfc2822
906
907 Internet mail message, defined by IETF RFC 2822
908
909 =item mime-1.0
910
911 MIME entity
912
913 =item mime-1.0-rfc1341
914
915 MIME entity, defined by RFC 1341 (and RFC 1342)
916
917 =item mime-1.0-rfc1521
918
919 MIME entity, defined by RFC 1521 and 1522
920
921 =item mime-1.0-rfc2045
922
923 MIME entity, defined by RFC 2045,..., 2049
924
925 =item news-bnews
926
927 Usenet Bnews format
928
929 =item news-rfc850
930
931 Usenet news format, defined by IETF RFC 850
932
933 =item news-rfc1036
934
935 Usenet news format, defined by IETF RFC 1036
936
937 =item news-son-of-rfc1036
938
939 Usenet news format, defined by son-of-RFC1036
940
941 =item news-usefor
942
943 Usenet news format, defined by usefor-article (IETF Internet Draft)
944
945 =item http-1.0-rfc1945
946
947 HTTP/1.0 message, defined by IETF RFC 1945
948
949 =item http-1.0-rfc1945-request
950
951 HTTP/1.0 request message, defined by IETF RFC 1945
952
953 =item http-1.0-rfc1945-response
954
955 HTTP/1.0 response message, defined by IETF RFC 1945
956
957 =item http-1.1-rfc2068
958
959 HTTP/1.1 message, defined by IETF RFC 2068
960
961 =item http-1.1-rfc2068-request
962
963 HTTP/1.1 request message, defined by IETF RFC 2068
964
965 =item http-1.1-rfc2068-response
966
967 HTTP/1.1 response message, defined by IETF RFC 2068
968
969 =item http-1.1-rfc2616
970
971 HTTP/1.1 message, defined by IETF RFC 2616
972
973 =item http-1.1-rfc2616-request
974
975 HTTP/1.1 request message, defined by IETF RFC 2616
976
977 =item http-1.1-rfc2616-response
978
979 HTTP/1.1 response message, defined by IETF RFC 2616
980
981 =item http-cgi-1.1
982
983 CGI/1.1 output (for HTTP), defined by coar-cgi-v11 (IETF Internet Draft)
984
985 =item http-cgi-1.2
986
987 CGI/1.2 output, defined by coar-cgi-v12 (to be IETF Internet Draft)
988
989 =item http-sip-2.0
990
991 SIP/2.0 message, defined by IETF RFC 2543
992
993 =item http-sip-2.0-request
994
995 SIP/2.0 request message, defined by IETF RFC 2543
996
997 =item http-sip-2.0-response
998
999 SIP/2.0 response message, defined by IETF RFC 2543
1000
1001 =item http-sip-cgi
1002
1003 SIP/2.0 CGI (IETF Internet Draft)
1004
1005 =item cpim-1.0
1006
1007 CPIM/1.0 (IETF Internet Draft)
1008
1009 =item uri-url-mailto
1010
1011 mailto: URL scheme
1012
1013 =item uri-url-mailto-rfc1738
1014
1015 mailto: URL scheme (defined by RFC 1738)
1016
1017 =item uri-url-mailto-rfc2368
1018
1019 mailto: URL scheme (defined by RFC 2368)
1020
1021 =item uri-url-mailto-to
1022
1023 C<to> part of mailto: URL scheme (for internal use only)
1024
1025 =back
1026
1027 =head1 EXAMPLE
1028
1029 use Message::Entity;
1030 my $msg = new Message::Entity From => '[email protected]',
1031 Subject => 'Example message',
1032 To => '[email protected]',
1033 -format => 'mail-rfc2822',
1034 body => $body;
1035 $msg->header ($header);
1036 $msg->body ($body);
1037 print $msg;
1038
1039 =head1 SEE ALSO
1040
1041 Message::* Perl modules
1042 <http://suika.fam.cx/~wakaba/Message-pm/>
1043
1044 =head1 LICENSE
1045
1046 Copyright 2002 wakaba E<lt>[email protected]<gt>.
1047
1048 This program is free software; you can redistribute it and/or modify
1049 it under the terms of the GNU General Public License as published by
1050 the Free Software Foundation; either version 2 of the License, or
1051 (at your option) any later version.
1052
1053 This program is distributed in the hope that it will be useful,
1054 but WITHOUT ANY WARRANTY; without even the implied warranty of
1055 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1056 GNU General Public License for more details.
1057
1058 You should have received a copy of the GNU General Public License
1059 along with this program; see the file COPYING. If not, write to
1060 the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
1061 Boston, MA 02111-1307, USA.
1062
1063 =head1 CHANGE
1064
1065 See F<ChangeLog>.
1066 $Date: 2002/07/27 04:44:25 $
1067
1068 =cut
1069
1070 1;

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24