Regex ligne de log Apache (Common Log Format)
Un développeur qui essayait de parser des logs Apache a buté sur un cas réel : certaines URL de son access log contenaient des espaces, et sa regex les coupait en plein milieu. Notre analyse ReDoS la classe en plus vulnérable. Voici un motif corrigé, comparé à sa tentative d'origine, à un gist GitHub qui ne capture même pas le code de statut, et à une autre réponse qui accepte des lignes sans version HTTP.
/^(\S+) (\S+) (\S+) \[([\w:\/]+\s[+\-]\d{4})\] "(\S+) (.+?) (\S+)" (\d{3}) (\S+)$/passe 20/20 cas
Comparatif des patterns existants
| Source | pattern | Score sur le corpus | ReDoS |
|---|---|---|---|
| Stack Overflow — tentative de l'auteur de la question (casse sur un espace dans l'URL) | ^(\S+) (\S+) (\S+) \[([\w:\/]+\s[+\-]\d{4})\] "(\S+) (\S+)\s*(\S*)" (\d{3}) (\S+)$ | 18 / 20 | vulnérable (+ " + [/ +0000] "/ +""""""""""""""""""""""""""""""""""""""""…) |
| GitHub Gist — motif court avec groupes nommés, ne capture ni le statut ni la taille | ^(?<IP>[^ ]*) \- \- \[(?<Datetime>[^\]]+)\] \"(?<Verb>[^\ ]+)\ (?<Path>.*) (?<HTTP>HTTP\/\d\.\d)"$ | 10 / 20 | sûr |
| Stack Overflow — réponse alternative, requête non décomposée en méthode/chemin/protocole | ^(\S+) - - \[([\w:\/]+\s[+\-]\d{4})\] "(.*?)" (\d{3}) (\d+)$ | 14 / 20 | sûr |
Implémentation
const re = new RegExp("^(\\S+) (\\S+) (\\S+) \\[([\\w:\\/]+\\s[+\\-]\\d{4})\\] \"(\\S+) (.+?) (\\S+)\" (\\d{3}) (\\S+)$", "");
re.test(input);Limites
- Valide seulement la forme d'une ligne du Common Log Format (%h %l %u %t \"%r\" %>s %b) : un serveur configuré avec un LogFormat personnalisé (champs supplémentaires, ordre différent) ne produira pas de lignes reconnues par ce motif.
- N'exige pas qu'une ligne sans protocole HTTP soit rejetée pour une bonne raison technique universelle : certains vieux logs (dont le jeu de données NASA-HTTP de 1995 utilisé dans les exemples ci-dessus) omettent parfois la version HTTP sur des requêtes malformées — ce motif les exclut volontairement, un serveur plus tolérant peut vouloir les accepter.
- Ne valide pas que le code de statut (3 chiffres) correspond à un code HTTP réellement défini : « 999 » passe la forme sans être un code valide.
- Le composant [\w:/]+ du timestamp accepte n'importe quelle combinaison de lettres/chiffres avant le fuseau horaire — il ne vérifie pas qu'il s'agit d'une date calendaire réelle (jour 01-31, mois existant).