quinta-feira, 20 de novembro de 2008

Base64

De vez enquando, não tem como não, nós programadores sempre esbarramos na Base64.

Base64 é um protocolo de codificação que usa apenas seis bits, o que significa um conjunto de sessenta e quatro (64) elementos – daí Base64.

A conversão de oito (byte) para seis bits é feita da seguinte forma:
xxxxxx.xx xxxx.xxxx xx.xxxxxx


Ou ainda:
aaaaaabb bbbbcccc ccdddddd


Os elementos usados são caracteres simples, começando com as letras maiúsculas, A (0) a Z (25), então as letras minúsculas, a (26) a z (51), os números, 0 (52) a 9 (61), e os caracteres + (62) e / (63).

Na conversão de bytes (8b) para Base64 (6b), cada três bytes é convertido em quatro dígitos, então um código Base64 é sempre pensado em grupos de quatro. Se o tamanho de um código Base64 não for múltiplo de quatro, caracteres = são acrescentados ao final até que o tamanho seja múltiplo de quatro.

Python


Em Python, Base64 é tão simples que nem tem graça:
>>> print "Kodumaro".encode("base64")
S29kdW1hcm8=
>>> print "S29kdW1hcm8=".decode("base64")
Kodumaro


Lua


Em Lua, é preciso baixar o módulo Mime do LuaSocket:
> require "mime"
> print(mime.b64 "Kodumaro")
S29kdW1hcm8=
> print(mime.unb64 "S29kdW1hcm8=")
Kodumaro


Smalltalk


Sendo muito sincero sobre o assunto, não sei como fazer conversão de Base64 em Smalltalk. =(

Mas sei que os módulos do Seaside providenciam isso!

Se alguém souber, por favor informe!

[update 2009-11-27]
Sugestão do Hugo:

No Squeak tem isso aqui:
(Base64MimeConverter mimeEncode: 'base64' readStream) contents
(Base64MimeConverter mimeDecode: 'S29kdW1hcm8' as: ByteString) contents


E no Pharo tem métodos para strings:
'base64' base64Encoded
'S29kdW1hcm8' base64Decoded


A implementação usa as coisas do Squeak:
String>>base64Decoded
↑ (Base64MimeConverter mimeDecode: self as: self class)

String>>base64Encoded
↑ (Base64MimeConverter mimeEncode: self readStream) contents


Para outros Smalltalks eu não sei…

Legal né? =)

Muito legal sim, Hugo, valeu!
[/update]


C


Aha! Aqui começa de verdade a brincadeira!

É claro que você pode usar as funcionalidades de Base64 da gLibC, mas descobri que nem todas as versões dela apresentam tais funcionalidades:
#include <glib/gbase64.h>


Vamos precisar usar os cabeçalhos stdlib.h, string.h e sys/types.h. Como também vamos criar um cabeçalho para «compartilhar» algumas funções, ele também será incluído no início de nosso arquivo base64.c:
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include "base64.h"


Agora vamos criar um array com todos os possíveis caracteres Base64 em sua ordem natural:
static const char b64all[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef"
"ghijklmnopqrstuvwxyz0123456789+/";


Também vamos precisar de três funções locais: uma para obter o índice de um elemento (_getindex), outra para codificar para Base64 um grupo de três bytes (_encode) e mais uma para decodificar um grupo de quatro elementos Base64 (_decode):
int _decode(u_int8_t *, const u_int8_t *);
void _encode(u_int8_t *, const u_int8_t *, int);


Não é preciso uma função para _getindex:
#define _getindex(c) (int) (index(b64all, c) - b64all)


Repara que, em vez de char, estamos usando u_int8_t, que é mais conveniente quando queremos lidar com bytes enquanto bytes, não caracteres.

A partir daqui, se preferir, organize as funções em ordem alfabética – ou na ordem que quiser.

A primeira função que implementaremos será para codificar uma string C (const char *) para Base64. Como a string pode não ser bem formada, a função deverá receber também seu tamanho:
const char *b64encode(const char *original, int length) {
// Se o tamanho não for informado, consideramos uma string bem
// formada
if (length == 0)
length = strlen(original);

// Inteiro com o tamanho do código a ser gerado
int b64length = ((length + 2) / 3) * 4 + 1;

// Contadores para percorrer as strings
int i=0, j=0;

// Alocando memória para o código
char *b64 = (char *) malloc(sizeof(char) * b64length);
memset(b64, 0, b64length);

while (i < length) {
// Codifica um grupo de três bytes...
_encode(
(u_int8_t *) b64 + j,
(const u_int8_t *) original + i,
(length - i)
);

// E segue para o próximo grupo
i += 3;
j += 4;
}

// Retorna o código
return (const char *) b64;
}


A próxima função deve fazer o contrário, converter um código Base64 para uma string. Como a string resultante pode não ser bem formada – pode não ser terminada em carácter nulo ou possuir caracteres nulos no meio –, é preciso uma forma de informar seu tamanho, então ela receberá um ponteiro para um inteiro:
const char *b64decode(const char *b64, int *length) {
// Inteiro com o tamanho do código
int b64length = strlen(b64);

// Se não for múltiplo de quatro, há algo errado
if (b64length % 4 != 0)
return NULL;

// Tamanho máximo da string decifrada
int prob = (b64length / 4) * 3 + 1;

// Contadores para percorrer as strings
int i=0, j=0;

// Alocando memória para o resultado
char *s = (char *) malloc(sizeof(char) * prob);

while (j < b64length) {
// Decifra um grupo de quatro elementos
// e conta o resultado
i += _decode(
(u_int8_t *) s + i,
(const u_int8_t *) b64 + j
);

// Segue para o próximo grupo
j += 4;
}

// Se foi fornecido um inteiro para contagem, informa o tamanho
if (length != NULL)
*length = i;

// Retorna a string decifrada
return (const char *) s;
}


Agora precisamos das funções específica para as conversões.

Primeiro para codificar:
void _encode(u_int8_t *dest, const u_int8_t *src, int len) {
// Menor que 1, nada a fazer
if (len < 1)
return;

// Dados a serem retornados
int aux[] = { 0, 0, 0, 0 };

// Primeiro elemento: os 6 bits mais significativos do primeiro
// byte
aux[0] = src[0] >> 2;

// Segundo elemento: os 2 bits menos significativos do primeiro e
// os quatro bits mais significativos do segundo byte
aux[1] = (src[0] & 0x03) << 4;

if (len > 1) {
// SE houver um segundo...
aux [1] |= (src[1] & 0xf0) >> 4;

// Terceiro elemento: os quatro bits menos significativos do
// segundo e os dois mais significativos do terceiro byte
aux [2] = (src[1] & 0x0f) << 2;

if (len > 2) {
// Se houver um terceiro...
aux[2] |= src[2] >> 6;

// Quarto elemento: os seis bits menos significatos do
// terceiro byte
aux[3] = src[2] & 0x3f;
}
}

// Codifica agora os valores numéricos para string
dest[0] = b64all[aux[0]];
dest[1] = b64all[aux[1]];
dest[2] = '=';
dest[3] = '=';
if (len > 1) {
dest[2] = b64all[aux[2]];
if (len > 2)
dest[3] = b64all[aux[3]];
}
}

int _decode(u_int8_t *dest, const u_int8_t *src) {
// Representação numérica do código
int aux[] = { 0, 0, 0, 0 };

// Contador
int i, c = 1;

// Converte código para valores numéricos
for (i = 0; i < 4; ++i)
aux[i] = _getindex(src[i]);

// Primeiro byte: primeiro elemento seguido dos quatro bits mais
// significativos do segundo
dest[0] = (u_int8_t) (aux[0] << 2) | ((aux[1] & 0x30) >> 4);

// Zera os bytes seguintes
dest[1] = '\0';
dest[2] = '\0';

if (aux[2] != -1) {
// Se houver um terceiro elemento...
++c;

// Segundo byte: quatro bits menos significativos do segundo
// elemento seguidos pelos quatro bits mais significativos do
// terceiro
dest[1] = (u_int8_t) ((aux[1] & 0x0f) << 4) | (aux[2] >> 2);

if (aux[3] != -1) {
// Se houver um quarto elemento...
++c;

// Terceiro byte: dois bits menos significativos do
// terceiro elemento seguidos pelo quarto elemento
dest[2] = (u_int8_t)
((aux[2] & 0x03) << 6) |
aux[3];
}
}

// Retorna o tamanho da string
return c;
}


Cabeçalho


Por último criamos o cabeçalho base64.h, tornando públicas as duas funções de codificação e decodificação:
#ifndef _BASE64_H
#define _BASE64_H

const char *b64decode(const char *, int *);
const char *b64encode(const char *, int);

#endif


Conclusão


Mesmo que ninguém vá implementar uma biblioteca de conversão Base64, espero que este artigo sirva para ajudar a entender melhor do que se trata Base64.

[update]

Se quiser acrescentar algum açucar sintático, coloque em base64.c:
#ifdef C_PLUS_PLUS
const char *b64decode(const char *b64, int &length) {
return b64decode(b64, &length);
}
#endif


E mude o conteúdo de base64.h para:
#ifndef _BASE64_H
#define _BASE64_H

#ifdef C_PLUS_PLUS
extern "C" {
#endif

const char *b64decode(const char *, int *);
const char *b64encode(const char *, int);

#ifdef C_PLUS_PLUS
}
const char *b64decode(const char *, int &);
#else
#define _b64decode(s, len) b64decode(s, &len)
#endif

#endif

[/update]


[]'s
Cacilhas, La Batalema
blog comments powered by Disqus