/[suikacvs]/messaging/manakai/lib/Message/Field/MsgID.pm
Suika

Contents of /messaging/manakai/lib/Message/Field/MsgID.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.9 - (show annotations) (download)
Sat Jul 13 09:27:35 2002 UTC (22 years, 4 months ago) by wakaba
Branch: MAIN
CVS Tags: before-dis2-200411, manakai-release-0-3-2, manakai-release-0-3-1, manakai-release-0-4-0, manakai-200612, msg-0-1, HEAD
Branch point for: branch-suikawiki-1, experimental-xml-parser-200401, stable
Changes since 1.8: +5 -5 lines
Error occurred while calculating annotation data.
2002-07-13  Wakaba <w@suika.fam.cx>

	* MDNDisposition.pm, ReportingUA.pm: New modules.

1
2 =head1 NAME
3
4 Message::Field::MsgID --- Perl module for Message-ID
5 of Internet messages
6
7 =head1 DESCRIPTION
8
9 This module supports C<msg-id> defined by RFC 2822.
10 Message-ID generating algorithm suggested by
11 draft-ietf-usefor-msg-id-alt-00 is also supported.
12
13 =cut
14
15 package Message::Field::MsgID;
16 use strict;
17 use vars qw(@ISA %REG $VERSION);
18 $VERSION=do{my @r=(q$Revision: 1.8 $=~/\d+/g);sprintf "%d."."%02d" x $#r,@r};
19 require Message::Util;
20 require Message::Field::Structured;
21 push @ISA, qw(Message::Field::Structured);
22
23 use overload '""' => sub { $_[0]->stringify },
24 fallback => 1;
25
26 *REG = \%Message::Util::REG;
27 ## Inherited: comment, quoted_string, domain_literal
28 ## WSP, FWS, phrase, NON_atom
29 ## msg_id
30 ## M_quoted_string
31
32 =head1 CONSTRUCTORS
33
34 The following methods construct new objects:
35
36 =over 4
37
38 =cut
39
40 ## Initialize of this class -- called by constructors
41 sub _init ($;%) {
42 my $self = shift;
43 my %options = @_;
44 my %DEFAULT = (
45 -encoding_after_encode => 'unknown-8bit',
46 -encoding_before_decode => 'unknown-8bit',
47 #field_param_name
48 #field_name
49 #format
50 -hash_name => '%none',
51 #hook_encode_string
52 #hook_decode_string
53 -software_name => 'MFMpm',
54 -software_name_hash => '%none',
55 -validate => 0,
56 );
57 $self->SUPER::_init (%DEFAULT, %options);
58 }
59
60 =item $m = Message::Field::MsgID->new ([%options])
61
62 Constructs a new object. You might pass some options as parameters
63 to the constructor.
64
65 =cut
66
67 sub new ($;%) {
68 my $self = shift->SUPER::new (@_);
69 my %option = @_;
70 if ($option{id_left} && $option{id_right}) {
71 $self->{id_left} = $option{id_left};
72 $self->{id_right} = $option{id_right};
73 } elsif ($option{addr_spec}
74 || (($option{fqdn} || $option{ip_address} || $option{uucp})
75 && ($option{login}))) {
76 $self->_newid (\%option);
77 }
78 $self;
79 }
80
81 sub parse ($;$%) {
82 my $class = shift;
83 my $self = bless {}, $class;
84 my $body = shift; my @c;
85 $self->_init (@_);
86 ($body, @c) = $self->Message::Util::delete_comment_to_array ($body);
87
88 $body = Message::Util::remove_wsp ($body);
89 if ($body =~ /$REG{M_addr_spec}/) {
90 ## BUG: <foo . bar@foo.example> is treated as <"foo . bar"@foo.example>
91 my %s = &{$self->{option}->{hook_decode_string}} ($self,
92 Message::Util::unquote_quoted_string ($1), type => 'quoted-string');
93 $self->{id_left} = $s{value};
94 ## Should we use Message::Field::Domain?
95 ## BUG: <foo@foo . example> will broken... (M_addr_spec should be fixed)
96 $self->{id_right} = $2;
97 }
98
99 $self;
100 }
101
102 sub _newid ($\%) {
103 my $self = shift;
104 my $o = shift;
105 $$o{addr_spec} = Message::Util::remove_wsp ($$o{addr_spec});
106 if ($$o{addr_spec} =~ /$REG{M_addr_spec}/) {
107 $$o{login} = $1; $$o{fqdn} = $2;
108 }
109 if ($self->{option}->{validate} && $$o{fqdn} =~
110 /[.@](example\.(?:com|org|net)|localdomain|localhost|example|invalid|test|arpa)$/) {
111 Carp::croak "Msg-ID generation: invalid TLD of FQDN: .$1";
112 }
113 if (!$$o{fqdn} && $$o{ip_address}) {
114 if ($self->{option}->{validate}
115 && $$o{ip_address}=~/([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/){
116 my ($c1, $c2, $c3, $c4) = ($1, $2, $3, $4);
117 Carp::croak "Msg-ID generation: invalid IPv4 address: $c1.$c2.$c3.$c4"
118 ## See [IANAREG] and draft-iana-special-ipv4
119 if ($c1 == 0) ## "this" network
120 || ($c1 == 10) ## private [RFC1918]
121 || ($c1 == 127) ## loopback
122 || ($c1 == 169 && $c2 == 254) ## "link local"
123 || ($c1 == 172 && 16 <= $c2 && $c2 < 32) ## private [RFC1918]
124 || ($c1 == 192 && (($c2 == 0 && $c3 == 2) ## "TEST-NET"
125 || ($c2 == 88 && $c3 == 99) ## 6to4 anycast [RFC3068]
126 || ($c2 == 168))) ## private [RFC1918]
127 || ($c1 == 198 && ($c2 == 18 || $c2 == 19)) ## benchmark [RFC2544]
128 || ($c1 >= 224); ## class D,E [RFC3171]
129 }
130 $$o{fqdn} ||= '['.$$o{ip_address}.']';
131 }
132 if (!$$o{fqdn} && $$o{uucp}) {
133 $$o{uucp} .= '.uucp' if $self->{option}->{validate} && $$o{uucp} !~ /\.uucp/i;
134 $$o{fqdn} = $$o{uucp};
135 }
136 Carp::croak "Msg-ID generation: no FQDN"
137 if $self->{option}->{validate} && !$$o{fqdn};
138
139 $self->{id_right} = $$o{fqdn};
140
141 Carp::croak "Msg-ID generation: no 'login'"
142 if $self->{option}->{validate} && !$$o{login};
143 $$o{login} = $self->_hash ($$o{login}, $self->{option}->{hash_name});
144
145 my @s = ('0'..'9','a'..'z','-','=','_');
146 my @t = ('0'..'9','a'..'z');
147 my $unique = $t[rand @t].$s[rand @s].$s[rand @s].$s[rand @s].'.';
148 $unique .= join ('.', $self->_base39 (time), $self->_base39 ($$),
149 $self->_hash ($self->{option}->{software_name},
150 $self->{option}->{software_name_hash}));
151 $self->{id_left} = $unique
152 .'%'.($self->{option}->{hash_name} ne '%none'?
153 $self->_hash ($self->{option}->{hash_name}, '%none') .'%': '')
154 .$$o{login}
155 .($$o{subject_changed}? '-_-': '');
156
157 $self;
158 }
159
160 sub _hash ($$;$$) {
161 my $self = shift;
162 my ($str, $hash_name, $add_unsafe) = (shift, lc shift, shift);
163 $add_unsafe ||= qr#[/.=\x09\x20]#;
164 undef $hash_name if $hash_name eq '%none';
165 if ($hash_name eq 'md5') {
166 eval {require Digest::MD5} or Carp::croak "Msg-ID generation: $@";
167 $str = Digest::MD5::md5_base64 ($str);
168 } elsif ($hash_name eq 'sha1') {
169 eval {require Digest::SHA1} or Carp::croak "Msg-ID generation: $@";
170 $str = Digest::SHA1::sha1_base64 ($str);
171 } elsif ($hash_name eq 'md2') {
172 eval {require Digest::MD2} or Carp::croak "Msg-ID generation: $@";
173 $str = Digest::MD2::md2_base64 ($str);
174 } elsif ($hash_name eq 'crypt') {
175 my @s = ('0'..'9','A'..'Z','a'..'z');
176 my $salt = crypt('foobar', '$1$ab$') eq '$1$ab$uAP8qWqcFs3q.Gfl5PkL2.'?
177 '$1$'.join('', map($s[rand @s], 1..8)).'$': $s[rand @s].$s[rand @s];
178 $str = crypt ($str, $salt);
179 }
180 $str =~ s#($add_unsafe)#sprintf('=%02X', ord($1))#ge;
181 $str =~ s/($REG{NON_atext_dot})/sprintf('=%02X', ord($1))/ge;
182 $str;
183 }
184
185 sub _base39 ($$) {
186 my $self = shift;
187 my $number = shift;
188 my @digit = ('0'..'9','a'..'z','-','=','_');
189 my $ret = '';
190
191 my ($rem);
192 while ($number > 0) {
193 $rem = $number % @digit;
194 $ret = $digit[ $rem ].$ret;
195 $number = ($number - $rem) / @digit;
196 }
197 $ret;
198 }
199
200 sub generate ($%) {
201 my $self = shift;
202 my %parameter = @_;
203 for (grep {/^-/} keys %parameter) {$parameter{substr ($_, 1)} = $parameter{$_}}
204 $self->_newid (\%parameter);
205 }
206
207 sub id_left ($) {
208 my $self = shift;
209 my %e = &{$self->{option}->{hook_encode_string}} ($self,
210 $self->{id_left}, type => 'local-part');
211 Message::Util::quote_unsafe_string ($e{value},
212 unsafe_regex => qr/$REG{NON_atext_dot}|^\.|\.$/);
213 }
214 sub id_right ($) {
215 my $self = shift;
216 #my %e = &{$self->{option}->{hook_encode_string}} ($self,
217 # $self->{id_right}, type => 'domain');
218 #Message::Util::quote_unsafe_domain ($e{value});
219 Message::Util::quote_unsafe_domain ($self->{id_right});
220 }
221 sub content ($) {
222 my $self = shift;
223 my ($l, $r) = ($self->id_left, $self->id_right);
224 sprintf '%s@%s', $l, $r if $l && $r;
225 }
226 *id = \&content;
227
228 sub stringify ($;%) {
229 my $self = shift;
230 my ($l, $r) = ($self->id_left, $self->id_right);
231 sprintf '<%s@%s>', $l, $r if $l && $r;
232 }
233 *as_string = \&stringify;
234
235 =head1 EXAMPLE
236
237 use Message::Field::MsgID;
238 my $from = 'foo@bar.example';
239 my $login = 'my-login-name';
240 my $domain = 'foo.bar.example';
241 my $ipv4 = '192.168.0.1';
242 my %mid;
243 $mid{no_crypt} = new Message::Field::MsgID
244 addr_spec => $from, -validate => 0;
245 $mid{md5} = new Message::Field::MsgID
246 login => $login, ip_address => $ipv4,
247 -hash_name => 'md5', -validate => 0;
248 $mid{sha1} = new Message::Field::MsgID
249 login => $login, fqdn => $domain, subject_changed => 1,
250 -hash_name => 'sha1', -validate => 0;
251 for (keys %mid) {
252 print $_, ":\t", $mid{$_}, "\n";
253 }
254 # sha1: <t-9.bbxkfu.xsem.MFMMpm%sha1%9pnH2R6iN8KSIMby+dPU0i3M8RU-_-@foo.bar.example>
255 # md5: <7fu.bbxkfu.xsem.MFMMpm%md5%eBpd+12mupwxZBc6kMWR9g@[192.168.0.1]>
256 # no_crypt: <3vy.bbxkfu.xsem.MFMMpm%foo@bar.example>
257
258 ## IMPORTANT NOTE: This example uses -validate option with
259 ## '0' (does not validate) value since it uses example (invalid)
260 ## resource names, such as 'foo.bar.example'. Usually, this option
261 ## shall not be used (and default value = '1' = does validate
262 ## should be used).
263
264 =head1 SEE ALSO
265
266 RFC 822 E<lt>urn:ietf:rfc:822E<gt>, RFC 2822 E<lt>urn:ietf:rfc:2822E<gt>
267
268 RFC 850 E<lt>urn:ietf:rfc:850E<gt>, RFC 1036 E<lt>urn:ietf:rfc:1036E<gt>,
269 son-of-RFC1036, draft-ietf-usefor-article-06.txt
270 E<lt>urn:ietf:id:draft-ietf-usefor-article-06E<gt>
271
272 draft-ietf-usefor-message-id-01.txt
273 E<lt>urn:ietf:id:draft-ietf-usefor-message-id-01E<gt>
274
275 draft-ietf-usefor-msg-id-alt-00.txt
276 E<lt>urn:ietf:id:draft-ietf-usefor-msg-id-alt-00E<gt>
277
278 =head1 LICENSE
279
280 Copyright 2002 wakaba E<lt>w@suika.fam.cxE<gt>.
281
282 This program is free software; you can redistribute it and/or modify
283 it under the terms of the GNU General Public License as published by
284 the Free Software Foundation; either version 2 of the License, or
285 (at your option) any later version.
286
287 This program is distributed in the hope that it will be useful,
288 but WITHOUT ANY WARRANTY; without even the implied warranty of
289 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
290 GNU General Public License for more details.
291
292 You should have received a copy of the GNU General Public License
293 along with this program; see the file COPYING. If not, write to
294 the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
295 Boston, MA 02111-1307, USA.
296
297 =head1 CHANGE
298
299 See F<ChangeLog>.
300 $Date: 2002/06/16 10:42:06 $
301
302 =cut
303
304 1;

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24