line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
############################################################################### |
2
|
|
|
|
|
|
|
# Purpose : Build HTML emails |
3
|
|
|
|
|
|
|
# Author : Tony Hennessy |
4
|
|
|
|
|
|
|
# Created : Aug 2006 |
5
|
|
|
|
|
|
|
############################################################################### |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
package Email::MIME::CreateHTML; |
8
|
|
|
|
|
|
|
|
9
|
2
|
|
|
2
|
|
111076
|
use strict; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
68
|
|
10
|
2
|
|
|
2
|
|
7
|
use Carp; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
123
|
|
11
|
2
|
|
|
2
|
|
8
|
use Exporter; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
52
|
|
12
|
2
|
|
|
2
|
|
601
|
use Email::MIME; |
|
2
|
|
|
|
|
26344
|
|
|
2
|
|
|
|
|
39
|
|
13
|
2
|
|
|
2
|
|
1026
|
use HTML::TokeParser::Simple; |
|
2
|
|
|
|
|
33059
|
|
|
2
|
|
|
|
|
59
|
|
14
|
2
|
|
|
2
|
|
14
|
use HTML::Tagset; |
|
2
|
|
|
|
|
2
|
|
|
2
|
|
|
|
|
60
|
|
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
our $VERSION = '1.041'; |
17
|
|
|
|
|
|
|
|
18
|
2
|
|
|
2
|
|
727
|
use Email::MIME::CreateHTML::Resolver; |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
66
|
|
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
#Globals |
21
|
2
|
|
|
2
|
|
10
|
use vars qw(%EMBED @EXPORT_OK @ISA); |
|
2
|
|
|
|
|
2
|
|
|
2
|
|
|
|
|
2115
|
|
22
|
|
|
|
|
|
|
%EMBED = ( |
23
|
|
|
|
|
|
|
'bgsound' => {'src'=>1}, |
24
|
|
|
|
|
|
|
'body' => {'background'=>1}, |
25
|
|
|
|
|
|
|
'img' => {'src'=>1}, |
26
|
|
|
|
|
|
|
'input' => {'src'=>1}, |
27
|
|
|
|
|
|
|
'table' => {'background'=>1}, |
28
|
|
|
|
|
|
|
'td' => {'background'=>1}, |
29
|
|
|
|
|
|
|
'th' => {'background'=>1}, |
30
|
|
|
|
|
|
|
'tr' => {'background'=>1}, |
31
|
|
|
|
|
|
|
); |
32
|
|
|
|
|
|
|
@EXPORT_OK = qw(embed_objects parts_for_objects build_html_email); |
33
|
|
|
|
|
|
|
@ISA = qw(Exporter); |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
# |
36
|
|
|
|
|
|
|
# Public routines used by create_html and also exportable |
37
|
|
|
|
|
|
|
# |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
sub embed_objects { |
40
|
9
|
|
|
9
|
1
|
12
|
my ($html, $args) = @_; |
41
|
9
|
100
|
66
|
|
|
38
|
my $embed = ( defined $args->{embed} && $args->{embed} eq '0' ) ? 0 : 1; |
42
|
9
|
100
|
66
|
|
|
33
|
my $inline_css = ( defined $args->{inline_css} && $args->{inline_css} eq '0' ) ? 0 : 1; |
43
|
9
|
|
|
|
|
53
|
my $resolver = new Email::MIME::CreateHTML::Resolver($args); |
44
|
8
|
|
50
|
|
|
32
|
my $embed_tags = $args->{'embed_elements'} || \%EMBED; |
45
|
|
|
|
|
|
|
|
46
|
8
|
50
|
66
|
|
|
25
|
return ($html, {}) unless ( $embed || $inline_css ); #No-op unless one of these is set |
47
|
|
|
|
|
|
|
|
48
|
8
|
|
|
|
|
7
|
my ($html_modified, %embedded_cids); |
49
|
8
|
|
|
|
|
56
|
my $parser = HTML::TokeParser::Simple->new( \$html ); |
50
|
8
|
|
|
|
|
1039
|
my $regex = '^(' . join('|',keys %HTML::Tagset::linkElements) . ')'; |
51
|
8
|
|
|
|
|
223
|
$regex = qr/$regex/; |
52
|
8
|
|
|
|
|
34
|
while ( my $token = $parser->get_token ) { |
53
|
|
|
|
|
|
|
|
54
|
172
|
100
|
|
|
|
4022
|
unless ( $token->is_start_tag( $regex ) ) { |
55
|
147
|
|
|
|
|
709
|
$html_modified .= $token->as_is; |
56
|
147
|
|
|
|
|
678
|
next; |
57
|
|
|
|
|
|
|
} |
58
|
25
|
|
|
|
|
281
|
my $token_tag = $token->get_tag(); |
59
|
25
|
|
|
|
|
117
|
my $token_attrs = $token->get_attr(); |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
# inline_css |
62
|
25
|
100
|
66
|
|
|
244
|
if ( $token_tag eq 'link' && $token_attrs->{type} eq 'text/css' ) { |
63
|
2
|
100
|
|
|
|
6
|
unless ( $inline_css ) { |
64
|
1
|
|
|
|
|
4
|
$html_modified .= $token->as_is; |
65
|
1
|
|
|
|
|
5
|
next; |
66
|
|
|
|
|
|
|
} |
67
|
1
|
|
|
|
|
2
|
my $link = $token_attrs->{'href'}; |
68
|
1
|
|
|
|
|
4
|
my ($content,$filename,$mimetype,$encoding) = $resolver->get_resource( $link ); |
69
|
1
|
|
|
|
|
4
|
$html_modified .= "\n".'\n"; |
72
|
1
|
|
|
|
|
4
|
next; |
73
|
|
|
|
|
|
|
} |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
# rewrite and embed |
76
|
23
|
|
|
|
|
22
|
for my $attr ( @{ $HTML::Tagset::linkElements{$token_tag} } ) { |
|
23
|
|
|
|
|
49
|
|
77
|
53
|
100
|
|
|
|
433
|
if ( defined $token_attrs->{$attr} ) { |
78
|
11
|
|
|
|
|
16
|
my $link = $token_attrs->{$attr}; |
79
|
11
|
100
|
|
|
|
32
|
next if ($link =~ m/^cid:/i); |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
# embed |
82
|
5
|
100
|
66
|
|
|
28
|
if ( $embed && $embed_tags->{$token_tag}->{$attr} ) { |
83
|
4
|
100
|
|
|
|
10
|
unless ( defined $embedded_cids{$link} ) { |
84
|
|
|
|
|
|
|
# make a unique cid |
85
|
3
|
|
|
|
|
14
|
my $newcid = time().$$.int(rand(1e6)); |
86
|
3
|
|
|
|
|
5
|
$embedded_cids{$link} = $newcid; |
87
|
|
|
|
|
|
|
} |
88
|
4
|
|
|
|
|
62
|
my $link_rewrite = "cid:".$embedded_cids{$link}; |
89
|
4
|
|
|
|
|
13
|
$token->set_attr( $attr => $link_rewrite ); |
90
|
|
|
|
|
|
|
} |
91
|
|
|
|
|
|
|
} |
92
|
|
|
|
|
|
|
} |
93
|
23
|
|
|
|
|
48
|
$html_modified .= $token->as_is; |
94
|
|
|
|
|
|
|
} |
95
|
|
|
|
|
|
|
|
96
|
8
|
|
|
|
|
121
|
my %objects = reverse %embedded_cids; #invert mapping |
97
|
8
|
|
|
|
|
86
|
return ($html_modified, \%objects); |
98
|
|
|
|
|
|
|
} |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
sub parts_for_objects { |
101
|
8
|
|
|
8
|
1
|
8
|
my ($objects, $args) = @_; |
102
|
8
|
|
|
|
|
27
|
my $resolver = new Email::MIME::CreateHTML::Resolver($args); |
103
|
|
|
|
|
|
|
|
104
|
8
|
|
|
|
|
7
|
my @html_mime_parts; |
105
|
8
|
|
|
|
|
17
|
foreach my $cid (keys %$objects) { |
106
|
9
|
50
|
|
|
|
52
|
croak "Content-Id '$cid' contains bad characters" unless ($cid =~ m/^[\w\-\@\.]+$/); |
107
|
9
|
50
|
|
|
|
19
|
croak "Content-Id must be given" unless length($cid); |
108
|
|
|
|
|
|
|
|
109
|
9
|
|
|
|
|
11
|
my $path = $objects->{$cid}; |
110
|
9
|
|
|
|
|
23
|
my ($content,$filename,$mimetype,$encoding) = $resolver->get_resource( $path ); |
111
|
|
|
|
|
|
|
|
112
|
9
|
|
50
|
|
|
28
|
$mimetype ||= 'application/octet-stream'; |
113
|
9
|
|
|
|
|
71
|
my $newpart = Email::MIME->create( |
114
|
|
|
|
|
|
|
attributes => { |
115
|
|
|
|
|
|
|
content_type => $mimetype, |
116
|
|
|
|
|
|
|
encoding => $encoding, |
117
|
|
|
|
|
|
|
disposition => 'inline', # maybe useful rfc2387 |
118
|
|
|
|
|
|
|
charset => undef, |
119
|
|
|
|
|
|
|
name => $filename, |
120
|
|
|
|
|
|
|
}, |
121
|
|
|
|
|
|
|
body => $content, |
122
|
|
|
|
|
|
|
); |
123
|
9
|
|
|
|
|
8013
|
$newpart->header_set('Content-ID',"<$cid>"); |
124
|
|
|
|
|
|
|
# $newpart->header_set("Content-Transfer-Encoding", "base64"); |
125
|
9
|
|
|
|
|
289
|
push @html_mime_parts , $newpart; |
126
|
|
|
|
|
|
|
} |
127
|
8
|
|
|
|
|
31
|
return @html_mime_parts; |
128
|
|
|
|
|
|
|
} |
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
sub build_html_email { |
131
|
8
|
|
|
8
|
1
|
13
|
my($header, $html, $body_attributes, $html_mime_parts, $plain_text_mime) = @_; |
132
|
|
|
|
|
|
|
|
133
|
8
|
100
|
|
|
|
24
|
$body_attributes->{charset} = 'UTF-8' unless exists $body_attributes->{charset}; |
134
|
8
|
100
|
|
|
|
21
|
$body_attributes->{encoding}= 'quoted-printable' unless exists $body_attributes->{encoding}; |
135
|
|
|
|
|
|
|
|
136
|
8
|
|
|
|
|
12
|
my $email; |
137
|
8
|
100
|
100
|
|
|
72
|
if ( ! scalar(@$html_mime_parts) && ! defined($plain_text_mime) ) { |
|
|
100
|
66
|
|
|
|
|
|
|
100
|
66
|
|
|
|
|
|
|
50
|
33
|
|
|
|
|
138
|
|
|
|
|
|
|
# HTML, no embedded objects, no text alternative |
139
|
2
|
|
|
|
|
13
|
$email = Email::MIME->create( |
140
|
|
|
|
|
|
|
header => $header, |
141
|
|
|
|
|
|
|
attributes => $body_attributes, |
142
|
|
|
|
|
|
|
body_str => $html, |
143
|
|
|
|
|
|
|
); |
144
|
|
|
|
|
|
|
} |
145
|
|
|
|
|
|
|
elsif ( ! scalar(@$html_mime_parts) && defined($plain_text_mime) ) { |
146
|
|
|
|
|
|
|
# HTML, no embedded objects, with text alternative |
147
|
1
|
|
|
|
|
6
|
$email = Email::MIME->create( |
148
|
|
|
|
|
|
|
header => $header, |
149
|
|
|
|
|
|
|
attributes => {content_type=>'multipart/alternative'}, |
150
|
|
|
|
|
|
|
parts => [ |
151
|
|
|
|
|
|
|
$plain_text_mime, |
152
|
|
|
|
|
|
|
Email::MIME->create( |
153
|
|
|
|
|
|
|
attributes => $body_attributes, |
154
|
|
|
|
|
|
|
body_str => $html, |
155
|
|
|
|
|
|
|
), |
156
|
|
|
|
|
|
|
], |
157
|
|
|
|
|
|
|
); |
158
|
|
|
|
|
|
|
} |
159
|
|
|
|
|
|
|
elsif ( scalar(@$html_mime_parts) && ! defined($plain_text_mime) ) { |
160
|
|
|
|
|
|
|
# HTML with embedded objects, no text alternative |
161
|
4
|
|
|
|
|
19
|
$email = Email::MIME->create( |
162
|
|
|
|
|
|
|
header => $header, |
163
|
|
|
|
|
|
|
attributes => {content_type=>'multipart/related'}, |
164
|
|
|
|
|
|
|
parts => [ |
165
|
|
|
|
|
|
|
Email::MIME->create( |
166
|
|
|
|
|
|
|
attributes => $body_attributes, |
167
|
|
|
|
|
|
|
body_str => $html, |
168
|
|
|
|
|
|
|
), |
169
|
|
|
|
|
|
|
@$html_mime_parts, |
170
|
|
|
|
|
|
|
], |
171
|
|
|
|
|
|
|
); |
172
|
|
|
|
|
|
|
} |
173
|
|
|
|
|
|
|
elsif ( scalar(@$html_mime_parts) && defined($plain_text_mime) ) { |
174
|
|
|
|
|
|
|
# HTML with embedded objects, with text alternative |
175
|
1
|
|
|
|
|
7
|
$email = Email::MIME->create( |
176
|
|
|
|
|
|
|
header => $header, |
177
|
|
|
|
|
|
|
attributes => {content_type=>'multipart/alternative'}, |
178
|
|
|
|
|
|
|
parts => [ |
179
|
|
|
|
|
|
|
$plain_text_mime, |
180
|
|
|
|
|
|
|
Email::MIME->create( |
181
|
|
|
|
|
|
|
attributes => {content_type=>'multipart/related'}, |
182
|
|
|
|
|
|
|
parts => [ |
183
|
|
|
|
|
|
|
Email::MIME->create( |
184
|
|
|
|
|
|
|
attributes => $body_attributes, |
185
|
|
|
|
|
|
|
body_str => $html, |
186
|
|
|
|
|
|
|
), |
187
|
|
|
|
|
|
|
@$html_mime_parts, |
188
|
|
|
|
|
|
|
], |
189
|
|
|
|
|
|
|
), |
190
|
|
|
|
|
|
|
], |
191
|
|
|
|
|
|
|
); |
192
|
|
|
|
|
|
|
} |
193
|
8
|
|
|
|
|
25268
|
return $email; |
194
|
|
|
|
|
|
|
} |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
# Add to Email::MIME |
197
|
|
|
|
|
|
|
package # Hide from PAUSE |
198
|
|
|
|
|
|
|
Email::MIME; |
199
|
|
|
|
|
|
|
|
200
|
2
|
|
|
2
|
|
10
|
use strict; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
70
|
|
201
|
2
|
|
|
2
|
|
8
|
use Carp; |
|
2
|
|
|
|
|
2
|
|
|
2
|
|
|
|
|
88
|
|
202
|
2
|
|
|
2
|
|
8
|
use Email::MIME::Creator; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
420
|
|
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
sub create_html { |
205
|
9
|
|
|
9
|
0
|
32796
|
my ($class, %args) = @_; |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
#Argument checking/defaulting |
208
|
9
|
|
33
|
|
|
33
|
my $html = $args{body} || croak "You must supply a body"; |
209
|
9
|
|
100
|
|
|
33
|
my $objects = $args{'objects'} || undef; |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
# Make plain text Email::MIME object, we will never use this alone so we don't need the headers |
212
|
9
|
|
|
|
|
8
|
my $plain_text_mime; |
213
|
9
|
100
|
|
|
|
28
|
if ( exists($args{text_body}) ) { |
214
|
2
|
50
|
|
|
|
3
|
my %text_body_attributes = ( (content_type=>'text/plain'), %{$args{text_body_attributes} || {}} ); |
|
2
|
|
|
|
|
13
|
|
215
|
2
|
|
|
|
|
10
|
$plain_text_mime = $class->create( |
216
|
|
|
|
|
|
|
attributes => \%text_body_attributes, |
217
|
|
|
|
|
|
|
body => $args{text_body}, |
218
|
|
|
|
|
|
|
); |
219
|
|
|
|
|
|
|
} |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
# Parse the HTML and create a CID mapping for objects to embed |
222
|
9
|
|
|
|
|
939
|
my $embedded_cids; |
223
|
9
|
|
|
|
|
25
|
($html, $embedded_cids) = Email::MIME::CreateHTML::embed_objects($html, \%args); |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
# Create parts for each embedded object |
226
|
8
|
|
|
|
|
13
|
my @html_mime_parts; |
227
|
8
|
100
|
|
|
|
26
|
push @html_mime_parts, Email::MIME::CreateHTML::parts_for_objects($objects, \%args) if ($objects); |
228
|
8
|
100
|
|
|
|
23
|
push @html_mime_parts, Email::MIME::CreateHTML::parts_for_objects($embedded_cids, \%args) if(%$embedded_cids); |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
# Create the mail |
231
|
8
|
|
|
|
|
14
|
my $header = $args{header}; |
232
|
8
|
100
|
|
|
|
10
|
my %body_attributes = ( (content_type=>'text/html'), %{$args{body_attributes} || {}}); |
|
8
|
|
|
|
|
50
|
|
233
|
8
|
|
|
|
|
27
|
my $email = Email::MIME::CreateHTML::build_html_email($header, $html, \%body_attributes, \@html_mime_parts, $plain_text_mime); |
234
|
8
|
|
|
|
|
76
|
return $email; |
235
|
|
|
|
|
|
|
} |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
#Log::Trace stubs |
238
|
0
|
|
|
0
|
0
|
|
sub DUMP {} |
239
|
0
|
|
|
0
|
0
|
|
sub TRACE {} |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
1; |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
__END__ |