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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.20 - (show annotations) (download)
Thu Aug 1 06:42:38 2002 UTC (22 years, 3 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.19: +3 -3 lines
Error occurred while calculating annotation data.
2002-08-01  Wakaba <w@suika.fam.cx>

	* Unstructured.pm: Rewritten.
	* Subject.pm: Likewise.  Support Japanese government's
	spam mail prefix if Perl has defined $^V (=~ has UTF-8 support).

1
2 =head1 NAME
3
4 Message::Field::Params --- Perl module for Internet message
5 field body consist of parameters, such as C<Content-Type:> field
6
7 =cut
8
9 package Message::Field::Params;
10 use strict;
11 require 5.6.0;
12 use re 'eval';
13 use vars qw(%DEFAULT @ISA %REG $VERSION);
14 $VERSION=do{my @r=(q$Revision: 1.19 $=~/\d+/g);sprintf "%d."."%02d" x $#r,@r};
15 require Message::Util;
16 require Message::MIME::Charset;
17 require Message::Field::Structured;
18 push @ISA, qw(Message::Field::Structured);
19
20 use overload '""' => sub { $_[0]->stringify },
21 '0+' => sub { $_[0]->count },
22 '.=' => sub { $_[0]->add ($_[1], ['', value => 1]); $_[0] },
23 fallback => 1;
24
25 %REG = %Message::Util::REG;
26
27 $REG{S_parameter} = qr/(?:[^\x22\x28\x3B\x3C]|$REG{comment}|$REG{quoted_string}|$REG{angle_quoted})+/;
28 $REG{S_parameter_separator} = qr/;/;
29 $REG{S_comma_parameter} = qr/(?:[^\x22\x28\x2C\x3C]|$REG{comment}|$REG{quoted_string}|$REG{angle_quoted})+/;
30 $REG{S_comma_parameter_separator} = qr/,/;
31 $REG{MS_parameter_avpair} = qr/([^\x22\x3C\x3D]+)=([\x00-\xFF]*)/;
32 $REG{M_parameter_avpair} = qr/([^\x22\x3C\x3D]+)=([^\x3D]*)/;
33
34 %DEFAULT = (
35 -_HASH_NAME => 'params',
36 -_MEMBERS => [qw/params/],
37 -_METHODS => [qw/add replace delete item parameter scan/],
38 ## count item_exist <- not implemented yet
39 -accept_coderange => '7bit',
40 -by => 'attribute',
41 #encoding_after_encode
42 #encoding_before_decode
43 #field_param_name
44 #field_name
45 #field_ns
46 #format
47 #header_default_charset
48 #header_default_charset_input
49 #hook_encode_string
50 #hook_decode_string
51 -output_comment => 1,
52 -output_parameter_extension => 0,
53 -parameter_rule => 'S_parameter', ## regex name of parameter
54 -parameter_attribute_case_sensible => 0,
55 -parameter_attribute_unsafe_rule => 'NON_http_attribute_char',
56 -parameter_av_Mrule => 'MS_parameter_avpair',
57 -parameter_no_value_attribute_unsafe_rule => 'NON_http_attribute_char',
58 -parameter_value_max_length => 60,
59 -parameter_value_split_length => 35,
60 -parameter_value_unsafe_rule => 'NON_http_attribute_char',
61 #parse_all
62 -separator => '; ',
63 -separator_rule => 'parameter_separator',
64 -use_comment => 1,
65 -use_parameter_extension => 1,
66 #value_type
67 );
68
69 =head1 CONSTRUCTORS
70
71 The following methods construct new objects:
72
73 =over 4
74
75 =cut
76
77 ## Initialize of this class -- called by constructors
78 sub _init ($;%) {
79 my $self = shift;
80 my %options = @_;
81 $self->SUPER::_init (%DEFAULT, %options);
82 $self->{param} = [];
83 my $field = $self->{option}->{field_name};
84 if ($field eq 'p3p') {
85 $self->{option}->{parameter_rule} = 'S_comma_parameter';
86 $self->{option}->{separator_rule} = 'S_comma_parameter_separator';
87 $self->{option}->{separator} = ', ';
88 }
89 if ($self->{option}->{format} =~ /news-usefor/) {
90 $self->{option}->{accept_coderange} = '8bit';
91 } elsif ($self->{option}->{format} =~ /http/) {
92 $self->{option}->{accept_coderange} = 'binary';
93 }
94 }
95
96 =item $p = Message::Field::Params->new ([%options])
97
98 Constructs a new object. You might pass some options as parameters
99 to the constructor.
100
101 =cut
102
103 ## Inherited
104
105 =item $p = Message::Field::Params->parse ($field-body, [%options])
106
107 Constructs a new object with given field body. You might pass
108 some options as parameters to the constructor.
109
110 =cut
111
112 sub parse ($$;%) {
113 my $class = shift;
114 my $self = bless {}, $class;
115 my $body = shift;
116 $self->_init (@_);
117 my @param;
118 $body =~ s{
119 ($REG{ $self->{option}->{parameter_rule} })
120 (?: $REG{ $self->{option}->{separator_rule} } | $ )
121 }{
122 push @param, $self->_parse_parameter_item ($1, $self->{option});
123 '';
124 }gesx;
125 $self->_decode_parameters (\@param, $self->{option});
126 $self->_save_parameters (\@param, $self->{option});
127 $self;
128 }
129
130 ## $self->_parse_parameter_item ($item, \%option)
131 ## -- parses a parameter item (into attribute/value pair or no-value-attribute)
132 sub _parse_parameter_item ($$\%) {
133 my $self = shift;
134 my ($item, $option) = @_;
135 my @comment;
136 ($item, @comment) = $self->Message::Util::delete_comment_to_array
137 ($item, -use_angle_quoted);
138 $item =~ s/^$REG{WSP}+//g;
139 $item =~ s/$REG{WSP}+$//g;
140 my %item;
141 if ($item =~ /^$REG{ $option->{parameter_av_Mrule} }$/) {
142 my $encoded = 0;
143 ($item{attribute}, $item{value}) = ($1, $2);
144 $item{attribute} =~ tr/\x09\x0A\x0D\x20//d;
145 $item{value} =~ s/^$REG{WSP}+//g;
146 if ($option->{use_parameter_extension}
147 && $item{attribute} =~ /^([^*]+)(?:\*([0-9]+)(\*)?|(\*))\z/) {
148 $item{attribute} = $1;
149 $item{section_no} = $2;
150 $encoded = $3 || $4;
151 $item{section_no} = -1 if $4;
152 if ($item{section_no} <= 0 && $encoded
153 && $item{value} =~ /^([^']*)'([^']*)'([\x00-\xFF]*)$/) {
154 $item{charset} = $1; $item{charset} =~ tr/\x09\x0A\x0D\x20//d;
155 $item{language} = $2; $item{language} =~ tr/\x09\x0A\x0D\x20//d;
156 $item{value} = $3; $item{value} =~ s/^$REG{WSP}+//g;
157 }
158 if ($encoded) {
159 $item{value} =~ s/%([0-9A-Fa-f][0-9A-Fa-f])/chr(hex($1))/ge;
160 }
161 } else {
162 $item{section_no} = -1;
163 }
164 ($item{value}, $encoded) = Message::Util::unquote_if_quoted_string ($item{value}) unless $encoded;
165 ($item{value}, $encoded) = Message::Util::unquote_if_angle_quoted ($item{value}) unless $encoded;
166 $item{charset} = '*bare' if !$encoded && !$item{charset};
167 $item{attribute} = lc $item{attribute}
168 unless $option->{parameter_attribute_case_sensible};
169 } else {
170 my $encoded = 0;
171 ($item, $encoded) = Message::Util::unquote_if_quoted_string ($item) unless $encoded;
172 ($item, $encoded) = Message::Util::unquote_if_angle_quoted ($item) unless $encoded;
173 $item{attribute} = $item;
174 $item{charset} = '*bare' if !$encoded;
175 $item{no_value} = 1;
176 }
177 $item{comment} = \@comment;
178 \%item;
179 }
180
181 ## $self->_decode_parameters (\@parameter, \%option)
182 ## -- join RFC 2231 splited fragments and decode each parameter
183 sub _decode_parameters ($\@\%) {
184 my $self = shift;
185 my ($param, $option) = @_;
186 my %fragment;
187 my @fparameter;
188 for my $parameter (@$param) {
189 if ($parameter->{no_value}) {
190 my %item;
191 $item{no_value} = 1;
192 $item{comment} = $parameter->{comment};
193 if ($parameter->{charset} ne '*bare') { ## non quoted-string
194 my %s = &{$self->{option}->{hook_decode_string}}
195 ($self, $parameter->{attribute},
196 charset => $option->{encoding_before_decode},
197 type => 'parameter/no-value-attribute');
198 if ($s{charset}) { ## Convertion failed
199 $item{charset} = $s{charset};
200 } elsif (!$s{success}) {
201 $item{charset} = $option->{header_default_charset_input};
202 }
203 $item{attribute} = $s{value};
204 } else {
205 $item{attribute} = $parameter->{attribute};
206 }
207 $parameter = \%item;
208 } elsif ($parameter->{section_no} < 0) {
209 my %item;
210 $item{attribute} = $parameter->{attribute};
211 $item{language} = $parameter->{language} if $parameter->{language};
212 $item{comment} = $parameter->{comment};
213 if ($parameter->{charset} ne '*bare') { ## non 2231 encoded
214 my %s = &{$self->{option}->{hook_decode_string}}
215 ($self, $parameter->{value},
216 charset => $parameter->{charset} || $option->{encoding_before_decode},
217 type => 'parameter/value/quoted-string');
218 if ($s{charset}) { ## Convertion failed
219 $item{charset} = $s{charset};
220 } elsif (!$s{success}) {
221 $item{charset} = $option->{header_default_charset_input};
222 } elsif ($parameter->{charset}) {
223 $item{output_charset} = $parameter->{charset};
224 }
225 $item{value} = $s{value};
226 } else {
227 $item{value} = $parameter->{value};
228 }
229 $parameter = \%item;
230 } else { ## fragment
231 $fragment{ $parameter->{attribute} }->[ $parameter->{section_no} ]
232 = $parameter->{value};
233 if ($parameter->{section_no} == 0) {
234 $fragment{'*property'}->{ $parameter->{attribute} }
235 ->{language} = $parameter->{language};
236 $fragment{'*property'}->{ $parameter->{attribute} }
237 ->{charset} = $parameter->{charset};
238 }
239 if (ref $parameter->{comment} && @{$parameter->{comment}} > 0) {
240 push @{ $fragment{'*property'}->{ $parameter->{attribute} }
241 ->{comment} }, @{$parameter->{comment}};
242 }
243 $parameter = undef;
244 }
245 }
246 for (keys %fragment) {
247 next if $_ eq '*property';
248 my %item;
249 $item{attribute} = $_;
250 $item{comment} = $fragment{'*property'}->{ $item{attribute} }->{comment};
251 $item{language} = $fragment{'*property'}->{ $item{attribute} }->{language};
252 delete $item{language} unless $item{language};
253 my $charset = $fragment{'*property'}->{ $item{attribute} }->{charset};
254 my %s = &{$self->{option}->{hook_decode_string}}
255 ($self, join ('', @{ $fragment{ $item{attribute} } }),
256 charset => $charset || $option->{encoding_before_decode},
257 type => 'parameter/extended-value/encoded');
258 if ($s{charset}) { ## Convertion failed
259 $item{charset} = $s{charset};
260 } elsif ($charset) {
261 $item{output_charset} = $charset;
262 }
263 $item{value} = $s{value};
264 push @fparameter, \%item;
265 }
266 @$param = (grep { ref $_ eq 'HASH' } @$param, @fparameter);
267 }
268
269 ## $self->_parse_values_of_paramters (\@parameter, \%option)
270 ## --- Parse each values of parameters
271 sub _parse_values_of_parameters ($\@\%) {
272 my $self = shift;
273 my ($param, $option) = @_;
274 @$param = map {
275 if (!$_->{no_value}) {
276 $_->{value} = $self->_parse_value ($_->{attribute} => $_->{value});
277 } else {
278 $_->{value} = $self->_parse_value ('*no_value_attribute' => $_->{value});
279 }
280 $_;
281 } @$param;
282 }
283
284 ## $self->_save_parameters (\@parameter, \%option)
285 ## -- Save parameters in $self
286 sub _save_parameters ($\@\%) {
287 my $self = shift;
288 my ($param, $option) = @_;
289 $self->_parse_values_of_parameters ($param, $option) if $option->{parse_all};
290 $self->{ $option->{_HASH_NAME} } = $param;
291 }
292 *__save_parameters = \&_save_parameters;
293
294
295 =back
296
297 =head1 METHODS
298
299 =over 4
300
301 =item $p->add ($name => [$value], [$name => $value,...])
302
303 Adds parameter name=value pair.
304
305 Example:
306 $p->add (title => 'foo of bar'); ## title="foo of bar"
307 $p->add (subject => 'hogehoge, foo'); ## subject*=''hogehoge%2C%20foo
308 $p->add (foo => ['bar', language => 'en']) ## foo*='en'bar
309 $p->add ('text/plain', ['', value => 1]) ## text/plain
310
311 This method returns array reference of (name, {value => value, attribute...}).
312
313 Available options: charset (charset name), language (language tag),
314 value (1/0, see example above).
315
316 =cut
317
318 sub _add_hash_check ($$$\%) {
319 my $self = shift;
320 my ($name, $value, $option) = @_;
321 my $value_option = {};
322 if (ref $value eq 'ARRAY') {
323 ($value, %$value_option) = @$value;
324 }
325 ## -- attribute only (no value) parameter
326 if ($value_option->{no_value}) {
327 $name = $self->_parse_value ('*no_value_attribute' => $name) if $$option{parse};
328 return (1, $name => {
329 attribute => $name, no_value => 1,
330 language => $value_option->{language},
331 comment => $value_option->{comment},
332 });
333 }
334 ## -- attribute=value pair
335 if ($$option{validate} && $name =~ /^$REG{NON_http_attribute_char}$/) {
336 if ($$option{dont_croak}) {
337 return (0);
338 } else {
339 Carp::croak qq{add: $name: Invalid parameter name};
340 }
341 }
342 $value = $self->_parse_value ($name => $value) if $$option{parse};
343 (1, $name => {
344 attribute => $name, value => $value,
345 output_charset => $value_option->{charset},
346 charset => $value_option->{current_charset},
347 language => $value_option->{language},
348 comment => $value_option->{comment},
349 });
350 }
351
352 *_add_return_value = \&_replace_return_value;
353
354 ## (1/0, $name => $value) = $self->_replace_hash_check ($name => $value, \%option)
355 ## -- Checks given value and prepares saving value (hash version)
356 *_replace_hash_check = \&_add_hash_check;
357
358 ## $value = $self->_replace_hash_shift (\%values, $name, $option)
359 ## -- Returns a value (from %values) and deletes it from %values
360 ## (like CORE::shift for array).
361 sub _replace_hash_shift ($\%$\%) {
362 shift; my $r = shift; my $n = $_[0]->{attribute};
363 if ($$r{$n}) {
364 my $d = $$r{$n};
365 $$r{$n} = undef;
366 return $d;
367 }
368 undef;
369 }
370
371 ## $value = $self->_replace_return_value (\$item, \%option)
372 ## -- Returns returning value of replace method
373 sub _replace_return_value ($\$\%) {
374 my $self = shift;
375 my ($item, $value) = @_;
376 if ($$item->{no_value}) {
377 $$item->{attribute};
378 } else {
379 $$item->{value};
380 }
381 }
382
383 ## 1/0 = $self->_delete_match ($by, \$item, \%delete_list, \%option)
384 ## -- Checks and returns whether given item is matched with
385 ## deleting item list
386 sub _delete_match ($$\$\%\%) {
387 my $self = shift;
388 my ($by, $item, $list, $option) = @_;
389 return 0 unless ref $$item; ## Already removed
390 if ($by eq 'attribute' || $by eq 'name') {
391 return 1 if $$list{ $$item->{attribute} };
392 } elsif ($by eq 'value') {
393 return 1 if $$list{ $$item->{value} };
394 } elsif ($by eq 'charset') {
395 return 1 if $$list{ $$item->{output_charset} } || $$list{ $$item->{charset} };
396 } elsif ($by eq 'language') {
397 return 1 if $$list{ $$item->{language} };
398 } elsif ($by eq 'type') {
399 if ($$item->{no_value}) {
400 return 1 if $$list{no_value_attribute};
401 } else {
402 return 1 if $$list{attribute_value_pair};
403 }
404 }
405 0;
406 }
407
408 ## Delete empty items
409 sub _delete_empty ($) {
410 my $self = shift;
411 my $array = $self->{option}->{_HASH_NAME};
412 $self->{ $array } = [grep { ref $_ } @{$self->{ $array }}] if $array;
413 }
414
415 =item @param = $p->parameter ($name => ($new_value), (%option))
416
417
418 =cut
419
420 sub parameter ($;@) {
421 my $self = shift;
422 if (@_ == 2) { ## $p->parameter (hoge => 'foo')
423 $self->replace (@_);
424 } else { ## $p->parameter ('foo')
425 $self->item (@_);
426 }
427 }
428
429 *_item_match = \&_delete_match;
430 *_item_return_value = \&_replace_return_value;
431
432 ## $item = $self->_item_new_value ($name, \%option)
433 ## -- Returns new item with key of $name (called when
434 ## no returned value is found and -new_value_unless_exist
435 ## option is true)
436 sub _item_new_value ($$\%) {
437 my $self = shift;
438 my ($key, $option) = @_;
439 if ($option->{by} eq 'attribute' || $option->{by} eq 'name') {
440 return {attribute => $key};
441 }
442 undef;
443 }
444
445 ## TODO: Implement count,item_exist method
446
447 =item $field-body = $p->stringify ()
448
449 Returns C<field-body> as a string.
450
451 =cut
452
453 sub stringify ($;%) {
454 my $self = shift;
455 my %o = @_; my %option = %{$self->{option}};
456 for (grep {/^-/} keys %o) {$option{substr ($_, 1)} = $o{$_}}
457 $option{output_parameter_extension} = 0
458 unless $option{use_parameter_extension};
459 $option{output_comment} = 0 unless $option{use_comment};
460 $self->_delete_empty;
461 my @param;
462 $self->scan( sub {shift;
463 my ($item, $option) = @_;
464 my $r = 1;
465 ($r, $item) = $self->_stringify_param_check ($item, $option);
466 return unless $r;
467 my $comment = '';
468 if ($option->{output_comment} && ref $item->{comment}
469 && @{$item->{comment}} > 0) {
470 my @c;
471 for (@{$item->{comment}}) {
472 push @c, '('. $self->Message::Util::encode_ccontent ($_) .')';
473 }
474 $comment = ' '. join ' ', @c;
475 }
476 if ($item->{no_value}) {
477 push @param, Message::Util::quote_unsafe_string ($item->{attribute},
478 unsafe => $option->{parameter_no_value_attribute_unsafe_rule}).$comment;
479 } else {
480 my $xparam = 0;
481 my $attribute = $item->{attribute};
482 return unless length $attribute;
483 my $value = ''.$item->{value};
484 if ($attribute =~ /$REG{ $option->{parameter_attribute_unsafe_rule} }/) {
485 #return 0;
486 $attribute =~ s/($REG{NON_http_attribute_char})/sprintf('%%%02X', ord $1)/ge;
487 }
488 my %e;
489 if ($option->{output_parameter_extension}) {
490 if ($item->{charset}) {
491 %e = %$item;
492 } else {
493 %e = &{$self->{option}->{hook_encode_string}}
494 ($self, $value,
495 charset => $item->{output_charset} || $option->{encoding_after_encode},
496 current_charset => $option->{internal_charset},
497 language => $item->{language},
498 type => 'parameter/value');
499 }
500 $xparam = 1 if (length $e{value} > $option->{parameter_value_max_length})
501 || $e{charset}
502 || $e{language}
503 || $e{value} =~ /\x0D|\x0A/s
504 || $e{value} =~ /$REG{WSP}$REG{WSP}+/s
505 || ($option->{accept_coderange} eq '7bit'
506 && $e{value} =~ /[\x80-\xFF]/)
507 || ($option->{accept_coderange} ne 'binary'
508 && $e{value} =~ /\x00/)
509 ;
510 } else { ## Don't use paramext
511 if ($item->{charset}) { ## But parameter value is undecodable charset value
512 %e = %$item;
513 $xparam = 1;
514 } else {
515 %e = &{$self->{option}->{hook_encode_string}}
516 ($self, $value,
517 charset => $option->{encoding_after_encode},
518 current_charset => $option->{header_default_charset},
519 language => $item->{language},
520 type => 'parameter/value');
521 }
522 }
523 if ($xparam) {
524 if (length $e{value} > $option->{parameter_value_max_length}) {
525 for my $i (0..(length ($e{value})
526 /$option->{parameter_value_split_length})) {
527 my $v = substr ($e{value},
528 $i * $option->{parameter_value_split_length},
529 $option->{parameter_value_split_length});
530 if ($i == 0) {
531 $v
532 =~ s/($REG{NON_http_attribute_char})/sprintf('%%%02X', ord $1)/ge;
533 my $charset = Message::MIME::Charset::name_minimumize
534 ($e{charset} || $option->{header_default_charset}, $value);
535 push @param, sprintf q{%s*0*=%s'%s'%s%s}, $attribute,
536 $charset, $e{language}, $v, $comment;
537 } else { # $i > 0
538 if ($e{charset} || $v =~ /\x0A|\x0D/s
539 || ($option->{accept_coderange} ne 'binary' && $v =~ /\x00/)
540 || ($option->{accept_coderange} eq '7bit' && $v =~ /[\x80-\xFF]/)) {
541 $v =~ s/($REG{NON_http_attribute_char})/sprintf('%%%02X', ord $1)/ge;
542 push @param, sprintf q{%s*%d*=%s}, $attribute, $i, $v;
543 } else {
544 $v = Message::Util::quote_unsafe_string ($v,
545 unsafe => $option->{parameter_value_unsafe_rule});
546 $v = q{""} if length $v == 0;
547 push @param, sprintf q{%s*%d=%s}, $attribute, $i, $v;
548 }
549 }
550 }
551 } else {
552 unless ($e{charset}) {
553 $e{charset} = Message::MIME::Charset::name_minimumize
554 ($option->{header_default_charset}, $e{value});
555 }
556 $e{value}
557 =~ s/($REG{NON_http_attribute_char})/sprintf('%%%02X', ord $1)/ge;
558 push @param, sprintf q{%s*=%s'%s'%s%s}, $attribute,
559 $e{charset}, $e{language}, $e{value}, $comment;
560 }
561 } else {
562 $e{value} = Message::Util::quote_unsafe_string ($e{value},
563 unsafe => $option->{parameter_value_unsafe_rule});
564 $e{value} = q{""} if length $e{value} == 0;
565 push @param, sprintf '%s=%s%s', $attribute, $e{value}, $comment;
566 }
567 }
568 }, options => \%option );
569 join $option{separator}, @param;
570 }
571 *as_string = \&stringify;
572
573 ## $self->_stringify_param_check (\%item, \%option)
574 ## -- Checks parameter (and modify if necessary).
575 ## Returns either 1 (ok) or 0 (don't output)
576 sub _stringify_param_check ($\%\%) {
577 my $self = shift;
578 my ($item, $option) = @_;
579 (1, $item);
580 }
581
582 ## scan: Inherited
583
584 ## TODO: ...
585 sub _scan_sort ($\@) {
586 #my $self = shift;
587 @{$_[1]};
588 }
589
590 =item $option-value = $p->option ($option-name)
591
592 Gets option value.
593
594 =item $p->option ($option-name, $option-value, ...)
595
596 Set option value(s). You can pass multiple option name-value pair
597 as parameter when setting.
598
599 =cut
600
601 ## $self->_option_recursive (\%argv)
602 sub _option_recursive ($\%) {
603 my $self = shift;
604 my $o = shift;
605 for (@{$self->{ $self->{option}->{_HASH_NAME} }}) {
606 $_->{value}->option (%$o) if ref $_ && ref $_->{value};
607 }
608 }
609
610 ## value_type: Inherited
611
612 =item $clone = $p->clone ()
613
614 Returns a copy of the object.
615
616 =cut
617
618 ## Inherited
619
620 =head1 LICENSE
621
622 Copyright 2002 wakaba E<lt>w@suika.fam.cxE<gt>.
623
624 This program is free software; you can redistribute it and/or modify
625 it under the terms of the GNU General Public License as published by
626 the Free Software Foundation; either version 2 of the License, or
627 (at your option) any later version.
628
629 This program is distributed in the hope that it will be useful,
630 but WITHOUT ANY WARRANTY; without even the implied warranty of
631 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
632 GNU General Public License for more details.
633
634 You should have received a copy of the GNU General Public License
635 along with this program; see the file COPYING. If not, write to
636 the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
637 Boston, MA 02111-1307, USA.
638
639 =head1 CHANGE
640
641 See F<ChangeLog>.
642 $Date: 2002/07/22 02:42:17 $
643
644 =cut
645
646 1;

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24