source: trunk/gsdl/perllib/plugins/EMAILPlug.pm@ 2847

Last change on this file since 2847 was 2847, checked in by sjboddie, 22 years ago

Altered EMAILPlug a little so it now treats all text that it used to
treat as ASCII as ISO-8859-1 encoded instead. This prevents problems
when text is assumed to be plain ASCII but isn't (that is, the resulting
XML documents couldn't be parsed by the XML::Parser module).

  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
File size: 22.3 KB
Line 
1###########################################################################
2#
3# EMAILPlug.pm - a plugin for parsing email files
4#
5# A component of the Greenstone digital library software
6# from the New Zealand Digital Library Project at the
7# University of Waikato, New Zealand.
8#
9# Copyright (C) 1999-2001 New Zealand Digital Library Project
10#
11# This program is free software; you can redistribute it and/or modify
12# it under the terms of the GNU General Public License as published by
13# the Free Software Foundation; either version 2 of the License, or
14# (at your option) any later version.
15#
16# This program is distributed in the hope that it will be useful,
17# but WITHOUT ANY WARRANTY; without even the implied warranty of
18# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19# GNU General Public License for more details.
20#
21# You should have received a copy of the GNU General Public License
22# along with this program; if not, write to the Free Software
23# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
24#
25###########################################################################
26
27
28
29# EMAILPlug
30#
31# by Gordon Paynter ([email protected])
32#
33# Email plug reads email files. These are named with a simple
34# number (i.e. as they appear in maildir folders) or with the
35# extension .mbx (for mbox mail file format)
36#
37# Document text:
38# The document text consists of all the text
39# after the first blank line in the document.
40#
41# Metadata (not Dublin Core!):
42# $Headers All the header content
43# $Subject Subject: header
44# $To To: header
45# $From From: header
46# $FromName Name of sender (where available)
47# $FromAddr E-mail address of sender
48# $DateText Date: header
49# $Date Date: header in GSDL format (eg: 19990924)
50#
51#
52# John McPherson - June/July 2001
53# added (basic) MIME support and quoted-printable and base64 decodings.
54# Minor fixes for names that are actually email addresses (ie <...> was lost)
55#
56# See: * RFC 822 - ARPA Internet Text Messages
57# * RFC 2045 - Multipurpose Internet Mail Extensions (MIME) -part1
58# * RFC 2046 - MIME (part 2) Media Types (and multipart messages)
59# * RFC 2047 - MIME (part 3) Message Header Extensions
60# * RFC 1806 - Content Dispositions (ie inline/attachment)
61package EMAILPlug;
62
63use SplitPlug;
64
65use unicode;
66
67use sorttools;
68use util;
69
70
71# EMAILPlug is a sub-class of SplitPlug.
72
73sub BEGIN {
74 @ISA = ('SplitPlug');
75}
76
77# Create a new EMAILPlug object with which to parse a file.
78# Accomplished by creating a new BasPlug and using bless to
79# turn it into an EMAILPlug.
80
81sub new {
82 my ($class) = @_;
83 my $self = new BasPlug ("EMAILPlug", @_);
84 # this might not actually be true at read-time, but after processing
85 # it should all be utf8.
86 $self->{'input_encoding'}="utf8";
87 return bless $self, $class;
88}
89
90sub get_default_process_exp {
91 my $self = shift (@_);
92 # mbx/email for mailbox file format, \d+ for maildir (each message is
93 # in a separate file, with a unique number for filename)
94 return q@([\\/]\d+|\.(mbx|email))$@;
95}
96
97# This plugin splits the mbox mail files at lines starting with From<sp>
98sub get_default_split_exp {
99 return q^\nFrom .*\n^;
100}
101
102
103# do plugin specific processing of doc_obj
104sub process {
105
106 my $self = shift (@_);
107 my ($textref, $pluginfo, $base_dir, $file, $metadata, $doc_obj) = @_;
108 my $outhandle = $self->{'outhandle'};
109
110 # Check that we're dealing with a valid mail file
111 return undef unless (($$textref =~ /From:/m) || ($$textref =~ /To:/m));
112
113 # slightly more strict validity check, to prevent us from matching
114 # .so.x files ...
115 return undef unless (($$textref =~ /^From /) ||
116 ($$textref =~ /^[-A-Za-z]{2,100}:/m));
117
118 print $outhandle "EMAILPlug: processing $file\n"
119 if $self->{'verbosity'} > 1;
120
121 my $cursection = $doc_obj->get_top_section();
122
123 #
124 # Parse the document's text and extract metadata
125 #
126
127 # Protect backslashes
128 $$textref =~ s@\\@\\\\@g;
129
130 # Separate header from body of message
131 my $Headers = $$textref;
132 $Headers =~ s/\r?\n\r?\n(.*)$//s;
133 $$textref = $1;
134
135 # See if headers include non-ascii - RFC says whole header should be ascii.
136# not yet implemented, as we don't know what character set is the
137# user's default... We can do textcat to guess, or we can just choose
138# one of the charset fields later in the document (if there are any...)
139# if ($Headers =~ /([[:^ascii:]])/) {
140# }
141
142 # Unfold headers - see rfc822
143 $Headers =~ s/\r?\n[\t\ ]+/ /gs;
144 # Extract basic metadata from header
145 my @headers = ("From", "To", "Subject", "Date");
146 my %raw;
147 foreach my $name (@headers) {
148 $raw{$name} = "No $name value";
149 }
150
151 # Examine each line of the headers
152 my ($line, $name, $value);
153 my @parts;
154 foreach $line (split(/\n/, $Headers)) {
155
156 # Ignore lines with no content or which begin with whitespace
157 next unless ($line =~ /:/);
158 next if ($line =~ /^\s/);
159
160 # Find out what metadata is on this line
161 @parts = split(/:/, $line);
162 $name = shift @parts;
163# uppercase the first character according to the current locale
164 $name=~s/(.+)/\u$1/;
165 next unless $name;
166 next unless ($raw{$name});
167
168 # Find the value of that metadata
169 $value = join(":", @parts);
170 $value =~ s/^\s+//;
171 $value =~ s/\s+$//;
172
173 # decode headers if stored using =?<charset>?[BQ]?<data>?= (rfc2047)
174 if ($value =~ /=\?/) {
175 my $original_value=$value;
176 my $encoded=$value;
177 $value="";
178 # this isn't quite right yet regarding spaces between encoded-texts
179 # (see examples, section 8. of rfc).
180 while ($encoded =~ s/(.*?)=\?([^\?]*)\?([bq])\?([^\?]+)\?=\s*//i) {
181 my ($charset, $encoding, $data)=($2,$3,$4);
182 my ($decoded_data);
183 $value.="$1"; # any leading chars
184 $data=~s/^\s*//; $data=~s/\s*$//; # strip whitespace from ends
185 chomp $data;
186 $encoding =~ tr/BQ/bq/;
187 if ($encoding eq "q") { # quoted printable
188 $data =~ s/_/\ /g; # from rfc2047 (sec 4.2.2)
189 $decoded_data=qp_decode($data);
190 } else { # base 64
191 $decoded_data=base64_decode($data);
192 }
193 $self->convert2unicode($charset, \$decoded_data);
194 $value .= $decoded_data;
195 } # end of while loop
196
197 # get any trailing characters
198 $self->convert2unicode("iso_8859_1", \$encoded);
199 $value.=$encoded;
200
201 if ($value =~ /^\s*$/) { # we couldn't extract anything...
202 $self->convert2unicode("iso_8859_1", \$original_value);
203 $value=original_value;
204 }
205 } # end of if =?...?=
206
207 # Store the metadata
208 $raw{$name} = $value;
209 }
210
211 # Extract the name and e-mail address from the From metadata
212 $frommeta = $raw{"From"};
213 my $fromnamemeta;
214 my $fromaddrmeta;
215
216 $frommeta =~ s/\s*$//; # Remove trailing space, if any
217
218 if ($frommeta =~ m/(.+)\s*<(.+)>/) {
219 $fromnamemeta=$1;
220 $fromaddrmeta=$2;
221 } elsif ($frommeta =~ m/(.+@.+)\s+\((.*)\)/) {
222 $fromnamemeta=$2;
223 $fromaddrmeta=$1;
224 }
225 if (!defined($fromaddrmeta)) {
226 $fromaddrmeta=$frommeta;
227 }
228 $fromaddrmeta=~s/<//; $fromaddrmeta=~s/>//;
229 # minor attempt to prevent spam-bots from harvesting addresses...
230 $fromaddrmeta=~s/@/&#64;/;
231 $doc_obj->add_utf8_metadata ($cursection, "FromAddr", $fromaddrmeta);
232
233 if (defined($fromnamemeta)) {
234 $fromnamemeta =~ s/\"//g;
235 }
236 else {
237 $fromnamemeta = $fromaddrmeta;
238 }
239 # if name is an address
240 $fromnamemeta =~ s/<//g; $fromnamemeta =~ s/>//g;
241 $fromnamemeta=~s/@/&#64\;/;
242 $doc_obj->add_utf8_metadata ($cursection, "FromName", $fromnamemeta);
243
244 # Escape < and > in the whole From field;
245 $raw{"From"}=$frommeta;
246
247 # Process Date information
248 if ($raw{"Date"} !~ /No Date/) {
249 $raw{"DateText"} = $raw{"Date"};
250
251 # Convert the date text to internal date format
252 $value = $raw{"Date"};
253 my ($day, $month, $year) = $value =~ /(\d?\d)\s([A-Z][a-z][a-z])\s(\d\d\d?\d?)/;
254 if ($year < 100) { $year += 1900; }
255 $raw{"Date"} = &sorttools::format_date($day, $month, $year);
256
257 } else {
258 # We have not extracted a date
259 $raw{"DateText"} = "Unknown.";
260 $raw{"Date"} = "19000000";
261 }
262
263 # Add extracted metadata to document object
264 foreach my $name (keys %raw) {
265 $value = $raw{$name};
266 if ($value) {
267 # assume subject, etc headers have no special HTML meaning.
268 $value =~ s@&@&amp\;@g;
269 $value =~ s/</&lt;/g; $value =~ s/>/&gt;/g;
270 $value = &text_into_html($value);
271 # escape [] so it isn't re-interpreted as metadata
272 $value =~ s/\[/&#91;/g; $value =~ s/\]/&#93;/g;
273 } else {
274 $value = "No $name field";
275 }
276 $doc_obj->add_utf8_metadata ($cursection, $name, $value);
277 }
278
279 my $mimetype="text/plain";
280 my $mimeinfo="";
281 my $charset = "iso_8859_1";
282 # Do MIME and encoding stuff
283 if ($Headers =~ /^content\-type:\s*([\w\/\-]+)\s*\;?\s*(.+?)\s*$/mi)
284 {
285 $mimetype=$1;
286 $mimetype =~ tr/[A-Z]/[a-z]/;
287 $mimeinfo=$2;
288 if ($mimeinfo =~ /charset=\"([^\"]+)\"/) {
289 $charset = $1;
290 }
291 }
292
293 my $transfer_encoding="7bit";
294 if ($Headers =~ /^content-transfer-encoding:\s*([^\s]+)\s*$/mi) {
295 $transfer_encoding=$1;
296 }
297 if ($mimetype ne "text/plain") {
298 $$textref= $self->text_from_mime_message($mimetype,$mimeinfo,$$textref,
299 $outhandle);
300 } elsif ($transfer_encoding =~ /quoted\-printable/) {
301 $$textref=qp_decode($$textref);
302 } elsif ($transfer_encoding =~ /base64/) {
303 $$textref=base64_decode($$textref);
304 } else {
305 $self->convert2unicode($charset, $textref);
306 }
307
308
309 # Add "All headers" metadata
310 $Headers = &text_into_html($Headers);
311 $Headers =~ s/&([lg])t\;/&amp\;$1t\;/g;
312
313 $Headers = "No headers" unless ($Headers =~ /\w/);
314 $Headers =~ s/@/&#64\;/g;
315 # escape [] so it isn't re-interpreted as metadata
316 $Headers =~ s/\[/&#91;/g; $Headers =~ s/\]/&#93;/g;
317
318 $doc_obj->add_utf8_metadata ($cursection, "Headers", $Headers);
319
320 # Add text to document object
321 if ($mimetype eq "text/plain") {
322 $$textref = &text_into_html($$textref);
323 }
324 $$textref = "No message" unless ($$textref =~ /\w/);
325 $doc_obj->add_utf8_text($cursection, $$textref);
326
327 return 1;
328}
329
330
331# Convert a text string into HTML.
332#
333# The HTML is going to be inserted into a GML file, so
334# we have to be careful not to use symbols like ">",
335# which ocurs frequently in email messages (and use
336# &gt instead.
337#
338# This function also turns links and email addresses into hyperlinks,
339# and replaces carriage returns with <BR> tags (and multiple carriage
340# returns with <P> tags).
341
342
343sub text_into_html {
344 my ($text) = @_;
345
346 # Convert problem characters into HTML symbols
347 $text =~ s/&/&amp;/go;
348 $text =~ s/</&lt;/go;
349 $text =~ s/>/&gt;/go;
350 $text =~ s/\"/&quot;/go;
351
352 # convert email addresses and URIs into links
353# don't markup email addresses for now
354# $text =~ s/([\w\d\.\-]+@[\w\d\.\-]+)/<a href=\"mailto:$1\">$1<\/a>/g;
355
356 # assume hostnames are \.\w\- only, then might have a trailing '/.*'
357 # assume URI doesn't finish with a '.'
358 $text =~ s@((http|ftp|https)://[\w\-]+(\.[\w\-]+)*/?((&amp;|\.)?[\w\?\=\-_/~]+)*)@<a href=\"$1\">$1<\/a>@g;
359
360
361 # Clean up whitespace and convert \n charaters to <BR> or <P>
362 $text =~ s/ +/ /go;
363 $text =~ s/\s*$//o;
364 $text =~ s/^\s*//o;
365 $text =~ s/\n/\n<BR>/go;
366 $text =~ s/<BR>\s*<BR>/<P>/go;
367
368 return $text;
369}
370
371
372
373
374#Process a MIME message.
375# the textref we are given DOES NOT include the header.
376sub text_from_mime_message {
377 my $self = shift(@_);
378 my ($mimetype,$mimeinfo,$text,$outhandle)=(@_);
379
380 # Check for multiparts - $mimeinfo will be a boundary
381 if ($mimetype =~ /multipart/) {
382 $boundary="";
383 if ($mimeinfo =~ m@boundary=(\"[^\"]+\"|[^\s]+)\s*$@im) {
384 $boundary=$1;
385 if ($boundary =~ m@^\"@) {
386 $boundary =~ s@^\"@@; $boundary =~ s@\"$@@;
387 }
388 } else {
389 print $outhandle "EMAILPlug: (warning) couldn't parse MIME boundary\n";
390 }
391 # parts start with "--$boundary"
392 # message ends with "--$boundary--"
393 # RFC says boundary is <70 chars, [A-Za-z'()+_,-./:=?], so escape any
394 # that perl might want to interpolate. Also allows spaces...
395 $boundary=~s/\\/\\\\/g;
396 $boundary=~s/([\?\+\.\(\)\:\/\'])/\\$1/g;
397 my @message_parts = split("\r?\n\-\-$boundary", "\n$text");
398 # remove first "part" and last "part" (final --)
399 shift @message_parts;
400 my $last=pop @message_parts;
401 # if our boundaries are a bit dodgy and we only found 1 part...
402 if (!defined($last)) {$last="";}
403 # make sure it is only -- and whitespace
404 if ($last !~ /^\-\-\s*$/ms) {
405 print $outhandle "EMAILPlug: (warning) last part of MIME message isn't empty\n";
406 }
407 foreach my $message_part (@message_parts) {
408 # remove the leading newline left from split.
409 $message_part=~s/^\r?\n//;
410 }
411 if ($mimetype eq "multipart/alternative") {
412 # check for an HTML version first, then TEXT, otherwise use first.
413 my $part_text="";
414 foreach my $message_part (@message_parts) {
415 if ($message_part =~ m@\s*content\-type:\s*text/html@mis)
416 {
417 # Use the HTML version
418 $part_text= $self->text_from_part($message_part);
419 $mimetype="text/html";
420 last;
421 }
422 }
423 if ($part_text eq "") { # try getting a text part instead
424 foreach my $message_part (@message_parts) {
425 if ($message_part =~ m@^content\-type:\s*text/plain@mis)
426 {
427 # Use the plain version
428 $part_text= $self->text_from_part($message_part);
429 if ($part_text =~/[^\s]/) {
430 $part_text="<pre>".$part_text."</pre>";
431 }
432 $mimetype="text/plain";
433 last;
434 }
435 }
436 }
437 if ($part_text eq "") { # use first part
438 $part_text= $self->text_from_part(shift @message_parts);
439 }
440 if ($part_text eq "") { # we couldn't get anything!!!
441 # or it was an empty message...
442 # do nothing...
443 print $outhandle "EMAILPlug: no text - empty body?\n";
444 } else {
445 $text=$part_text;
446 }
447 } elsif ($mimetype =~ m@multipart/(mixed|digest|related)@) {
448 $text="";
449 foreach my $message_part (@message_parts) {
450 my $part_header=$message_part;
451 my $part_body;
452 if ($message_part=~ /^\s*\n/) {
453 # no header... use defaults
454 $part_body=$message_part;
455 $part_header="Content-type: text/plain; charset=us-ascii";
456 } elsif ($part_header=~s/\r?\n\r?\n(.*)$//sg) {
457 $part_body=$1;
458 } else {
459 # something's gone wrong...
460 $part_header="";
461 $part_body=$message_part;
462 }
463
464 $part_header =~ s/\r?\n[\t\ ]+/ /gs; #unfold
465 my $part_content_type="";
466 my $part_content_info="";
467 if ($mimetype eq "multipart/digest") {
468 # default type - RTFRFC!!
469 $part_content_type="message/rfc822";
470 }
471 if ($part_header =~ m@^content\-type:\s*([\w+/\-]+)\s*\;?\s*(.*?)\s*$@mi) {
472 $part_content_type=$1; $part_content_type =~ tr/A-Z/a-z/;
473 $part_content_info=$2;
474 }
475 my $filename="";
476 if ($part_header =~ m@name=\"?([\w\.\-\\/]+)\"?@mis) {
477 $filename=$1;
478 }
479
480 # disposition - either inline or attachment.
481 # NOT CURRENTLY USED - we display all text types instead...
482 # $part_header =~ /^content\-disposition:\s*([\w+])/mis;
483
484 # add <<attachment>> to each part except the first...
485 if ($text ne "") {
486 $text.="\n<p><hr><strong>&lt;&lt;attachment&gt;&gt;</>";
487 # add part info header
488 $text.="<br>Type: $part_content_type<br>\n";
489 if ($filename ne "") {
490 $text.="Filename: $filename\n";
491 }
492 $text.="</strong></p>\n";
493 }
494
495 if ($part_content_type =~ m@text/@)
496 {
497 my $part_text= $self->text_from_part($message_part);
498 if ($part_content_type !~ m@text/(ht|x)ml@) {
499 $part_text=text_into_html($part_text);
500 }
501 if ($part_text eq "") {
502 $part_text='&lt;&lt;empty message&gt;&gt;';
503 }
504 $text.=$part_text;
505 } elsif ($part_content_type =~ m@message/rfc822@) {
506 # This is a forwarded message
507 my $message_part_headers=$part_body;
508 $message_part_headers =~ s/\r?\n[\t\ ]+/ /gs; #unfold
509 $message_part_headers=~s/\r?\n\r?\n(.*)$//s;
510 my $message_part_body=$1;
511
512 my $rfc822_formatted_body=""; # put result in here
513 if ($message_part_headers =~
514 /^content\-type:\s*([\w\/\-]+)\s*\;?\s*(.*?)\s*$/ims)
515 {
516 # The message header uses MIME flags
517 my $message_content_type=$1;
518 my $message_content_info=$2;
519 if (!defined($message_content_info)) {
520 $message_content_info="";
521 }
522 $message_content_type =~ tr/A-Z/a-z/;
523 if ($message_content_type =~ /multipart/) {
524 $rfc822_formatted_body=
525 $self->text_from_mime_message($message_content_type,
526 $message_content_info,
527 $message_part_body,
528 $outhandle);
529 } else {
530 $message_part_body= $self->text_from_part($part_body);
531 $rfc822_formatted_body=text_into_html($message_part_body);
532 }
533 } else {
534 # message doesn't use MIME flags
535 $rfc822_formatted_body=text_into_html($message_part_body);
536 }
537 # Add the returned text to the output
538 # don't put all the headers...
539 $message_part_headers =~ s/^(X\-.*|received|message\-id|return\-path):.*\n//img;
540 $text.=text_into_html($message_part_headers);
541 $text.="<p>\n";
542 $text.=$rfc822_formatted_body;
543 # end of message/rfc822
544 } elsif ($part_content_type =~ /multipart/) {
545 # recurse again
546
547 $tmptext= $self->text_from_mime_message($part_content_type,
548 $part_content_info,
549 $part_body,
550 $outhandle);
551 $text.=$tmptext;
552 } elsif ($text eq "") {
553 # we can't do anything with this part, but if it's the first
554 # part then make sure it is mentioned..
555
556 $text.="\n<p><hr><strong>&lt;&lt;attachment&gt;&gt;</>";
557 # add part info header
558 $text.="<br>Type: $part_content_type<br>\n";
559 if ($filename ne "") {
560 $text.="Filename: $filename\n";
561 }
562 $text.="</strong></p>\n";
563 }
564 } # foreach message part.
565 } else {
566 # we can't handle this multipart type (not mixed or alternative)
567 # the RFC also mentions "parallel".
568 }
569 } # end of multipart
570 return $text;
571}
572
573
574
575
576
577
578# Process a MIME part. Return "" if we can't decode it.
579sub text_from_part {
580 my $self = shift(@_);
581 my $text=shift;
582 my $part_header=$text;
583 # check for empty part header (leading blank line)
584 if ($text =~ /^\s*\r?\n/) {
585 $part_header="Content-type: text/plain; charset=us-ascii";
586 } else {
587 $part_header =~ s/\r?\n\r?\n(.*)$//s;
588 $text=$1; if (!defined($text)) {$text="";}
589 }
590 $part_header =~ s/\r?\n[\t ]+/ /gs; #unfold
591 $part_header =~ /content\-type:\s*([\w\/]+).*?charset=\"?([^\;\"\s]+)\"?/is;
592 my $type=$1;
593 my $charset=$2;
594 if (!defined($type)) {$type="";}
595 if (!defined($charset)) {$charset="ascii";}
596 my $encoding="";
597 if ($part_header =~ /^content\-transfer\-encoding:\s*([^\s]+)/mis) {
598 $encoding=$1; $encoding=~tr/A-Z/a-z/;
599 }
600 # Content-Transfer-Encoding is per-part
601 if ($encoding ne "") {
602 if ($encoding =~ /quoted\-printable/) {
603 $text=qp_decode($text);
604 } elsif ($encoding =~ /base64/) {
605 $text=base64_decode($text);
606 } elsif ($encoding !~ /[78]bit/) { # leave 7/8 bit as is.
607 # rfc2045 also allows binary, which we ignore (for now).
608 # maybe this shouldn't go to stderr, but anyway...
609 print STDERR "EMAILPlug: unknown encoding: $encoding\n";
610 return "";
611 }
612 }
613 if ($type eq "text/html") {
614 # only get stuff between <body> tags, or <html> tags.
615 $text =~ s@^.*<html[^>]*>@@is;
616 $text =~ s@</html>.*$@@is;
617 $text =~ s/^.*?<body[^>]*>//si;
618 $text =~ s/<\/body>.*$//si;
619 }
620 elsif ($type eq "text/xml") {
621 $text=~s/</&lt;/g;$text=~s/>/&gt;/g;
622 $text="<pre>\n$text\n</pre>\n";
623 }
624 # convert to unicode
625 $self->convert2unicode($charset, \$text);
626 return $text;
627}
628
629
630# decode quoted-printable text
631sub qp_decode {
632 my $text=shift;
633
634 my @lines=split('\n', $text);
635
636 # if a line ends with "=\s*", it is a soft line break, otherwise
637 # keep in any newline characters.
638 foreach my $line (@lines) {
639 if ($line =~ s/=\s*$//) {}
640 else {$line.="\n";}
641
642 if ($line =~ /=[0-9A-Fa-f]{2}/) { # it contains an escaped char
643 my @hexcode_segments=split('=',$line);
644 shift @hexcode_segments;
645 my @hexcodes;
646 foreach my $hexcode (@hexcode_segments) {
647 $hexcode =~ s/^(..).*$/$1/; # only need first 2 chars
648 chomp($hexcode); # just in case...
649 my $char=chr (hex "0x$hexcode");
650 $line =~ s/=$hexcode/$char/g;
651 }
652 }
653 }
654 $text= join('', @lines);
655 return $text;
656}
657
658# decode base64 text. This is fairly slow (since it's interpreted perl rather
659# than compiled XS stuff like in the ::MIME modules, but this is more portable
660# for us at least).
661# see rfc2045 for description, but basically, bits 7 and 8 are set to zero;
662# 4 bytes of encoded text become 3 bytes of binary - remove 2 highest bits
663# from each encoded byte.
664
665
666sub base64_decode {
667 my $enc_text = shift;
668# A=>0, B=>1, ..., '+'=>62, '/'=>63
669# also '=' is used for padding at the end, but we remove it anyway.
670 my $mimechars="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
671# map each MIME char into it's value, for more efficient lookup.
672 my %index;
673 map { $index{$_} = index ($mimechars, $_) } (split ('', $mimechars));
674# remove all non-base64 chars. eval to get variable in transliteration...
675# also remove '=' - we'll assume (!!) that there are no errors in the encoding
676 eval "\$enc_text =~ tr|$mimechars||cd";
677 my $decoded="";
678 while (length ($enc_text)>3)
679 {
680 my $fourchars=substr($enc_text,0,4,"");
681 my @chars=(split '',$fourchars);
682 $decoded.=chr( $index{$chars[0]} << 2 | $index{$chars[1]} >> 4);
683 $decoded.=chr( ($index{$chars[1]} & 15) << 4 | $index{$chars[2]} >> 2);
684 $decoded.=chr( ($index{$chars[2]} & 3 ) << 6 | $index{$chars[3]});
685 }
686# if there are any input chars left, there are either
687# 2 encoded bytes (-> 1 raw byte) left or 3 encoded (-> 2 raw) bytes left.
688 my @chars=(split '',$enc_text);
689 if (length($enc_text)) {
690 $decoded.=chr($index{$chars[0]} << 2 | (int $index{$chars[1]} >> 4));
691 }
692 if (length($enc_text)==3) {
693 $decoded.=chr( ($index{$chars[1]} & 15) << 4 | $index{$chars[2]} >> 2);
694 }
695 return $decoded;
696}
697
698sub convert2unicode {
699 my $self = shift(@_);
700 my ($charset, $textref) = @_;
701
702 # first get our character encoding name in the right form.
703 $charset = "iso_8859_1" unless defined $charset;
704 $charset=~tr/A-Z/a-z/;
705 $charset=~s/\-/_/g;
706 $charset=~s/gb2312/gb/;
707 # assumes EUC-KR, not ISO-2022 !?
708 $charset=~s/ks_c_5601_1987/korean/;
709
710 # It appears that we can't always trust ascii text so we'll treat it
711 # as iso-8859-1 (letting characters above 0x80 through without
712 # converting them to utf-8 will result in invalid XML documents
713 # which can't be parsed at build time).
714 $charset = "iso_8859_1" if ($charset eq "us_ascii" || $charset eq "ascii");
715
716 $$textref=&unicode::unicode2utf8(&unicode::convert2unicode($charset,$textref));
717}
718
719
720# Perl packages have to return true if they are run.
7211;
Note: See TracBrowser for help on using the repository browser.