File Coverage

blib/lib/CGI/ContactForm.pm
Criterion Covered Total %
statement 28 289 9.6
branch 2 148 1.3
condition 0 92 0.0
subroutine 9 34 26.4
pod 0 18 0.0
total 39 581 6.7


line stmt bran cond sub pod time code
1             package CGI::ContactForm;
2              
3             $VERSION = '1.53';
4             # $Id: ContactForm.pm,v 1.76 2009/03/03 22:46:53 gunnarh Exp $
5              
6             =head1 NAME
7              
8             CGI::ContactForm - Generate a web contact form
9              
10             =head1 SYNOPSIS
11              
12             use CGI::ContactForm;
13              
14             contactform (
15             recname => 'John Smith',
16             recmail => 'john.smith@example.com',
17             styleurl => '/style/ContactForm.css',
18             );
19              
20             =head1 DESCRIPTION
21              
22             This module generates a contact form for the web when the routine C
23             is called from a CGI script. Arguments are passed to the module as a list of
24             key/value pairs.
25              
26             C sends a well formated (plain text format=flowed in accordance
27             with RFC 2646) email message, with the sender's address in the C header.
28              
29             By default the sender gets a C copy. If the email address stated by the
30             sender is invalid, by default the failure message is sent to the recipient address,
31             through which you know that you don't need to bother with a reply, at least not to
32             that address... However, by setting the C argument you can prevent the
33             sender copy from being sent.
34              
35             =head2 Arguments
36              
37             C takes the following arguments:
38              
39             Default value
40             =============
41             Compulsory
42             ----------
43             recname (none)
44             recmail (none)
45              
46             Optional
47             --------
48             smtp 'localhost'
49             styleurl (none)
50             returnlinktext 'Main Page'
51             returnlinkurl '/'
52             subject (none)
53             nocopy 0
54             bouncetosender 0
55             formtmplpath (none)
56             resulttmplpath (none)
57             maxsize 100 (KiB)
58             maxperhour 5 (messages per hour per host)
59             tempdir (none)
60             spamfilter '(?is:|\[/url]|https?:/(?:.+https?:/){3})' (Perl regex)
61              
62             Additional arguments, intended for forms at non-English sites
63             -------------------------------------------------------------
64             title 'Send email to'
65             namelabel 'Your name:'
66             emaillabel 'Your email:'
67             subjectlabel 'Subject:'
68             msglabel 'Message:'
69             reset 'Reset'
70             send 'Send'
71             erroralert 'Fields with %s need to be filled or corrected.'
72             marked 'marked labels'
73             thanks 'Thanks for your message!'
74             sent_to 'The message was sent to %s with a copy to %s.'
75             sent_to_short 'The message was sent to %s.'
76             encoding 'ISO-8859-1'
77              
78             =head2 Customization
79              
80             There are only two compulsory arguments. The example CGI script
81             C, that is included in the distribution, also uses the C
82             argument, assuming the use of the enclosed style sheet C.
83             That results in a decently styled form with a minimum of effort.
84              
85             If the default value C isn't sufficient to identify the local SMTP
86             server, you may need to explicitly state its host name or IP address via the
87             C argument.
88              
89             As you can see from the list over available arguments, all the text strings
90             can be changed, and as regards the presentation, you can of course edit the
91             style sheet to your liking.
92              
93             If you want to modify the HTML markup, you can have C make
94             use of one or two templates. The enclosed example templates
95             C and C can be activated via
96             the C respective C arguments, and used as a
97             starting point for a customized markup.
98              
99             =head2 Spam prevention
100              
101             Behind the scenes C performs a few checks aiming to complicate
102             and/or discourage abuse in the form of submitted spam messages.
103              
104             =over 4
105              
106             =item *
107              
108             The number of messages that can be sent from the same host is restricted. The
109             default is 5 messages per hour.
110              
111             =item *
112              
113             A customizable spamfilter is applied to the body of the message. By default it
114             allows max 3 URLs that start with C or C, and it rejects
115             submissions with C/aE> or C<[/url]> in the message body.
116              
117             =item *
118              
119             When sending a message, the request must include a cookie.
120              
121             =back
122              
123             The thought is that normal use, i.e. establishing contact with somebody,
124             should typically not be affected by those checks.
125              
126             =head1 INSTALLATION
127              
128             =head2 Installation with Makefile.PL
129              
130             Type the following:
131              
132             perl Makefile.PL
133             make
134             make install
135              
136             =head2 Manual Installation
137              
138             =over 4
139              
140             =item *
141              
142             Download the distribution file and extract the contents.
143              
144             =item *
145              
146             Designate a directory as your local library for Perl modules, for instance
147              
148             /www/username/cgi-bin/lib
149              
150             =item *
151              
152             Create the directory C, and upload
153             C to that directory.
154              
155             =item *
156              
157             Create the directory C, and
158             upload C to that directory.
159              
160             =item *
161              
162             In the CGI scripts that use this module, include a line that tells Perl
163             to look for modules also in your local library, such as
164              
165             use lib '/www/username/cgi-bin/lib';
166              
167             =back
168              
169             =head2 Other Installation Matters
170              
171             If you have previous experience from installing CGI scripts, making
172             C (or whichever name you choose) work should be easy.
173             Otherwise, this is a B short lesson:
174              
175             =over 4
176              
177             =item 1.
178              
179             Upload the CGI file in ASCII transfer mode to your C.
180              
181             =item 2.
182              
183             Set the file permission 755 (chmod 755).
184              
185             =back
186              
187             If that doesn't do it, there are many CGI tutorials for beginners
188             available on the web. This is one example:
189              
190             http://my.execpc.com/~keithp/bdlogcgi.htm
191              
192             On some servers, the CGI file must be located in the C directory
193             (or in a C subdirectory). At the same time it's worth noting,
194             that the style sheet typically needs to be located somewhere outside the
195             C.
196              
197             =head1 DEPENDENCIES
198              
199             C requires the non-standard modules L and
200             L.
201              
202             =head1 AUTHENTICATION
203              
204             If you have access to a mail server that is configured to automatically
205             accept sending messages from a CGI script to any address, you don't need
206             to worry about authentication. Otherwise you need to somehow authenticate
207             to the server, for instance by adding something like this right after the
208             C line in C:
209              
210             %Mail::Sender::default = (
211             auth => 'LOGIN',
212             authid => 'username',
213             authpwd => 'password',
214             );
215              
216             C is the SMTP authentication protocol. Common protocols are C
217             and C. You may need help from the mail server's administrator to
218             find out which protocol and username/password pair to use.
219              
220             If there are multiple forms, a more convenient way to deal with a need
221             for authentication may be to make use of the C file that
222             is included in the distribution. You just edit it and upload it to the
223             same directory as the one where C is located.
224              
225             See the L documentation for further guidance.
226              
227             =head1 AUTHOR, COPYRIGHT AND LICENSE
228              
229             Copyright (c) 2003-2019 Gunnar Hjalmarsson
230             http://www.gunnar.cc/cgi-bin/contact.pl
231              
232             This module is free software; you can redistribute it and/or modify it
233             under the same terms as Perl itself.
234              
235             =head1 SEE ALSO
236              
237             L,
238             L
239              
240             =cut
241              
242 1     1   5790 use strict;
  1         2  
  1         93  
243 1     1   8 use File::Basename;
  1         2  
  1         137  
244 1     1   8 use File::Spec;
  1         2  
  1         23  
245 1     1   5 use Fcntl qw(:DEFAULT :flock);
  1         2  
  1         380  
246 1     1   7 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
  1         2  
  1         74  
247 1     1   14 use Exporter;
  1         2  
  1         196  
248             @ISA = 'Exporter';
249             @EXPORT = 'contactform';
250             @EXPORT_OK = 'CFdie';
251              
252             BEGIN {
253             sub CFdie($) {
254 0     0 0   print "Status: 400 Bad Request\n";
255 0           print "Content-type: text/html\n\n

Error

\n", shift;
256 0 0         if ( $ENV{MOD_PERL} ) {
257 0 0         if ( $] < 5.006 ) {
258 0           require Apache;
259 0           Apache::exit();
260             }
261             }
262 0           exit 1;
263             }
264              
265 1     1   71 eval "use CGI 'escapeHTML'";
  1     1   921  
  1         34574  
  1         6  
266 1 50       107 CFdie($@) if $@;
267 1     1   61 eval "use Mail::Sender";
  1         884  
  1         121689  
  1         41  
268 1 50       4377 CFdie($@) if $@;
269             }
270              
271             sub contactform {
272 0     0 0   local $^W = 1; # enables warnings
273 0           my ($error, $in) = {};
274 0           my $time = time;
275 0 0         my $host = $ENV{'REMOTE_ADDR'} or die "REMOTE_ADDR not set\n";
276 0           umask 0;
277 0           my $args = &arguments;
278 0 0         if ($ENV{REQUEST_METHOD} eq 'POST') {
279 0           checktimestamp( $args->{tempdir}, $time );
280 0           $in = formdata( $args->{maxsize} );
281 0 0         if (formcheck($in, $args->{subject}, $error) == 0) {
282 0           checkspamfilter( $in->{message}, $args->{spamfilter} );
283 0           checkmaxperhour($args, $time, $host);
284 0           eval { mailsend($args, $in, $host) };
  0            
285 0 0         CFdie( escapeHTML(my $msg = $@) ) if $@;
286 0           return;
287             }
288             } else {
289 0           settimestamp( $args->{tempdir}, $time );
290             }
291 0           formprint($args, $in, $error);
292             }
293              
294             sub arguments {
295 0     0 0   my %defaults = (
296             recname => '',
297             recmail => '',
298             smtp => 'localhost',
299             styleurl => '',
300             returnlinktext => 'Main Page',
301             returnlinkurl => '/',
302             subject => '',
303             nocopy => 0,
304             bouncetosender => 0,
305             formtmplpath => '',
306             resulttmplpath => '',
307             maxsize => 100,
308             maxperhour => 5,
309             tempdir => '',
310             spamfilter => '(?is:|\[/url]|https?:/(?:.+https?:/){3})',
311             title => 'Send email to',
312             namelabel => 'Your name:',
313             emaillabel => 'Your email:',
314             subjectlabel => 'Subject:',
315             msglabel => 'Message:',
316             reset => 'Reset',
317             send => 'Send',
318             erroralert => 'Fields with %s need to be filled or corrected.',
319             marked => 'marked labels',
320             thanks => 'Thanks for your message!',
321             sent_to => 'The message was sent to %s with a copy to %s.',
322             sent_to_short => 'The message was sent to %s.',
323             encoding => 'ISO-8859-1',
324             );
325 0           my $error;
326 0 0         if ( @_ % 2 ) {
327 0           $error .= "Odd number of elements in argument list:\n"
328             . " The contactform() function expects a number of key/value pairs.\n";
329             }
330 0           my %args = ( %defaults, @_ );
331 0           for (qw/recname recmail/) {
332 0 0         $error .= "The compulsory argument '$_' is missing.\n" unless $args{$_};
333             }
334 0           for (keys %args) {
335 0 0         $error .= "Unknown argument: '$_'\n" unless defined $defaults{$_};
336             }
337 0 0 0       if ($args{recmail} and emailsyntax($args{recmail})) {
338 0           $error .= "'$args{recmail}' is not a valid email address.\n";
339             }
340 0 0 0       unless ($args{tempdir}) {
341 0 0 0       unless (-d $CGITempFile::TMPDIRECTORY and -w _ and -x _) {
      0        
342 0           $error .= "You need to state a temporary directory via the 'tempdir' argument.\n";
343             }
344             } elsif (!(-d $args{tempdir} and -w _ and -x _)) {
345             $error .= "'$args{tempdir}' is not a writable directory.\n";
346             }
347 0           for ('formtmplpath', 'resulttmplpath') {
348 0 0 0       if ($args{$_} and !-f $args{$_}) {
349 0           $error .= "Argument '$_': Can't find the file $args{$_}\n";
350             }
351             }
352             {
353 0     0     local $SIG{__WARN__} = sub { die $_[0] };
  0            
  0            
354 0           eval { $args{spamfilter} = qr($args{spamfilter}) };
  0            
355 0 0         if ( $@ ) {
356 0           my $mod_path = $INC{'CGI/ContactForm.pm'};
357 0           $@ =~ s/ at $mod_path.+//;
358 0           $error .= "Argument 'spamfilter': " . escapeHTML(my $err = $@);
359             }
360             }
361              
362 0 0         CFdie("
$error" . <<'EXAMPLE' 
363              
364             Example:
365              
366             contactform (
367             recname => 'John Smith',
368             recmail => 'john.smith@example.com',
369             );
370             EXAMPLE
371              
372             ) if $error;
373              
374 0           \%args;
375             }
376              
377             sub formdata {
378 0     0 0   my $max = shift;
379 0 0         if ($ENV{CONTENT_LENGTH} > 1024 * $max) {
380 0           CFdie("The message size exceeds the $max KiB limit.\n"
381             . '

Back');

382             }
383              
384             # create hash reference to the form data
385 0           my $in = new CGI->Vars;
386              
387             # trim whitespace in message headers
388 0           for (qw/name email subject/) {
389 0           $in->{$_} =~ s/^\s+//;
390 0           $in->{$_} =~ s/\s+$//;
391 0           $in->{$_} =~ s/\s+/ /g;
392             }
393              
394 0           $in;
395             }
396              
397             sub formcheck {
398 0     0 0   my ($in, $defaultsubject, $error) = @_;
399 0 0         for (qw/name message/) { $error->{$_} = ' class="error"' unless $in->{$_} }
  0            
400 0 0 0       $error->{subject} = ' class="error"' unless $in->{subject} or $defaultsubject;
401 0 0         $error->{email} = ' class="error"' if emailsyntax( $in->{email} );
402 0 0         %$error ? 1 : 0;
403             }
404              
405             sub emailsyntax {
406 0 0   0 0   return 1 unless my ($localpart, $domain) = shift =~ /^(.+)@(.+)/;
407 0           my $atom = '[^[:cntrl:] "(),.:;<>@\[\\\\\]]+';
408 0           my $qstring = '"(?:\\\\.|[^"\\\\\s]|[ \t])*"';
409 0           my $word = qr($atom|$qstring);
410 0 0         return 1 unless $localpart =~ /^$word(?:\.$word)*$/;
411 0 0         $domain =~ /^$atom(?:\.$atom)+$/ ? 0 : 1;
412             }
413              
414             sub mailsend {
415 0     0 0   my ($args, $in, $host) = @_;
416              
417             # Extra headers
418 0           my @extras = "X-Originating-IP: [$host]";
419 0 0         if ( my $agent = $ENV{'HTTP_USER_AGENT'} ) {
420 0           my @lines;
421 0           while ( $agent =~ /(.{1,66})(?:\s+|$)/g ) {
422 0           push @lines, $1;
423             }
424 0           push @extras, 'User-Agent: ' . join("\r\n\t", @lines);
425             }
426 0 0         push @extras, "Referer: $ENV{'HTTP_REFERER'}" if $ENV{'HTTP_REFERER'};
427 0           push @extras, "X-Mailer: CGI::ContactForm $VERSION at $ENV{HTTP_HOST}";
428              
429             # Make message format=flowed (RFC 2646)
430 0           eval "use Encode 2.23 ()";
431 0 0         my $convert = $@ ? 0 : 1;
432 0 0         $in->{message} = Encode::decode( $args->{encoding}, $in->{message} ) if $convert;
433 0           $in->{message} = reformat( $in->{message}, { max_length => 66, opt_length => 66 } );
434 0 0         $in->{message} = Encode::encode( $args->{encoding}, $in->{message} ) if $convert;
435 0           push @extras, "Content-type: text/plain; charset=$args->{encoding}; format=flowed";
436              
437             # Send message
438 0           $Mail::Sender::NO_X_MAILER = 1;
439 0           $Mail::Sender::SITE_HEADERS = join "\r\n", @extras;
440             ref (new Mail::Sender -> MailMsg( {
441             smtp => $args->{smtp},
442             encoding => ( $in->{message} =~ /[[:^ascii:]]/ ? 'quoted-printable' : '7bit' ),
443             from => ( $args->{bouncetosender} ? $in->{email} : $args->{recmail} ),
444             fake_from => namefix( $in->{name}, $args->{encoding} ) . " <$in->{email}>",
445             to => namefix( $args->{recname}, $args->{encoding} ) . " <$args->{recmail}>",
446             bcc => ( $args->{nocopy} ? '' : $in->{email} ),
447             subject => mimeencode( $in->{subject}, $args->{encoding} ),
448             msg => $in->{message},
449 0 0         } )) or die "Cannot send mail. $Mail::Sender::Error\n";
    0          
    0          
    0          
450              
451             # Print resulting page
452 0           my @resultargs = qw/recname returnlinktext returnlinkurl title thanks/;
453 0           $args->{$_} = escapeHTML( $args->{$_} ) for @resultargs;
454             my $sent_to = sprintf escapeHTML( $args->{nocopy} ? $args->{sent_to_short} : $args->{sent_to} ),
455 0 0         "$args->{recname}", '' . escapeHTML( $in->{email} ) . '';
456 0           $args->{returnlinkurl} =~ s/ /%20/g;
457 0 0         if ( $args->{resulttmplpath} ) {
458 0           my %result_vars;
459 0           $result_vars{style} = stylesheet( $args->{styleurl} );
460 0           $result_vars{sent_to} = \$sent_to;
461 0           $result_vars{$_} = \$args->{$_} for @resultargs;
462 0           templateprint($args->{resulttmplpath}, $args->{encoding}, %result_vars);
463             } else {
464 0           headprint($args);
465              
466 0           print <
467            

$args->{thanks}

468            

$sent_to

469            
470            
471            
472             RESULT
473             }
474             }
475              
476             sub formprint {
477 0     0 0   my ($args, $in, $error) = @_;
478 0   0       my $scriptname = basename( $0 or $ENV{SCRIPT_FILENAME} );
479             my $erroralert = %$error ? '

'

480             . sprintf( escapeHTML( $args->{erroralert} ), ''
481 0 0         . "\n" . escapeHTML( $args->{marked} ) . '' ) . '

' : '';
482 0           my @formargs = qw/recname returnlinktext returnlinkurl title namelabel
483             emaillabel subjectlabel msglabel reset send/;
484 0           $args->{$_} = escapeHTML( $args->{$_} ) for @formargs;
485 0           $args->{returnlinkurl} =~ s/ /%20/g;
486 0   0       $in->{subject} ||= $args->{subject};
487 0           for (qw/name email subject message/) {
488 0 0         $in->{$_} = $in->{$_} ? escapeHTML( $in->{$_} ) : '';
489 0   0       $error->{$_} ||= '';
490             }
491              
492             # Prevent horizontal scrolling in NS4
493             my $softwrap = ($ENV{HTTP_USER_AGENT} and $ENV{HTTP_USER_AGENT} =~ /Mozilla\/[34]/
494 0 0 0       and $ENV{HTTP_USER_AGENT} !~ /MSIE|Opera/) ? ' wrap="soft"' : '';
495              
496 0 0         if ( $args->{formtmplpath} ) {
497 0           my %form_vars;
498 0           $form_vars{style} = stylesheet( $args->{styleurl} );
499 0           $form_vars{scriptname} = \$scriptname;
500 0           $form_vars{erroralert} = \$erroralert;
501 0           $form_vars{$_} = \$args->{$_} for @formargs;
502 0           for (qw/name email subject message/) {
503 0           $form_vars{$_} = \$in->{$_};
504 0           $form_vars{$_.'error'} = \$error->{$_};
505             }
506 0           $form_vars{softwrap} = \$softwrap;
507 0           templateprint($args->{formtmplpath}, $args->{encoding}, %form_vars);
508             } else {
509 0           headprint($args);
510              
511 0           print <
512            
513            
514            
515            

$args->{title} $args->{recname}

516            
517             {name}>$args->{namelabel}

518             value="$in->{name}" size="20" /> 
519             {email}>$args->{emaillabel}

520             value="$in->{email}" size="20" />
521            
522             {subject}>$args->{subjectlabel}

523            
524            
525             {message}>$args->{msglabel}

526            
527            
528            
529            
530            
531            
532             $erroralert
533               
534            
535            
536            
537             $args->{returnlinktext}

538            
539            
540            
541            
542            
543             FORM
544             }
545             }
546              
547             sub headprint {
548 0     0 0   my $args = shift;
549 0           print "Content-type: text/html; charset=$args->{encoding}\n\n";
550 0           print <
551            
552             "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
553            
554            
555             $args->{title} $args->{recname}
556            
559 0           ${ stylesheet( $args->{styleurl} ) }
560            
561            
562             HEAD
563             }
564              
565             sub stylesheet {
566 0   0 0 0   my $url = shift || return \'';
567 0           $url =~ s/ /%20/g;
568 0           \('');
569             }
570              
571             sub templateprint {
572 0     0 0   my ($template, $encode, %tmpl_vars) = @_;
573 0           my $error;
574 0 0         open FH, "< $template" or die "Can't open $template\n$!";
575 0           my $output = do { local $/; };
  0            
  0            
576 0           close FH;
577 0           $output =~ s[<(?:!--\s*)?tmpl_var\s*(?:name\s*=\s*)?
578             (?:"([^">]*)"|'([^'>]*)'|([^\s=>]*))
579             \s*(?:--)?>][
580 0 0         if ( $tmpl_vars{lc $+} ) {
581 0           ${ $tmpl_vars{lc $+} };
  0            
582             } else {
583 0           $error .= "Unknown template variable: '$+'\n";
584             }
585             ]egix;
586 0 0         CFdie("
$error") if $error; 
587 0           print "Content-type: text/html; charset=$encode\n\n";
588 0           print $output;
589             }
590              
591             sub namefix {
592 0     0 0   my $name = $_[0];
593 0 0         if ($name =~ /[[:^ascii:]]/) {
594 0           return &mimeencode;
595             }
596 0 0         if ($name =~ /[^ \w]/) {
597 0           $name =~ tr/"/'/;
598 0           $name = qq{"$name"};
599             }
600 0           $name;
601             }
602              
603             sub mimeencode {
604 0     0 0   my ($str, $enc) = @_;
605 0 0         return $str unless $str =~ /[[:^ascii:]]/;
606 0           my @parts;
607 0           while ( $str =~ /(.{1,40}.*?(?:\s|$))/g ) {
608 0           my $part = $1;
609 0           push @parts, MIME::QuotedPrint::encode($part, '');
610             }
611 0           join "\r\n\t", map { "=?$enc?Q?$_?=" } @parts;
  0            
612             }
613              
614             sub reformat {
615             # This subroutine was initially copied from Text::Flowed v0.14, written by
616             # Philip Mak. It has undergone a couple of changes since.
617              
618             # Help functions in Text::Flowed nested into this copy of reformat()
619 0     0     sub _num_quotes { $_[0] =~ /^(>*)/; length $1 }
  0            
620 0     0     sub _unquote { my $line = shift; $line =~ s/^(>+)//g; $line }
  0            
  0            
621             sub _flowed {
622 0     0     my $line = shift;
623             # Lines with only spaces in them are not considered flowed
624             # (heuristic to recover from sloppy user input)
625 0 0         return 0 if $line =~ /^ *$/;
626 0           $line =~ / $/;
627             }
628 0     0     sub _trim { local *_ = \shift; s/ +$//g; $_ }
  0            
  0            
629             sub _stuff {
630 0     0     my ($text, $num_quotes) = @_;
631 0 0 0       if ($text =~ /^ / || $text =~ /^>/ || $text =~ /^From / || $num_quotes > 0) {
      0        
      0        
632 0           return " $text";
633             }
634 0           $text;
635             }
636 0     0     sub _unstuff { local *_ = \shift; s/^ //; $_ }
  0            
  0            
637              
638 0     0 0   my @input = split "\n", $_[0];
639 0           my $args = $_[1];
640 0   0       $args->{max_length} ||= 79;
641 0   0       $args->{opt_length} ||= 72;
642 0           my @output = ();
643              
644             # Process message line by line
645 0           while (@input) {
646             # Count and strip quote levels
647 0           my $line = shift @input;
648 0           my $num_quotes = _num_quotes($line);
649 0           $line = _unquote($line);
650              
651             # Should we interpret this line as flowed?
652 0 0 0       if ( !$args->{fixed} || ( $args->{fixed} == 1 && $num_quotes ) ) {
      0        
653 0           $line = _unstuff($line);
654             # While line is flowed, and there is a next line, and the
655             # next line has the same quote depth
656 0   0       while (_flowed($line) && @input && _num_quotes($input[0]) == $num_quotes) {
      0        
657             # Join the next line
658 0           $line .= _unstuff(_unquote(shift @input));
659             }
660             }
661             # Ensure line is fixed, since we joined all flowed lines
662 0           $line = _trim($line);
663              
664             # Increment quote depth if we're quoting
665 0 0         $num_quotes++ if $args->{quote};
666              
667 0 0 0       if ( !( defined $line and length $line ) ) {
    0          
668             # Line is empty
669 0           push @output, '>' x $num_quotes;
670             } elsif (length($line) + $num_quotes <= $args->{max_length} - 1) {
671             # Line does not require rewrapping
672 0           push @output, '>' x $num_quotes . _stuff($line, $num_quotes);
673             } else {
674             # Rewrap this paragraph
675 0   0       while ( defined $line and length $line ) {
676             # Stuff and re-quote the line
677 0           $line = '>' x $num_quotes . _stuff($line, $num_quotes);
678              
679             # Set variables used in regexps
680 0           my $min = $num_quotes + 1;
681 0           my $opt1 = $args->{opt_length} - 1;
682 0           my $max1 = $args->{max_length} - 1;
683 0 0 0       if ( length($line) <= $args->{opt_length} ) {
    0 0        
684             # Remaining section of line is short enough
685 0           push @output, $line;
686 0           last;
687             } elsif ( $line =~ /^(.{$min,$opt1}) (.*)/ ||
688             $line =~ /^(.{$min,$max1}) (.*)/ || $line =~ /^(.{$min,}) (.*)/ ) {
689             # 1. Try to find a string as long as opt_length.
690             # 2. Try to find a string as long as max_length.
691             # 3. Take the first word.
692 0           push @output, "$1 ";
693 0           $line = $2;
694             } else {
695             # One excessively long word left on line
696 0           push @output, $line;
697 0           last;
698             }
699             }
700             }
701             }
702              
703 0           join("\n", @output)."\n";
704             }
705              
706             sub checktimestamp {
707 0     0 0   my ($tempdir, $time) = @_;
708 0   0       $tempdir ||= $CGITempFile::TMPDIRECTORY;
709 0           my $cookie;
710 0 0 0       unless ( $ENV{HTTP_COOKIE} and ($cookie) = $ENV{HTTP_COOKIE} =~ /\bContactForm_time=(\d+)/ ) {
711 0           CFdie("Your browser is set to refuse cookies.
\n"
712             . "Change that setting to accept at least session cookies, and try again.\n");
713             }
714 0 0         open FH, File::Spec->catfile( $tempdir, 'ContactForm_time' )
715             or die "Couldn't open timestamp file: $!";
716 0           chomp( my @timestamps = );
717 0 0         close FH or die $!;
718 0 0 0       if ( $cookie + 7200 < $time or ! grep $cookie eq $_, @timestamps ) {
719 0           settimestamp($tempdir, $time);
720 0           CFdie("Timeout due to more than an hour of inactivity.\n"
721             . '

Go back one page and try again.');

722             }
723             }
724              
725             sub settimestamp {
726 0     0 0   my ($tempdir, $time) = @_;
727 0   0       $tempdir ||= $CGITempFile::TMPDIRECTORY;
728              
729 0 0         sysopen FH, File::Spec->catfile( $tempdir, 'ContactForm_time' ), O_RDWR|O_CREAT
730             or die "Couldn't open timestamp file: $!";
731 0 0         flock FH, LOCK_EX or die $!;
732 0           chomp( my @timestamps = );
733 0 0         sysseek FH, 0, 0 or die $!;
734 0 0 0       if ( @timestamps == 2 && $time > $timestamps[0] + 3600 or @timestamps == 1 ) {
    0 0        
735 0 0         truncate FH, 0 or die $!;
736 0           print FH join( "\n", $time, $timestamps[0] ), "\n";
737 0           print "Set-cookie: ContactForm_time=$time\n";
738             } elsif ( @timestamps == 0 ) {
739 0 0         truncate FH, 0 or die $!;
740 0           print FH "$time\n";
741 0           print "Set-cookie: ContactForm_time=$time\n";
742             } else {
743 0           print "Set-cookie: ContactForm_time=$timestamps[0]\n";
744             }
745 0 0         close FH or die $!;
746             }
747              
748             sub checkspamfilter {
749 0     0 0   my ($msg, $filter) = @_;
750 0 0 0       if ( $filter and $msg =~ /$filter/ ) {
751 0           CFdie("The message was trapped in a spam filter and not sent.\n"
752             . "You may want to try again with a modified message body.\n"
753             . '

Back');

754             }
755             }
756              
757             sub checkmaxperhour {
758 0     0 0   my ($args, $time, $host) = @_;
759 0   0       my $tempdir = $args->{tempdir} || $CGITempFile::TMPDIRECTORY;
760 0           my (@senders, %senders);
761              
762 0 0         sysopen FH, File::Spec->catfile( $tempdir, 'ContactForm_sent' ), O_RDWR|O_CREAT
763             or die "Couldn't open request file: $!";
764 0 0         flock FH, LOCK_EX or die $!;
765 0           while ( ) {
766 0           my ($timestamp, $ip) = /^(\d+)\t(.+)/;
767 0 0         next if $timestamp < $time - 3600;
768 0           push @senders, $_;
769 0           $senders{$ip}++;
770             }
771 0           push @senders, "$time\t$host\n";
772 0           $senders{$host}++;
773 0 0         seek FH, 0, 0 or die $!;
774 0 0         truncate FH, 0 or die $!;
775 0           print FH @senders;
776 0 0         close FH or die $!;
777              
778 0 0         if ( $senders{$host} > $args->{maxperhour} ) {
779 0           CFdie('Too many send attempts from the same host. You may want to try later.');
780             }
781             }
782              
783             1;
784