Thread Hilfe mit Formular Script: CGI - Perl ### Formular (30 answers)
Opened by Strat at 2004-01-16 12:37

Dubu
 2004-01-16 12:42
#28174 #28174
User since
2003-08-04
2145 Artikel
ModeratorIn + EditorIn

user image
[quote=Captain Future,16.01.2004, 01:24]
Bin noch ein Perl-Newbie habe hier mein erstes Script gepostet.
[/quote]
Fuer einen Neuling ist der Code schon recht gut. :)

Trotzdem wuerde ich dir eher raten, einen guten(! ) FormMailer anzupassen (siehe z.B. NMS-FormMailer), statt hier in diverse Fallen zu geraten. Eine gute Uebung ist das Selberschreiben sicherlich, aber fuer den Produktiveinsatz halte ich den Code so fuer zu gefaehrlich.

Quote
Leider sind darin noch einige Fehler, da ich sozusagen einen Köpfler in Perl gemacht habe.
Da ich keine Perlumgebung @ Home laufen habe ist das Script also so runtergeschrieben.

Mein Tipp: Installier dir Perl! Selbst wenn du keinen Webserver laufen hast, kannst du zumindest deine Programme auf syntaktische Korrektheit pruefen.

Quote
Code: (dl )
1
2
#!/usr/bin/perl
# Pfad zu Prel

Hier fehlen auf jeden Fall
use strict;
use warnings;

Diese sind zwar nicht syntaktisch notwendig, erleichtern aber die Entwicklung ungemein, wenn du die Fehler- und Warnmeldungen erhaeltst. Fuer "strict" muessen allerdings saemtliche Variablen deklariert (oder voll qualifiziert angegeben) werden.

Quote
Code: (dl )
1
2
3
...
# Pfad zum Date-Kommando:
$date = '/usr/bin/date';

Dieses Date-Kommando brauchst du danach nie wieder.
Den Variablennamen dagegen verwendest du unten nochmal!

Quote
Code: (dl )
1
2
3
...
# E-Mail Adresse des Empfängers (mit \@):
@email_list = ("info\@xyz4711.de", "webmaster\@xyz4711.de");

Wenn man statt doppelter einfache Anfuehrungszeichen nimmt, kann man sich das Escapen der '@' sparen.

Quote
Code: (dl )
1
2
3
4
5
6
7
###################################################################
# Perl-Programm ist für Übergabemethode POST sowie GET geeignet:

if ($ENV{"REQUEST_METHODE"} eq "POST")
  {
read(STDIN, $daten, $ENV{"CONTENT_LENGTH"});
...

Auahauaha.
Nein, bitte nicht diese kaputten selbstgeschriebenen Formularfunktionen! Nimmt CGI.pm, bitte! Es gehoert seit Perl 5.6.1 zum Standardumfang:

Code: (dl )
1
2
use CGI;
my %FORM = CGI::Vars();


Quote
Code: (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
###################################################################
# ------------------ Formular-Elemente ------------------

$recipient = $FORM{'recipient'};
$recipient_bcc = $FORM{'recipient_bcc'};
$subject = $FORM{'subject'};
$CB01 = $FORM{'Kacheloefen'};
$CB02 = $FORM{'Kaminoefen'};
$CB03 = $FORM{'Herde'};
$CB04 = $FORM{'Heizkamine'};
$input05 = $FORM{'Vorname'};
$input06 = $FORM{'Nachname'};
$input07 = $FORM{'Strasse'};
$input08 = $FORM{'PLZ'};
$input09 = $FORM{'Ort'};
$input10 = $FORM{'Land'};
$input11 = $FORM{'Telefon'};
$input12 = $FORM{'Telefax'};
$input13 = $FORM{'eMail'};
$input14 = $FORM{'Betreff'};
$input15 = $FORM{'Text'};

Warum kopierst du hier die Formularinhalte von einem Hash mit sinnvoll benannten Feldern in durchnummerierte Variablen? Ich finde, der Code wird dadurch eher weniger verstaendlich, aber okay.

Quote
Code: (dl )
1
2
3
4
###################################################################

$input15 =~ s/\cM//g;
$input15 =~ s/\n/  /g;

Die Benutzereingaben zu bearbeiten ist eine gute Idee. Aber warum nur $input15? Der ist gerade am wenigsten gefaehrlich. $input05 und $input06 faende ich hier wichtiger, weil diese direkt im Header der Mail landen und durch Manipulation dieser Felder der Formmailer missbraucht werden kann.

Quote
Code: (dl )
1
2
3
4
5
6
###################################################################
# ------- Check auf fehlende und falsche Eingabe -------
&missing(Vorname) unless $input05;
&missing(Nachname) unless $input06;
&missing(eMail) unless $input13;
&missing(Text) unless $input15;

Hier sollte das Argument der Funktionsaufrufe jeweils in Anfuehrungszeichen stehen, da es sich um Strings handelt.

Also:
Code: (dl )
1
2
missing('Vorname') unless $input05;
...



Quote
Code: (dl )
1
2
### Zeile 74 ###
&falsch(Fehler 01: Bitte eine gueltige Postleitzahl(PLZ) eingeben !) unless ($input08 =~ m/\d{5});

Hier gilt das gleiche: Argument der Funktion in Anfuehrungszeichen.
Ausserdem ist hier die Regex nicht beendet und wahrscheinlich auch falshc. Ich vermute, du moechtest pruefen, ob $input08 exakt aus fuenf Ziffern besteht. Dann lautete es so:
Code: (dl )
falsch ("Fehler 01: Bitte eine gueltige Postleitzahl(PLZ) eingeben!") unless $input08 =~ m/^\d{5}$/;


Quote
Code: (dl )
1
2
### Zeile 75 ###
&falsch(Fehler 02: Bitte eine gueltige eMail Adresse eingeben !) unless ($input13 =~ m/^[a-zA-Z0-9][-\_\.]*[a-zA-Z0-9]\@[a-zA-Z0-9][-\_\.]*[a-zA-Z0-9][\.]([a-zA-Z]){2,4}$/);

Wie oben, nur dass zusaetzlich die Regex fuer die E-Mail-Adresse alles andere als vollstaendig ist. Nimm Email::Valid oder Mail::RFC822::Address oder aehnliches fuer diesen Test.

Quote
Code: (dl )
&falsch(Fehler 03: Bitte eine gueltige Telefonnummer eingeben !) unless ($input11 =~ m/^[0]\d+.\d+$/);

Siehe oben.

Quote
Code: (dl )
&falsch(Fehler 04: Bitte eine gueltige Telefaxnummer eingeben !) unless ($input12 =~ m/^[0]\d+.\d+$/);

Die Regex bedeutet: "Eine Null am Anfang, gefolgt von beliebig vielen Ziffern, gefolgt von einem beliebigen Zeichen, gefolgt von beliebig vielen Ziffern und Ende des Strings." War das so gemeint?

Quote
Code: (dl )
&falsch(Fehler 05: Bitte einen gueltigen Strassennamen eingeben !) unless ($input07 =~ m/\w?\-/);

Diese Regex bedeutet: "Der String enthaelt evtl. ein Wortzeichen (also a-z, A-Z, 0-9 oder '_' ), auf jeden Fall gefolgt von einem Bindestrich."

Quote
Code: (dl )
&falsch(Fehler 06: Bitte einen gueltigen Ortsnamen eingeben !) unless ($input09 =~ m/\D/);

"Der String enthaelt mindestens ein Zeichen, das keine Ziffer ist."

Quote
Code: (dl )
&falsch(Fehler 07: Bitte einen gueltigen Landesnamen eingeben !) unless ($input09 =~ m/\D/);

S.o.

Quote
Code: (dl )
1
2
3
sub falsch {
         ($fehler) = @_;
          }

Gut, die Fehlerbehandlung steht wohl noch am Anfang. ;)

Quote
Code: (dl )
1
2
3
4
5
6
7
8
9
10
11
##################################################################

&getdate;

if($os eq "UNIX")
    {
    &SendIt;
    } else {
           require "winmail.pl";
           &SendMail($mailprog, $tempdirectory, $subject, $email_list[$recipient], $Text);
           }

Ich weiss nicht, was "winmail.pl" enthaelt, aber wahrscheinlich muss $tempdirectory irgendwo gesetzt werden, und $mailprog ist in diesem Programm auch nicht passend fuer ein Windows-System.

Wusstest du schon, dass es Perl-Module gibt, die das Versenden von E-Mails deutlich vereinfachen, wie Mail::Mailer oder MIME::Lite?

Quote
Code: (dl )
1
2
3
4
5
6
&PrintResponse;

###################################################################
# ---------- Empfänger-Mail aus den Formulardaten (im TXT-Format) ----------
sub SendIt {
open (MAIL, "|$mailprog -t") || die "Kann Sendmail nicht starten: $!";

Besser waere bei Sendmail der Aufruf
"|$mailprog -oit"
weil damit verhindert wird, dass man den eingebenen Text leicht so manipulieren kann, dass man damit beliebige E-Mails ueber den Server verschickt.

Quote
Code: (dl )
1
2
print MAIL "To: $email_list[$recipient, $recipient_cc]\n";
print MAIL "From: $input05, $input06\n\n";

Here be dragons!
Da die Eingaben $input05 und $input06 nicht geprueft wurden, kann ein Angreifer alles erdenkliche in den Header injizieren, inkl. neuer Empfaenger-Adressen in einem CC-Feld.

Quote
Code: (dl )
1
2
3
print MAIL "------ KUNDENANFRAGE via WebFormular ------\n";
print MAIL "===========================================\n";
print MAIL "Subject: $subject - ";

Den Betreff im Body der Mail zu haben ist zwar ungewoehnlich, aber nicht verboten. ;)

Quote
Code: (dl )
if ($FORM{'input14'} ne "") {print MAIL "$FORM{'input14'}\n";

Von $FORM{input14} war bisher nicht die Rede. Wahrscheinlich meinst du $input14 oder $FORM{Subject}.
Ausserdem: Wenn das Feld leer ist, wird in dieser Zeile auch kein Zeilenumbruch ausgegeben. Absicht?

Quote
Code: (dl )
1
2
3
4
5
6
7
8
9
print MAIL "===========================================\n";
print MAIL "Text des Kunden:\n";
print MAIL "$input15\n";
print MAIL "===========================================\n";
print MAIL "Kunde interessiert sich fuer:\n\n";
if ($FORM{'CB01'} ne "") {print MAIL "$FORM{'CB01'}\n";
if ($FORM{'CB02'} ne "") {print MAIL "$FORM{'CB02'}\n";
if ($FORM{'CB03'} ne "") {print MAIL "$FORM{'CB03'}\n";
if ($FORM{'CB04'} ne "") {print MAIL "$FORM{'CB04'}\n\n";

Auch hier soll es wahrscheinlich $CB01 etc. heissen.

Quote
Code: (dl )
1
2
3
4
5
6
7
8
9
print MAIL "===========================================\n";
print MAIL "Kundendaten:\n\n";
print MAIL "$input05, $input06\n";
if ($FORM{'input07'} ne "" and ne "Musterweg 10") {print MAIL "$FORM{'input07'}\n\n";
if ($FORM{'input08'} ne "" and ne "12345") {print MAIL "$FORM{'input08'}";
if ($FORM{'input09'} ne "" and ne "Musterhausen") {print MAIL "$FORM{'input09'}\n";
if ($FORM{'input10'} ne "" and ne "Musterland") {print MAIL "$FORM{'input10'}\n\n";
if ($FORM{'input11'} ne "" and ne "08382-72176") {print MAIL "Tel.:  $FORM{'input11'}\n";
if ($FORM{'input12'} ne "" and ne "08382-75140") {print MAIL "Fax.:  $FORM{'input12'}\n";

Dito fuer $FORM{inputXX} vs. $inputXX.

Quote
Code: (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
print MAIL "eMail: $input13\n\n";
print MAIL "===========================================\n";
print MAIL "Das Formular wurde am $date um $time Uhr (MEZ) vom Kunden versendet.\n";
print MAIL "IP des Absenders:   $ipaddr\n";
print MAIL "Host des Absenders: $hostname\n\n";
print MAIL "Formular-Nr.: $count\n";
print MAIL "===========================================\n\n";
print MAIL "<<< Dies ist ein Service des Webmasters: Captain Future >>>";
close (MAIL);
          }
###################################################################
# ------------ Remote_Host auslesen (Variante für 1&1) ------------

# Using Socket-Library
use Socket;

# Fatal-Fehler an den Browser schicken
use CGI::Carp 'fatalsToBrowser';

# Die Variable $ipaddr auf die Remote IP-Adresse setzten
$ipaddr = $ENV{'REMOTE_ADDR'};

# Die IP muss codiert werden:
$iptocheck = inet_aton($ipaddr);

# Host-Lookup auf die codierte IP machen:
$hostname = gethostbyaddr($iptocheck, AF_INET);

Nur eine Warnung: Ein gethostbyaddr() kann laenger dauern, weil dafuer ein reverse DNS Lookup gemacht werden muss. Fuer Skripte, die sehr haeufig aufgerufen werden, wuerde ich darauf verzichten.

Quote
Code: (dl )
1
2
3
4
5
6
7
8
9
10
11
###################################################################
# ------------ Datums und Zeitstempel ------------

sub getdate {
@days = ("Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag");
@months = ("Januar", "Februar", "Maerz", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember");
($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(time);
$time = sprinf("%02d:%02d:%02d", $hour, $min, $sec);
$year += 1900;
$date = "$days[$wday], $mday. $months[$mon] $year";
           }

Tipp: Gewoehne dir an, deine Variablen in moeglichst kleinem Geltungsbereich ("Scope") zu deklarieren und moeglichst wenige globale Variablen zu benutzen. Wenn man sich an "use strict" gewoehnt hat, faellt einem das sogar leichter. Generell sollte man es vermeiden, in Subroutinen andere Variablen als lokal erzeugte oder uebergebene zu verwenden.

Quote
Code: (dl )
1
2
3
4
##################################################################
# ------------ Counter-Funktion ------------
$countfile = "formcount.txt";
$count = 'cat $countfile';

Das sollen wahrscheinlich keine einfachen Anfuehrungszeichen sein, sondern Backticks.

Quote
Code: (dl )
1
2
3
4
5
chop($count);
$count = $count + 1;
open (INF, ">$countfile");
print INF "$count\n";
close (INF);

Dieser Code faellt schnell auf die Nase, wenn das Programm mehrere Male parallel laeuft. Da wird entweder falshc gezaehlt oder sogar der Zaehler geloescht.
Siehe dazu auch perldoc -q "get locking".

HTH

View full thread Hilfe mit Formular Script: CGI - Perl ### Formular