diff --git a/README.md b/README.md index 589273a..904f0fa 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Unibabel -========== +======== -Base64, TypedArrays, and UTF-8 / Unicode conversions in Browser (and Node) JavaScript +Minimalistic Base64, TypedArrays, and UTF-8 / Unicode conversions in Browser (and Node) JavaScript See @@ -45,6 +45,13 @@ var uint8Array = Unibabel.base64ToArr(base64) * hexToBuffer(hexstr) => array * bufferToHex(array) => hexstr +**Base32 APIs** + +`unibabel.base32.js` + +* base32ToBuffer(b32str) => array +* bufferToBase32(array) => b32str + **Helper APIs** * utf8ToBinaryString(utf8str) => binstr @@ -80,14 +87,22 @@ alert(sMyOutput); License ======= -Mozilla has licensed this code in the Public Domain, which means that I am at liberty to re-license my copy -under the Apache 2, which is something that, general speaking, your legal department will feel more comfortable with. +* `unibabel.js` and `unibabel.hex.js` are dual-licensed as Apache 2.0 and MIT. +* `unibabel.base32.js` is a modified version of [thirty-two](https://github.com/chrisumbel/thirty-two) and is therefore licensed MIT. + +Some parts of the code were taken from MDN, which Mozilla has licensed in the Public Domain, +which means that I am at liberty to re-license my copy under the Apache 2 and MIT licenses. See ChangeLog ==== +v2.1.0 +------ + +Added `unibabel.base32.js` + v2.0.0 ------ diff --git a/index.html b/index.html index ea40a01..0a1a8e4 100644 --- a/index.html +++ b/index.html @@ -1,8 +1,12 @@ - - - + + + This is for testing. Look in the console. + + + + diff --git a/node-test-base32.js b/node-test-base32.js new file mode 100644 index 0000000..7883cae --- /dev/null +++ b/node-test-base32.js @@ -0,0 +1,8 @@ +var base32 = require('thirty-two'); +var str = "I ½ ♥ 𩶘"; +var buf = new Buffer(str, 'utf8'); +console.log('charLen', 7); +console.log('byteLen', buf.byteLength, JSON.stringify(buf.toString('utf8'))); +var b32 = base32.encode(buf); // to base32 +console.log('encoded', b32.toString('utf8')); +console.log('decoded', base32.decode(b32).toString('utf8')); diff --git a/test.js b/test.js index acb0317..52c1a2f 100644 --- a/test.js +++ b/test.js @@ -5,10 +5,27 @@ var Unibabel = window.Unibabel; //UTF-8 var pass = true; -var str = "I ½ ♥ 𩶘"; -var buf = Unibabel.utf8ToBuffer(str); -var base64 = Unibabel.bufferToBase64(buf); -var hex = Unibabel.bufferToHex(buf); +var references = { + string: "I ½ ♥ 𩶘" +, array: [ 73, 32, 194, 189, 32, 226, 153, 165, 32, 240, 169, 182, 152 ] +, hex: "4920c2bd20e299a520f0a9b698" +, base64: "SSDCvSDimaUg8Km2mA==" +, base32: 'JEQMFPJA4KM2KIHQVG3JQ===' +}; +references.buffer = new Uint8Array(references.array); +var binrefs = { + // Note that the binary string "ÿâó<86>Î<93>k" can't be serialized to text + array: [ 255, 226, 26, 243, 134, 206, 147, 107 ] +, hex: "ffe21af386ce936b" +, base64: "/+Ia84bOk2s=" +}; +binrefs.buffer = new Uint8Array(binrefs.array); + +var str = references.string; +var buf = Unibabel.utf8ToBuffer(references.string); +var base64 = Unibabel.bufferToBase64(references.buffer); +var hex = Unibabel.bufferToHex(references.buffer); +var b32 = Unibabel.bufferToBase32(references.buffer); function buffersAreEqual(buf1, buf2) { if (buf1.length !== buf2.length) { @@ -21,25 +38,23 @@ function buffersAreEqual(buf1, buf2) { }); } -console.log('buffer', buf); // TODO compare buffer -var reference = [ 73, 32, 194, 189, 32, 226, 153, 165, 32, 240, 169, 182, 152 ]; -if (!buffersAreEqual(buf, reference)) { +if (!buffersAreEqual(buf, references.array)) { pass = false; - console.log('[FAIL] utf8 -> buffer', buf); + console.warn('[FAIL] utf8 -> buffer', buf); } -if (base64 !== "SSDCvSDimaUg8Km2mA==") { +if (base64 !== references.base64) { pass = false; - console.log('[FAIL] utf8 -> base64', base64); + console.warn('[FAIL] utf8 -> base64', base64); } -if (hex !== "4920c2bd20e299a520f0a9b698") { +if (hex !== references.hex) { pass = false; - console.log('[FAIL] utf8 -> hex', hex); + console.warn('[FAIL] utf8 -> hex', hex); } // binary -var bytes = [ 255, 226, 26, 243, 134, 206, 147, 107 ]; +var bytes = binrefs.array; buf = new Uint8Array(bytes); str = Unibabel.bufferToBinaryString(buf); base64 = Unibabel.bufferToBase64(buf); @@ -50,17 +65,31 @@ hex = Unibabel.bufferToHex(buf); // pass = false; // console.log('[FAIL] binary -> str', str); // } -if ("/+Ia84bOk2s=" !== base64) { +if (binrefs.base64 !== base64) { pass = false; - console.log('[FAIL] binary -> base64', base64); + console.warn('[FAIL] binary -> base64', base64); } -if (hex !== "ffe21af386ce936b") { +if (binrefs.hex !== hex) { pass = false; - console.log('[FAIL] binary -> hex', hex); + console.warn('[FAIL] binary -> hex', hex); +} + +// +// Base32 +// +b32 = Unibabel.bufferToBase32(references.buffer); +if (references.base32 !== b32) { + pass = false; + console.warn('[FAIL] binary -> base32', references.base32, '!==', b32); +} +buf = Unibabel.base32ToBuffer(references.base32); +if (!buffersAreEqual(buf, references.buffer)) { + pass = false; + console.warn('[FAIL] base32 -> binary', references.buffer, '!==', buf); } if (pass) { - console.log('[PASS] :-D'); + console.info('[PASS] :-D'); } }()); diff --git a/unibabel.base32.js b/unibabel.base32.js new file mode 100644 index 0000000..9b57d7c --- /dev/null +++ b/unibabel.base32.js @@ -0,0 +1,137 @@ +/* +Copyright (c) 2011, Chris Umbel +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +(function (exports) { +'use strict'; + +var charTable = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; +var byteTable = [ + 0xff, 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, + 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, + 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff +]; + +function quintetCount(buff) { + var quintets = Math.floor(buff.length / 5); + return buff.length % 5 === 0 ? quintets: quintets + 1; +} + +exports.bufferToBase32 = function(plain) { + // plain MUST come in either as Array or Uint8Array + if('undefined' !== typeof Uint8Array) { + if (!(plain instanceof Uint8Array)){ + plain = new Uint8Array(plain); + } + } + var i = 0; + var j = 0; + var shiftIndex = 0; + var digit = 0; + var encoded = new Array(quintetCount(plain) * 8); + + /* byte by byte isn't as pretty as quintet by quintet but tests a bit + faster. will have to revisit. */ + while(i < plain.length) { + var current = plain[i]; + + if(shiftIndex > 3) { + digit = current & (0xff >> shiftIndex); + shiftIndex = (shiftIndex + 5) % 8; + digit = (digit << shiftIndex) | ((i + 1 < plain.length) ? + plain[i + 1] : 0) >> (8 - shiftIndex); + i++; + } else { + digit = (current >> (8 - (shiftIndex + 5))) & 0x1f; + shiftIndex = (shiftIndex + 5) % 8; + if(shiftIndex === 0) { i++; } + } + + encoded[j] = charTable[digit]; + j++; + } + + for(i = j; i < encoded.length; i++) { + encoded[i] = '='; + } + + return encoded.join(''); +}; + +exports.base32ToBuffer = function(encoded) { + var shiftIndex = 0; + var plainDigit = 0; + var plainChar; + var plainPos = 0; + var len = Math.ceil(encoded.length * 5 / 8); + var decoded; + encoded = encoded.split('').map(function (ch) { + return ch.charCodeAt(0); + }); + if('undefined' !== typeof Uint8Array) { + encoded = new Uint8Array(encoded); + decoded = new Uint8Array(len); + } else { + decoded = new Array(len); + } + + /* byte by byte isn't as pretty as octet by octet but tests a bit + faster. will have to revisit. */ + for(var i = 0; i < encoded.length; i++) { + if(encoded[i] === 0x3d){ //'=' + break; + } + + var encodedByte = encoded[i] - 0x30; + + if(encodedByte < byteTable.length) { + plainDigit = byteTable[encodedByte]; + + if(shiftIndex <= 3) { + shiftIndex = (shiftIndex + 5) % 8; + + if(shiftIndex === 0) { + plainChar |= plainDigit; + decoded[plainPos] = plainChar; + plainPos++; + plainChar = 0; + } else { + plainChar |= 0xff & (plainDigit << (8 - shiftIndex)); + } + } else { + shiftIndex = (shiftIndex + 5) % 8; + plainChar |= 0xff & (plainDigit >>> shiftIndex); + decoded[plainPos] = plainChar; + plainPos++; + + plainChar = 0xff & (plainDigit << (8 - shiftIndex)); + } + } else { + throw new Error('Invalid input - it is not base32 encoded string'); + } + } + return decoded.slice(0, plainPos); +}; + +}(window.Unibabel || window));