File Coverage

blib/lib/CGI/ContactForm.pm
Criterion Covered Total %
statement 26 287 9.0
branch 1 146 0.6
condition 0 92 0.0
subroutine 9 34 26.4
pod 0 18 0.0
total 36 577 6.2


line stmt bran cond sub pod time code
1             package CGI::ContactForm;
2              
3             $VERSION = '1.50';
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 DEPENDENCY
198              
199             C requires the non-standard module
200             L. If C needs to be installed
201             manually, you shall create C and upload
202             C to that directory.
203              
204             =head1 AUTHENTICATION
205              
206             If you have access to a mail server that is configured to automatically
207             accept sending messages from a CGI script to any address, you don't need
208             to worry about authentication. Otherwise you need to somehow authenticate
209             to the server, for instance by adding something like this right after the
210             C line in C:
211              
212             %Mail::Sender::default = (
213             auth => 'LOGIN',
214             authid => 'username',
215             authpwd => 'password',
216             );
217              
218             C is the SMTP authentication protocol. Common protocols are C
219             and C. You may need help from the mail server's administrator to
220             find out which protocol and username/password pair to use.
221              
222             If there are multiple forms, a more convenient way to deal with a need
223             for authentication may be to make use of the C file that
224             is included in the distribution. You just edit it and upload it to the
225             same directory as the one where C is located.
226              
227             See the L documentation for further guidance.
228              
229             =head1 AUTHOR, COPYRIGHT AND LICENSE
230              
231             Copyright (c) 2003-2009 Gunnar Hjalmarsson
232             http://www.gunnar.cc/cgi-bin/contact.pl
233              
234             This module is free software; you can redistribute it and/or modify it
235             under the same terms as Perl itself.
236              
237             =head1 SEE ALSO
238              
239             L,
240             L
241              
242             =cut
243              
244 1     1   4487 use strict;
  1         2  
  1         25  
245 1     1   706 use CGI 'escapeHTML';
  1         26851  
  1         5  
246 1     1   113 use File::Basename;
  1         2  
  1         99  
247 1     1   6 use File::Spec;
  1         2  
  1         19  
248 1     1   4 use Fcntl qw(:DEFAULT :flock);
  1         1  
  1         311  
249 1     1   6 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
  1         1  
  1         57  
250 1     1   6 use Exporter;
  1         1  
  1         132  
251             @ISA = 'Exporter';
252             @EXPORT = 'contactform';
253             @EXPORT_OK = 'CFdie';
254              
255             BEGIN {
256             sub CFdie($) {
257 0     0 0   print "Status: 400 Bad Request\n";
258 0           print "Content-type: text/html\n\n

Error

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

Back');

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

$args->{thanks}

469            

$sent_to

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

'

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

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

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

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

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

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

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

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

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

Go back one page and try again.');

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

Back');

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