Motifs1 chasse jouable incluse

Regex pour les URL : ce qu'il faut reconnaître et ce qu'il faut laisser à un analyseur

Trouver des liens dans un texte et valider une URL sont deux tâches différentes. Voici un motif pour chacune, les pièges qui laissent passer de mauvaises URL (ponctuation finale, points non échappés, ancres manquantes), et quand confier le travail à new URL() ou à urllib.

Logo de Regex Hunter

· 8 min de lecture · Mis à jour le

/^https?://\S+$/

à attraper

  • https://a.io
  • http://x.com/p
  • https://b.dev?q=1

à épargner

  • ftp://x.com
  • www.x.com
  • http:/x.com

« Regex d'URL » désigne en général l'une de ces deux choses :

  1. Trouver des URL dans un bloc de texte, comme une messagerie qui transforme les liens en éléments cliquables.
  2. Valider une saisie unique, comme un champ « Site web » dans un formulaire de profil.

Il leur faut des motifs différents, et la seconde relève surtout d'un analyseur d'URL. Cet article couvre les deux, ainsi que les pièges propres à chacune. Tous les exemples sont interactifs : modifiez les motifs et observez ce qui change.

Réponse rapideJavaScript
/^(https?)://([^/:?#\s]+)(:\d+)?(/[^?#\s]*)?(\?[^#\s]*)?(#\S*)?$/i

Une URL http ou https avec un hôte, puis un port, un chemin, une requête et un fragment facultatifs, sans espaces.

Accepte

  • https://example.com
  • http://localhost:3000/api?x=1
  • https://example.com/blog#top

Rejette

  • ftp://example.com
  • www.example.com
  • https://exa mple.com

01Trouver des URL dans du texte

La version rapide : un schéma suivi de tout ce qui n'est pas un espace :

Correspondances5/5 comme prévu
/https?://\S+/

Doit correspondre3/3

  • Docs: https://regexhunter.com/guide.
  • (see https://example.com/a)
  • <https://example.com>

Ne doit pas2/2

  • see www.example.com
  • ftp://files.example.com
Essayez-le dans le bac à sable
Chaque ligne correspond, mais regardez ce qui est surligné. Le point, la parenthèse fermante et le > finissent tous dans l'URL.

\S+ prend tout jusqu'à l'espace suivant, et dans un vrai texte, une URL est souvent suivie de ponctuation. Faites-en un lien, et le lecteur tombe sur une 404.

02Écarter la ponctuation finale

La solution consiste à dire ce que peut être le dernier caractère. Le corps peut contenir n'importe quoi sauf des espaces, des chevrons et des guillemets. Le dernier caractère ne peut pas non plus être une ponctuation courante :

Correspondances4/4 comme prévu
/https?://[^\s<>"]*[^\s<>".,;:!?)]/
  • Docs: https://regexhunter.com/guide.
  • (see https://example.com/a)
  • <https://example.com>
  • https://example.com/search?q=regex&page=2
Essayez-le dans le bac à sable
L'URL peut toujours contenir des points et des deux-points au milieu. Elle ne peut simplement pas se terminer par l'un d'eux.

Cela marche parce que le [^\s<>"]* gourmand court jusqu'au bout de l'URL, puis recule d'un caractère à la fois jusqu'à ce que la classe finale convienne. (Notre article sur les quantificateurs gourmands et paresseux détaille ce recul.)

Il n'existe pas de règle parfaite ici. Des liens Wikipédia comme https://en.wikipedia.org/wiki/Regex_(disambiguation) se terminent vraiment par ), et ce motif la retire. Les bibliothèques de détection de liens règlent ça en vérifiant si les parenthèses sont équilibrées, ce qui est plus de logique qu'une seule regex ne devrait en porter.

Remarquez que www.example.com n'est pas trouvé, faute de schéma. Si vous voulez aussi les liens www. sans schéma, commencez plutôt le motif par (https?://|www\.).

03Valider une URL : les parties

Une URL web compte jusqu'à six parties. Les voici dans https://regexhunter.com:443/blog/regex-urls?ref=home#top :

Partie Exemple Obligatoire ?
schéma https oui
hôte regexhunter.com oui
port :443 non
chemin /blog/regex-urls non
requête ?ref=home non
fragment #top non

Un motif qui vérifie la forme de chaque partie ressemble à ceci :

Correspondances9/9 comme prévu
/^(https?)://([^/:?#\s]+)(:\d+)?(/[^?#\s]*)?(\?[^#\s]*)?(#\S*)?$/i

Doit correspondre4/4

  • https://example.com
  • HTTPS://EXAMPLE.COM
  • http://localhost:3000/api?x=1
  • https://regexhunter.com/blog/regex-urls#top

Ne doit pas5/5

  • ftp://example.com
  • https://
  • https://exa mple.com
  • https://example.com:port
  • example.com
Essayez-le dans le bac à sable
Le schéma, l'hôte, puis un port, un chemin, une requête et un fragment facultatifs, chacun dans son propre groupe. Le marqueur i autorise HTTPS:// en majuscules.

Lisez-le partie par partie :

  1. ^(https?):// le schéma, http ou https, et le ://
  2. ([^/:?#\s]+) l'hôte : tout jusqu'au prochain /, :, ? ou #
  3. (:\d+)? un port facultatif, uniquement des chiffres
  4. (/[^?#\s]*)? un chemin facultatif, qui commence par /
  5. (\?[^#\s]*)? une requête facultative, qui commence par ?
  6. (#\S*)?$ un fragment facultatif, puis la fin

C'est une bonne vérification de forme. Mais elle ne dit rien de la cohérence de l'hôte. https://999.999.999.999 passe, et https://- aussi. Vous pourriez continuer à ajouter des règles pour les noms d'hôte, les adresses IP, les crochets IPv6, les identifiants et mots de passe, et les noms de domaine internationalisés. Certains l'ont fait, et le résultat se compte en centaines de caractères.

04Laissez un analyseur faire l'analyse

Tous les langages courants fournissent un analyseur d'URL qui connaît déjà ces règles. En JavaScript (navigateurs et Node), le constructeur URL lève une exception sur tout ce qu'il ne sait pas analyser :

function isHttpUrl(s) {
  try {
    const u = new URL(s);
    return u.protocol === 'http:' || u.protocol === 'https:';
  } catch {
    return false;
  }
}

new URL("https://exa mple.com"), new URL("https://999.999.999.999") et new URL("example.com") lèvent tous une exception. La vérification du protocole compte : new URL("javascript:alert(1)") s'analyse sans problème, et vous ne voulez pas de ça dans un lien.

Vous obtenez aussi les morceaux gratuitement : u.hostname, u.port, u.pathname, u.search, u.hash, et u.searchParams.get("x") pour une valeur de la requête. Et l'URL revient normalisée : HTTPS://Example.COM:443/a/../b devient https://example.com/b.

En Python, urllib.parse.urlsplit découpe une URL en ces mêmes parties :

from urllib.parse import urlsplit

u = urlsplit("http://localhost:3000/api?x=1#top")
(u.scheme, u.hostname, u.port, u.path, u.query, u.fragment)
# ('http', 'localhost', 3000, '/api', 'x=1', 'top')

Mais urlsplit découpe, il ne valide pas. Il lève rarement une erreur. urlsplit("example.com") renvoie un schéma et un hôte vides, avec example.com comme chemin, et urlsplit("https://exa mple.com") annonce sans sourciller exa mple.com comme hôte. Vérifiez donc u.scheme in ("http", "https") et que u.netloc n'est pas vide, puis ajoutez vos propres règles sur l'hôte si besoin.

05Piège : le point non échappé

Dans un motif, . veut dire « n'importe quel caractère ». Oubliez la barre oblique inverse dans un nom de domaine, et vous autorisez des domaines sosies :

Correspondances2/2 comme prévu
/^https://www.example.com/
  • https://www.example.com
  • https://wwwxexample.com
Essayez-le dans le bac à sable
Les deux lignes correspondent. Les points après www et example acceptent n'importe quel caractère : quelqu'un pourrait enregistrer wwwxexample.com.

Écrivez www\.example\.com. Chaque point littéral d'un motif a besoin d'une barre oblique inverse.

06Piège : la fin manquante

Celui-ci compte pour les redirections et les listes d'autorisation, du genre « ne renvoyer les gens que vers des URL de notre propre site » :

Correspondances2/2 comme prévu
/^https://example\.com/
  • https://example.com/login
  • https://example.com.evil.io
Essayez-le dans le bac à sable
Les points sont échappés et le début est ancré, mais rien ne dit que l'hôte s'arrête là. La deuxième ligne est le domaine de quelqu'un d'autre.

L'hôte doit s'arrêter juste après .com. Exigez l'un des caractères qui peuvent le terminer, ou la fin de la chaîne :

Correspondances6/6 comme prévu
/^https://example\.com([/?#:]|$)/

Doit correspondre3/3

  • https://example.com
  • https://example.com/login
  • https://example.com?ref=mail

Ne doit pas3/3

  • https://example.com.evil.io
  • https://example.company.io
  • https://evil.io/?next=https://example.com
Essayez-le dans le bac à sable
Après .com, il doit y avoir un /, un ?, un #, le deux-points d'un port, ou rien du tout.

Encore mieux pour les contrôles de sécurité : analysez avec new URL() ou urlsplit, puis comparez le nom d'hôte avec === ou ==. Une comparaison exacte de chaînes n'a aucun cas limite à oublier.

07À vous de jouer

Chaque monstre est une adresse web. Les innocents ont le mauvais schéma, un :// cassé, une faute de frappe, ou pas de schéma du tout :

Chasse jouablepar 10

Attrapez les adresses http et https, épargnez le reste.

Cibles · 0/4 attrapées

  • https://regexhunter.comcible
  • http://localhost:3000cible
  • https://a.io/x?y=1cible
  • http://example.org/cible

Innocents · erreurs : 0

  • ftp://example.orginnocent
  • https:/broken.cominnocent
  • example.cominnocent
  • mailto:ana@site.cominnocent
  • htps://typo.ioinnocent

Tapez une regex — le plateau se met à jour en direct.

Essayez-le dans le bac à sable
Ça vous plaît ? Une nouvelle chasse chaque jour.Jouer la chasse du jour

08Récapitulatif

  • Pour trouver des URL dans du texte : https?://[^\s<>"]*[^\s<>".,;:!?)], pour laisser la ponctuation finale dehors.
  • Pour valider une URL : utilisez new URL() en JavaScript et vérifiez le protocole, ou urlsplit en Python et vérifiez le schéma et l'hôte.
  • Échappez chaque point littéral : example\.com.
  • Dans les listes d'autorisation, ancrez le début et la fin de l'hôte, ou mieux, comparez les noms d'hôte analysés.
  • Ajoutez le marqueur i si des majuscules peuvent apparaître. HTTPS://EXAMPLE.COM est une URL valide.

09Continuez à vous entraîner