regex · procesamiento de texto · seguridad
¿Qué es exactamente una regex?
Una expresión regular, o regex, es un pequeño lenguaje que describe la forma del texto en lugar de su contenido exacto. En vez de preguntar «¿es esta cadena igual a abc?», preguntas «¿tiene esta cadena forma de tres letras, un guion y cuatro dígitos?».
Esa es toda la idea. Lo demás es notación.
La notación, de una pasada
Casi todo lo que leerás se construye con un puñado de piezas:
.cualquier carácter,\dun dígito,\wun carácter de palabra,\sun espacio*cero o más,+uno o más,?cero o uno,5entre dos y cinco[abc]cualquiera de estos,[^abc]todo menos estos,[a-z]un rango^el principio de la cadena,$su final(...)un grupo que puedes repetir o capturar,|uno u otro
Así ^\d3-\d4$ se lee: desde el principio, tres dígitos, un guion, cuatro dígitos, y el final. Nada más.
Por qué el mismo patrón se comporta distinto en dos lenguajes
No existe una regex. Existe una familia de dialectos que coinciden en lo básico y divergen más allá: POSIX, PCRE, el sabor integrado en JavaScript, el de Python, el de Go. El lookbehind, los grupos con nombre, los escapes de propiedades Unicode e incluso lo que \d considera un dígito son puntos donde se separan.
La consecuencia práctica es que un patrón copiado de una respuesta escrita para otro lenguaje puede hacer en silencio otra cosa en el tuyo. Pruébalo donde va a ejecutarse, no donde lo encontraste. Para eso está el probador de regex de este sitio.
El fallo que conviene conocer: backtracking catastrófico
Aquí está lo que convierte una utilidad de coincidencia de texto en un problema de explotación.
La mayoría de los motores de regex de los lenguajes habituales funcionan por retroceso. Cuando un patrón puede coincidir de varias maneras, el motor prueba una división y, si el resto falla, vuelve atrás y prueba otra. Para patrones corrientes eso es barato. Para algunos, no.
La forma peligrosa es una repetición dentro de otra repetición, donde ambas pueden repartirse el mismo texto de muchas maneras. El ejemplo de manual es (a+)+$. Ante una larga serie de a seguida de un carácter que no puede coincidir, el motor debe probar todas las formas posibles de repartir esas a entre la repetición interna y la externa antes de concluir que falla. El número de combinaciones crece exponencialmente con la longitud de la entrada.
Veinte caracteres pueden ser instantáneos. Treinta pueden tardar un segundo. Cuarenta pueden no terminar mientras sigas en la empresa. Y como esto solo ocurre con entradas que casi coinciden, nunca aparece en las pruebas que escribiste con entradas que sí coinciden.
Cuando esa regex se ejecuta sobre entrada proporcionada por el usuario en un servidor, el caso exponencial está al alcance de cualquiera que pueda enviar una petición. Es la clase de denegación de servicio conocida como ReDoS, catalogada por OWASP como denegación de servicio por expresión regular.
Cómo mantenerte al margen
Desconfía de los cuantificadores anidados. Un + o un * aplicado a un grupo que ya contiene uno es la forma que hay que buscar. A menudo puede aplanarse en algo con una única manera de coincidir.
Ancla y sé específico. ^, $ y clases de caracteres precisas reducen el número de divisiones que el motor debe considerar. [^"]* suele ser mejor que .*.
No pases entrada de usuario por una regex escrita a mano a la ligera. Para correos, URL y fechas, un analizador o una biblioteca probada gana a un patrón ingenioso, y falla de un modo legible.
Conoce tu motor. Algunos, como RE2 en Go, se basan en otro algoritmo y no retroceden en absoluto. Renuncian a funciones como las retrorreferencias a cambio de una coincidencia garantizada en tiempo lineal. Si una regex debe ejecutarse sobre entrada hostil, ese intercambio suele ser el correcto.
En resumen
Una regex describe la forma del texto, con una notación más pequeña de lo que parece y menos portable de lo que aparenta. Aprende la docena de símbolos, verifica el dialecto donde correrá el código, y trata un cuantificador dentro de otro como un defecto mientras no se demuestre lo contrario. El patrón que supera todas tus pruebas no es el que tumbará el sitio.