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.52';
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   5554 use strict;
  1         2  
  1         32  
243 1     1   854 use CGI 'escapeHTML';
  1         33473  
  1         7  
244 1     1   134 use File::Basename;
  1         2  
  1         114  
245 1     1   7 use File::Spec;
  1         4  
  1         24  
246 1     1   5 use Fcntl qw(:DEFAULT :flock);
  1         2  
  1         390  
247 1     1   7 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
  1         2  
  1         73  
248 1     1   6 use Exporter;
  1         2  
  1         163  
249             @ISA = 'Exporter';
250             @EXPORT = 'contactform';
251             @EXPORT_OK = 'CFdie';
252              
253             BEGIN {
254             sub CFdie($) {
255 0     0 0   print "Status: 400 Bad Request\n";
256 0           print "Content-type: text/html\n\n

Error

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

Back');

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

$args->{thanks}

467            

$sent_to

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

'

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

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

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

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

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

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

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

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

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

Go back one page and try again.');

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

Back');

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