質問
Java 8 Java.util.Base64
MIMEエンコーダーおよびデコーダードロップインの置換は、サポートされていない内部Java API Sun.misc.BASE64Encoder
およびSun.misc.BASE64Decoder
?
これまでの考えと理由
私の調査と迅速なテストに基づいて(以下のコードを参照)それはドロップイン置換である必要があります
Sun.misc.BASE64Encoder
は、そのJavaDocに基づいてRFC1521で指定されているBASE64文字エンコーダーです。このRFCは[〜#〜] mime [〜#〜]仕様の一部です...Java.util.Base64
JavaDocに基づいてRFC 2045の表1で指定されている「The Base64 Alphabet」をエンコードおよびデコード操作に使用します。 。[〜#〜] mime [〜#〜]の下RFC 1521および2045に大きな変更がないと想定し(何も見つかりませんでした)、Java 8 Base64 MIMEエンコーダー/デコーダーを使用したクイックテストに基づいて問題ありません。
探しているもの
参考のために
私のテストコード
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");
}
}
エンコードされた文字列の違いを示す小さなテストプログラムを次に示します。
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));
出力は次のとおりです。
enc1 = <AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
>
enc2 = <AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA>
false
Sun.misc.BASE64Encoder
のエンコードされた出力には、最後に改行があることに注意してください。 alwaysは常に改行を追加しませんが、エンコードされた文字列の最後の行に正確に76文字が含まれている場合は追加します。 (Java.util.Base64
の作成者は、これをSun.misc.BASE64Encoder
実装の小さなバグであると見なしました。 レビュースレッド を参照してください)。
これはささいなことのように思えるかもしれませんが、この特定の動作に依存するプログラムがある場合、エンコーダーを切り替えると出力が不正になる可能性があります。したがって、Java.util.Base64
はnotであり、Sun.misc.BASE64Encoder
のドロップイン置換ではないと結論付けます。
もちろん、Java.util.Base64
のintentは、コードの移行をサポートすることを目的とした、機能的に同等で、RFC準拠、高性能、完全にサポートされ、指定された代替品であることですSun.misc.BASE64Encoder
から離れています。ただし、移行時には、このようなEdgeのいくつかのケースに注意する必要があります。
Rfc1521とrfc2045の間でbase64仕様に変更はありません。
すべてのbase64実装は、お互いのドロップイン置換と見なすことができます。base64実装間の唯一の違いは次のとおりです。
MIME base64アルファベットは、RFCバージョン間で一定のままであり(それhasまたは古いソフトウェアが壊れます)、次のとおりです:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/
Wikipedia notesのように、base64実装間で変更できるのは最後の2文字のみです。
doesが最後の2文字を変更するbase64実装の例として、 IMAP MUTF-7 仕様は次のbase64を使用しますアルファベット:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+,
変更の理由は、/
文字がパス区切り文字としてよく使用され、非ASCIIディレクトリパスをASCIIにフラット化するためにMUTF-7エンコードが使用されるため、/
文字がエンコードされたセグメントでは回避されます。
SunからJava.util.base64に移動したときに同じ問題が発生しましたが、org.Apache.commons.codec.binary.Base64は問題によって解決しました
両方のエンコーダにバグがないと仮定すると、RFCでは、0バイト、1バイト、2バイト、および3バイトのシーケンスごとに個別のエンコーディングが必要です。長いシーケンスは、必要な数の3バイトシーケンスに分割され、最後にシーケンスが続きます。したがって、2つの実装が16,843,009(1 + 256 + 65536 + 16777216)のすべての可能なシーケンスを正しく処理する場合、2つの実装も同一です。
これらのテストの実行には数分しかかかりません。テストコードをわずかに変更することで、これを実行し、Java 8インストールがすべてのテストに合格しました。したがって、パブリック実装を使用して、Sun.misc実装を安全に置き換えることができます。
これが私のテストコードです。
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));
}
}
}