web-dev-qa-db-fra.com

Migration de Sun.misc.BASE64 vers Java 8 Java.util.Base64

Question

Les Java 8 Java.util.Base64 Encodeur et décodeur MIME un remplacement sans rendez-vous pour les éléments internes non pris en charge Java API Sun.misc.BASE64Encoder et Sun.misc.BASE64Decoder?

Ce que je pense jusqu'à présent et pourquoi

Sur la base de mon enquête et de mes tests rapides (voir le code ci-dessous) cela devrait être un remplacement sans rendez-vous parce que

  • Sun.misc.BASE64Encoder basé sur son JavaDoc est un encodeur de caractères BASE64 comme spécifié dans RFC1521 . Ce RFC fait partie de la spécification [~ # ~] mime [~ # ~] ...
  • Java.util.Base64 basé sur son JavaDoc Utilise "l'alphabet Base64" comme spécifié dans le tableau 1 de RFC 2045 pour les opérations d'encodage et de décodage ... sous [~ # ~] mime [~ # ~]

En supposant qu'aucun changement significatif dans les RFC 1521 et 2045 (je n'ai pu en trouver) et basé sur mon test rapide en utilisant le Java 8 Base64 MIME Encoder/Decoder devrait être bien.

Ce que je recherche

  • une source faisant autorité confirmant ou infirmant le point de "remplacement sans rendez-vous" OU
  • un contre-exemple qui montre un cas où Java.util.Base64 a un comportement différent de celui de Sun.misc.BASE64Encoder OpenJDK Java 8 implementation (8u40-b25) (BASE64Decoder) OU
  • tout ce que vous pensez répond à la question ci-dessus certainement

Pour référence

Mon code de test

public class Base64EncodingDecodingRoundTripTest {

    public static void main(String[] args) throws IOException {
        String test1 = " ~!@#$%^& *()_+=`| }{[]\\;: \"?><,./ ";
        String test2 = test1 + test1;

        encodeDecode(test1);
        encodeDecode(test2);
    }

    static void encodeDecode(final String testInputString) throws IOException {
        Sun.misc.BASE64Encoder unsupportedEncoder = new Sun.misc.BASE64Encoder();
        Sun.misc.BASE64Decoder unsupportedDecoder = new Sun.misc.BASE64Decoder();

        Base64.Encoder mimeEncoder = Java.util.Base64.getMimeEncoder();
        Base64.Decoder mimeDecoder = Java.util.Base64.getMimeDecoder();

        String sunEncoded = unsupportedEncoder.encode(testInputString.getBytes());
        System.out.println("Sun.misc encoded: " + sunEncoded);

        String mimeEncoded = mimeEncoder.encodeToString(testInputString.getBytes());
        System.out.println("Java 8 Base64 MIME encoded: " + mimeEncoded);

        byte[] mimeDecoded = mimeDecoder.decode(sunEncoded);
        String mimeDecodedString = new String(mimeDecoded, Charset.forName("UTF-8"));

        byte[] sunDecoded = unsupportedDecoder.decodeBuffer(mimeEncoded); // throws IOException
        String sunDecodedString = new String(sunDecoded, Charset.forName("UTF-8"));

        System.out.println(String.format("Sun.misc decoded: %s | Java 8 Base64 decoded:  %s", sunDecodedString, mimeDecodedString));

        System.out.println("Decoded results are both equal: " + Objects.equals(sunDecodedString, mimeDecodedString));
        System.out.println("Mime decoded result is equal to test input string: " + Objects.equals(testInputString, mimeDecodedString));
        System.out.println("\n");
    }
}
28
Ivo Mori

Voici un petit programme de test qui illustre une différence dans les chaînes codées:

byte[] bytes = new byte[57];
String enc1 = new Sun.misc.BASE64Encoder().encode(bytes);
String enc2 = new String(Java.util.Base64.getMimeEncoder().encode(bytes),
                         StandardCharsets.UTF_8);

System.out.println("enc1 = <" + enc1 + ">");
System.out.println("enc2 = <" + enc2 + ">");
System.out.println(enc1.equals(enc2));

Sa sortie est:

enc1 = <AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
>
enc2 = <AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA>
false

Notez que la sortie codée de Sun.misc.BASE64Encoder a une nouvelle ligne à la fin. Il ne fait pas toujours ajouter une nouvelle ligne, mais cela arrive si la chaîne encodée a exactement 76 caractères sur sa dernière ligne. (L'auteur de Java.util.Base64 considérait qu'il s'agissait d'un petit bogue dans le Sun.misc.BASE64Encoder implémentation - voir le revue de discussion ).

Cela peut sembler banal, mais si vous aviez un programme qui reposait sur ce comportement spécifique, la commutation des encodeurs pourrait entraîner une sortie mal formée. Par conséquent, je conclus que Java.util.Base64 n'est pas pas un remplacement direct pour Sun.misc.BASE64Encoder.

Bien sûr, le intention de Java.util.Base64 est qu'il s'agit d'un remplacement fonctionnellement équivalent, conforme aux RFC, hautes performances, entièrement pris en charge et spécifié, destiné à prendre en charge la migration du code à partir de Sun.misc.BASE64Encoder. Cependant, vous devez être conscient de certains cas Edge comme celui-ci lors de la migration.

46
Stuart Marks

Il n'y a aucun changement à la spécification base64 entre rfc1521 et rfc2045.

Toutes les implémentations base64 peuvent être considérées comme des remplacements directs les uns des autres, les seules différences entre les implémentations base64 sont:

  1. l'alphabet utilisé.
  2. les API fournies (par exemple, certaines peuvent n'agir que sur un tampon d'entrée complet, tandis que d'autres peuvent être des machines à états finis vous permettant de continuer à pousser des morceaux d'entrée à travers elles jusqu'à ce que vous ayez terminé).

L'alphabet MIME base64 est resté constant entre les versions RFC (il a ou un logiciel plus ancien se briserait) et est: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/

Comme Wikipedia note, seuls les 2 derniers caractères peuvent changer entre les implémentations base64.

Comme exemple d'implémentation en base64 qui modifie les 2 derniers caractères, la spécification IMAP MUTF-7 utilise la base64 suivante alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+,

La raison de ce changement est que le / est souvent utilisé comme délimiteur de chemin et comme le codage MUTF-7 est utilisé pour aplatir les chemins de répertoire non ASCII en ASCII, le / le caractère devait être évité dans les segments codés.

3
jstedfast

J'ai eu le même problème lorsque je suis passé de Sun à Java.util.base64, mais org.Apache.commons.codec.binary.Base64 cela a été résolu par un problème

2
user369151

En supposant que les deux encodeurs sont exempts de bogues, la RFC nécessite des encodages distincts pour chaque séquence de 0 octet, 1 octet, 2 octets et 3 octets. Les séquences plus longues sont divisées en autant de séquences de 3 octets que nécessaire, suivies d'une séquence finale. Par conséquent, si les deux implémentations gèrent correctement les 16 843 009 (1 + 256 + 65536 + 16777216) séquences possibles, les deux implémentations sont également identiques.

Ces tests ne prennent que quelques minutes à exécuter. En modifiant légèrement votre code de test, je l'ai fait et mon installation Java 8 a réussi tout le test. Par conséquent, l'implémentation publique peut être utilisée pour remplacer en toute sécurité l'implémentation Sun.misc.

Voici mon code de test:

import Java.util.Base64;
import Java.util.Arrays;
import Java.io.IOException;

public class Base64EncodingDecodingRoundTripTest {

    public static void main(String[] args) throws IOException {
        System.out.println("Testing zero byte encoding");
        encodeDecode(new byte[0]);

        System.out.println("Testing single byte encodings");
        byte[] test = new byte[1];
        for(int i=0;i<256;i++) {
            test[0] = (byte) i;
            encodeDecode(test);
        }
        System.out.println("Testing double byte encodings");
        test = new byte[2];
        for(int i=0;i<65536;i++) {
            test[0] = (byte) i;
            test[1] = (byte) (i >>> 8);
            encodeDecode(test);
        }
        System.out.println("Testing triple byte encodings");
        test = new byte[3];
        for(int i=0;i<16777216;i++) {
            test[0] = (byte) i;
            test[1] = (byte) (i >>> 8);
            test[2] = (byte) (i >>> 16);
            encodeDecode(test);
        }
        System.out.println("All tests passed");
    }

    static void encodeDecode(final byte[] testInput) throws IOException {
        Sun.misc.BASE64Encoder unsupportedEncoder = new Sun.misc.BASE64Encoder();
        Sun.misc.BASE64Decoder unsupportedDecoder = new Sun.misc.BASE64Decoder();

        Base64.Encoder mimeEncoder = Java.util.Base64.getMimeEncoder();
        Base64.Decoder mimeDecoder = Java.util.Base64.getMimeDecoder();

        String sunEncoded = unsupportedEncoder.encode(testInput);
        String mimeEncoded = mimeEncoder.encodeToString(testInput);

        // check encodings equal
        if( ! sunEncoded.equals(mimeEncoded) ) {
            throw new IOException("Input "+Arrays.toString(testInput)+" produced different encodings (Sun=\""+sunEncoded+"\", mime=\""+mimeEncoded+"\")");
        }

        // Check cross decodes are equal. Note encoded forms are identical
        byte[] mimeDecoded = mimeDecoder.decode(sunEncoded);
        byte[] sunDecoded = unsupportedDecoder.decodeBuffer(mimeEncoded); // throws IOException
        if(! Arrays.equals(mimeDecoded,sunDecoded) ) {
            throw new IOException("Input "+Arrays.toString(testInput)+" was encoded as \""+sunEncoded+"\", but decoded as Sun="+Arrays.toString(sunDecoded)+" and mime="+Arrays.toString(mimeDecoded));
        }

    }
}
1
Simon G.