Vérifier la validité d'une URL en PHP

Le contrôle des données utilisateurs dans vos formulaires est primordial pour la sécurité de votre système d'informations. Cela passe bien évidemment par le contrôle des injections SQL et autres failles XSS. Cependant, cette protection n'assure pas forcément l'intégrité de vos données. Un internaute peut très bien saisir en guise de site Internet son numéro de téléphone. Voyons donc une fonction permettant de contrôler la validité d'une URL.

/**
 * Checks that an URL is valid and really exists (checking with a basic ping).
 * @param $url Address to check.
 * @param $required Required field?
 * @return string
 */
function ValidateUrl($url, $required = false)
{
	if(empty($url))
	{
		if(!$required) return '';
		else throw new Exception('Please fill in all mandatory fields.');
	}
	else
	{
		$regex = '#(((http|ftp|https|ftps)://)|(www\.))+(([a-zA-Z0-9\._-]+\.[a-zA-Z]{2,6})|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(/[a-zA-Z0-9\&%_\./-~-]*)?#';

		// Checking only syntax
		if(preg_match($regex, $url))
		{
			// And checking that the website really exists.
			$curl = curl_init($url);

			// Setting the URL.
			curl_setopt($curl, CURLOPT_URL, $url);
			// We want to read the header.
			curl_setopt($curl, CURLOPT_HEADER, 1);
			// We do not need the page content.
			curl_setopt($curl, CURLOPT_NOBODY, 1);
			// Do not display the page, just return us its content.
			curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);

			$datas = curl_exec($curl);

			curl_close($curl);

			if(!empty($datas)) return $url;
			else throw new Exception('Unable to join site at '.$url.'.');
		}
		else throw new Exception('Please enter a valid address.');
	}
}

La vérification de l'URL se déroule en trois temps.

  1. Champ obligatoire : on vérifie tout d'abord que l'utilisateur a bien rempli le champ correspondant dans le formulaire. Si celui-ci est obligatoire (attribut $required), alors on lève une exception.
  2. Vérification syntaxique : on s'assure que l'URL est correctement formée syntaxiquement, à travers une expression régulière plutôt lourde. Vous pouvez lui faire confiance, elle fonctionne. Elle prend tout aussi bien un nom de domaine qu'une adresse IP.
  3. Existence du site : enfin, on effectue un ping vers le site indiqué, grâce à l'extension curl. Si le site n'existe pas, nous ne retrouverons rien dans la variable $datas, et donc nous léverons une exception afin d'indiquer à l'internaute qu'il s'est trompé dans l'adresse.

Comme vous l'aurez compris, cette fonction nécessite l'utilisation de l'extension PHP curl, permettant la lecture de pages Web via PHP.

Le code est à mon sens suffisamment commenté. N'hésitez pas cependant à demander plus d'informations dans les commentaires, ainsi qu'à donner des suggestions d'amélioration. :)

Cette fonction fait partie du framework PEJO. Petit changement de stratégie le concernant : je ne vais publier que des snippets de code sur ce blog. Un site (en cours de construction) sera réservé à ce framework, et permettra un suivi bien plus simple : centralisation de toutes les classes sans billets parasites, suivi des modifications, mise en ligne de certains utilitaires forts pratiques (générateur de classes, de formulaires, etc.). Patience donc...

Mots-clefs : , , , ,

4 réactions sur cet article.

  1. Romain says:

    Pourquoi le choix de CURL ?

    Si elle est pas installée c'est dommage !

    Pourquoi pas un fopen distant ou un socket ?

  2. Merci says:

    Parce que les fopen distant sont comme CURL : ils peuvent être désactivés dans le php.ini. Et comment réagit fopen() face à une erreur 404 ? D'ailleurs, je ne vois pas dans cette fonction la gestion des erreurs des headers.

  3. Brax says:

    Je viens d'essayer avec :

    Comment pouvez-vous dire de faire confiance alors que vous n'avez fait aucun tests...

    (((http|ftp|https|ftps)://)|(www\.))+

    Une ou plusieurs fois ce motif...

    [a-zA-Z0-9\._-]+

    Encore une fois ... en mettant les characteres speciaux dans les [] on peut mettre 50 "." a la suit !

    Je m'arrête là dans les tests mais je suis sur de pouvoir trouver d'autres failles.

  4. @Merci :

    Effectivement, la gestion des headers pourrait être intéressante à implémenter. Je vais y réfléchir. Même si pour l'instant, je n'ai pas trop le temps (quelques projets pros, et une refonte majeure du blog). Mais je le note dans ma (trop) longue to-do list. :)

    @Brax :

    Je te sens bien énervé. Personne ne t'oblige à utiliser ce code. ;)

    Concernant les trois tests que tu as fait, effectivement, il y a une erreur. La première adresse ne devrait pas passer, sémantiquement du moins. Car le test du ping du site échouera forcément, ce qui va donc conduire à un refus de l'adresse. N'est-ce pas là le but ultime après tout ? Merci néanmoins de m'avoir remonté ce petit souci.

    Pour les autres adresses, celles-ci sont correctes et il est donc normal que cela passe. Pour la seconde, les extensions personnalisées ne devraient pas tarder à arriver en masse. Il s'agit donc d'anticipation. Quant à la troisième, libre à l'administrateur système de définir des sous-sous domaines. Même si ton exemple est tout de même assez farfelu. Mais, why not?

    Enfin, pour rappel, si je mets à disposition ces morceaux de code, c'est d'une part pour simplifier les tâches des développeurs visitant ce site, et d'autre part pour améliorer les fonctions mises à dispositions. Chose à laquelle tu as contribué en me remontant ce souci de double-points. ;)

Réagissez sur cet article !