Commit | Line | Data |
---|---|---|
86530b38 AT |
1 | ##---------------------------------------------------------------------------## |
2 | ## File: | |
3 | ## $Id: mhutil.pl,v 2.20 2002/10/20 02:53:00 ehood Exp $ | |
4 | ## Author: | |
5 | ## Earl Hood mhonarc@mhonarc.org | |
6 | ## Description: | |
7 | ## Utility routines for MHonArc | |
8 | ##---------------------------------------------------------------------------## | |
9 | ## MHonArc -- Internet mail-to-HTML converter | |
10 | ## Copyright (C) 1995-1999 Earl Hood, mhonarc@mhonarc.org | |
11 | ## | |
12 | ## This program is free software; you can redistribute it and/or modify | |
13 | ## it under the terms of the GNU General Public License as published by | |
14 | ## the Free Software Foundation; either version 2 of the License, or | |
15 | ## (at your option) any later version. | |
16 | ## | |
17 | ## This program is distributed in the hope that it will be useful, | |
18 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of | |
19 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
20 | ## GNU General Public License for more details. | |
21 | ## | |
22 | ## You should have received a copy of the GNU General Public License | |
23 | ## along with this program; if not, write to the Free Software | |
24 | ## Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA | |
25 | ## 02111-1307, USA | |
26 | ##---------------------------------------------------------------------------## | |
27 | ||
28 | package mhonarc; | |
29 | ||
30 | ## RFC 2369 header fields to check for URLs | |
31 | my %HFieldsList = ( | |
32 | 'list-archive' => 1, | |
33 | 'list-help' => 1, | |
34 | 'list-owner' => 1, | |
35 | 'list-post' => 1, | |
36 | 'list-subscribe' => 1, | |
37 | 'list-unsubscribe' => 1, | |
38 | ); | |
39 | ||
40 | ## Header fields that contain addresses | |
41 | my %HFieldsAddr = ( | |
42 | 'apparently-from' => 1, | |
43 | 'apparently-to' => 1, | |
44 | 'bcc' => 1, | |
45 | 'cc' => 1, | |
46 | 'dcc' => 1, | |
47 | 'from' => 1, | |
48 | 'mail-reply-to' => 1, | |
49 | 'original-bcc' => 1, | |
50 | 'original-cc' => 1, | |
51 | 'original-from' => 1, | |
52 | 'original-sender' => 1, | |
53 | 'original-to' => 1, | |
54 | 'reply-to' => 1, | |
55 | 'resent-bcc' => 1, | |
56 | 'resent-cc' => 1, | |
57 | 'resent-from' => 1, | |
58 | 'resent-sender' => 1, | |
59 | 'resent-to' => 1, | |
60 | 'return-path' => 1, | |
61 | 'sender' => 1, | |
62 | 'to' => 1, | |
63 | 'x-envelope' => 1, | |
64 | ); | |
65 | ||
66 | ||
67 | ##--------------------------------------------------------------------------- | |
68 | ## Clip text to specified length. | |
69 | ## | |
70 | sub clip_text { | |
71 | my $str = \shift; # Prevent unnecessary copy. | |
72 | my $len = shift; # Clip length | |
73 | my $is_html = shift; # If entity references should be considered | |
74 | my $has_tags = shift; # If html tags should be stripped | |
75 | ||
76 | if (!$is_html) { | |
77 | return substr($$str, 0, $len); | |
78 | } | |
79 | ||
80 | my $text = ""; | |
81 | my $subtext = ""; | |
82 | my $html_len = length($$str); | |
83 | my($pos, $sublen, $erlen, $real_len); | |
84 | my $er_len = 0; | |
85 | ||
86 | for ( $pos=0, $sublen=$len; $pos < $html_len; ) { | |
87 | $subtext = substr($$str, $pos, $sublen); | |
88 | $pos += $sublen; | |
89 | ||
90 | # strip tags | |
91 | if ($has_tags) { | |
92 | # Strip full tags | |
93 | $subtext =~ s/<[^>]*>//g; | |
94 | # Check if clipped part of a tag | |
95 | if ($subtext =~ s/<[^>]*\Z//) { | |
96 | my $gt = index($$str, '>', $pos); | |
97 | $pos = ($gt < 0) ? $html_len : ($gt+1); | |
98 | } | |
99 | } | |
100 | ||
101 | # check for clipped entity reference | |
102 | if (($pos < $html_len) && ($subtext =~ /\&[^;]*\Z/)) { | |
103 | my $semi = index($$str, ';', $pos); | |
104 | if ($semi < 0) { | |
105 | # malformed entity reference | |
106 | $subtext .= substr($$str, $pos); | |
107 | $pos = $html_len; | |
108 | } else { | |
109 | $subtext .= substr($$str, $pos, $semi-$pos+1); | |
110 | $pos = $semi+1; | |
111 | } | |
112 | } | |
113 | ||
114 | # compute entity reference lengths to determine "real" character | |
115 | # count and not raw character count. | |
116 | while ($subtext =~ /(\&[^;]+);/g) { | |
117 | $er_len += length($1); | |
118 | } | |
119 | ||
120 | $text .= $subtext; | |
121 | ||
122 | # done if we have enough | |
123 | $real_len = length($text)-$er_len; | |
124 | if ($real_len >= $len) { | |
125 | last; | |
126 | } | |
127 | $sublen = $len - (length($text)-$er_len); | |
128 | } | |
129 | $text; | |
130 | } | |
131 | ||
132 | ##--------------------------------------------------------------------------- | |
133 | ## Get an e-mail address from (HTML) $str. | |
134 | ## | |
135 | sub extract_email_address { | |
136 | return '' unless defined $_[0]; | |
137 | my $str = shift; | |
138 | ||
139 | if ($str =~ /($AddrExp)/o) { | |
140 | return $1; | |
141 | } | |
142 | if ($str =~ /<(\S+)>/) { | |
143 | return $1; | |
144 | } | |
145 | if ($str =~ s/\([^\)]+\)//) { | |
146 | $str =~ /\s*(\S+)\s*/; | |
147 | return $1; | |
148 | } | |
149 | $str =~ /\s*(\S+)\s*/; | |
150 | return $1; | |
151 | } | |
152 | ||
153 | ##--------------------------------------------------------------------------- | |
154 | ## Get an e-mail name from $str. | |
155 | ## | |
156 | sub extract_email_name { | |
157 | my($str) = shift; | |
158 | my($ret); | |
159 | ||
160 | if ($str =~ s/<(\S+)>//) { # Check for: name <addr> | |
161 | $ret = $1; | |
162 | if ($str =~ /\S/) { | |
163 | $ret = $str; | |
164 | } else { # no name | |
165 | $ret =~ s/@.*//; | |
166 | } | |
167 | } elsif ($str =~ /"([^"]+)"/) { # Name in ""'s | |
168 | $ret = $1; | |
169 | } elsif ($str =~ /\(([^\)]+)\)/) { # Name in ()'s | |
170 | $ret = $1; | |
171 | } else { # Just address | |
172 | ($ret = $str) =~ s/@.*//; | |
173 | } | |
174 | $ret =~ s/^["\s]+//g; $ret =~ s/["\s]+$//g; | |
175 | $ret; | |
176 | } | |
177 | ||
178 | ##--------------------------------------------------------------------------- | |
179 | ## Routine to sort messages | |
180 | ## | |
181 | sub sort_messages { | |
182 | my($nosort, $subsort, $authsort, $revsort) = @_; | |
183 | $nosort = $NOSORT if !defined($nosort); | |
184 | $subsort = $SUBSORT if !defined($subsort); | |
185 | $authsort = $AUTHSORT if !defined($authsort); | |
186 | $revsort = $REVSORT if !defined($revsort); | |
187 | ||
188 | if ($nosort) { | |
189 | ## Process order | |
190 | if ($revsort) { | |
191 | return sort { $IndexNum{$b} <=> $IndexNum{$a} } keys %Subject; | |
192 | } else { | |
193 | return sort { $IndexNum{$a} <=> $IndexNum{$b} } keys %Subject; | |
194 | } | |
195 | ||
196 | } elsif ($subsort) { | |
197 | ## Subject order | |
198 | my(%sub, $idx, $sub); | |
199 | use locale; | |
200 | eval { | |
201 | my $hs = scalar(%Subject); $hs =~ s|^[^/]+/||; | |
202 | keys(%sub) = $hs; | |
203 | }; | |
204 | while (($idx, $sub) = each(%Subject)) { | |
205 | $sub = lc $sub; | |
206 | 1 while $sub =~ s/$SubReplyRxp//io; | |
207 | $sub =~ s/$SubArtRxp//io; | |
208 | $sub{$idx} = $sub; | |
209 | } | |
210 | if ($revsort) { | |
211 | return sort { ($sub{$a} cmp $sub{$b}) || | |
212 | (get_time_from_index($b) <=> get_time_from_index($a)) | |
213 | } keys %Subject; | |
214 | } else { | |
215 | return sort { ($sub{$a} cmp $sub{$b}) || | |
216 | (get_time_from_index($a) <=> get_time_from_index($b)) | |
217 | } keys %Subject; | |
218 | } | |
219 | ||
220 | } elsif ($authsort) { | |
221 | ## Author order | |
222 | my(%from, $idx, $from); | |
223 | use locale; | |
224 | eval { | |
225 | my $hs = scalar(%From); $hs =~ s|^[^/]+/||; | |
226 | keys(%from) = $hs; | |
227 | }; | |
228 | while (($idx, $from) = each(%From)) { | |
229 | $from = lc extract_email_name($from); | |
230 | $from{$idx} = $from; | |
231 | } | |
232 | if ($revsort) { | |
233 | return sort { ($from{$a} cmp $from{$b}) || | |
234 | (get_time_from_index($b) <=> get_time_from_index($a)) | |
235 | } keys %Subject; | |
236 | } else { | |
237 | return sort { ($from{$a} cmp $from{$b}) || | |
238 | (get_time_from_index($a) <=> get_time_from_index($b)) | |
239 | } keys %Subject; | |
240 | } | |
241 | ||
242 | } else { | |
243 | ## Date order | |
244 | if ($revsort) { | |
245 | return sort { (get_time_from_index($b) <=> get_time_from_index($a)) | |
246 | || ($IndexNum{$b} <=> $IndexNum{$a}) | |
247 | } keys %Subject; | |
248 | } else { | |
249 | return sort { (get_time_from_index($a) <=> get_time_from_index($b)) | |
250 | || ($IndexNum{$a} <=> $IndexNum{$b}) | |
251 | } keys %Subject; | |
252 | } | |
253 | ||
254 | } | |
255 | } | |
256 | ||
257 | ##--------------------------------------------------------------------------- | |
258 | ## Message-sort routines for sort(). | |
259 | ## | |
260 | sub increase_index { | |
261 | (&get_time_from_index($a) <=> &get_time_from_index($b)) || | |
262 | ($IndexNum{$a} <=> $IndexNum{$b}); | |
263 | } | |
264 | ||
265 | ##--------------------------------------------------------------------------- | |
266 | ## Routine for formating a message number for use in filenames or links. | |
267 | ## | |
268 | sub fmt_msgnum { | |
269 | sprintf("%05d", $_[0]); | |
270 | } | |
271 | ||
272 | ##--------------------------------------------------------------------------- | |
273 | ## Routine to get filename of a message number. | |
274 | ## | |
275 | sub msgnum_filename { | |
276 | my($fmtstr) = "$MsgPrefix%05d.$HtmlExt"; | |
277 | $fmtstr .= ".gz" if $GzipLinks; | |
278 | sprintf($fmtstr, $_[0]); | |
279 | } | |
280 | ||
281 | ##--------------------------------------------------------------------------- | |
282 | ## Routine to get filename of an index | |
283 | ## | |
284 | sub get_filename_from_index { | |
285 | &msgnum_filename($IndexNum{$_[0]}); | |
286 | } | |
287 | ||
288 | ##--------------------------------------------------------------------------- | |
289 | ## Routine to get time component from index | |
290 | ## | |
291 | sub get_time_from_index { | |
292 | (split(/$X/o, $_[0], 2))[0]; | |
293 | } | |
294 | ||
295 | ##--------------------------------------------------------------------------- | |
296 | ## Routine to get annotation of a message | |
297 | ## | |
298 | sub get_note { | |
299 | my $index = shift; | |
300 | my $file = join($DIRSEP, get_note_dir(), | |
301 | msgid_to_filename($Index2MsgId{$index})); | |
302 | if (!open(NOTEFILE, $file)) { return ""; } | |
303 | my $ret = join("", <NOTEFILE>); | |
304 | close NOTEFILE; | |
305 | $ret; | |
306 | } | |
307 | ||
308 | ##--------------------------------------------------------------------------- | |
309 | ## Routine to determine if a message has an annotation | |
310 | ## | |
311 | sub note_exists { | |
312 | my $index = shift; | |
313 | -e join($DIRSEP, get_note_dir(), | |
314 | msgid_to_filename($Index2MsgId{$index})); | |
315 | } | |
316 | ||
317 | ##--------------------------------------------------------------------------- | |
318 | ## Routine to get full pathname to annotation directory | |
319 | ## | |
320 | sub get_note_dir { | |
321 | if (!OSis_absolute_path($NoteDir)) { | |
322 | return join($DIRSEP, $OUTDIR, $NoteDir); | |
323 | } | |
324 | $NoteDir; | |
325 | } | |
326 | ||
327 | ##--------------------------------------------------------------------------- | |
328 | ## Routine to get lc author name from index | |
329 | ## | |
330 | sub get_base_author { | |
331 | lc extract_email_name($From{$_[0]}); | |
332 | } | |
333 | ||
334 | ##--------------------------------------------------------------------------- | |
335 | ## Determine time from date. Use %Zone for timezone offsets | |
336 | ## | |
337 | sub get_time_from_date { | |
338 | my($mday, $mon, $yr, $hr, $min, $sec, $zone) = @_; | |
339 | my($time) = 0; | |
340 | ||
341 | $yr -= 1900 if $yr >= 1900; # if given full 4 digit year | |
342 | $yr += 100 if $yr <= 37; # in case of 2 digit years | |
343 | if (($yr < 70) || ($yr > 137)) { | |
344 | warn "Warning: Bad year (", $yr+1900, ") using current\n"; | |
345 | $yr = (localtime(time))[5]; | |
346 | } | |
347 | ||
348 | ## If $zone, grab gmt time, else grab local | |
349 | if ($zone) { | |
350 | $zone =~ tr/a-z/A-Z/; | |
351 | $time = &timegm($sec,$min,$hr,$mday,$mon,$yr); | |
352 | ||
353 | # try to modify time/date based on timezone | |
354 | OFFSET: { | |
355 | # numeric timezone | |
356 | if ($zone =~ /^[\+-]\d+$/) { | |
357 | $time -= &zone_offset_to_secs($zone); | |
358 | last OFFSET; | |
359 | } | |
360 | # Zone | |
361 | if (defined($Zone{$zone})) { | |
362 | # timezone abbrev | |
363 | $time += &zone_offset_to_secs($Zone{$zone}); | |
364 | last OFFSET; | |
365 | ||
366 | } | |
367 | # Zone[+-]DDDD | |
368 | if ($zone =~ /^([A-Z]\w+)([\+-]\d+)$/) { | |
369 | $time -= &zone_offset_to_secs($2); | |
370 | if (defined($Zone{$1})) { | |
371 | $time += &zone_offset_to_secs($Zone{$1}); | |
372 | last OFFSET; | |
373 | } | |
374 | } | |
375 | # undefined timezone | |
376 | warn qq|Warning: Unrecognized time zone, "$zone"\n|; | |
377 | } | |
378 | ||
379 | } else { | |
380 | $time = &timelocal($sec,$min,$hr,$mday,$mon,$yr); | |
381 | } | |
382 | $time; | |
383 | } | |
384 | ||
385 | ##--------------------------------------------------------------------------- | |
386 | ## Routine to check if time has expired. | |
387 | ## | |
388 | sub expired_time { | |
389 | ($ExpireTime && (time - $_[0] > $ExpireTime)) || | |
390 | ($_[0] < $ExpireDateTime); | |
391 | } | |
392 | ||
393 | ##--------------------------------------------------------------------------- | |
394 | ## Get HTML tags for formatting message headers | |
395 | ## | |
396 | sub get_header_tags { | |
397 | my($f) = shift; | |
398 | my($ftago, $ftagc, $tago, $tagc); | |
399 | ||
400 | ## Get user specified tags (this is one funcky looking code) | |
401 | $tag = (defined($HeadHeads{$f}) ? | |
402 | $HeadHeads{$f} : $HeadHeads{"-default-"}); | |
403 | $ftag = (defined($HeadFields{$f}) ? | |
404 | $HeadFields{$f} : $HeadFields{"-default-"}); | |
405 | if ($tag) { $tago = "<$tag>"; $tagc = "</$tag>"; } | |
406 | else { $tago = $tagc = ''; } | |
407 | if ($ftag) { $ftago = "<$ftag>"; $ftagc = "</$ftag>"; } | |
408 | else { $ftago = $ftagc = ''; } | |
409 | ||
410 | ($tago, $tagc, $ftago, $ftagc); | |
411 | } | |
412 | ||
413 | ##--------------------------------------------------------------------------- | |
414 | ## Format message headers in HTML. | |
415 | ## $html = htmlize_header($fields_hash_ref); | |
416 | ## | |
417 | sub htmlize_header { | |
418 | my $fields = shift; | |
419 | my($key, | |
420 | $tago, $tagc, | |
421 | $ftago, $ftagc, | |
422 | $item, | |
423 | @array); | |
424 | my($tmp); | |
425 | ||
426 | my $mesg = ""; | |
427 | my %hf = %$fields; | |
428 | foreach $item (@FieldOrder) { | |
429 | if ($item eq '-extra-') { | |
430 | foreach $key (sort keys %hf) { | |
431 | next if $FieldODefs{$key}; | |
432 | next if $key =~ /^x-mha-/; | |
433 | delete $hf{$key}, next if &exclude_field($key); | |
434 | ||
435 | @array = @{$hf{$key}}; | |
436 | foreach $tmp (@array) { | |
437 | $tmp = $HFieldsList{$key} ? mlist_field_add_links($tmp) : | |
438 | &$MHeadCnvFunc($tmp); | |
439 | $tmp = field_add_links($key, $tmp, $fields); | |
440 | ($tago, $tagc, $ftago, $ftagc) = get_header_tags($key); | |
441 | $mesg .= join('', $LABELBEG, | |
442 | $tago, htmlize(ucfirst($key)), $tagc, | |
443 | $LABELEND, | |
444 | $FLDBEG, $ftago, $tmp, $ftagc, $FLDEND, | |
445 | "\n"); | |
446 | } | |
447 | delete $hf{$key}; | |
448 | } | |
449 | } else { | |
450 | if (!&exclude_field($item) && $hf{$item}) { | |
451 | @array = @{$hf{$item}}; | |
452 | foreach $tmp (@array) { | |
453 | $tmp = $HFieldsList{$item} ? mlist_field_add_links($tmp) : | |
454 | &$MHeadCnvFunc($tmp); | |
455 | $tmp = field_add_links($item, $tmp, $fields); | |
456 | ($tago, $tagc, $ftago, $ftagc) = &get_header_tags($item); | |
457 | $mesg .= join('', $LABELBEG, | |
458 | $tago, htmlize(ucfirst($item)), $tagc, | |
459 | $LABELEND, | |
460 | $FLDBEG, $ftago, $tmp, $ftagc, $FLDEND, | |
461 | "\n"); | |
462 | } | |
463 | } | |
464 | delete $hf{$item}; | |
465 | } | |
466 | } | |
467 | if ($mesg) { $mesg = $FIELDSBEG . $mesg . $FIELDSEND; } | |
468 | $mesg; | |
469 | } | |
470 | ||
471 | ##--------------------------------------------------------------------------- | |
472 | ||
473 | sub mlist_field_add_links { | |
474 | my $txt = shift; | |
475 | my $ret = ""; | |
476 | local($_); | |
477 | foreach (split(/(<[^>]+>)/, $txt)) { | |
478 | if (/^<\w+:/) { | |
479 | chop; substr($_, 0, 1) = ""; | |
480 | $ret .= qq|<<a href="$_">$_</a>>|; | |
481 | } else { | |
482 | $ret .= &$MHeadCnvFunc($_); | |
483 | } | |
484 | } | |
485 | $ret; | |
486 | } | |
487 | ||
488 | ##--------------------------------------------------------------------------- | |
489 | ## Routine to add mailto/news links to a message header string. | |
490 | ## | |
491 | sub field_add_links { | |
492 | my $label = lc shift; | |
493 | my $fld_text = shift; | |
494 | my $fields = shift; | |
495 | ||
496 | LBLSW: { | |
497 | if ($HFieldsAddr{$label}) { | |
498 | if (!$NOMAILTO) { | |
499 | $fld_text =~ s|([\!\%\w\.\-+=/]+@[\w\.\-]+) | |
500 | |&mailUrl($1, $fields->{'x-mha-message-id'}, | |
501 | $fields->{'x-mha-subject'}, | |
502 | $fields->{'x-mha-from'}); | |
503 | |gex; | |
504 | } else { | |
505 | $fld_text =~ s|([\!\%\w\.\-+=/]+@[\w\.\-]+) | |
506 | |&htmlize(&rewrite_address($1)) | |
507 | |gex; | |
508 | } | |
509 | last LBLSW; | |
510 | } | |
511 | if ($label eq 'newsgroup') { | |
512 | $fld_text = newsurl($fld_text) unless $NONEWS; | |
513 | last LBLSW; | |
514 | } | |
515 | last LBLSW; | |
516 | } | |
517 | $fld_text; | |
518 | } | |
519 | ||
520 | ||
521 | ##--------------------------------------------------------------------------- | |
522 | ## Routine to add news links of newsgroups names | |
523 | ## | |
524 | sub newsurl { | |
525 | my $str = shift; | |
526 | my $h = ""; | |
527 | ||
528 | if ($str =~ s/^([^:]*:\s*)//) { | |
529 | $h = $1; | |
530 | } | |
531 | $str =~ s/\s//g; # Strip whitespace | |
532 | my @groups = split(/,/, $str); # Split groups | |
533 | foreach (@groups) { # Make hyperlinks | |
534 | s|(.*)|<a href="news:$1">$1</a>|; | |
535 | } | |
536 | $h . join(', ', @groups); # Rejoin string | |
537 | } | |
538 | ||
539 | ##--------------------------------------------------------------------------- | |
540 | ## $html = mailUrl($email_addr, $msgid, $subject, $from); | |
541 | ## | |
542 | sub mailUrl { | |
543 | my $eaddr = shift || ''; | |
544 | my $msgid = shift || ''; | |
545 | my $sub = shift || ''; | |
546 | my $from = shift || ''; | |
547 | ||
548 | local $_; | |
549 | my($url) = ($MAILTOURL); | |
550 | my($to) = (&urlize($eaddr)); | |
551 | my($toname, $todomain) = map { urlize($_) } split(/@/,$eaddr,2); | |
552 | my($froml, $msgidl) = (&urlize($from), &urlize($msgid)); | |
553 | my($fromaddrl) = (&extract_email_address($from)); | |
554 | my($faddrnamel, $faddrdomainl) = map { urlize($_) } split(/@/,$fromaddrl,2); | |
555 | $fromaddrl = &urlize($fromaddrl); | |
556 | my($subjectl); | |
557 | ||
558 | # Add "Re:" to subject if not present | |
559 | if ($sub !~ /^$SubReplyRxp/io) { | |
560 | $subjectl = 'Re:%20' . &urlize($sub); | |
561 | } else { | |
562 | $subjectl = &urlize($sub); | |
563 | } | |
564 | $url =~ s/\$FROM\$/$froml/g; | |
565 | $url =~ s/\$FROMADDR\$/$fromaddrl/g; | |
566 | $url =~ s/\$FROMADDRNAME\$/$faddrnamel/g; | |
567 | $url =~ s/\$FROMADDRDOMAIN\$/$faddrdomainl/g; | |
568 | $url =~ s/\$MSGID\$/$msgidl/g; | |
569 | $url =~ s/\$SUBJECT\$/$subjectl/g; | |
570 | $url =~ s/\$SUBJECTNA\$/$subjectl/g; | |
571 | $url =~ s/\$TO\$/$to/g; | |
572 | $url =~ s/\$TOADDRNAME\$/$toname/g; | |
573 | $url =~ s/\$TOADDRDOMAIN\$/$todomain/g; | |
574 | $url =~ s/\$ADDR\$/$to/g; | |
575 | qq|<a href="$url">| . &htmlize(&rewrite_address($eaddr)) . q|</a>|; | |
576 | } | |
577 | ||
578 | ##---------------------------------------------------------------------------## | |
579 | ## Routine to parse variable definitions in a string. The | |
580 | ## function returns a list of variable/value pairs. The format of | |
581 | ## the string is similiar to attribute specification lists in | |
582 | ## SGML, but NAMEs are any non-whitespace character. | |
583 | ## | |
584 | sub parse_vardef_str { | |
585 | my($org) = shift; | |
586 | my($lower) = shift; | |
587 | my(%hash) = (); | |
588 | my($str, $q, $var, $value); | |
589 | ||
590 | ($str = $org) =~ s/^\s+//; | |
591 | while ($str =~ s/^([^=\s]+)\s*=\s*//) { | |
592 | $var = $1; | |
593 | if ($str =~ s/^(['"])//) { | |
594 | $q = $1; | |
595 | if (!($q eq "'" ? $str =~ s/^([^']*)'// : | |
596 | $str =~ s/^([^"]*)"//)) { | |
597 | warn "Warning: Unclosed quote in: $org\n"; | |
598 | return (); | |
599 | } | |
600 | $value = $1; | |
601 | ||
602 | } else { | |
603 | if ($str =~ s/^(\S+)//) { | |
604 | $value = $1; | |
605 | } else { | |
606 | warn "Warning: No value after $var in: $org\n"; | |
607 | return (); | |
608 | } | |
609 | } | |
610 | $str =~ s/^\s+//; | |
611 | $hash{$lower? lc($var): $var} = $value; | |
612 | } | |
613 | if ($str =~ /\S/) { | |
614 | warn "Warning: Trailing characters in: $org\n"; | |
615 | } | |
616 | %hash; | |
617 | } | |
618 | ||
619 | ##---------------------------------------------------------------------------## | |
620 | ||
621 | sub msgid_to_filename { | |
622 | my $msgid = shift; | |
623 | if ($VMS) { | |
624 | $msgid =~ s/([^\w\-])/sprintf("=%02X",unpack("C",$1))/geo; | |
625 | } else { | |
626 | $msgid =~ s/([^\w.\-\@])/sprintf("=%02X",unpack("C",$1))/geo; | |
627 | } | |
628 | $msgid; | |
629 | } | |
630 | ||
631 | ##---------------------------------------------------------------------------## | |
632 | ## Check if new follow up list for a message is different from | |
633 | ## old follow up list. | |
634 | ## | |
635 | sub is_follow_ups_diff { | |
636 | my $f = $Follow{$_[0]}; | |
637 | my $o = $FollowOld{$_[0]}; | |
638 | if (defined($f) && defined($o)) { | |
639 | return 1 unless @$f == @$o; | |
640 | local $^W = 0; | |
641 | my $i; | |
642 | for ($i=0; $i < @$f; ++$i) { | |
643 | return 1 if $f->[$i] ne $o->[$i]; | |
644 | } | |
645 | return 0; | |
646 | } | |
647 | return (defined($f) || defined($o)); | |
648 | } | |
649 | ||
650 | ##---------------------------------------------------------------------------## | |
651 | ## Retrieve icon URL for specified content-type. | |
652 | ## | |
653 | sub get_icon_url { | |
654 | my $ctype = shift; | |
655 | my $icon = $Icons{$ctype}; | |
656 | ICON: { | |
657 | last ICON if defined $icon; | |
658 | if ($ctype =~ s|/.*||) { | |
659 | $ctype .= '/*'; | |
660 | $icon = $Icons{$ctype}; | |
661 | last ICON if defined $icon; | |
662 | } | |
663 | $icon = $Icons{'*/*'} || $Icons{'unknown'}; | |
664 | } | |
665 | if (!defined($icon)) { | |
666 | return (undef, undef, undef); | |
667 | } | |
668 | if ($icon =~ s/\[(\d+)x(\d+)\]//) { | |
669 | return ($icon, $1, $2); | |
670 | } | |
671 | ($icon, undef, undef); | |
672 | } | |
673 | ||
674 | ##---------------------------------------------------------------------------## | |
675 | ||
676 | sub log_mesg { | |
677 | my $fh = shift; | |
678 | my $doDate = shift; | |
679 | ||
680 | if ($doDate) { | |
681 | my($sec,$min,$hour,$mday,$mon,$year) = localtime(time); | |
682 | print $fh sprintf("[%4d-%02d-%02d %02d:%02d:%02d] ", | |
683 | $year+1900, $mon+1, $mday, $hour, $min, $sec); | |
684 | } | |
685 | print $fh @_; | |
686 | } | |
687 | ||
688 | ##---------------------------------------------------------------------------## | |
689 | ||
690 | sub dump_hash { | |
691 | my $fh = shift; | |
692 | my $h = shift; | |
693 | local $_; | |
694 | foreach (sort keys %$h) { | |
695 | print $fh "$_ => ", $h->{$_}, "\n"; | |
696 | } | |
697 | } | |
698 | ||
699 | ##---------------------------------------------------------------------------## | |
700 | 1; |