Sobre base64. Para que usarlo, para que no, y como manejarlo en .net y Asp.Net

6 11 2009

¿Que es base64?

Base64 no es en principio otra cosa mas que un sistema numérico, el cual debido a sus características se emplea en muchos ámbitos de la informática para representar información binaria.

Todos los sistemas de numeración tienen una lista de símbolos que utilizan para representar valores, por ejemplo:

Binario: ’01’
Decimal: ‘0123456789’
Hexadecimal: ‘0123456789ABCDEF’
y para base64 el conjunto es:
‘ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/’

Como vemos, es un subconjunto de ASCII, y tiene como particularidad que todos sus caracteres son imprimibles, de hecho 64 es la mayor potencia de 2 que permite ser representada por un subconjunto de caracteres ASCII imprimibles, por eso, si pasamos cualquier información a su representación de base64, tenemos la seguridad de que no tendremos problemas al transmitirla, almacenarla o leerla, incluso aunque esta contega los más remotos caracteres unicode, una imagen o un mp3, ya que para convertir algo a base64 trataremos directamente con los bits.

¿Como codificamos en base64?

Veamos (en este ejemplo flagrantemente robado de Wikipedia) como se codifica la palabra “Man”.

Texto de entrada M a n
ASCII 77 97 110
Bits 0 1 0 0 1 1 0 1 0 1 1 0 0 0 0 1 0 1 1 0 1 1 1 0
Índice 19 22 5 46
Resultado en Base64 T W F u

Tenemos que la M es en ASCII el caracter 77 (01001101 en binario), y como el primer paso para nuestra tarea es convertir la información de origen en bits (más sobre esto en un momento), transformamos toda la cadena en una secuencia de unos y ceros, los cuales tomamos en grupos de a seis.

El primero de estos grupos (010011) representa 19, y si tomamos la posición 19 en la lista de símbolos de base64 (empezando a contar desde cero, claro) obtenemos la T, y continuando con este algoritmo llegamos al resultado “TWFu”.

Caracteres de Relleno

base64 se codifica tomando los bytes de tres como entrada en cuatro sextetos, o sea de a 24 bits. Cuando esta cantidad no coincide, completamos con el caracter de relleno “=” los sextetos restantes.

Por ejemplo, si hubiéramos codificado solamente el texto “Ma”, veríamos que no coincide la cantidad de octetos con la de sextetos, luego de “TW” me quedaría 0001, por lo que completo con ocho ceros y obtengo “TWE”. El sexteto vacío que queda se completa con el caraceter “=” quedando como resultado: “TWE=”

Hasta aquí como se codifica. De la misma manera, podría codificar un archivo ejecutable, una imagen, archivos de sonido o simplemente como en este caso, Texto Plano.

Ooops, acabo de decir “texto plano”, pero como bien dice Joel Spolsky (parafraseando a famoso economista que parafraseaba a conocido escritor de ciencia ficción): “No existe tal cosa como el texto plano”. Y acá es donde entra a fastidiarnos la palabra “Encoding”.

Encoding

La representación lógica de una cadena de texto, como puede serlo un String de .net, se puede convertir a bits de muchas maneras. No es lo mismo la representación binaria de texto en ASCII que en Unicode, al respecto recomiendo encarecidamente leer el citado post de Joel Spolsky, pero lo que quiero destacar ahora a los efectos prácticos de comprender el código es que, cuando trabamos con texto, este es para nosotros la representación lógica de la información, la cual se perderá al momento de trabajar directramente con los bits, y como la conversión de esta información lógica a bits puede realizarse de muchas maneras distintas, debemos tener control de este paso al momento de codificar y decodificar texto en y desde base64.

Base64 en .net

Para convertir texto a bytes .net nos ofrece la clase abstracta Encoding y sus derivados concretos, en el ejemplo utilizaremos UTF8Encoding. Si no necesitamos usarlas en grandes iteraciones, podemos obtener instancias de estas clases desde las propiedades estáticas que nos da la clase Encoding, (.UTF8, .UTF7, .ASCII).

Y para obtener los bytes utilizamos el método GetBytes, dado un string “s”:

Encoding.UTF8.GetBytes(s)

Estos bytes se los pasamos a Convert.ToBase64String:

String b64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(s));

y a la inversa, mediante el método GetString de la misma clase de Encoding que hayamos utilizado en la conversión:

String s = Encoding.UTF8.GetString(Convert.FromBase64String(b64));

URLTokenEncode, base64 en ASP.NET

Utilizar parámetros codificados en base64 en un query string nos aporta la seguridad de que no tendremos problemas con los caracteres reservados por HTML, así como la ofuscación de la información ante los usuarios, pero nos encontramos ante el problema de los símbolos “+”, “/” y “=” que tienen un significado dentro de una URI.

Ante esta limitación .net nos da una alternativa a la codificación clásica mediante el método estático UrlTokenEncode de la clase HttpServerUtility, en System.Web:

String b64=HttpServerUtility.UrlTokenEncode(Encoding.UTF8.GetBytes(s));
String s=Encoding.UTF8.GetString(HttpServerUtility.UrlTokenDecode(b64));

Con este método, los caracteres “+” y “/” son reemplazados por “-“ y “_” respectivamente y los simbolos “=” de complemento se reemplazan por un dígito numérico indicando la cantidad de los mismos, de esta manera la cadena “ûï¾x%óaxz” se códifica (en UTF8) como: “w7vDr8K+eCXDs2F4eg==” según el método estándar y con UrlTokenEncode “w7vDr8K-eCXDs2F4eg2”, donde el “2” final representa los “==” al final del resultado estándar y el “-“ reemplaza al “+”.

De esta manera obtenemos una cadena totalmente segura para utilizar en el ámbito web.

Algunos hechos sobre base64

  • Base64 no es un método de encriptación. Si bien ofusca la información ante la simple vista no hace falta mucho conocimiento ni pericia para recuperar la información original.
  • Base64 “pesa” un 33% más que la información original. Esta ralación está dada por la relación 8 a 6 bits por caracter, (o sea, un caracter de base64 representa menos información que un caracter normal).
  • Aparte del nombrado Issue en su utilización en Web, en otros ámbitos también hay caracteres que se tornan problemáticos, por ejemplo en regular expressions, se suelen reemplazar “+” y “/” por “[“ y “]”. Observesé que la codificación que utiliza UrlTokenEncode no serviría en este caso ya que los “+” tienen significado propio en Regular Expression, por lo que habría que escaparlos.
  • Insisto: No sirve para encriptar información. No utiliza claves ni certificados, la conversión a la información original es prácticamente directa y no está pensado para este fin.
  • Si podemos, en cambio, utilizarlo para guardar o transmitir información previamente cifrada que suele presentar caracteres problemáticos para ser transmitidos o almacenados.




Checkear todos los CheckBox con jQuery

17 06 2009

Uno de los artículos más consultados de este blog explica como configurar una tabla para que al clickear en un Checkbox en la cabecera de una columna, todos los checks de la misma tomen el mismo estado “checked/unchecked”.

Sin embargo, ese código requiere de algunas condiciones:

  • La tabla debe poseer un ID.
  • Cada checkbox debe ser el primer elemento de la celda.
  • Hay que escribir una llamada al evento onclick del checkbox en la cabecera, pasando como parámetro el ID de la tabla, ya sea escribiéndolo o navegando por el DOM para obtenerlo.

Y tiene un grave problema, no funciona en navegadores no-IE.

Cuando empecé a investigar jQuery, quise emular como práctica este código y me encontré con que jQuery nos lleva a plantear una solución mucho mas completa, que no solo resuelve todos los problemas anteriores, si no que nos lleva a escribir en el body la cantidad de javascript: ¡cero!.

Vamos al grano:

Primer paso: Agregar el include del archivo js donde está el código. (aparte del de jQuery por supuesto)

<script src="js/jquery.js" type="text/javascript"></script>
<script src="js/miScript.js" type="text/javascript"></script>

Segundo y último paso: En miScript.js el siguiente código:

$(function() {
    $("table th :checkbox").click(function() {
        $(this).closest("table")
            .find("td:nth-child(" + ($(this).closest("th").prevAll().size() + 1) + ") :checkbox")
            .attr("checked", $(this).attr("checked"));
    });
});

Aparte de esto, solo nos queda asegurarnos de que los checkbox de la cabecera estén en un TH y no en un TD.

Ahora ya no nos importa si usamos un GridView, una tabla estática, ni si posee tags THEAD y TBODY o solo TRs, ni si el checkbox es un control de servidor o simple HTML. Todos los checkboxs que tengamos en la cabecera (TH) de una tabla provocarán al cambiar su estado que cambien todos los de la misma columna.

No se a Uds. pero a mi me parece fantástico que solamente incluyendo un tag para jQuery y otro para un .js con el script (que seguramente ya estoy incorporando a nuestras páginas para utilizar otras funciones), tenga el problema resuelto en todas las páginas sin hacer más que olvidarme del tema.

Respecto al código, voy a usarlo como parte de la continuación de mi artículo anterior de introducción a jQuery para los que quieran entenderlo.

Nota: Debido al uso de la (excelente) función closest el código requiere jQuery 1.3+.

Saludos: Alejandro





Chequear todos los checkbox de un gridview con javascript

3 11 2007

NOTA 16/06/2009: He publicado como hacer esta misma tarea con jQuery en este artículo, recomiendo utilizar esa alternativa por los motivos que comento en dicho artículo.

Muchas veces queremos chequear o deschequear todos los checkbox de un gridview de una sola vez, puede ser cuando el usuario realiza determinada acción en otro control, por ejemplo un botón para “seleccionar todo”, o que cuando se activa un CheckBox en la cabecera de la columna, cambie el estado de todos los checks al de la cabecera.

Si bien esto se puede hacer con código de servidor, normalmente no queremos que se realice un postback solo para esto, la solución es javascript.

(Nuevamente, si solo te interesa un código js que solucione el problema que acabo de describir, te recomiendo ir directamente a copiar los dos fragmentos de javascript al final del post.)

En su momento utilicé el código que Scott Mitchell publicó en este artículo, pero esta solución, si bien funciona y no genera un postback, requiere código de servidor, ya que en la creación de la página se registra mediante el método RegisterArrayDeclaration un array con los ids que tendrá cada checkbox en el cliente, lo que nos obliga a hacerlo en cada postback o a recargar el estado, ya sea ViewState o Session.

Buscando otra alternativa encontré este otro artículo de Mohammad Azam, el autor de GridViewGuy. Este si es un ejemplo totalmente sobre javascript, pero tiene un gran defecto: opera sobre todos los checkbox de la página, sin importar si están dentro de la grilla o no, ni hablar si tenemos dos grillas.

En este punto decidí escribir mi propia solución, o sea una totalmente en el cliente y que permita operar sobre los checkbox de una grilla en particular y, por que no, sobre una columna en particular, de manera que podamos tener más de una columna con checkbox y cambiar el estado de los checks de una de ellas sin afectar la otra.

Un poco de background que pueden saltarse si les parece:

Como incluir CheckBox en una columna de un gridview

Antes que nada hay que generar una columna con los checkbox. Hay dos maneras de hacer esto como cuenta Scott Mitchell en el artículo nombrado, yo utilizo también un TemplateField (no encontré todavía utilidad para los CheckBoxField), de modo que colocando un checkbox en el ItemTemplate genero uno en cada celda del TemplateField:

<asp:TemplateField HeaderText="chk">
   <ItemTemplate>
      <asp:CheckBox ID="chkColumna1" runat="server" />
   </ItemTemplate>
</asp:TemplateField>

Visualmente (en VisualStudio), agregamos un TemplateField al gridview y seleccionamos EditTemplate del menú contextual. Luego arrastramos un CheckBox a la zona ItemTemplate. Para finalizar: End Template Editing, en el menú contextual.

Como chequear todos CheckBox de un GridView

Esta rutina javascript cambia el estado de todos los checkbox de una grilla sin importar su ubicación:

function ChangeAllChecks(gridViewName,newState)
{
   var tabla = document.getElementById(gridViewName);
   celdas = tabla.cells;
   for(i=0;i<celdas.length-1;i++)
   {
   if (celdas[i].firstChild.type=="checkbox"
   && celdas[i].firstChild.checked != newState)
      {
         celdas[i].firstChild.click();
      }
   }
}

Para llamarla utilizamos ChangeAllChecks(‘GridView1’,true); o false si es el caso

Como chequear una columna específica

Puede ocurrir que en un gridview tengamos más de un TemplateField con CheckBox, y que solo queramos chequear o deschequear una sola columna, para ello hice un nuevo método que toma como paramétro el índice de la columna (empezando por 0). (Actualizado 29/09/2008 según respuesta al comentario de Martín)

function ChangeChecksByColumn(gridViewName, newState, columnIndex){
    var tabla = document.getElementById(gridViewName);
    var columnas = tabla.cells.length / tabla.rows.length;
    celdas = tabla.cells;
    for (i = columnas + columnIndex; i < celdas.length; i += columnas){
        if (celdas[i].firstChild.type == "checkbox"
               && celdas[i].firstChild.checked != newState
            /* && agregar aquí otras condiciones */){
            celdas[i].firstChild.click();
        }
    }
}

Como chequear a partir de un CheckBox en la cabecera

El código anterior no es muy útil porque nos solicita el estado (true=checked/false=unchequed) en el que queremos dejar los checkbox, la solución es agregar un checkbox en la cabecera de la TemplateColumn:

y agregamos una rutina que permita identificar el estado del mismo y la columna en que se encuentra, luego llamamos al método anterior: ChangeChecksByColumn:

function CopyCheckStateByColumn(HeaderCheckBox, gridViewName)
{
    var columnIndex = HeaderCheckBox.parentElement.cellIndex;
    var newState = HeaderCheckBox.checked;
    ChangeChecksByColumn(gridViewName, newState, columnIndex);
}

Para terminar, completamos el evento onclick del checkbox con una llamada a CopyCheckStateByColumn pasando como parámetro el propio checkbox y el nombre del gridview:
(lo que sigue cambiado el 08/11 según la respuesta al comentario de Roger)

<asp:TemplateField HeaderText="chk">
<HeaderTemplate>
    <asp:CheckBox ID="chkHeader" runat="server" onclick="javascript:CopyCheckStateByColumn(this,this.offsetParent.offsetParent.id);"/>
</HeaderTemplate>
<ItemTemplate>
    <asp:CheckBox ID="chkTest" runat="server" />
</ItemTemplate>
</asp:TemplateField>

Una precaución: Tener en cuenta que el código javascript consulta si el contenido es un CheckBox mediante la propiedad firstChild para cada objeto de la colección cells del objeto table, o sea: el contenido de cada TD. Si ponemos otro objeto en el ItemTemplate junto con el CheckBox, podemos tener problemas si nos descuidamos.

Un tip: Para llamar métodos javascript en el evento click de un control Button, se debe utilizar el evento OnClientClick en vez de onclick.

Una desventaja: No funciona en Firefox.

Saludos