Expresiones regulares más entendibles en .NET – II

En la primera parte en el que recomendaba el uso de comentarios y la asignación de nombres a los grupos que se definían en la expresión regular, Eber Irigoyen comentó otra alternativa, que también había visto antes 🙂 , donde se hace uso del patrón Fluent Interface para que las expresiones regulares sean más fáciles de entender, este conjunto de clases es obra de Joshua Flanagan:

El siguiente ejemplo muestra un equivalente a la siguiente expresión regular: <(?<tag>[a-zA-Z][a-zA-Z]*)[^>]*>(?<innerHTML>.*?)</\k<tag>>

csharp:
using System;
using FlimFlan.ReadableRex;
using System.Text.RegularExpressions;

class Program
{
    static void Main(string[] args)
    {
        //Regex htmlTag = new Regex(@"<(?<tag>[a-zA-Z][a-zA-Z]*)[^>]*>(?<innerHTML>.*?)</\k<tag>>");

        Pattern pattern =
            Pattern.With.Literal("<"). // <
            NamedGroup("tag", Pattern.With.Set(Range.AnyLetter).Set(Range.AnyLetter).Repeat.ZeroOrMore). // (?<tag>[a-zA-Z][a-zA-Z]*)
                NegatedSet(Pattern.With.Literal(">")).Repeat.ZeroOrMore.Literal(">"). // [^>]*>
            NamedGroup("innerHTML", Pattern.With.Anything.Repeat.Lazy.ZeroOrMore). // (?<innerHTML>.*?)
            RegEx(@"</\k<tag>>"); // </\k<tag>>

        Match match = Regex.Match(@"<a href=""http://www.buayacorp.com"">Enlace</a>", pattern.ToString());

        Console.WriteLine("Patrón: {0}\nGrupo innerHTML: {1}", pattern.ToString(), match.Groups["innerHTML"].Value);
    }
}

Esta última alternativa es más adecuada para aquellos que no están tan familiarizados con las expresiones regulares, pero es bastante fastidioso cuando se requiere expresar un patrón más largo y complejo; en todo caso el uso de uno u otro método, dependerá de las prácticas que se sigan en cada empresa -o proyecto.

Expresiones regulares más entendibles en .NET

Seguramente más de uno que ha tenido la oportunidad de revisar código ajeno donde se hacía uso de expresiones regulares, sufrió de algún modo para entender algunos patrones; esto sucede generalmente porque pocos suelen poner nombres a los grupos que definen y menos aún usan comentarios dentro de los patrones.

El siguiente ejemplo ilustra de manera básica un patrón confuso:

csharp:
using System;
using System.Text.RegularExpressions;

class Program
{
        static void Main(string[] args)
        {
                string patern = @"(https?://)([a-z\d-]+\.)*([a-z\d-]+)((\.[a-zA-Z]+){1,2})(/.*$)?";

                RegexOptions options = RegexOptions.IgnoreCase;

                Match match = Regex.Match("http://google.com", pattern, options);

                foreach (Group group in match.Groups)
                {
                        Console.WriteLine(group.Value);
                }           
        }
}

Ese mismo ejemplo, usando la opción RegexOptions.IgnorePatternWhitespace permite entender más fácilmente la expresión regular mostrada:

csharp:
using System;
using System.Text.RegularExpressions;

class Program
{
    static void Main(string[] args)
    {
        string pattern = @"
               (?# Ejemplo de comentario: captura el protocolo http o https incluyendo ://)
               (?<protocol>https?://)
               (?<subDomain>[a-z\d-]+\.)*
               # Algún otro comentario relevante #
               (?<domain>[a-z\d-]+)
               (?<TLD>(\.[a-zA-Z]+){1,2})
               (?<requestUri>/.*$)?
           ";       
        RegexOptions options = RegexOptions.IgnorePatternWhitespace | RegexOptions.IgnoreCase;

        Match match = Regex.Match("http://google.com", pattern, options);

        foreach (Group group in match.Groups)
        {
            Console.WriteLine(group.Value);
        }
    }
}

Como se puede apreciar, el segundo ejemplo da una idea más clara de lo que captura el patrón definido.

Si desarrollan en .NET y usan expresiones regulares, no hay excusas para hacer sufrir a los que revisan o dan mantenimiento al código que hicieron 😉

PHP: Uso adecuado de expresiones regulares

Actualización 17/04/2007: Al parecer el problema descrito ya no es válido en versiones recientes de PHP.

PHP incluye el soporte para expresiones regulares usando una sintáxis compatible con Perl (preg_match, preg_replace, preg_split, etc) y también las expresiones con sintáxis POSIX extendido (ereg, eregi, ereg_replace, eregi_replace, etc).

En esta oportunidad no hablaré de los problemas derivados del abuso o uso inadecuado de las expresiones regulares, sino un problema más específico para aquellos que usan las funciones POSIX-extendido de PHP, sobre el cual existe la siguiente advertencia en el manual de PHP:

Estas expresiones regulares no son seguras con material binario. Las Funciones PCRE lo son.

Bien, veamos un ejemplo en el que por usar este tipo de funciones, una aplicación puede llegar a sufrir problemas de XSS o Inyección de SQL (si es que sólo se usa este tipo de cosas para validar los parámetros):

php:
<?php
// re.php

header('Content-type: text/html; charset=utf-8');

if ( eregi('^[a-z0-9]{4}$', $_GET['key']) ) {
        echo $_GET['key'];
} else {
        echo 'Caracteres no válidos';
}

// ...
?>

La expresión regular definida para validar el parámetro key supuestamente debería considerar sólo aquellos valores constituidos por cuatro letras y/o números, pero debido a que éstas funciones no son seguras con material binario, es posible saltar fácilmente esa validación añadiendo el caracter \0 (fin de cadena en C/C++).

Por ejemplo, para el siguiente valor de key:

php:
<?php

header('Content-type: text/html; charset=utf-8');

$_GET['key'] = 'test' . chr(0) . '<script>alert("Texto no tomado en cuenta")</script>';

if ( eregi('^[a-z0-9]{4}$', $_GET['key']) ) {
        echo $_GET['key'];
} else {
        echo 'Caracteres no válidos';
}

// ...
?>

Se mostrará en el navegador lo siguiente:

code:
test?<script>alert("Texto no tomado en cuenta")</script>

Para conseguir este mismo resultado desde el navegador, sólo basta usar %00 en la URL para representar el caracter \0: key=test%00<foo>.

Si están usando este tipo de funciones, ya están advertidos de los problemas que podrían tener si se les olvida la recomendación del manual de PHP.

Plugin de WordPress a la Lista Negra (Acronym Replacer Revisited)

Acronym Replacer Revisited, es un plugin modificado por Nicolás Fantino, que tiene la finalidad de reemplazar los acrónimos que aparecen en el contenido de una o más entradas.

Gracias a este plugin, he perdido unas cuantas horas intentando determinar porque el blog en el que estaba instalado demoraba mucho en cargar, incluso cuando intentaba validar el feed, el código de respuesta era : Server returned timed out.

A continuación los tiempos de respuesta con el plugin activado/desactivado:

code:
-- Plugin activado
Request Count:  1
Bytes Sent:     56
Bytes Received: 52,125

RESPONSE CODES
--------------
HTTP/200:       1

RESPONSE BYTES (by Content-Type)
--------------
 ~headers:      326
text/html:      51,799

PERFORMANCE
--------------
Time to First byte:     11,125ms
Time to Last byte:      13,547ms
code:
-- Plugin desactivado
Request Count:  1
Bytes Sent:     56
Bytes Received: 51,882

RESPONSE CODES
--------------
HTTP/200:       1

RESPONSE BYTES (by Content-Type)
--------------
 ~headers:      326
text/html:      51,556

PERFORMANCE
--------------
Time to First byte:     1,172ms
Time to Last byte:      1,453ms

Si se fijan los valores de Time to First byte y Time to Last byte se puede observar que hay una gran diferencia en los tiempos de respuesta.

No sé si a otros bloggers que usen este plugin les pase lo mismo, pero por mi parte no he intentado corregir el problema descrito -me dá la ligera impresión que es por el mal uso de expresiones regulares. Por otro lado, tampoco probé la versión original para ver si el problema se reproduce o no, pero por lo pronto es el primer plugin para WordPress que agrego a mi lista negra :D.

Evalúa tus expresiones regulares con “The Regulator”

The Regulator, es un proyecto Open Source que permite evaluar expresiones regulares, algunas características de esta herramienta son:

  • Permite buscar, reemplazar y separar cadenas en base a una expresión regular.
  • Analizador de las expresiones regulares.
  • Buscar e importar expresiones desde regexlib.com, que es un repositorio con una gran cantidad de expresiones regulares.
  • Coloreado de sintáxis e Intellisense para las expresiones regulares.
  • Posibilidad para crear snippets, para las expresiones regulares más usadas.
  • Posibilidad de generación de código para C# y VB.NET (se puede implementar un plugin para que soporte más lenguajes)
  • ...

Analizador de expresiones regulares
Analizador de expresiones regulares

Intellisense de expresiones
Intellisense de expresiones regulares

Búsqueda de expresiones regulares
Búsqueda de expresiones regulares

En la versión original de esta herramienta no funcionan las búsquedas de expresiones regulares, esto debido a que la dirección e interfaz del servicio web brindado por regexlib.com ha cambiado.

Si desean obtener una versión -talvez inestable- con este pequeño bug corregido*, pueden hacerlo desde este sitio, pero necesitarán -a diferencia de la versión original- el .NET Framework 2

He publicado esta versión modificada, ya que según Roy Osherove -autor de esta herramienta, por el momento no puede solucionarlo por falta de tiempo.

*: en realidad es más un hack para que funcione la búsqueda, que la solución al problema descrito.

Descargas