Cette semaine, nous vous proposons d'étudier le codage Base64. N'ayez pas peur si vous ne savez pas ce que c'est : toutes les notions pré-requises et externes à la programmation seront expliquées.
Si vous souhaitez plus de renseignements sur les défis, rendez vous sur le topic de recensement des défis, vous y trouverez également les règles principales.
Tout en Base64
Base64 est un codage permet de représenter une suite de bit quelconque avec seulement 65 caractères. Le but étant un gain de portabilité vu que les 65 caractères utilisés sont très courant. L'avantage le plus important étant qu'il permet de coder facilement des données binaires comme une image ou un exécutable par exemple, il permet dans ce cas, un gain de taille lors du codage.
Le codage Base64 est utilisée par exemple dans l'envoi de Mail ou encore en CSS
Plus d'info sur Wikipédia
Comment ça marche ?
Oui bonne question, on va voir cela ensemble.
Vous savez que chaque caractère (char) est en fait un octet ? Sinon, je vous l'apprend Ainsi 'a' correspond à 91 en décimale ... La table de correspondante la plus courante (char-set en anglais) est l'ASCII, d'où la table ASCII ...
Vous savez que chaque octet est codé sur 8 bits ? Et que par conséquent, 3 octets font 24 bits ?
Mais si on se base sur 4 paquets de 6 bits ? On obtient aussi 24 bits.
Le but du codage Base64 est de transformer les 3 octets de 24 bits en 4 paquets de 6 bits. Ensuite, on utilisera l'alphabet du Base64 qui est : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
Afin de faire correspondre les octets obtenus à des caractères.
Le principe, on part de 3 octets par exemple :
11011110 00110000 00101100 soit en décimal : 222 48 44
et on va les découper ainsi :
110111 100011 000000 101100 soit en décimal : 55 35 0 44
Si on prend le 55 caractères de l'alphabet du codage Base64 on obtient le caractère '3'.
Donc :
55 -> 3
35 -> j
00 -> A
44 -> s
Donc la suite de bit 11011110 00110000 00101100 en Base64 donne 3jAs
Pour coder une suite plus longue, on répète l'opération pour chaque groupe de 3 caractères
Mais si le dernier groupe d'octet n'est pas composé de 3 octets mais de 1 ou 2 octets ?
Tout est prévu ne vous en faites pas
Si cela arrive, on remplit le caractères manquant de 0.
Par-contre, le paquet de 3 octets change
Si il manque 1 octet, seul les 3 premiers paquets de 6 bits sont utilisés. Le dernier est remplacé par '='.
Si il manque 2 octets, seul les 2 premiers paquets de 6 bits sont utilisés. Les 2 derniers sont remplacés par '='.
Niveau 1 :
Votre programme devra demander à l'utilisateur de rentrer une chaine de caractères que vous encoderez en Base64.
Vous devez aussi laisser le choix à l'utilisateur de décoder ce code. Bon courage ...
Exemple :
Voulez-vous encoder(1) ou decoder ?
1
Quel chaine souhaitez-vous encoder ?
Ache
"Ache" en Base64 donne : "QWNoZQ=="
Niveau 2 :
Votre programme devra demander à l'utilisateur un nom de fichier, il encodera ce fichier en Base64. Peu importe le type du fichier original, souvenez-vous que ce n'est qu'une suite de bit.
Exemple :
Voulez-vous encoder(1) ou decoder ?
1
Quel fichier souhaitez-vous encoder ?
./a.out
"./a.out" en Base64 ...
Retrouver le résultat dans le fichier "./a.out.base64"
Si il manque 1 octet, seul les 3 premiers octets du paquet de 4 octets sont utilisés. Le dernier est remplacé par '='.
Si il manque 2 octets, seul les 2 premiers octets du paquet de 4 octets sont utilisés. Les 2 derniers sont remplacés par '='.
Cela ne serait pas plutôt:
Citation
Si il manque 1 octet, seul les 3 premiers paquets de 6 bits sont utilisés. Le dernier est remplacé par '='.
Si il manque 2 octets, seul les 2 premiers paquets de 6 bits sont utilisés. Les 2 derniers sont remplacés par '='.
Sous Mac, et peut-être sous d'autres OS, il existe le logiciel (payant, hélas) Antidote, qui fait dictionnaire et correcteur orthographique. Et c'est bien...
Merci de l'attention que vous portez au défis
Je vous invite à m'indiquer les autres aberrations du genre par MP
Histoire d'éviter d'être submergé par le flood ...
🍊 - Étudiant - Codeur en C | Zeste de Savoir apprenez avec une communauté | Articles- ♡ Copying is an act of love.
Pourquoi ne pas demander le décodeur qui va avec ?
Sinon, dommage, je l'ai fait il y a quelques années déjà (un de mes premiers contacts avec les opérateurs de décalage). Enfin, je crois que je n'ai fait que l'encodage, donc je peux toujours faire le décodeur si j'ai le temps.
Pourquoi ne pas demander le décodeur qui va avec ?
Sinon, dommage, je l'ai fait il y a quelques années déjà (un de mes premiers contacts avec les opérateurs de décalage). Enfin, je crois que je n'ai fait que l'encodage, donc je peux toujours faire le décodeur si j'ai le temps.
C'est aussi demandé Seulement, il n'y a pas d'explication à ce sujet (un peu de recherche n'as jamais fais de mal)... Relis la consigne de l'exercice 1.
Tu peux toujours essayer de le refaire , c'est pas si compliqué que ça (c'est même très simple pour toi connaissant à peu prêt ton niveau ...)
🍊 - Étudiant - Codeur en C | Zeste de Savoir apprenez avec une communauté | Articles- ♡ Copying is an act of love.
Voici un code pour le niveau 2, avec codeur et décodeur inclus. Note : la partie encodage a été écrite il y a 2/3 ans dans le cadre d'un projet de mailer. Seul la partie décodage à été réalisée pour l'exo.
Je n'ai pas trouver comment écrire un décodeur sans utiliser une fonction spéciale pour donner la valeur d'un code b64. Ce ne doit pas être optimal. Je me souviens avoir vu un jour un code qui faisait autrement, mais la flemme de chercher.
Il est vraiment bien construit ton code yoch, j'aime beaucoup
Il me semble juste qu'il y a deux petits soucis:
- tu as oublié de spécifier à getopt la présence de l'option "-o";
- si on lit sur l'entrée standard, le '\n' final est lu par fread alors que ce n'est pas forcément le comportement attendu par l'utilisateur si il entre une chaîne "à la main". EDIT: en fait, c'est même problématique dans le cas du décodage puisque le '\n' est lu et considéré comme encodé en base64.
Et aussi, pourquoi écrire (in[1]>>2)>>2 et (in[2]>>4)>>2 et pas simplement in[1]>>4 et in[2]>>6?
Il est vraiment bien construit ton code yoch, j'aime beaucoup
Merci ! L'organisation du code est inspirée des outils GNU dont j'ai eu l'occasion de lire les codes.
Citation : Taurre
- tu as oublié de spécifier à getopt la présence de l'option "-o";
Oups, je corrige de suite.
Citation : Taurre
- si on lit sur l'entrée standard, le '\n' final est lu par fread alors que ce n'est pas forcément le comportement attendu par l'utilisateur si il entre une chaîne "à la main". EDIT: en fait, c'est même problématique dans le cas du décodage puisque le '\n' est lu et considéré comme encodé en base64.
Oui, j'y ai pensé, mais je ne vois pas comment faire : si je traite les '\n' comme des cas particuliers, la lecture d'un fichier sera altérée.
Citation : Taurre
Et aussi, pourquoi écrire (in[1]>>2)>>2 et (in[2]>>4)>>2 et pas simplement in[1]>>4 et in[2]>>6?
Si tu fais attention aux parenthèses, tu verras que c'est plutôt un truc du genre : ((a<<6)|(b>>2))>>2. Mais j'avoue une chose : j'ai écrit ce code sans vraie cohérence entre le choix de l'emploi des masques et celui des opérateurs de décalage, et n'ai pas la prétention que ce soit optimal.
Mmmh... on devrait pouvoir y arriver en modifiant un peu la fonction traitement. Je pense que ceci devrait fonctionner:
[...]
Joli ! Il manque la gestion pour le décodage, mais l'idée est sympa en soi. (perso, je suis un peu mitigé, car on introduit en quelque sorte une incohérence, mais il semble que ce soit justifié).
<humour>Ce qui est bien aussi, c'est qu'on se rapproche encore un peu du style GNU. </humour>
Citation : Taurre
Il faut juste également modifié la fonction decode_b64 car il y a une petite erreur:
static size_t decode_b64 (char* in, char* out) // len == 4
{
unsigned v1 = val(in[1]);
unsigned v2 = val(in[2]);
out[0] = (val(in[0]) << 2) | (v1 >> 4);
if (in[2] == '=') /* in[2] et non in[1] */
return 1;
out[1] = (v1 << 4) | (v2 >> 2);
if (in[3] == '=') /* in[3] et non in[2] */
return 2;
out[2] = ((v2 & 0x03) << 6) | val(in[3]);
return 3;
}
Il manque la gestion pour le décodage, mais l'idée est sympa en soi.
En fait, la gestion pour le décodage est également présente. La modification est très discrète, mais le fait de passer de fread(/* ... */)!=0 à fread(/* ... */)==4 garantit que le '\n' final sera ignoré. En effet, comme on attends en entré une chaîne encodée en base64, on est certains de lire un multiple de 4 caractères. Donc, si on lit autre chose que 4 caractères c'est que l'on est arrivé à la fin (avec ou sans '\n')
Citation : yoch
<humour>Ce qui est bien aussi, c'est qu'on se rapproche encore un peu du style GNU. </humour>
En fait, la gestion pour le décodage est également présente. La modification est très discrète, mais le fait de passer de fread(/* ... */)!=0 à fread(/* ... */)==4 garantit que le '\n' final sera ignoré. En effet, comme on attends en entré une chaîne encodée en base64, on est certains de lire un multiple de 4 caractères. Donc, si on lit autre chose que 4 caractères c'est que l'on est arrivé à la fin (avec ou sans '\n')
Ah oui, très bien vu.
Bon, je pinaille un peu, mais on aura alors plus de mal à distinguer les entrées invalides (je n'ai d'ailleurs pas mis la gestion complète pour ce cas dans mon code, il faudrait remplacer la fonction val par :
unsigned val (char c)
{
if (c >= 'A' && c <= 'Z')
return c - 'A';
else if (c >= 'a' && c <= 'z')
return 26 + c - 'a';
else if (c >= '0' && c <= '9')
return 52 + c - '0';
else if (c == '+')
return 62;
else if (c == '/')
return 63;
else
{
fprintf(stderr, "Invalid input file\n");
exit(EXIT_FAILURE);
}
}
EDIT :
Version modifiée du code prenant en compte les erreurs éventuelles (attention, c'est déjà nettement plus moche) :
yoch j'ai incorporé tes foctions encod64 et decod64 sache c'est tres rapide sur une application de 40Mo l'encodage fait entre 2 et 5 à 7 s pour le decodage c'est 4s je suis en admiration de ces codes c'est vraiment bien fait neamoins pour le decod64 j'ai du revoir
static void encode_b64 (char* in, char* out, int len)
{
out[0] = b64[in[0] >> 2];
Si je comprends bien le source, in[0] peut avoir une valeur négative puisque que le type char peut être signé, et que du binaire est encodé, et que:
Citation : C99 §6.5.7.5
The result of E1 >> E2 is E1 right-shifted E2 bit positions. ... If E1 has a signed type and a negative value, the resulting value is implementation-defined.
Ce source me paraît donc peu portable.
Il y a aussi un soucis de portabilité avec une expression comme c>='A'&&c<='Z', mais c'est un cas rare (encodage non compatible ASCII).
en tout cas c'est bien vu pour les paramettre de encod64 et decod64 il faut utiliser un unsign char* sinon au decodage d'un fichier autre que du texte celui-ci ne s'ouvrirait pas. je l'ai testé
J'utilise un tableau statique pour le décodage et un fichier temporaire afin de stocker les données encodées ou décodées. Je n'aime pas trop la fonction menu qui fait un peu "fourre-tout", mais bon, je ne vois pas trop comment faire autrement si l'on suit l'énoncé
Je ne connaissais pas cette façon de déclarer et initialiser un tableau, tu pourrais donner une référence, s'il te plait ?
Citation : Taurre
J'utilise un tableau statique pour le décodage et un fichier temporaire afin de stocker les données encodées ou décodées.
A priori, l'utilisation d'un tel tableau pour le décodage est excellente pour les perfs. Sauf que ton implémentation de isbase64 n'est malgré tout pas très efficace, car tu utilise strchr pour chaque caractère).
Ah bah voilà ce que je cherchais !
J'ai fait une table d'équivalence en tapant sur la table ASCII (ASCII obligatoire pour ce code ). Je cherchais un moyen de faire comme toi, bah merci de m'avoir montré ça. J'avais carrément oublié !
Je ne connaissais pas cette façon de déclarer et initialiser un tableau, tu pourrais donner une référence, s'il te plait ?
Ce type d'initialisation n'existe que depuis le C99, il y a d'ailleurs un mécanisme similaire pour initialiser certains champs d'une structure/union. Comme l'a dit Pouet_forever, c'est au point 6.7.8 de la Norme. Le paragraphe en question:
Citation : Norme C99 6.7.8 § 6 p 125
If a designator has the form
[ constant-expression ]
then the current object (defined below) shall have array type and the expression shall be
an integer constant expression. If the array is of unknown size, any nonnegative value is
valid.
Citation : yoch
Sauf que ton implémentation de isbase64 n'est malgré tout pas très efficace, car tu utilise strchr pour chaque caractère).
En fait, mon but est d'être certains que les caractères lus correspondent à des cases du tableau b64_d histoire d'éviter tout dépassement. Or, je ne vois pas d'autres solutions pour l'instant.
Sinon, dans le code que tu montres, à cette condition:
if ( ((v0 | v1 | v2 | v3) & 0x80) /* Invalids characters */
|| (in[2] == '=' && in[3] != '=') /* Valid character after '=' */
)
le masque utilisé ne devrait pas plutôt être 0xc0 puisque l'encodage en base64 n'utilise que 6 bits?
En fait, mon but est d'être certains que les caractères lus correspondent à des cases du tableau b64_d histoire d'éviter tout dépassement.
Oui, j'ai bien compris ça.
Citation : Taurre
Sinon, dans le code que tu montres, à cette condition:
if ( ((v0 | v1 | v2 | v3) & 0x80) /* Invalids characters */
|| (in[2] == '=' && in[3] != '=') /* Valid character after '=' */
)
le masque utilisé ne devrait pas plutôt être 0xc0 puisque l'encodage en base64 n'utilise que 6 bits?
Non, car comme toi, je donne la valeur 64 (0x40) pour '=', et j'utilise la valeur 128 (0x80) pour tous les caractères non reconnus (cf. fonction init_decode_array), ce qui me permet justement d'avoir un test performant pour les caractères inconnus. (si 0 <= v_ <= 64, ok, sinon v_ == 128 ce qui signifie que le caractère rencontré est invalide).
Cependant, je n'ai pas trouvé comment définir ça en dur dans le code, je suis obligé de passer par une initialisation dynamique.
En fait, mon but est d'être certains que les caractères lus correspondent à des cases du tableau b64_d histoire d'éviter tout dépassement.
Oui, j'ai bien compris ça.
Citation : Taurre
Sinon, dans le code que tu montres, à cette condition:
if ( ((v0 | v1 | v2 | v3) & 0x80) /* Invalids characters */
|| (in[2] == '=' && in[3] != '=') /* Valid character after '=' */
)
le masque utilisé ne devrait pas plutôt être 0xc0 puisque l'encodage en base64 n'utilise que 6 bits?
Non, car comme toi, je donne la valeur 64 (0x40) pour '=', et j'utilise la valeur 128 (0x80) pour tous les caractères non reconnus (cf. fonction init_decode_array), ce qui me permet justement d'avoir un test performant pour les caractères inconnus. (si 0 <= v_ <= 64, ok, sinon v_ == 128 ce qui signifie que le caractère rencontré était invalide).
Cependant, je n'ai pas trouvé comment définir ça en dur dans le code, je suis obligé de passer par une initialisation dynamique.
Ah oui, pardon, j'avais lu le code un peu vite.
Je mettrais juste UCHAR_MAX comme taille maximale de b64_decode, mais sinon c'est effectivement nettement plus performant que ma solution.
Bon, voilà mon code. J'ai voulu faire un truc, mais je suis pas trop fier de ce que j'ai fait.
Comme j'ai dit plus bas, j'ai utilisé un tableau qui tape sur l'ascii, donc non portable sur un système non ascii.
Je ne gère pas non plus les caractères invalides pour le b64 (bon, c'est pas méchant à rajouter).
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
🍊 - Étudiant - Codeur en C | Zeste de Savoir apprenez avec une communauté | Articles - ♡ Copying is an act of love.
🍊 - Étudiant - Codeur en C | Zeste de Savoir apprenez avec une communauté | Articles - ♡ Copying is an act of love.