Archive for the 'String.Format' Category

String.Format (el buen uso): formateo de fechas y horas

Siguiendo con la serie del string.Format, en este capítulo vamos a hablar del formateo de fechas (DateTime y TimeSpan).

Dado que el formateo de fechas permite una gran cantidad de personalización, vamos a centrarnos en las más útiles.

Cadena de formato Acción resultante
d Fecha corta: 28/09/2007
D Fecha larga: viernes, 28 de Septiembre de 2007
t Hora corta: 11:03
T Hora larga: 11:03:45
f Fecha y hora: viernes, 28 de Septiembre de 2007 11:04
F Fecha y hora (con segundos): viernes, 28 de Septiembre de 2007 11:04:21
g Fecha y hora cortas (por defecto del sistema): 28/09/2007 11:06
G Fecha y hora cortas (por defecto del sistema) con segundos: 28/09/2007 11:06:46
m ó M Día y mes: 28 septiembre
r Fecha en formato RFC1123: Fri, 28 Sep 2007 11:08:39 GMT
s Fecha ordenable: 2007-09-28T11:09:03 (Como veis, va de mayor importancia a menor)
u Formato universal: 2007-09-28 11:09:54Z

Después, tenemos otros parámetros como: o, y, H, K… que podeis consultar en los links de abajo. Estos permiten crear fechas a nuestro antojo. Así, con la expresión:

string.Format("{0:'en el día' dd 'de' MMMM 'del año de nuestro señor jesucristo :P' yyyy}", DateTime.Now)

Podeis conseguir cosas muy curiosas }:)

Para más información:
Cadenas de formata de fecha y hora estándar
Cadenas de formata de fecha y hora personalizado

String.Format (el buen uso): formateo de tipos enumerados

Siguiendo con la serie de String.Format del .NET Framework, vamos a presentar en esta ocasión para qué sirve el string.Format y dónde debemos usarlo.

Para empezar, vamos a empezar por mencionar algo muy curioso. En el .NET Framework existe una interfaz: IFormattable. Si tenemos pensamientos de agregar funcionalidad de formato a una clase y queremos que rápidamente se puede acoplar con otro elementos del Framework, implementa esta interfaz.

Dicho esto, hablemos del formateo de strings en .NET

En .NET vamos a encontrar una variedad de clases con métodos sobrecargados como ToString(), que traen varios parámetros más como un string y un IFormatProvider. Además, seguimos teniendo la función Format que los que vienen del Visual Basic Clasico conocerán bien, aunque ahora es un método estático de la clase String (String.Format).

Dentro del maravilloso mundo del formateo de strings hay tres grandes bloques bien diferenciados:

  • Formateo de tipos enumerados. Enums con o sin Flags.
  • Formateo de fechas y horas. DateTime y TimeSpan.
  • Formateo de tipos numéricos. Integers, Double,…
  • Formatos personalizados. Formato para nuestras clases, como podría ser representar la representación de un número imaginario en Modulo-Argumento, etc…

En esta entrega (que va a ser la primera de cuatro) vamos a hablar del formateo de los tipos enumerados, que es la más fácil de entender de todas y nos ayudará a meternos en materia.

Formateo de los tipos enumerados

Los tipos enumerados dentro del .Net Framework heredan de manera transparente de la clase System.Enum, una clase abstracta que sirve de base para todas las enumeraciones. Además de ello, a los tipos enumerados se les puede indicar el tamaño/valor que pueden tener siempre que sea uno de estos tipos: byte, ushort, uint, ulong, sbyte, short, int y long. Es decir, que podemos jugar desde 8 hasta 64 bits. Por último, se pueden marcar con el atributo flags si vamos a permitir que pueda utilizarse como tales. Un ejemplo de esto, sería lo siguiente:

//Este tipo enumerado tiene un tamaño de 8 bits (byte)
//y sus valores permiten que puedan definirse flags sin ambigüedad.
[Flags]
public enum TipoBlogs: byte
{
    Ninguna = 0,	//0000 0000
    Personales = 1,	//0000 0001
    Tecnicos = 2,	//0000 0010
    Opinion = 4,	//0000 0100
    Politica = 8	//0000 1000
}

Gracias al tipo enumerado TipoBlogs podríamos crear variables y almacenar en ellas varias cosas. Por ejemplo, veamos cómo definiríamos un blog que hablase de cosas Personales y Políticas.

TipoBlogs tipo = TipoBlogs.Personales | TipoBlogs.Politica;

Dicho esto, pasemos a ver qué posibilidades nos pueden ofrecer el formateo de tipos enumerados del .NET Framework.

Cadena de formato Acción resultante
G ó g Muestra el valor de la enumeración con su nombre, en vez de su valor numérico. Si la enumeración permite flags y el valor tiene varios flags, entonces muestra una lista separada por comas con los distintos valores que toma. En caso de no poder representarse así, mostrará su valor numérico.

Ejemplo:

TipoBlogs tipo = TipoBlogs.Personales | TipoBlogs.Politica;
string text = string.Format("{0:G}", tipo);
Console.WriteLine(text);

Resultado:
“Personales, Politica”

F ó f Ídem. En el .NET Framework se utiliza “f” como el genérico cuando no se sabe las distintas variedades disponibles de formato.
D ó d Muestra el valor numérico con la notación más compacta posible. Es decir, que si el valor es 9 no podemos utilizar “dd” para conseguir 09. Lanza una FormatException.

Ejemplo:

TipoBlogs tipo = TipoBlogs.Personales | TipoBlogs.Politica;
string text = string.Format("{0:d}", tipo);
Console.WriteLine(text);

Resultado:
“9″

Dado que utilizamos flags, era evidente. El resultado es un OR entre 00001000 y 00000001 lo cual es 00001001.

X ó x Muestra el valor numérico en hexadecimal, con el mínimo número de caractéres hexadecimales para el tipo numérico. En otras palabras, utilizará dos caracteres para el byte, cuatro para el short y así sucesivamente. Indistintamente de X ó x, mostrará los caracteres hexadecimales en mayúsculas.

Ejemplo:

TipoBlogs tipo = TipoBlogs.Opinion | TipoBlogs.Politica;
string text = string.Format("{0:x}", tipo);
Console.WriteLine(text);

Resultado:
“OC”

Lo cual equivale a 12 en hexadecimal, como ya sabeis.

Y ahora entraremos en la parte difícil, os preguntareis como funciona el esto del formateo de strings…

Bien, en la clase padre de todos los tipos enumerados (System.Enum), se implementa la interfaz IFormattable. Cuando hacemos una llamada a como string.Format("{0:x}", tipo); por debajo lo que se está haciendo es llamar al método ToString(string format, System.IFormatProvider formatProvider) de IFormattable implementado en Enum. Cuando las llamadas internas del String.Format se han compleado, se concatena todo y se devuelve el resultado.

De ahí, que si implementamos IFormattable en nuestras clases, podremos crear los formatos que necesitemos a nuestra manera. Cosa de la que hablaremos en el capítulo 4 de estas entregas sobre formateo.

Hasta la próxima entrega!

String.Format (el mal uso de)

Advertencia: Este bug ya está puesto en conocimiento de Microsoft.

Desde los tiempo más remotos, Microsoft ha incluido un método Format en todos sus entornos RAD. Format nos permite hacer cosas como imprimir un número en formato Octal o Hexadecimal, imprimir la fecha en un formato personalizado o incluso definir nuestro propio formato para números (utilizando la vieja notación del VB, #;-#;#).

Cuando uno entra en contacto con string.Format, se enamora de ella. Evita tener que estar utilizando constantemente el método ToString() de todo lo que no sea un string. Por ejemplo, cuando uno quiere rellenar una cadena de tareas completadas, resulta más fácil y legible por ejemplo hacer esto:

string.Format("Tareas completadas: {0} de {1}", numeroTarea, totalTareas);

Sin embargo, hay cosas para las que string.Format no fue creada como:

  • Completar consultas SQL parametrizadas.
  • Crear las rutas de acceso a archivo.

Ambos de estos casos hacen que nuestras aplicaciones tengan problemas de seguridad que podrían comprometer de manera notable el sistema.

En el primer caso, las consultas SQL parametrizadas haciendo uso de format consiguen que nuestra aplicación sea vulnerable a una SQL Injection. Sobre la SQL Injection, si no conoceis los riesgos que puede entrañar, recomiento la lectura de el blog de Chema Alonso: http://www.elladodelmal.com/

La cuestión es que para hacer consultas parametrizadas se dispone de un número de clases en el namespace System.Data.SqlClient que permitirán hacer estas de manera automatica.

Así, por tanto, lo que comunmente es fácil de encontrar como:
string.Format("SELECT COUNT(*) FROM users WHERE username = '{0}' AND password = '{1}'", username, password);

Se debe crear de otra manera: como un SqlCommand.

SqlCommand cmd = new SqlCommand("SELECT COUNT(*) FROM users WHERE username = @username AND password = @password");
cmd.Parameters.Add("@username", username);
cmd.Parameters.Add("@password", password);

La diferencia entre la primera y la segunda es que en la segunda se prepara la inserción y se elimina todo aquello que pueda ser nocivo para la cadena SQL. (Se duplican las ‘, etc…) y sin embargo los programadores (sobre todo aquellos que vienen del Visual Basic 6.0) no lo usan…

El segundo de los casos es otro igualmente interesante: combinar directorios tanto físicos como virtuales.

En este caso, voy a pegar un ejemplo muy usado: se trata del MyWebPagesStarterKit (version 1.1.3) disponible a través de Codeplex: http://www.codeplex.com/MyWebPagesStarterKit/

Se trata del fichero: DownloadHandler.ashx, localizado en el directorio raíz.

//===============================================================================================
//
// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Permissive License.
// See http://www.microsoft.com/resources/sharedsource/licensingbasics/sharedsourcelicenses.mspx.
// All other rights reserved.
//
//===============================================================================================

using System;
using System.Web;
using System.IO;
using MyWebPagesStarterKit;

///
/// This handler serves files from the "~/App_Data/_Downloads/" folder (which is by default not accessible over the web).
///
public class DownloadHandler : IHttpHandler
{
#region IHttpHandler Members
public bool IsReusable
{
get { return true; }
}
public void ProcessRequest(HttpContext context)
{
try
{
//get the sectionId
string section = context.Request.QueryString["section"];
//get the filename
string file = context.Request.QueryString["file"];
//build the path
string path = context.Server.MapPath(string.Format("~/App_Data/_Downloads/{0}/{1}", section, file));

// get the pageId for the authentication
string pageId = context.Request.QueryString["pg"];

//user authentication:
WebPage _page = new WebPage(pageId);
if (_page.AllowAnonymousAccess == false && context.User.Identity.IsAuthenticated == false)
throw new Exception("User is not allowed to view this file");

if (File.Exists(path))
{
context.Response.Clear();
context.Response.AddHeader("Pragma", "public");
context.Response.AddHeader("Expires", "0");
context.Response.AddHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0");
context.Response.AddHeader("Content-Type", "application/force-download");
context.Response.AddHeader("Content-Type", "application/octet-stream");
context.Response.AddHeader("Content-Type", "application/download");
context.Response.AddHeader("Content-Disposition", string.Format("attachment; filename={0}", file));
context.Response.AddHeader("Content-Transfer-Encoding", "binary");
context.Response.AddHeader("Content-Length", new FileInfo(path).Length.ToString());

context.Response.WriteFile(path);
}
}
catch
{
context.Response.Clear();
context.Response.End();
}
}
#endregion
}

Pues bien, para empezar a “despotricar” en estas 72 lineas de código se utiliza string.Format dos veces y en ambas se utiliza MAL. :P

Comenzaré por la segunda, ya que es la menos relevante en este post.
string.Format("attachment; filename={0}", file)
En este caso, parecen no haberse dado cuenta que los ficheros pueden contener espacios en el nombre. Al hacer esto así, el archivo llegará sin extensión dada la naturaleza de los encabezados http (al menos, pasa así en FireFox). La solución sería incluir unas comillas escapadas dentro de la cadena, como estas:
string.Format("attachment; filename=\"{0}\"", file)

Bien, solucionar esto no requiere demasiados conocimientos, sino más bien, experiencia en el desarrollo web. Además, la culpa no es del string.Format… pero bueno, por despotricar que no falte… }:-)

Ahora pasemos al primer problema:

string path = context.Server.MapPath(string.Format("~/App_Data/_Downloads/{0}/{1}", section, file));

Para empezar a criticar, leamos los comentarios en el código:

This handler serves files from the “~/App_Data/_Downloads/” folder (which is by default not accessible over the web).

Jejeje… :P

Para explotar el este bug, podríamos hacer uno o varios ejemplos… para empezar, vamos a hacer algo que lógicamente no podemos hacer en ASP.NET: descargarnos el Web.Config de un site.
Para ello, no hay más que:

  1. Buscar un id de página que sea público en el site.
  2. Algún archivo que bajar.
    1. Así, podríamos descargar cualquier cosa que se nos antoje… utilizando la siguiente URL:
      ~/DownloadHandler.ashx?pg=d96dfbe6-5cc2-4256-80c5-b765c766b68b&section=../..&file=Web.Config

      Cuando se resulve el string.Format, lo que ocurre es esto:
      ~/App_Data/_Downloads/../../Web.Config y nos envia directamente hacia el archivo Web.Config. Parece que los chicos de Microsoft que han desarrollado este CMS no conocen a nuestro viejo amigo “..” que permite subir en el árbol de directorios… al poner el id de cualquier página pública, podemos descargar lo que nos plazca.
      Lo mejor de descargar el web.config es encontrar algunas ConnectionStrings sin cifrar, etc…

      Pero, lo mejor de todo, es descargar la base de datos de los usuarios del site: para ello, no hay más que llamar a la siguiente URL:
      ~/DownloadHandler.ashx?pg=d96dfbe6-5cc2-4256-80c5-b765c766b68b&section=..&file=Users.Config
      Con un id de página válido y público en las aplicaciones webs que utilicen MyWebPagesStarterKit.

      Lo siguiente, después de haberse descargado la base de usuarios sería romper por fuerza bruta el hash SHA1 de la contraseña (para lo cual existen programas muy útiles) y tened en cuenta que, por defecto, este CMS no requiere caracteres no-alfanuméricos para ésta…

      Sin embargo, como se trata de contruir y no de destruir, lo que podríamos hacer es añadir una linea de código más y solucionar este problema reemplazando “../” por string.Empty, para que nadie pueda subir de directorio:
      string path = context.Server.MapPath(string.Format("~/App_Data/_Downloads/{0}/{1}", section, file).Replace("../", "");

      Finalmente para no seguir extendiendo más este post, he de comentarios que existen distintos métodos para hacer este tipo de cosas de buena manera, haciendo uso de las clases:

      • System.Web.Hosting.VirtualDirectory
      • System.Web.Hosting.VirtualFile
      • System.Web.Hosting.VirtualPathProvider
      • System.Web.Security.FileAuthorizationModule

      Así que ya sabeis: a securizar vuestras aplicaciones y aprended a usar el string.Format.

      PD0: Por lo demás, el MyWebPagesStarterKit es seguro y ofrece innumerables ventajas. En ningún momento pretendo desprestigiar a Microsoft con esto.
      PD1: En el Web.Config del MyWebPagesStarterKit viene el atributo <compilation debug=”true”>, ponedlo a false!!!
      PD2: Esta información se ofrece con buenos propósitos, para securizar las aplicaciones web. Cualquier daño producido por ella no es de mi responsabilidad.


About me


My name is Rafa Vargas. I'm an undergraduate student of Computer Science at University of Seville, Spain. I am mainly interested in computer security, usability and the business of software.

Click here to read the full story.

Twitter subscription

Error: Twitter did not respond. Please wait a few minutes and refresh this page.

Enter your email address to subscribe to this blog and receive notifications of new posts by email.

Archives