Regex URL web
Un commentaire à 15 votes sur la question de référence (779 000 vues) cite une regex tirée d'une ancienne page de documentation Microsoft comme « la bonne ». Notre analyse ReDoS la classe pourtant vulnérable, complexité exponentielle, avec une chaîne d'attaque concrète. Voici le motif « web URL » de Diego Perini (1600+ étoiles sur GitHub), comparé à 3 tentatives réellement copiées.
/^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9¡-][a-z0-9¡-_-]{0,62})?[a-z0-9¡-]\.)+(?:[a-z¡-]{2,}\.?))(?::\d{2,5})?(?:[/?#]\S*)?$/ipasse 20/20 cas
Comparatif des patterns existants
| Source | pattern | Score sur le corpus | ReDoS |
|---|---|---|---|
| Stack Overflow — commentaire citant une regex Microsoft (page MSDN retirée), 15 votes | ^(ht|f)tp(s?)\:\/\/[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*(:(0-9)*)*(\/?)([a-zA-Z0-9\-\.\?\,\'\/\\\+&%\$#_]*)?$ | 11 / 20 | vulnérable (ftp://psssssssssssssssssssssssssssssssssssssssssssssssssssss…) |
| Stack Overflow — réponse de Divya-Systematix (classe de caractères mal formée) | ^(http|https):\/\/+[\www\d]+\.[\w]+(\/[\w\d]+)?$ | 13 / 20 | sûr |
| Stack Overflow — le motif « en une ligne » très largement copié | ^(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}(\.[a-z]{2,6}|:[0-9]{3,4})\b([-a-zA-Z0-9@:%_\+.~#?&\/\/=]*)$ | 13 / 20 | indéterminé |
Implémentation
const re = new RegExp("^(?:(?:(?:https?|ftp):)?\\/\\/)(?:\\S+(?::\\S*)?@)?(?:(?!(?:10|127)(?:\\.\\d{1,3}){3})(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z0-9¡-][a-z0-9¡-_-]{0,62})?[a-z0-9¡-]\\.)+(?:[a-z¡-]{2,}\\.?))(?::\\d{2,5})?(?:[/?#]\\S*)?$", "i");
re.test(input);Limites
- Le port n'est vérifié que par son nombre de chiffres (2 à 5), pas par sa plage réelle : « https://example.com:99999 » passe alors que ce port n'existe pas (les ports réels vont de 0 à 65535).
- Exclut volontairement les noms à un seul label comme « localhost » — un choix assumé pour une URL web publique, pas adapté à un contexte de développement local.
- Exclut les adresses IP privées ou réservées (10.x, 172.16-31.x, 192.168.x…) : une URL techniquement valide sur un réseau interne sera rejetée, un comportement volontaire pour éviter qu'un formulaire public pointe vers un réseau local.
- Accepte la forme protocole-relatif (« //example.com ») et l'authentification intégrée à l'URL (« user:pass@… »), deux formes valides mais dont la seconde est dépréciée par les navigateurs modernes pour des raisons de sécurité.