SQL-Anweisung vorbereiten über prepare()

Jetzt erstellen wir die prepare-Anweisung:

$einfuegen = $erg->prepare(
        "INSERT INTO kontakte (vorname, nachname, anmerkung, erstellt) 
        VALUES (?, ?, ?, NOW())");

Jetzt werden die Variablen „gebunden“:

$einfuegen->bind_param('sss', $vorname, $nachname, $anmerkungen);

Es handelt sich bei den 3 Inhalten um Strings, daher ‚sss’. Danach erfolgen die Variablen in der entsprechenden Reihenfolge.

Jetzt führen wir die SQL-Anweisung über execute() aus. Wir wollen aber eine Kontrolle, ob es erfolgreich war oder ein Problem aufgetreten ist.

if ($einfuegen->execute()) {
    header('Location: index.php');
    die();
}

Was passiert hier genau und was macht die Anweisung header()?

Wenn das Ausführen erfolgreich war, ruft sich das Programm wieder selber auf. Das unterbindet ein versehentlich nochmaliges Absenden des Formulars. Dass kann durchaus passieren, wenn der Nutzer einfach Reload im Browser macht. F5 ist da ein Problem. Daher ruft sich das Programm selber auf und die über $_POST im Header übergebenen Daten sind somit „weg“.

Wenn wir eine Meldung über das erfolgreiche Speichern wollen, müssen wir bei den Befehl header('Location: index.php'); noch erweitern und dies dann abfragen.

header('Location: index.php?aktion=feedbackgespeichert');

Jetzt können wir eine eigenständige if-Abfrage erstellen, die nur den Zweck hat, dem Nutzer ein Feedback zu geben, dass der Datensatz gespeichert wurde.

Dem Absatz wurde noch eine CSS-Klasse mit dem Namen „feedbackerfolg“ mitgegeben, damit wir später die Ausgabe designtechnisch über CSS aufhübschen können. Auf Seiten von PHP sieht der Code dann so aus:

if (isset($_POST['aktion']) and $_POST['aktion']=='feedbackgespeichert') {
    echo '<p class="feedbackerfolg">Datensatz wurde gespeichert</p>';
}

Und hier nun der komplette Code für das Speichern:

if (isset($_POST['aktion']) and $_POST['aktion']=='speichern') {
    $vorname = "";
    if (isset($_POST['vorname'])) {
        $vorname = trim($_POST['vorname']);
    }
    $nachname = "";
    if (isset($_POST['nachname'])) {
        $nachname = trim($_POST['nachname']);
    }
    $anmerkung = "";
    if (isset($_POST['anmerkung'])) {
        $anmerkungen = trim($_POST['anmerkung']);
    }
    $erstellt = date("Y-m-d H:i:s");
    if ( $vorname != '' or $nachname != '' or $anmerkung != '' )
    {
        // speichern
        $einfuegen = $erg->prepare("
                 INSERT INTO kontakte (vorname, nachname, anmerkung, erstellt) 
                 VALUES (?, ?, ?, NOW())
                 ");
        $einfuegen->bind_param('sss', $vorname, $nachname, $anmerkung);
        if ($einfuegen->execute()) {
            header('Location: index.php?aktion=feedbackgespeichert');
            die();
        }
    }
}
if (isset($_POST['aktion']) and $_POST['aktion']=='feedbackgespeichert') {
    echo '<p class="feedbackerfolg">Datensatz wurde gespeichert

'
; }

Zum kompletten Testen des bisherigen Programmes löschen wir über PHPmyAdmin alle eingetragenen Datensätze.

Sicherheit

Bisher haben wir wenig für die Sicherheit getan. Bisher ist es noch sehr einfach, für einen übel gesinnten Zeitgenosse Schadcode einzuschmuggeln. Im Extremfall kann über JavaScript Nutzerdaten abgezogen werden. Daher dürfen wir unter keinen Umständen zulassen, dass uns jemand über das Formular ausführbares JavaScript einschleusen kann.

Bisher ist das noch problemlos für unseren Bösewicht-Nutzer möglich. Lassen wir diesen einmal das harmlose (aber nervige) <script>alert(1);</script> in eines unserer Felder schreiben und speichern.

Jetzt wird jedes Mal, wenn die Inhalte anzeigt werden auch das JavaScript ausgeführt. Das ging ins Auge!

Daher werden wir nun alle Ausgaben aus der Datenbank über eine eigene Funktion mit den Namen „bereinigen()“, laufen lassen. Unser bisheriger Code für aus Ausgabe wird erweitert um:

<td><?php echo $inhalt->id; ?></td>
<td><?php echo bereinigen($inhalt->vorname); ?></td>
<td><?php echo bereinigen($inhalt->nachname); ?></td>
<td><?php echo bereinigen($inhalt->anmerkung); ?></td>
<td><?php echo $inhalt->erstellt; ?></td>

Das Feld „id“ und „erstellt“ brauchen wir nicht zwingend zu bereinigen, weil hier keine Nutzereingaben vorliegen. Es schadet aber auch nicht, wenn diese beiden Felder auch durch die Funktion bereinigen() geschickt werden.

Wir machen nun unsere eigene Funktion „bereinigen()“, damit wir diese nach beliebige (und nach bekanntwerden von neuen Sicherheitslöchern) erweitern können.

In unsere Funktion „bereinigen()“; kommen nun mehrere Aktionen:

Wir lassen alle Daten über die PHP-Funktion htmlentities() bereinigen. Alle geeigneten Zeichen werden in entsprechenden HTML-Codes umgewandelt. Wir wollen sowohl einfache wie doppelte Anführungszeichen umwandeln (daher der Parameter ENT_QUOTES)

Und das Ganze im UTF8 encoding. Danach soll das Ergebnis wieder zurückgeliefert werden (return).

function bereinigen($inhalt='') {
    $inhalt = trim($inhalt);
    $inhalt = htmlentities($inhalt, ENT_QUOTES, "UTF-8");
    return($inhalt);
}

Lassen wir nun unsere Datenbank ausgeben, wird das JavaScript nicht mehr ausgeführt, sondern steht als Klartext auf dem Bildschirm. Es ist somit unschädlich (wenn auch nicht besonders hübsch als Ausgabe).

Kompletter Quellcode aus diesem Kapitel

<?php
require 'inc/db.php';
if (isset($_POST['aktion']) and $_POST['aktion']=='speichern') {
    $vorname = "";
    if (isset($_POST['vorname'])) {
        $vorname = trim($_POST['vorname']);
    }
    $nachname = "";
    if (isset($_POST['nachname'])) {
        $nachname = trim($_POST['nachname']);
    }
    $anmerkung = "";
    if (isset($_POST['anmerkung'])) {
        $anmerkung = trim($_POST['anmerkung']);
    }
    $erstellt = date("Y-m-d H:i:s");
    if ( $vorname != '' or $nachname != '' or $anmerkung != '' )
    {
        // speichern
        $einfuegen = $db->prepare("
                INSERT INTO kontakte (vorname, nachname, anmerkung, erstellt) 
                VALUES (?, ?, ?, NOW())
                ");
        $einfuegen->bind_param('sss', $vorname, $nachname, $anmerkung);
        if ($einfuegen->execute()) {
            header('Location: index.php?aktion=feedbackgespeichert');
            die();
            echo "<h1>gespeichert</h1>";
        }
    }   
}
if (isset($_GET['aktion']) and $_GET['aktion']=='feedbackgespeichert') {
    echo '<p class="feedbackerfolg">Datensatz wurde gespeichert</p>';
}
$daten = array();
if ($erg = $db->query("SELECT * FROM kontakte")) {
    if ($erg->num_rows) {
        while($datensatz = $erg->fetch_object()) {
            $daten[] = $datensatz;
        }
        $erg->free();
    }   
}
if (!count($daten)) {
    echo "<p>Es liegen keine Daten vor :(</p>";
} else {
?>
    <table>
        <thead>
            <tr>
                <th>ID</th>
                <th>Vorname</th>
                <th>Nachname</th>
                <th>Anmerkung</th>
                <th>erstellt</th>
            </tr>
        </thead>
        <tbody>
            <?php
            foreach ($daten as $inhalt) {
            ?>
                <tr>
                    <td><?php echo $inhalt->id; ?></td>
                    <td><?php echo bereinigen($inhalt->vorname); ?></td>
                    <td><?php echo bereinigen($inhalt->nachname); ?></td>
                    <td><?php echo bereinigen($inhalt->anmerkung); ?></td>
                    <td><?php echo $inhalt->erstellt; ?></td>
                </tr>
            <?php
            }
            ?>
        </tbody>
    </table>
<?php   
}
function bereinigen($inhalt='') {
    $inhalt = trim($inhalt);
    $inhalt = htmlentities($inhalt, ENT_QUOTES, "UTF-8");
    return($inhalt);
}
?>
<form action="" method="post">
    <label>Vorname: 
        <input type="text" name="vorname" id="vorname">
    </label>
    <label>Nachname: 
        <input type="text" name="nachname" id="nachname">
    </label>
    <label>Anmerkung: 
        <textarea name="anmerkung" id="anmerkung"></textarea>
    </label>
    <input type="hidden" name="aktion" value="speichern">
    <input type="submit" value="speichern">
</form>