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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.23 - (hide annotations) (download)
Mon Aug 5 09:40:54 2002 UTC (24 years ago) by wakaba
Branch: MAIN
Changes since 1.22: +23 -6 lines
2002-08-05  Wakaba <w@suika.fam.cx>

	* Util.pm:
	- (sprintxf): Use Message::Util::Wide::unquote_if_quoted_string
	instead of Message::Util::unquote_if_quoted_string.
	- (Message::Util::Wide): New package.
	- (%Message::Util::Wide::REG): New hash.
	- (Message::Util::unquote_if_quoted_string): New function.
	- NOTE: "Wide" package is created to support utf8 string
	of perl 5.7.3 or later.  Utf8 string does not work
	only for [\x00-\xFF] regex of current functions,
	and this regex is used as (?:.|\x0D|\x0A).  (Without
	's' option, "." does not match with newline character.)
	When we can do away problematic code from all
	Message::* modules, we can also do away "Wide" package.

1 wakaba 1.1
2     =head1 NAME
3    
4 wakaba 1.4 Message::Util -- Utilities for Message::* Perl modules.
5 wakaba 1.1
6     =head1 DESCRIPTION
7    
8 wakaba 1.4 Useful functions for Message::* Perl modules.
9     This module is only intended for internal use.
10 wakaba 1.20 Note that there is another module, Message::Tool.
11 wakaba 1.1
12     =cut
13    
14     package Message::Util;
15 wakaba 1.4 require 5.6.0;
16 wakaba 1.1 use strict;
17 wakaba 1.4 use re 'eval';
18 wakaba 1.17 use vars qw(%FMT2STR %OPTION %REG $VERSION);
19 wakaba 1.23 $VERSION=do{my @r=(q$Revision: 1.22 $=~/\d+/g);sprintf "%d."."%02d" x $#r,@r};
20 wakaba 1.1
21 wakaba 1.20 require Carp;
22 wakaba 1.12 require Message::MIME::EncodedWord;
23     require Message::MIME::Charset;
24 wakaba 1.1
25 wakaba 1.5 =head1 REGEXPS (%Message::Util::REG)
26 wakaba 1.4
27 wakaba 1.5 =head2 Naming Rules
28 wakaba 1.4
29 wakaba 1.5 key = *(prefix) [format] token-name
30    
31     prefix = 'M_' ;; With matching "(" ")"s
32     / 'S_' ;; Simple (not strict) expression
33     / 'NON_' ;; Negative character class
34     format = E<lt>specification id, such as C<http>E<gt> ;; if necessary
35     token-name = E<lt>BNF name =~ tr/-/_/E<gt>
36    
37     =cut
38    
39 wakaba 1.6 $REG{MATCH_NONE} = qr/(?!)/;
40     $REG{MATCH_ALL} = qr/[\x00-\xFF]/;
41 wakaba 1.5 ## Whitespace
42     $REG{WSP} = qr/[\x09\x20]/;
43 wakaba 1.7 $REG{FWS} = qr/[\x09\x20]*/; ## not same as 2822's
44 wakaba 1.5 ## Basic structure
45     $REG{comment} = qr/\x28(?:\x5C[\x00-\xFF]|[\x00-\x0C\x0E-\x27\x2A-\x5B\x5D-\xFF]|(??{$REG{comment}}))*\x29/;
46 wakaba 1.13 $REG{M_comment} = qr/\x28((?:\x5C[\x00-\xFF]|[\x00-\x0C\x0E-\x27\x2A-\x5B\x5D-\xFF]|(??{$REG{comment}}))*)\x29/;
47    
48 wakaba 1.5 $REG{quoted_string} = qr/\x22(?:\x5C[\x00-\xFF]|[\x00-\x0C\x0E-\x21\x23-\x5B\x5D-\xFF])*\x22/;
49 wakaba 1.23 $REG{M_quoted_string} = qr/\x22((?:\x5C[\x00-\xFF]|[^\x0D\x22\x5C])*)\x22/;
50 wakaba 1.13
51 wakaba 1.5 $REG{domain_literal} = qr/\x5B(?:\x5C[\x00-\xFF]|[\x00-\x0C\x0E-\x5A\x5E-\xFF])*\x5D/;
52 wakaba 1.13 $REG{M_domain_literal} = qr/\x5B((?:\x5C[\x00-\xFF]|[\x00-\x0C\x0E-\x5A\x5E-\xFF])*)\x5D/;
53    
54     #$REG{angle_quoted} = qr/\x3C[\x09\x20\x21\x23-\x3B\x3D\x3F-\x5B\x5D\x5F\x61-\x7A\x7E]*\x3E/;
55     $REG{angle_qcontent} = qr/(?:$REG{quoted_string}|$REG{domain_literal}|[^\x3C\x3E\x22\x5B])+/;
56     $REG{angle_quoted} = qr/<$REG{angle_qcontent}>|<>/;
57     $REG{M_angle_quoted} = qr/<($REG{angle_qcontent})>|<>/;
58 wakaba 1.5
59    
60     =head2 tokens
61    
62     atext NON_atext 822.atext
63     atext_dot NON_atext_dot 822.atext / "."
64     NON_atext_dot_wsp 822.atext / "." / WSP
65     http_token NON_http_token http.token
66     NON_http_token_wsp http.token / WSP
67     attribute_char rfc2231.attribute-char
68 wakaba 1.6 NON_http_attribute_char http.token AND rfc2231.attribute-char
69 wakaba 1.5
70     =cut
71    
72     $REG{atext} = qr/[\x21\x23-\x27\x2A\x2B\x2D\x2F\x30-\x39\x3D\x3F\x41-\x5A\x5E-\x7E]+/;
73     $REG{atext_dot} = qr/[\x21\x23-\x27\x2A\x2B\x2D-\x39\x3D\x3F\x41-\x5A\x5E-\x7E]+/;
74 wakaba 1.7 $REG{atext_dot_wsp} = qr/[\x09\x20\x21\x23-\x27\x2A\x2B\x2D-\x39\x3D\x3F\x41-\x5A\x5E-\x7E]+/;
75     $REG{atext_dot8} = qr/[\x21\x23-\x27\x2A\x2B\x2D-\x39\x3D\x3F\x41-\x5A\x5E-\x7E\x80-\xFF]+/;
76 wakaba 1.6 $REG{token} = qr/[\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E]+/;
77 wakaba 1.5 $REG{http_token} = qr/[\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7A\x7C\x7E]+/;
78     $REG{attribute_char} = qr/[\x21\x23-\x24\x26\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E]+/;
79    
80 wakaba 1.7 $REG{NON_atext} = qr/[^\x21\x23-\x27\x2A\x2B\x2D\x2F\x30-\x39\x3D\x3F\x41-\x5A\x5E-\x7E]/;
81     $REG{NON_atext_wsp} = qr/[^\x09\x20\x21\x23-\x27\x2A\x2B\x2D\x2F\x30-\x39\x3D\x3F\x41-\x5A\x5E-\x7E]/;
82 wakaba 1.5 $REG{NON_atext_dot} = qr/[^\x21\x23-\x27\x2A\x2B\x2D-\x39\x3D\x3F\x41-\x5A\x5E-\x7E]/;
83     $REG{NON_atext_dot_wsp} = qr/[^\x09\x20\x21\x23-\x27\x2A\x2B\x2D-\x39\x3D\x3F\x41-\x5A\x5E-\x7E]/;
84 wakaba 1.6 $REG{NON_token} = qr/[^\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E]/;
85 wakaba 1.5 $REG{NON_http_token} = qr/[^\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7A\x7C\x7E]/;
86     $REG{NON_http_token_wsp} = qr/[^\x09\x20\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7A\x7C\x7E]/;
87 wakaba 1.6 $REG{NON_attribute_char} = qr/[^\x21\x23-\x24\x26\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E]/;
88     $REG{NON_http_attribute_char} = qr/[^\x21\x23-\x24\x26\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7A\x7C\x7E]/;
89 wakaba 1.8 $REG{NON_http_attribute_char_wsp} = qr/[^\x09\x20\x21\x23-\x24\x26\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7A\x7C\x7E]/;
90 wakaba 1.6 ## Yes, C<attribute-char> does not appear in HTTP spec.
91 wakaba 1.5
92 wakaba 1.22 $REG{S_uri} = qr#[\x21\x23-\x3B\x3D\x3F-\x5B\x5D\x5F\x61-\x7A\x7E]+#;
93    
94 wakaba 1.16 $REG{NON_base64alphabet} = qr#[^A-Za-z0-9+/=]#;
95    
96 wakaba 1.5 $REG{dot_atom} = qr/$REG{atext}(?:$REG{FWS}\x2E$REG{FWS}$REG{atext})*/;
97 wakaba 1.7 $REG{dot_atom_dot} = qr/$REG{atext_dot}(?:$REG{FWS}\x2E$REG{FWS}$REG{atext_dot})*/;
98 wakaba 1.5 $REG{dot_word} = qr/(?:$REG{atext}|$REG{quoted_string})(?:$REG{FWS}\x2E$REG{FWS}(?:$REG{atext}|$REG{quoted_string}))*/;
99 wakaba 1.7 $REG{dot_word_dot} = qr/(?:$REG{atext_dot}|$REG{quoted_string})(?:$REG{FWS}\x2E$REG{FWS}(?:$REG{atext_dot}|$REG{quoted_string}))*/;
100 wakaba 1.5 $REG{phrase} = qr/(?:$REG{atext}|$REG{quoted_string})(?:$REG{atext}|$REG{quoted_string}|\.|$REG{FWS})*/;
101     ## RFC 822 phrase (not strict)
102    
103 wakaba 1.7 #$REG{domain} = qr/(?:$REG{dot_atom}|$REG{domain_literal})/;
104     $REG{domain} = qr/(?:$REG{dot_atom_dot}|$REG{domain_literal})/;
105     #$REG{addr_spec} = qr/$REG{dot_word}$REG{FWS}\x40$REG{FWS}$REG{domain}/;
106     $REG{addr_spec} = qr/$REG{dot_word_dot}$REG{FWS}\x40$REG{FWS}$REG{domain}/;
107     $REG{msg_id} = qr/<$REG{FWS}$REG{addr_spec}$REG{FWS}>/;
108    
109     $REG{M_addr_spec} = qr/($REG{dot_word_dot})$REG{FWS}\x40$REG{FWS}($REG{domain})/;
110    
111     $REG{date_time} = qr/(?:[A-Za-z]+$REG{FWS},$REG{FWS})?[0-9]+$REG{WSP}*[A-Za-z]+$REG{WSP}*[0-9]+$REG{WSP}+[0-9]+$REG{FWS}:$REG{WSP}*[0-9]+(?:$REG{FWS}:$REG{WSP}*[0-9]+)?$REG{FWS}(?:[A-Za-z]+|[+-]$REG{WSP}*[0-9]+)/;
112     $REG{asctime} = qr/[A-Za-z]+$REG{WSP}*[A-Za-z]+$REG{WSP}*[0-9]+$REG{WSP}+[0-9]+$REG{FWS}:$REG{WSP}*[0-9]+$REG{FWS}:$REG{WSP}*[0-9]+$REG{WSP}+[0-9]+/;
113    
114 wakaba 1.5 ## MIME encoded-word
115     $REG{M_encoded_word} = qr/=\x3F($REG{attribute_char})(?:\x2A($REG{attribute_char}))?\x3F($REG{attribute_char})\x3F([\x21-\x3E\x40-\x7E]+)\x3F=/;
116     $REG{S_encoded_word} = qr/=\x3F$REG{atext_dot}\x3F=/;
117     #$REG{S_encoded_word_comment} = qr/=\x3F[\x21-\x27\x2A-\x5B\x5D-\x7E]+\x3F=/;
118     ## not used anywhere
119 wakaba 1.4
120 wakaba 1.13 ## See also 'sprintxf'
121     %FMT2STR = (
122     char => sub {
123     my $p = $_[0];
124     if ($p->{ucs} =~ /^0[xob][0-9A-Fa-f]+$/) {
125     return pack 'U', oct $p->{ucs};
126     } elsif (defined $p->{ucs}) {
127     return pack 'U', $p->{ucs};
128     } else {
129     return "\x{FFFD}";
130     }
131     },
132     percent => '%',
133     );
134    
135 wakaba 1.4 =head1 STRUCTURED FIELD FUNCTIONS
136    
137     =over 4
138    
139 wakaba 1.5 =item $nocomment:-) = Message::Util::delete_comment ($string)
140 wakaba 1.4
141     Gets rid of all C<comment>s. Inserts a SP instead.
142    
143     =cut
144    
145     sub delete_comment ($) {
146     my $body = shift;
147 wakaba 1.5 $body =~ s{($REG{quoted_string}|$REG{domain_literal}|$REG{angle_quoted})|$REG{comment}}{
148 wakaba 1.4 my $o = $1; $o? $o : ' ';
149     }gex;
150     $body;
151     }
152    
153 wakaba 1.7 sub delete_wsp ($) {
154     my $body = shift;
155     $body =~ s{($REG{quoted_string}|$REG{domain_literal})|((?:$REG{token}|$REG{S_encoded_word})(?:$REG{WSP}+(?:$REG{token}|$REG{S_encoded_word}))+)|$REG{WSP}+}{
156     my ($o,$p) = ($1,$2);
157     if ($o) {$o}
158     elsif ($p) {$p=~s/$REG{WSP}+/\x20/g;$p}
159     else {''}
160     }gex;
161     $body;
162     }
163    
164     sub remove_meaningless_wsp ($) {
165     my $body = shift;
166     $body =~ s{($REG{quoted_string}|$REG{domain_literal})|$REG{WSP}+}{
167     $1 || '';
168     }gex;
169     $body;
170     }
171    
172 wakaba 1.8 sub wsps_to_sp ($) {
173     my $body = shift;
174     $body =~ s{($REG{quoted_string}|$REG{domain_literal})|$REG{WSP}+}{
175     $1 || ' ';
176     }gex;
177     $body;
178     }
179    
180 wakaba 1.21 =item Message::Util::quote_ccontent ($yourself, $ccontent)
181    
182     Quotes C<ccontent> (to be).
183    
184     =cut
185    
186     sub quote_ccontent ($;%) {
187     my $ccontent = shift;
188     my %option = @_;
189     if ($option{strict_quoted_pair}) {
190     $ccontent =~ s/([\x28\x29\x5C]|\x3D\x3F)/\x5C$1/g;
191     } else {
192     $ccontent =~ s/([\x28\x29\x5C]|\x3D\x3F)([\x21-\x7E])?/"\x5C$1".(defined $2?"\x5C$2":'')/ge;
193     }
194     $ccontent;
195     }
196    
197 wakaba 1.5 =item $unquoted = Message::Util::unquote_ccontent ($string)
198 wakaba 1.4
199     Unquotes C<quoted-pair> in C<comment>s.
200    
201     =cut
202    
203     sub unquote_ccontent ($) {
204     my $comment = shift;
205     $comment =~ s{$REG{M_comment}}{
206     my $ctext = $1;
207     $ctext =~ s/\x5C([\x00-\xFF])/$1/g;
208     '('.$ctext.')';
209     }goex;
210     $comment;
211     }
212    
213 wakaba 1.5 =item $unquoted = Message::Util::unquote_quoted_string ($string)
214 wakaba 1.4
215     Unquotes C<quoted-pair> in C<quoted-string>s and
216     unquotes C<quoted-string> (or gets rid of C<DQUOTE>s).
217    
218     =cut
219    
220     sub unquote_quoted_string ($) {
221     my $quoted_string = shift;
222     $quoted_string =~ s{$REG{M_quoted_string}}{
223     my $qtext = $1;
224     $qtext =~ s/\x5C([\x00-\xFF])/$1/g;
225     $qtext;
226     }goex;
227     $quoted_string;
228     }
229    
230 wakaba 1.5 =item Message::Util::unquote_if_quoted_string ($string)
231    
232     Unquotes if and only if given string is A C<quoted-string>.
233     This function returns two value, (C<$unquoted-string>,
234     C<$was-quoted-string?>).
235    
236     =cut
237    
238     sub unquote_if_quoted_string ($) {
239     my $quoted_string = shift; my $isq = 0;
240     $quoted_string =~ s{^$REG{M_quoted_string}$}{
241     my $qtext = $1;
242 wakaba 1.23 $qtext =~ s/\x5C([\x00-\xFF])/$1/gs;
243 wakaba 1.5 $isq = 1;
244     $qtext;
245     }goex;
246     wantarray? ($quoted_string, $isq): $quoted_string;
247     }
248    
249 wakaba 1.13 sub unquote_if_angle_quoted ($) {
250     my $quoted_string = shift; my $isq = 0;
251     $quoted_string =~ s{^$REG{M_angle_quoted}$}{
252     my $qtext = $1;
253     $qtext =~ s/\x5C([\x00-\xFF])/$1/g;
254     $isq = 1;
255     $qtext;
256     }goex;
257     wantarray? ($quoted_string, $isq): $quoted_string;
258     }
259    
260 wakaba 1.7 sub unquote_if_domain_literal ($) {
261     my $quoted_string = shift; my $isq = 0;
262     $quoted_string =~ s{^$REG{M_domain_literal}$}{
263     my $qtext = $1;
264     $qtext =~ s/\x5C([\x00-\xFF])/$1/g;
265     $isq = 1;
266     $qtext;
267     }goex;
268     wantarray? ($quoted_string, $isq): $quoted_string;
269     }
270    
271 wakaba 1.5 =item $quoted = Message::Util::quote_unsafe_string ($string)
272 wakaba 1.4
273     Quotes string itself by C<DQUOTES> if it contains of
274     I<unsafe> character.
275    
276     Default I<unsafe> is defined as E<lt>not ( atom / "." / %x09 / %x20 ) E<gt>.
277    
278     =cut
279    
280 wakaba 1.5 sub quote_unsafe_string ($;%) {
281 wakaba 1.4 my $string = shift;
282 wakaba 1.5 my %option = @_;
283     $option{unsafe} ||= 'NON_atext_dot';
284 wakaba 1.6 $option{unsafe_regex} = $option{unsafe} if $option{unsafe} =~ /^\(\?-xism:/;
285 wakaba 1.14 $option{unsafe_regex} ||= qr/$REG{$option{unsafe}}|$REG{WSP}$REG{WSP}|^$REG{WSP}|$REG{WSP}$|^=\x3F/;
286 wakaba 1.7 my $r = qr/([\x22\x5C])([\x21-\x7E])?/;
287     $r = qr/([\x22\x5C])/ if $option{strict}; ## usefor-article
288 wakaba 1.6 if ($string =~ /$option{unsafe_regex}/) {
289 wakaba 1.7 $string =~ s/$r/"\x5C$1".(defined $2?"\x5C$2":'')/ge;
290 wakaba 1.4 $string = '"'.$string.'"';
291     }
292     $string;
293     }
294    
295 wakaba 1.7 sub quote_unsafe_domain ($) {
296     my $string = shift;
297     if ($string =~ /^\[[^\[\]]+\]$/) {
298     #
299     } elsif ($string =~ /$REG{NON_atext_dot}/ || $string =~ /^\.|\.$/) {
300 wakaba 1.14 $string =~ s/([\x0D\x5B-\x5D])/\x5C$1/g;
301 wakaba 1.7 $string = '['.$string.']';
302     }
303     $string;
304     }
305    
306     sub remove_wsp ($) {
307     my $s = shift;
308     $s =~ s{($REG{quoted_string}|$REG{domain_literal}|$REG{angle_quoted})|$REG{WSP}+}{
309     $1
310     }gex;
311     $s;
312     }
313    
314 wakaba 1.15 =item $encoded = Message::Util::encode_printable_string ($string)
315     =item $decoded = Message::Util::decode_printable_string ($string)
316    
317     Encodes or decodes string in PrintableString, described in
318     RFC 1327, RFC 2156. These functions supports 8bit octets encoded
319     as '(ddd)' format, although RFC 2156 allows it for only 7bit
320     octets.
321    
322     =cut
323    
324     my %To_Printable_String = (
325     '@' => '(a)',
326     '%' => '(p)',
327     '!' => '(b)',
328     '"' => '(q)',
329     '_' => '(u)',
330     '(' => '(l)',
331     ')' => '(r)',
332     );
333     my %From_Printable_String = reverse %To_Printable_String;
334     sub encode_printable_string ($) {
335     my $s = shift;
336     $s =~ s{ ([^0-9A-Za-z\x20'+,./:=?-]) }{
337     my $c = $1;
338     unless ($To_Printable_String{$c}) {
339     $To_Printable_String{$c} = sprintf '(%03d)', ord $c;
340     }
341     $To_Printable_String{$c};
342     }gex;
343     $s;
344     }
345     sub decode_printable_string ($) {
346     my $s = shift;
347     $s =~ s{ \( ([0-9A-Za-z]+) \) }{
348     my $c = lc $1; my $p = "($c)";
349     if ($c !~ /[^0-9]/) {
350     $From_Printable_String{$p} = pack 'C', 0+$c;
351     } elsif (!defined $From_Printable_String{$p}) {
352     $From_Printable_String{$p} = $p; ## Invalid!
353     }
354     $From_Printable_String{$p};
355     }gex;
356     $s;
357     }
358    
359     =item $encoded = Message::Util::encode_t61_string ($string)
360     =item $decoded = Message::Util::decode_t61_string ($string)
361    
362     Encodes or decodes string in T.61String described in RFC 1327,
363     RFC 2056.
364    
365     =cut
366    
367     sub encode_t61_string ($) {
368     my $s = shift;
369     $s =~ s{ ([^0-9A-Za-z\x20'+,./:=?-]) }{
370     sprintf '{%03d}', ord $1;
371     }gex;
372     $s;
373     }
374     sub decode_t61_string ($) {
375     my $s = shift;
376     $s =~ s{ \{ ([0-9]+) \} }{
377     my $c = $1; my $i = 0;
378     my $r = '';
379     while (my $d = substr ($c, $i * 3, 3)) {
380     $r .= pack 'C', 0+$d; $i++;
381     }
382     $r;
383     }gex;
384     $s;
385     }
386     =item $encoded = Message::Util::encode_printable_string ($string)
387     =item $decoded = Message::Util::decode_printable_string ($string)
388    
389     Encodes or decodes string in RFC 822 with restricted encoding,
390     defined by RFC 1137.
391    
392     =cut
393    
394     my %To_Encoded_Atom = (
395     "\x20" => '_',
396     '_' => '#u#',
397     '(' => '#l#',
398     ')' => '#r#',
399     ',' => '#m#',
400     ':' => '#c#',
401     "\x5C" => '#b#',
402     '#' => '#h#',
403     '=' => '#e#',
404     '/' => '#s#',
405     );
406     my %From_Encoded_Atom = reverse %To_Encoded_Atom;
407     sub encode_restricted_rfc822 ($) {
408     my $s = shift;
409     $s =~ s{ ([^\x21\x24-\x27\x2A\x2B\x2D\x2F\x30-\x39\x3D\x3F\x41-\x5A\x5E\x60-\x7E]) }{
410     my $c = $1; ## \x2E \x40
411     unless ($To_Encoded_Atom{$c}) {
412     $To_Encoded_Atom{$c} = sprintf '#%03d#', ord $c;
413     }
414     $To_Encoded_Atom{$c};
415     }gex;
416     $s;
417     }
418     sub decode_restricted_rfc822 ($) {
419     my $s = shift;
420     $s =~ s{ \# ([0-9A-Za-z]+) \# | _ }{
421     my $c = lc $1; my $p = "#$c#";
422     if ($c !~ /[^0-9]/) {
423     $From_Encoded_Atom{$p} = pack 'C', 0+$c;
424     } elsif (!defined $From_Encoded_Atom{$p}) {
425     $From_Encoded_Atom{$p} = $p; ## Invalid!
426     }
427     $From_Encoded_Atom{$p};
428     }gex;
429     $s;
430     }
431    
432 wakaba 1.5 =item $Message::Util::make_clone ($parent)
433    
434     Returns clone.
435    
436     =cut
437    
438     sub make_clone ($) {
439     my $s = shift;
440 wakaba 1.7 if (ref $s eq 'ARRAY') {
441     $s = [map {make_clone ($_)} @$s];
442     } elsif (ref $s eq 'HASH') {
443     $s = {map {make_clone ($_)} (%$s)};
444 wakaba 1.9 } elsif (ref $s && ref $s ne 'CODE' && ref $s ne 'Regexp') {
445 wakaba 1.5 $s = $s->clone;
446     }
447     $s;
448     }
449    
450 wakaba 1.4 =head1 ENCODER and DECODER
451    
452     =over 4
453    
454     =item Message::Util::encode_header_string ($yourself, $string, [%options])
455    
456     =cut
457    
458 wakaba 1.2 sub encode_header_string ($$;%) {
459 wakaba 1.3 my $yourself = shift; my $s = shift; my %o = @_;
460     $o{charset} ||= $yourself->{option}->{encoding_after_encode};
461 wakaba 1.1 $o{charset} = Message::MIME::Charset::name_normalize ($o{charset});
462     $o{current_charset} = Message::MIME::Charset::name_normalize ($o{current_charset});
463 wakaba 1.11 my ($t,%r) = Message::MIME::Charset::encode ($o{charset}, $s);
464 wakaba 1.1 my @o = (language => $o{language});
465 wakaba 1.12 if ($r{success}) { ## Convertion succeed
466 wakaba 1.11 $o{charset} = $r{charset} if $r{charset};
467 wakaba 1.13 $o{charset} = '' if $o{charset} =~ /\*/;
468     (value => $t, @o, Message::MIME::Charset::name_minimumize ($o{charset}, $t));
469 wakaba 1.1 } else { ## Fault
470 wakaba 1.13 $o{current_charset} = '' if $o{current_charset} =~ /\*/;
471 wakaba 1.18 (value => $t, failed => 1, @o,
472 wakaba 1.13 Message::MIME::Charset::name_minimumize ($o{current_charset}, $t));
473 wakaba 1.1 }
474     }
475    
476 wakaba 1.2 sub decode_header_string ($$;%) {
477 wakaba 1.3 my $yourself = shift; my $s = shift; my %o = @_;
478     $o{charset} ||= $yourself->{option}->{encoding_before_decode};
479 wakaba 1.1 $o{charset} = Message::MIME::Charset::name_normalize ($o{charset});
480 wakaba 1.19 my ($t, %r); ## decoded-text, success?
481 wakaba 1.7 if ($o{type} !~ /quoted|encoded|domain|word/) {
482 wakaba 1.5 my (@s, @r);
483 wakaba 1.13 $s =~ s{(([\x09\x20]*(?:\x5C[\x00-\xFF]
484     |[\x00-\x08\x0A-\x1F\x21-\x5B\x5D-\xFF])+|[\x09\x20]+$))}
485     { push @s, $1; '' }goesx;
486 wakaba 1.5 for my $i (0..$#s) {
487     if ($s[$i] =~ /^($REG{FWS})$REG{M_encoded_word}$/) {
488     my ($t, $w) = ('', $1);
489     ($t, $r[$i]) = (Message::MIME::EncodedWord::_decode_eword ($2, $3, $4, $5));
490     if ($r[$i]) {
491     $s[$i] = $t;
492     if ($i == 0 || $r[$i-1] == 0) {
493     $s[$i] = $w.$s[$i];
494     }
495     }
496     } else {
497 wakaba 1.19 my ($u, %q) = ($s[$i]);
498 wakaba 1.13 $u =~ s/\x5C([\x00-\xFF])/$1/g unless $o{type} =~ /text/;
499 wakaba 1.19 ($u,%q) = Message::MIME::Charset::decode ($o{charset}, $u);
500     $s[$i] = $u if $q{success};
501 wakaba 1.5 }
502     }
503 wakaba 1.19 $t = join '', @s; $r{success} = 1;
504 wakaba 1.5 } else {
505 wakaba 1.19 ($t,%r) = Message::MIME::Charset::decode ($o{charset}, $s);
506 wakaba 1.5 }
507 wakaba 1.19 $r{success} ? (value => $t, success => 1, language => $o{language}): ## suceess
508     (value => $s, language => $o{language}, success => 0,
509 wakaba 1.1 charset => ($o{charset}=~/\*/?'':$o{charset})); ## fault
510     }
511    
512     sub encode_body_string {
513 wakaba 1.3 my $yourself = shift; my $s = shift; my %o = @_;
514     $o{charset} ||= $yourself->{option}->{encoding_after_encode};
515 wakaba 1.1 $o{charset} = Message::MIME::Charset::name_normalize ($o{charset});
516     $o{current_charset} = Message::MIME::Charset::name_normalize ($o{current_charset});
517 wakaba 1.11 my ($t,%r) = Message::MIME::Charset::encode ($o{charset}, $s);
518 wakaba 1.1 my @o = ();
519 wakaba 1.11 if ($r{success}) { ## Convertion successed
520     $o{charset} = $r{charset} if $r{charset};
521 wakaba 1.13 $o{charset} = '' if $o{charset} =~ /\*/;
522     (value => $t, @o, Message::MIME::Charset::name_minimumize ($o{charset}, $t));
523     } else { ## Fault
524     $o{current_charset} = '' if $o{current_charset} =~ /\*/;
525 wakaba 1.18 (value => $t, failed => 1, @o,
526 wakaba 1.13 Message::MIME::Charset::name_minimumize ($o{current_charset}, $t));
527 wakaba 1.1 }
528     }
529    
530     sub decode_body_string {
531 wakaba 1.3 my $yourself = shift; my $s = shift; my %o = @_;
532     $o{charset} ||= $yourself->{option}->{encoding_before_decode};
533 wakaba 1.1 $o{charset} = Message::MIME::Charset::name_normalize ($o{charset});
534 wakaba 1.19 my ($t,%r) = Message::MIME::Charset::decode ($o{charset}, $s);
535     $r{success} ? (value => $t, success => 1): ## suceess
536     (value => $s, success => 0,
537 wakaba 1.1 charset => ($o{charset}=~/\*/?'':$o{charset})); ## fault
538     }
539    
540 wakaba 1.5 =item Message::Util::decode_quoted_string ($yourself, $quoted-string)
541    
542     Returns unquoted and decoded a given C<quoted-string>
543     or a string containing one or multiple C<quoted-string>s.
544    
545     =cut
546    
547 wakaba 1.7 sub decode_quoted_string ($$;%) {
548 wakaba 1.5 my $yourself = shift;
549     my $quoted_string = shift;
550 wakaba 1.7 my %option = @_;
551     $option{type} ||= 'phrase';
552 wakaba 1.5 $quoted_string =~ s{$REG{M_quoted_string}|([^\x22]+)}{
553     my ($qtext, $t) = ($1, $2);
554     if (length $t) {
555 wakaba 1.8 $t =~ s/$REG{WSP}+/\x20/g;
556 wakaba 1.5 my %s = &{$yourself->{option}->{hook_decode_string}}
557 wakaba 1.7 ($yourself, $t, type => $option{type},
558     charset => $option{charset});
559 wakaba 1.5 $s{value};
560     } else {
561     $qtext =~ s/\x5C([\x00-\xFF])/$1/g;
562     my %s = &{$yourself->{option}->{hook_decode_string}}
563 wakaba 1.7 ($yourself, $qtext, type => $option{type}.'/quoted',
564     charset => $option{charset});
565 wakaba 1.5 $s{value};
566     }
567     }goex;
568     $quoted_string;
569     }
570    
571 wakaba 1.4 =item Message::Util::encode_qcontent ($yourself, $string)
572    
573     Encodes (by C<hook_encode_string> of C<$yourself-E<gt>{option}>)
574     C<qcontent> (content of C<quoted-string>) within C<$string>.
575    
576     =cut
577    
578     sub encode_qcontent ($$) {
579     my $yourself = shift;
580     my $quoted_strings = shift;
581     $quoted_strings =~ s{$REG{M_quoted_string}}{
582     my ($qtext) = ($1);
583     $qtext =~ s/\x5C([\x00-\xFF])/$1/g;
584     my %s = &{$yourself->{option}->{hook_encode_string}} ($yourself, $qtext,
585     type => 'phrase/quoted');
586 wakaba 1.14 $s{value} =~ s/([\x0D\x22\x5C])([\x20-\xFF])?/"\x5C$1".($2?"\x5C$2":'')/ges;
587 wakaba 1.4 '"'.$s{value}.'"';
588     }goex;
589     $quoted_strings;
590     }
591    
592     =item Message::Util::decode_qcontent ($yourself, $string)
593    
594     Decodes (by C<hook_decode_string> of C<$yourself-E<gt>{option}>)
595     C<qcontent> (content of C<quoted-string>) within C<$string>.
596    
597     =cut
598    
599     sub decode_qcontent ($$) {
600     my $yourself = shift;
601     my $quoted_string = shift;
602     $quoted_string =~ s{$REG{M_quoted_string}}{
603     my ($qtext) = ($1);
604     $qtext =~ s/\x5C([\x00-\xFF])/$1/g;
605     my %s = &{$yourself->{option}->{hook_decode_string}} ($yourself, $qtext,
606     type => 'phrase/quoted');
607     $s{value} =~ s/([\x22\x5C])([\x20-\xFF])?/"\x5C$1".($2?"\x5C$2":'')/ge;
608     '"'.$s{value}.'"';
609     }goex;
610     $quoted_string;
611     }
612    
613     =item @comments = Message::Util::comment_to_array ($youtself, $comments)
614    
615     Replaces C<comment>s to C< > (a SP), decodes C<ccontent>s,
616     and returns them as array.
617    
618     =cut
619    
620     sub comment_to_array ($$) {
621     my $yourself = shift;
622     my $body = shift;
623     my @r = ();
624     $body =~ s{($REG{quoted_string}|$REG{domain_literal})|$REG{M_comment}}{
625     my ($o, $c) = ($1, $2);
626     if ($o) {$o}
627     else {
628     push @r, decode_ccontent ($yourself, $c);
629     ' ';
630     }
631     }gex;
632     @r;
633     }
634    
635 wakaba 1.8 sub delete_comment_to_array ($$;%) {
636 wakaba 1.7 my $yourself = shift;
637     my $body = shift;
638 wakaba 1.8 my %option = @_;
639     my $areg = ''; $areg = '|'.$REG{angle_quoted} if $option{-use_angle_quoted};
640 wakaba 1.7 my @r = ();
641 wakaba 1.8 $body =~ s{($REG{quoted_string}|$REG{domain_literal}$areg)|$REG{M_comment}}{
642 wakaba 1.7 my ($o, $c) = ($1, $2);
643     if ($o) {$o}
644     else {
645     push @r, decode_ccontent ($yourself, $c);
646     ' ';
647     }
648     }gex;
649     ($body, @r);
650     }
651    
652 wakaba 1.4 =item Message::Util::encode_ccontent ($yourself, $ccontent)
653    
654     Encodes C<ccontent> (content of C<comment>).
655    
656     =cut
657    
658     sub encode_ccontent ($$) {
659     my $yourself = shift;
660     my $ccontent = shift;
661     my %f = &{$yourself->{option}->{hook_encode_string}} ($yourself,
662     $ccontent, type => 'ccontent');
663 wakaba 1.9 $f{value} =~ s/([\x28\x29\x5C]|\x3D\x3F)([\x21-\x7E])?/"\x5C$1".(defined $2?"\x5C$2":'')/ge;
664 wakaba 1.4 $f{value};
665     }
666    
667     =item Message::Util::decode_ccontent ($yourself, $ccontent)
668    
669     Decodes C<ccontent> (content of C<comment>).
670    
671     =cut
672    
673     sub decode_ccontent ($$) {
674 wakaba 1.12 Message::MIME::EncodedWord::decode_ccontent (@_);
675 wakaba 1.4 }
676    
677 wakaba 1.13 sub sprintxf ($;\%) {
678     my $format = shift;
679     my $gparam = shift;
680     $format =~ s{%([A-Za-z0-9_]+)(?:\(([^\x29]*)\))?;}{
681     my ($f, $a) = ($1, $2);
682     my $function = $gparam->{fmt2str}->{$f} || $FMT2STR{$f};
683     if (ref $function) {
684     my %a;
685     for (split /[\x09\x20]*,[\x09\x20]*/, $a) {
686     if (/^([^=]*[^\x09\x20=])[\x09\x20]*=>[\x09\x20]*([^\x09\x20].*)$/) {
687 wakaba 1.23 $a{ Message::Util::Wide::unquote_if_quoted_string ($1) } = Message::Util::Wide::unquote_if_quoted_string ($2);
688 wakaba 1.13 } else {
689 wakaba 1.23 $a{ Message::Util::Wide::unquote_if_quoted_string ($_) } = 1;
690 wakaba 1.13 }
691     }
692     my $r = &$function (\%a, $gparam);
693     length $r? $a{prefix}.$r.$a{suffix}: '';
694     } elsif (length $function) {
695     $function;
696     } else {
697     "[$f: undef]";
698     }
699     }gex;
700     $format;
701     }
702 wakaba 1.10
703 wakaba 1.14 sub decide_newline ($) {
704     my $s = shift;
705     my $nl = "\x0D\x0A";
706     my $crlf = $s =~ s/\x0D\x0A/\x0D\x0A/gs;
707     my $lfcr = $s =~ s/\x0A\x0D/\x0A\x0D/gs;
708     my $cr = $s =~ s/\x0D(?!\x0A)/\x0D/gs;
709     my $lf = $s =~ s/(?<!\x0D)\x0A/\x0A/gs;
710     if ($crlf >= $cr && $crlf >= $lf && $crlf >= $lfcr ) { $nl = "\x0D\x0A" }
711     elsif ($lfcr >= $cr && $lfcr >= $lf) { $nl = "\x0A\x0D" }
712     elsif ($cr >= $lf) { $nl = "\x0D" }
713     else { $nl = "\x0A" }
714     $nl;
715     }
716    
717 wakaba 1.17 =item $fqdn = Message::Util::get_host_fqdn
718    
719     Returns FQDN of THIS host. If it is unable to get the FQDN,
720     returns undef.
721    
722     =over 3
723    
724     =item $Message::Util::OPTION{use_Net_Domain} = 1/0
725    
726     Whether using Net::Domain module to get FQDN or not.
727    
728     =item $Message::Util::OPTION{use_Sys_Hostname} = 1/0
729    
730     Whether using Sys::Hostnamen module to get FQDN or not.
731    
732     =item $Message::Util::OPTION{use_Sys_Hostname_Long} = 1/0
733    
734     Whether using Sys::Hostnamen::Long module to get FQDN or not.
735    
736     =back
737    
738     Note that the value returned by Sys::Hostname::hostname
739     usually does not match with the FQDN. This module is prepared
740     as the last way to get. If you want not to get non-FQDN,
741     set 0 to use_Sys_Hostname. Sys::Hostname is bundled with
742     Perl. This is why its default is 1.
743    
744     =cut
745    
746     $OPTION{use_Net_Domain} = 1;
747     $OPTION{use_Sys_Hostname} = 1;
748     $OPTION{use_Sys_Hostname_Long} = 1;
749     $OPTION{use_cache_host_fqdn} = 1;
750     $OPTION{__cache_host_fqdn} = undef;
751    
752     sub get_host_fqdn () {
753     my $f = undef;
754     return $OPTION{__cache_host_fqdn}
755     if $OPTION{use_cache_host_fqdn} && $OPTION{__cache_host_fqdn};
756     if ($OPTION{use_Net_Domain}) {
757     eval q{require Net::Domain;
758     $f = &Net::Domain::hostfqdn;
759     } or Carp::carp "get_host_fqdn: get by Net::Domain: $@";
760     if ($f) {
761     $OPTION{__cache_host_fqdn} = $f;
762     return $f;
763     }
764     }
765     if ($OPTION{use_Sys_Hostname_Long}) {
766     eval q{require Sys::Hostname::Long;
767     $f = &Sys::Hostname::Long::hostname_long;
768     } or Carp::carp "get_host_fqdn: get by Sys::Hostname::Long: $@";
769     if ($f) {
770     $OPTION{__cache_host_fqdn} = $f;
771     return $f;
772     }
773     }
774     if ($OPTION{use_Sys_Hostname}) {
775     eval q{require Sys::Hostname;
776     $f = &Sys::Hostname::hostname;
777     } or Carp::carp "get_host_fqdn: get by Sys::Hostname: $@";
778     if ($f) {
779     $OPTION{__cache_host_fqdn} = $f;
780     return $f;
781     }
782     }
783     undef;
784     }
785    
786 wakaba 1.21 sub is_utf8 ($) {
787     my $s = shift;
788     return Encode::is_utf8 ($s) if $Encode::VERSION;
789     0;
790     }
791    
792 wakaba 1.23 package Message::Util::Wide;
793     use vars qw(%REG);
794    
795     $REG{M_quoted_string} = qr/\x22((?:\x5C.|[^\x0D\x22\x5C])*)\x22/;
796    
797     sub unquote_if_quoted_string ($) {
798     my $quoted_string = shift; my $isq = 0;
799     $quoted_string =~ s{^$REG{M_quoted_string}$}{
800     my $qtext = $1;
801     $qtext =~ s/\x5C(.)/$1/gs;
802     $isq = 1;
803     $qtext;
804     }goex;
805     wantarray? ($quoted_string, $isq): $quoted_string;
806     }
807    
808    
809 wakaba 1.1 =head1 LICENSE
810    
811     Copyright 2002 wakaba E<lt>[email protected]<gt>.
812    
813     This program is free software; you can redistribute it and/or modify
814     it under the terms of the GNU General Public License as published by
815     the Free Software Foundation; either version 2 of the License, or
816     (at your option) any later version.
817    
818     This program is distributed in the hope that it will be useful,
819     but WITHOUT ANY WARRANTY; without even the implied warranty of
820     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
821     GNU General Public License for more details.
822    
823     You should have received a copy of the GNU General Public License
824     along with this program; see the file COPYING. If not, write to
825     the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
826     Boston, MA 02111-1307, USA.
827    
828     =head1 CHANGE
829    
830     See F<ChangeLog>.
831 wakaba 1.23 $Date: 2002/08/04 00:16:32 $
832 wakaba 1.1
833     =cut
834    
835     1;

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24