dankogai/js-base64

Encoding Decoding + and / has unexpected results in Jest

trajano opened this issue · 2 comments

  it('base64 migration test 5', () => {

    const decoded = base64.decode("da+//Z==");
    expect(decoded).toHaveLength(4);
    expect(base64.encode(decoded, true)).toBe("da-__Z"); // this fails
    expect(base64.encode(decoded, false)).toBe("da+//Z=="); // this fails
  })

Just to make sure it's not because of encoding I tested with this as well

  it('base64 migration test 5', () => {
    const specimen = "+s/s/Z==";
    const decoded = base64.toUint8Array(specimen);
    expect(decoded).toHaveLength(4);
    expect(base64.fromUint8Array(decoded, false)).toBe(specimen); // fails here.
    expect(base64.fromUint8Array(decoded, true)).toBe("-s_s_Z");
  })

No. They do not supposed to round-trip because the given base64-encode strings are somewhat malformed.

decode_encode.pl

#!/usr/bin/env perl
use strict;
use warnings;
use MIME::Base64;
print encode_base64(decode_base64(shift)), "\n";
$ ./decode_encode.pl da+//Z== 
da+//Q==

decode_encode.py

#!/usr/bin/env python
import sys
import base64
print(base64.b64encode(base64.b64decode(sys.argv[1])))
$ ./decode_encode.py da+//Z== 
b'da+//Q=='

decode_encode.rb

#!/usr/bin/env ruby
require 'base64'
puts Base64.encode64(Base64.decode64(ARGV.shift))
./decode_encode.rb da+//Z==
da+//Q==

The trick is the last character can be anything between Q and f. da+//R==, da+//S== … and of course, da+//Z== (and da+//f== ). The trick is that the lower 4 bits are truncated in this case. Q represents 0b010000 and only upper 2 bits matter. Virtually all encoders including this module choose Q but as I said [Q-Za-f] are accidentally acceptable.

Thanks for the explanation