PHP regex plantant apache

J’ai une regex qui fait la correspondance avec un système de template, qui semble malheureusement faire planter apache (qui tourne sous Windows) sur certaines recherches modestement sortingviales. J’ai fait des recherches sur le problème et il y a quelques suggestions pour augmenter la taille de la stack, etc.

Quoi qu’il en soit, des idées sur la façon de modifier la regex pour la rendre moins susceptible de s’encrasser?

L’idée est d’attraper le bloc le plus interne (dans ce cas, {block:test}This should be caught first!{/block:test} ) que str_replace des balises de début / fin et relance le tout le regex jusqu’à ce qu’il n’y ait plus de blocs.

Regex:

 ~(?P{(?P[!])?block:(?P[a-z0-9\s_-]+)})(?P(?:(?!{/?block:[0-9a-z-_]+}).)*)(?P{/block:\3})~ism 

Modèle d’échantillon:

 
«
    {block:sponsors}
  • {var:name} {block:test}This should be caught first!{/block:test}
  • {/block:sponsors}
»

C’est un long coup je suppose. 🙁

Essaye celui-là:

 '~(?P\{(?P[!])?block:(?P[a-z0-9\s_-]+)\})(?P[^{]*(?:\{(?!/block:(?P=name)\})[^{]*)*)(?P\{/block:(?P=name)\})~i' 

Ou, sous forme lisible:

 '~(?P \{ (?P[!])? block: (?P[a-z0-9\s_-]+) \} ) (?P [^{]*(?:\{(?!/block:(?P=name)\})[^{]*)* ) (?P \{ /block:(?P=name) \} )~ix' 

La partie la plus importante se trouve dans le groupe (?P..) :

 [^{]*(?:\{(?!/block:(?P=name)\})[^{]*)* 

Au départ, le seul personnage qui nous intéresse est l’accolade d’ouverture, donc nous pouvons traiter tout autre personnage avec [^{]* . Ce n’est qu’après avoir vu un { ne vérifions-nous pas qu’il s’agit du début d’une balise {/block} . Si ce n’est pas le cas, nous allons de l’avant et consommons-le et commençons à scanner le suivant, et répétons si nécessaire.

En utilisant RegexBuddy, j’ai testé chaque regex en plaçant le curseur au début de la balise {block:sponsors} et le débogage. Ensuite, j’ai enlevé l’accolade finale de la balise de fermeture {/block:sponsors} pour forcer une correspondance échouée et la déboguer à nouveau. Votre regex a pris 940 étapes pour réussir et 2265 étapes pour échouer. Le mien a pris 57 mesures pour réussir et 83 étapes pour échouer.

Sur une note de côté, j’ai supprimé le modificateur s parce que je n’utilise pas le modificateur point ( . ) Et le modificateur m car il n’était jamais nécessaire. J’ai également utilisé le backreference nommé (?P=name) au lieu de \3 selon l’excellente suggestion de @ DaveRandom. Et j’ai échappé à toutes les accolades ( { et } ) parce que je trouve plus facile de lire de cette façon.


EDIT: Si vous voulez faire correspondre le bloc nommé le plus à l’ intérieur , changez la partie centrale de la regex de ceci:

 (?P [^{]*(?:\{(?!/block:(?P=name)\})[^{]*)* ) 

… à cela (comme suggéré par @Kobi dans son commentaire):

 (?P [^{]*(?:\{(?!/?block:[a-z0-9\s_-]+\})[^{]*)* ) 

A l’origine, le groupe (?P...) prendrait la première balise d’ouverture qu’il voyait, puis le groupe (?P..) consumrait n’importe quoi – y compris d’autres balises – tant La balise de fermeture ne correspond pas à celle trouvée par le groupe (?P...) . (Ensuite, le groupe (?P...) irait de l’avant et consumrait ça.)

Maintenant, le groupe (?P...) refuse de faire correspondre n’importe quel tag, qu’il soit ouvert ou fermé (notez le /? Au début), quel que soit le nom. La regex commence donc à correspondre à la balise {block:sponsors} , mais lorsqu’elle rencontre la balise {block:test} , elle abandonne cette correspondance et retourne à la recherche d’une balise d’ouverture. Il recommence à la balise {block:test} , cette fois avec succès la fin du match quand il trouve la balise de fermeture {/block:test} .

Cela semble inefficace de le décrire comme ça, mais ce n’est vraiment pas le cas. Le truc que j’ai décrit plus tôt, qui aspire les non-accolades, noie l’effet de ces faux départs. Là où vous faisiez une tête de lecture négative à presque toutes les positions, vous ne le faites plus que lorsque vous rencontrez un { . Vous pouvez même utiliser des quantificateurs possessifs, comme @godspeedlee l’a suggéré:

 (?P [^{]*+(?:\{(?!/?block:[a-z0-9\s_-]+\})[^{]*+)*+ ) 

… parce que vous savez qu’il ne consumra jamais rien qu’il devra rembourser plus tard. Cela accélérerait un peu les choses, mais ce n’est pas vraiment nécessaire.

La solution doit-elle être une seule expression régulière? Une approche plus efficace pourrait consister simplement à rechercher la première occurrence de {/block: (qui pourrait être une simple recherche de chaîne ou une expression régulière), puis rechercher à partir de ce point pour trouver la balise d’ouverture correspondante, remplacer la plage de manière appropriée et répéter jusqu’à ce qu’il n’y ait plus de blocs. Si chaque fois que vous cherchez la première balise de fermeture en partant du haut du modèle, cela vous donnera le bloc le plus profondément nested.

L’algorithme d’image miroir fonctionnerait tout aussi bien – recherchez la dernière balise d’ouverture et recherchez ensuite la balise de fermeture correspondante:

 < ?php $template = //... while(true) { $last_open_tag = strrpos($template, '{block:'); $last_inverted_tag = strrpos($template, '{!block:'); // $block_start is the index of the '{' of the last opening block tag in the // template, or false if there are no more block tags left $block_start = max($last_open_tag, $last_inverted_tag); if($block_start === false) { // all done break; } else { // extract the block name (the foo in {block:foo}) - from the character // after the next : to the character before the next }, inclusive $block_name_start = strpos($template, ':', $block_start) + 1; $block_name = substr($template, $block_name_start, strcspn($template, '}', $block_name_start)); // we now have the start tag and the block name, next find the end tag. // $block_end is the index of the '{' of the next closing block tag after // $block_start. If this doesn't match the opening tag something is wrong. $block_end = strpos($template, '{/block:', $block_start); if(strpos($template, $block_name.'}', $block_end + 8) !== $block_end + 8) { // non-matching tag print("Non-matching tag found\n"); break; } else { // now we have found the innermost block // - its start tag begins at $block_start // - its content begins at // (strpos($template, '}', $block_start) + 1) // - its content ends at $block_end // - its end tag ends at ($block_end + strlen($block_name) + 9) // [9 being the length of '{/block:' plus '}'] // - the start tag was inverted iff $block_start === $last_inverted_tag $template = // do whatever you need to do to replace the template } } } echo $template; 

Vous pouvez utiliser atomic group: (?>...) ou des possessive quantifiers: ?+ *+ ++.. pour supprimer / limiter le retour en arrière et accélérer la correspondance en unrolling loop technique de unrolling loop . Ma solution:

\{block:(\w++)\}([^< {]++(?:(?!\{\/?block:\1\b)[<{][^<{]*+)*+)\{/block:\1\}

J'ai testé à partir de http://regexr.com?31p03 .

match {block:sponsors}...{/block:sponsors} :
\{block:(sponsors)\}([^< {]++(?:(?!\{\/?block:\1\b)[<{][^<{]*+)*+)\{/block:\1\}
http://regexr.com?31rb3

match {block:test}...{/block:test} :
\{block:(test)\}([^< {]++(?:(?!\{\/?block:\1\b)[<{][^<{]*+)*+)\{/block:\1\}
http://regexr.com?31rb6

l'autre solution:
dans le code source PCRE, vous pouvez supprimer le commentaire de config.h :
/* #undef NO_RECURSE */

copie de texte suivante de config.h :
PCRE utilise des appels de fonction récursifs pour gérer le retour en arrière lors de la correspondance. Cela peut parfois poser problème sur les systèmes qui ont des stacks de taille limitée. Définissez NO_RECURSE pour obtenir une version qui n'utilise pas la récursivité dans la fonction match (); au lieu de cela, il crée sa propre stack en utilisant pcre_recurse_malloc () pour obtenir de la mémoire du tas.

ou vous pouvez changer pcre.backtrack_limit et pcre.recursion_limit de php.ini (http://www.php.net/manual/en/pcre.configuration.php)