PHP: URL auf korrekte Syntax und Existenz prüfen
Egal wie groß oder klein das Webprojekt auch ist, Eingaben von Benutzern stellen fast immer die Grundlage für weitere Aktion dar. Seien es Formulare zur Kontaktaufnahme, Nutzerregistrierung oder Parameterübergaben mittels GET. Gleichzeitig sind Benutzereingaben jedoch auch eine der größten Gefahren für Webanwendungen und daher mit höchster Vorsicht zu behandeln. Denn vom Programmierer werden hier meist nur bestimmte Werte erwartet, in Wahrheit steht es dem Benutzer aber völlig frei, welche Daten er übermittelt. Eine der wichtigsten Grundregeln bzgl. Sicherheit in Webanwendungen lautet deshalb:
Prüfe alle Eingaben!
Für ein aktuelles Projekt war ich deshalb auf der Suche nach einer Möglichkeit eine URL auf Gültigkeit zu prüfen. Denn allzu oft werden in ein Formularfeld zur Angabe der Website einfach Fantasiedomains, wie ich://habe.keine oder http://foo.bar, eingetragen.
Da mit PHP 5.2 die filter_var-Funktion eingeführt wurde, hoffte ich, dass der Parameter FILTER_VALIDATE_URL in Kombination mit FILTER_FLAG_SCHEME_REQUIRED oder FILTER_FLAG_HOST_REQUIRED mich meinem Ziel etwas näher bringen würde. Jedoch mußte ich nach einigen Tests feststellen, dass FILTER_VALIDATE_URL nur sehr bedacht eingesetzt werden sollte. Denn laut filter_var sind beispielsweise auch folgende URLs gültig:
filter_var('keine://domain', FILTER_VALIDATE_URL) !== false; //true filter_var('foo://bar', FILTER_VALIDATE_URL) !== false; //true filter_var('javascript://test%0Aalert(xss)', FILTER_VALIDATE_URL) !== false; //true
Besonders das Beispiel mit dem Javascript-Code zeigt anschaulich, dass filter_var für eine zuverlässige URL-Prüfung in Webanwendungen deshalb ungeeignet ist und zu großen Problemen führen kann.
Daher entschied ich mich, für die Syntaxüberprüfung der URL weiterhin auf reguläre Ausdrücke zu setzen. Als Regex für die URL-Syntax verwende ich eine sehr umfangreiche und gut geteste Variante von Diego Perini. Kombiniert man diesen zusätzlich mit einer Abfrage des HTTP-Statuscodes, lassen sich alle nicht existierenden Domains dadurch ausfiltern.
function urlValidate($url) { $url = trim($url); if(preg_match('%^(?:(?:https?)://)(?:\S+(?::\S*)?@|\d{1,3}(?:\.\d{1,3}){3}|(?:(?:[a-z\d\x{00a1}-\x{ffff}]+-?)*[a-z\d\x{00a1}-\x{ffff}]+)(?:\.(?:[a-z\d\x{00a1}-\x{ffff}]+-?)*[a-z\d\x{00a1}-\x{ffff}]+)*(?:\.[a-z\x{00a1}-\x{ffff}]{2,6}))(?::\d+)?(?:[^\s]*)?$%iu', $url)) { if(ini_get('allow_url_fopen')) { $headers = @get_headers($url, 1); if (preg_match('/^HTTP\/.*\s+(200|401)/', $headers[0])) { return true; } elseif (preg_match('/^HTTP\/.*\s+(300|301|302|303|307|308)/', $headers[0])) { if ($headers !== false && isset($headers['Location'])) { if($headers['Location'] != $url) { return urlValidate($headers['Location']); } else // Never ending story { return false; } } else { return false; } } else { return false; } } elseif (function_exists('curl_version')) { $user_agent = "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:16.0) Gecko/20121026 Firefox/16.0"; $ch = @curl_init($url); @curl_setopt($ch, CURLOPT_HEADER, 1); @curl_setopt($ch, CURLOPT_NOBODY, 1); @curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0); @curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); @curl_setopt($ch, CURLOPT_HEADER, 1); @curl_setopt($ch, CURLOPT_TIMEOUT, 5); @curl_setopt($ch, CURLOPT_USERAGENT, $user_agent); @curl_exec($ch); $http_statuscode = curl_getinfo($ch, CURLINFO_HTTP_CODE); if(preg_match('/^200|301|302$/', $http_statuscode)) { return true; } } else { throw new Exception('curl and allow_url_fopen are not avaiable.'); } } return false; }
Der Aufruf der Funktion ist denkbar einfach:
if(urlValidate('http://www.datenreise.de/php-url-korrekte-syntax-und-existenz-pruefen')) { echo "URL existiert"; } else { echo "URL existiert nicht"; }
Um IDN-Domains zu prüfen, müssen diese vor dem Funktionsaufruf in Punycode umgewandelt werden. Beispiel (börse.de):
urlValidate('http://www.xn--brse-5qa.de')
Danke für den Codeschnipsel,
ich benutze diesen in einigen Scripten und bisher habe ich damit keine Probleme :-)
Also weiter so und thanks
Gruss pyr0
Der Code prüft leider nur, ob eine Website existiert, aber nicht, ob es wirklich die gewünschte ist. Mein Problem: Wenn die gewünschte Website nicht mehr vorhanden ist und statt dessen auf ein anderes Ziel weitergeleitet wird, existiert die gewünschte Seite nicht wirklich. Wie kann ich das prüfen ?
Diese Aufgabe soll der Code gar nicht erfüllen. Er ist lediglich zur Prüfung einer URL hinsichtlich Syntax da
@Delf
Dann sollte die aufgerufene URL (also die eigentlich gewünschte) einen 301 oder 302-Code zurückgeben, anhand dessen dann ersichtlich ist dass eine Umleitung stattfindet.
Danke für die tolle Funktion, habe bisher versucht, mit filter_var($url, FILTER_SANITIZE_URL) und filter_var($url, FILTER_VALIDATE_URL) URL’s zu prüfen, dies funktionert leider nur mässig, muß man mit weiteren Filtern ergänzen…
Gruß
Georg