/[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.4 - (hide annotations) (download)
Sat May 4 06:03:58 2002 UTC (22 years, 6 months ago) by wakaba
Branch: MAIN
Changes since 1.3: +216 -201 lines
2002-05-04  wakaba <w@suika.fam.cx>

	* XMoe.pm: New module.
	* CSV.pm: Use XMoe.pm.

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24