From 96fcbdbcad2cd0c354c10cca9aac76b4a3068b3b Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Tue, 8 Oct 2019 03:58:46 -0600 Subject: [PATCH] v1.0.0: common encodings for browsers and node.js --- README.md | 368 ++++++++++++++++++++++++++++++++++++++- base64.js | 59 +++++++ browser/base64.js | 62 +++++++ browser/bytes.js | 70 ++++++++ browser/hex.js | 70 ++++++++ build.sh | 16 ++ bytes.js | 50 ++++++ dist/encoding.all.js | 199 +++++++++++++++++++++ dist/encoding.all.min.js | 1 + encoding.js | 5 + hex.js | 40 +++++ package-lock.json | 5 + package.json | 31 ++++ tests/index.html | 5 + tests/index.js | 216 +++++++++++++++++++++++ 15 files changed, 1195 insertions(+), 2 deletions(-) create mode 100644 base64.js create mode 100644 browser/base64.js create mode 100644 browser/bytes.js create mode 100644 browser/hex.js create mode 100644 build.sh create mode 100644 bytes.js create mode 100644 dist/encoding.all.js create mode 100644 dist/encoding.all.min.js create mode 100644 encoding.js create mode 100644 hex.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 tests/index.html create mode 100644 tests/index.js diff --git a/README.md b/README.md index a0e1563..9056a5e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,367 @@ -# omnibuffer.js +# @root/encoding -Leightweight, Zero-dependency, translation between Unicode, Buffers, Base64, Hex, Binary Strings, UCS-2, UTF-8, etc. \ No newline at end of file +Lightweight, Zero-dependency, translation between Unicode Strings, Binary Strings, Buffers, Base64, Hex, UCS-2, UTF-8, etc. + +Works identically on all platforms: + +- [x] Web Browsers + - Chrome + - Firefox + - Microsoft Edge + - Internet Explorer +- [x] Node.js + +# Usage + +```js +var Enc = require('@root/encoding'); + +Enc.strToBuf('Hello, 世界!'); +``` + +# Use cases + +Typically you want to use this in a browser when you need to convert user input to some sort +of Byte Array for hashing or encoding in an ancient format. + +For example: + +- [x] Hashing passwords +- [x] Secure Remote Password +- [x] JWT and JWS signing and verifying +- [x] ASN1 parsing and packing + - [x] DER + - [x] x509 + - [x] CSR + - [x] PEM + +The purpose of this library is to make it easy to support common string and buffer encoding and decoding +in both Browsers and node with minimal code. + +# Examples + +Strings and Byte Arrays + +```js +var Enc = require('@root/encoding/bytes'); + +Enc.binToStr(bin); +Enc.binToBuf(bin); + +Enc.bufToBin(buf); +Enc.bufToStr(buf); + +Enc.strToBin(str); +Enc.strToBuf(str); +``` + +Hex + +```js +var Enc = require('@root/encoding/hex'); + +Enc.hexToBuf(hex); +Enc.hexToStr(hex); + +Enc.bufToHex(buf); +Enc.strToHex(str); +``` + +Base64 + +```js +var Enc = require('@root/encoding/base64'); + +Enc.base64ToBuf(b64); +Enc.base64ToStr(b64); + +Enc.bufToBase64(buf); +Enc.strToBase64(str); +``` + +URL Safe Base64 + +(all of `base64To*()` accept URL Safe Base64) + +```js +var Enc = require('@root/encoding/base64'); + +Enc.base64ToUrlBase64(b64); +Enc.urlBase64ToBase64(u64); + +Enc.bufToUrlBase64(buf); +Enc.strToUrlBase64(str); +``` + +Base64 and Hex + +``` +require('@root/encoding/base64'); +require('@root/encoding/hex'); + +var Enc = require('@root/encoding'); + +Enc.hexToBase64(hex); +Enc.base64ToHex(b64); +``` + +# Browser API + +(the Node API signatures are the same, but implemented with `Buffer`) + +Conversions between these formats are supported: + +- Strings and Buffers +- Hex +- Base64 + +## Strings and Buffers + +JavaScript has two types of strings: + +- _Binary Strings_, which we call `bin` +- _Unicode Strings_, which we call `str` (USC-2, essentially UTF-16) + - treated as UTF-8 for the purposes of `encodeURIComponent` + +JavaScript has two (and a half) ways to support Byte Arrays: + +- `Array`, which we call `arr` +- `Uint8Array`, which we call `buf` (of the `ArrayBuffer` family) +- `Buffer` (node-only, but implemented as `Uint8Array`) + +The API for the conversions is centered around `Uint8Array` (`Buffer`) but, +for browser compatibility, sometimes requires the use of _Binary Strings_. + +**API** + +We provide conversions directly to each of the following: + +| Name | Type | Description | +| :---- | :------------- | :-------------------------------------------- | +| `str` | Unicode String | Handled by `split('')` as two-byte characters | +| `bin` | Binary String | Handled by `split('')` as single-byte chars | +| `buf` | Byte Array | Truncated to single-byte chars | + +The names and signatures of the functions are as follows: + +To Buffer + +- Binary String to Buffer + - binToBuf(bin) +- Unicode String (UTF-8) to Buffer + - strToBuf(str) + +To Unicode String + +- Binary String to Unicode String (UTF-8) + - binToStr(bin) +- Buffer to Unicode String (UTF-8) + - bufToStr(buf) + +To Binary String + +- Buffer to Binary String + - bufToBin(buf) +- Unicode String to Binary String + - strToBin(str) + +It's very easy to convert from Binary Strings to Byte Arrays (`Uint8Array.from(bin.split(''))`) +and from `Uint8Array` to Binary String (`Array.from(buf).join('')`). + +The real value is converting between Unicode Strings to (UTF-8) Binary Strings, and back: + +```js +function toBin(str) { + var escstr = encodeURIComponent(str); + return escstr.replace(/%([0-9A-F]{2})/g, function(match, p1) { + return String.fromCharCode(parseInt(p1, 16)); + }); +} +``` + +```js +function toStr(bin) { + var escstr = bin.replace(/(.)/g, function(m, p) { + var code = p + .charCodeAt(0) + .toString(16) + .toUpperCase(); + if (code.length < 2) { + code = '0' + code; + } + return '%' + code; + }); + + return decodeURIComponent(escstr); +} +``` + +## Hex + +JavaScript does not have a native way to create hex, aside from small numbers: + +```js +(12345).toString(16); +``` + +The hex functions convert to and from hexidecimal: + +| Name | Type | Description | +| :---- | :--------- | :--------------------------------------------- | +| `hex` | Hex String | Handled by `split('')` as half-byte characters | + +To Hex + +- Binary String to Hex + - Enc.bufToHex(Enc.binToBuf(bin)) +- Byte Array to Hex + - bufToHex +- Unicode String (UTF-8) to Hex + - strToHex + +From Hex + +- Hex to Binary String + - Enc.hexToBuf(Enc.bufToBin(hex)) +- Hex to Byte Array + - hexToBuf +- Hex to Unicode String (UTF-8) + - hexToStr + +However, assuming you have a single-byte string, it's very easy to convert back and forth: + +```js +function toHex(any) { + var hex = []; + var i, h; + var len = any.byteLength || any.length; + + for (i = 0; i < len; i += 1) { + h = any[i].toString(16); + if (h.length % 2) { + h = '0' + h; + } + hex.push(h); + } + + return hex.join('').toLowerCase(); +} +``` + +```js +function fromHex(hex) { + var arr = hex.match(/.{2}/g).map(function(h) { + return parseInt(h, 16); + }); + return Uint8Array.from(arr); +} +``` + +## Base64 + +Browser JavaScript _does_ have a native way convert between Binary Strings and Base64: + +```js +var b64 = btoa('An ASCII string is a Binary String'); +// Note: A Unicode String is NOT +``` + +```js +var bin = atob('SGVsbG8sIOS4lueVjCE='); +``` + +However, it does **not** have a native way to convert between Unicode Strings and Binary Strings, +nor to and from URL Safe Base64. + +The base64 module provides simpler conversions to and from Base 64 and URL Safe Base64: + +| Name | Type | Description | +| :---- | :-------------- | :-------------------------------------------------------- | +| `b64` | Base64 | Standard Base64 as handled by `btoa` and `atob` | +| `u64` | URL Safe Base64 | Replaces `+` with `-` and `/` with `_`, and omits padding | + +To Base64 + +- Unicode String (UTF-8) to Base64 + - strToBase64(str) +- Binary String to Base64 + - Enc.bufToBase64(Enc.binToBuf(bin)) +- Byte Array to Base64 + - bufToBase64(buf) + +From Base64 (and URL Safe Base64) + +- Base64 to Unicode String (UTF-8) + - base64ToStr(b64) +- Base64 to Binary String + - Enc.bufToBin(Enc.base64ToBuf(b64))) +- Base64 to Byte Array + - base64ToBuf(b64) + +To URL Safe Base64 + +- Base64 to URL Safe Base64 + - base64ToUrlBase64(b64); +- URL Safe Base64 to Base64 + - urlBase64ToBase64(u64); +- Binary String to URL Safe Base64 + - Enc.bufToUrlBase64(Enc.binToBuf(bin)); +- Byte Array to URL Safe Base64 + - bufToUrlBase64(buf); +- Unicode String (UTF-8) to URL Safe Base64 + - strToUrlBase64(str); + +# FAQ + +## Why yet another encoding library? + +We write code that works both in node and in browsers, +and we like to keep it small, light, and focused. + +By using browser native functions rather than 're-inventing the wheel' + +## Why not 'browserified' Buffer? + +The most common 'browserified' `Buffer` implementations are quite large - +either because they don't use browser-native code or they guarantee perfect +compatibility with node's `Buffer`, which isn't necessary for most people. + +On the other hand, Browsers have already been able to translate between +Base64, UTF-8, Binary Strings, and Byte Arrays (Buffers) all the way back +since _before_ IE6! + +Using these browser-native methods eliminates hundreds of lines of code: + +- `btoa` Binary String to Base64 (ASCII) +- `atob` Base64 (ASCII) to Binary String +- `encodeURIComponent` Unicode String to Hex-Escaped String +- `decodeURIComponent` Hex-Escaped String to Unicode String +- `String.prototype.charCodeAt` ASCII to Byte +- `String.fromCharCode` Byte to ASCII + +The code is typically also much easier to read. In many cases the conversion is only one line long. + +Since a node `Buffer` is actually an `ArrayBuffer`, node's `Buffer` really only has the advantage +of convenient conversions, so that's really all that needs to be implemented. + +In the case of ancient browsers which do not support `Uint8Array`, the native `Array` is still +the best substitute. + +## Why use this in node? + +Unless you're writing code that's intended to work in the browser, you probably shouldn't - +Node's `Buffer` does the job quite well. + +The one function you may still be interested in, which Node's `Buffer` omits, is this one: + +```js +function toUrlSafeBase64(base64) { + return base64 + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=/g, ''); +} +``` + +HOWEVER, if you believe that browser users would benefit from your library, this is a much +better alternative for simple use cases where you're dealing with small bits of code. diff --git a/base64.js b/base64.js new file mode 100644 index 0000000..f817033 --- /dev/null +++ b/base64.js @@ -0,0 +1,59 @@ +'use strict'; + +var Enc = require('./bytes.js'); + +// to Base64 + +function bufToBase64(buf) { + // we want to maintain api compatability with browser APIs, + // so we assume that this could be a Uint8Array + return Buffer.from(buf).toString('base64'); +} + +Enc.bufToBase64 = bufToBase64; + +Enc.strToBase64 = function(str) { + return Buffer.from(str).toString('base64'); +}; + +// from Base64 + +function base64ToBuf(b64) { + // node handles URL Safe Base64 automatically + return Buffer.from(b64, 'base64'); +} + +Enc.base64ToBuf = base64ToBuf; + +Enc.base64ToStr = function(b64) { + return base64ToBuf(b64).toString('utf8'); +}; + +// URL Safe Base64 + +Enc.urlBase64ToBase64 = function(u64) { + var r = u64 % 4; + if (2 === r) { + u64 += '=='; + } else if (3 === r) { + u64 += '='; + } + return u64.replace(/-/g, '+').replace(/_/g, '/'); +}; + +Enc.base64ToUrlBase64 = function(b64) { + return b64 + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=/g, ''); +}; + +Enc.bufToUrlBase64 = function(buf) { + return Enc.base64ToUrlBase64(bufToBase64(buf)); +}; + +Enc.strToUrlBase64 = function(str) { + return Enc.base64ToUrlBase64(bufToBase64(Buffer.from(str))); +}; + +module.exports = Enc; diff --git a/browser/base64.js b/browser/base64.js new file mode 100644 index 0000000..5564ed7 --- /dev/null +++ b/browser/base64.js @@ -0,0 +1,62 @@ +'use strict'; + +var Enc = require('./bytes.js'); + +// To Base64 + +Enc.bufToBase64 = function(u8) { + var bin = ''; + u8.forEach(function(i) { + bin += String.fromCharCode(i); + }); + return btoa(bin); +}; + +Enc.strToBase64 = function(str) { + return btoa(Enc.strToBin(str)); +}; + +// From Base64 + +function _base64ToBin(b64) { + return atob(Enc.urlBase64ToBase64(b64)); +} + +Enc._base64ToBin = _base64ToBin; + +Enc.base64ToBuf = function(b64) { + return Enc.binToBuf(_base64ToBin(b64)); +}; + +Enc.base64ToStr = function(b64) { + return Enc.binToStr(_base64ToBin(b64)); +}; + +// URL Safe Base64 + +Enc.urlBase64ToBase64 = function(u64) { + var r = u64 % 4; + if (2 === r) { + u64 += '=='; + } else if (3 === r) { + u64 += '='; + } + return u64.replace(/-/g, '+').replace(/_/g, '/'); +}; + +Enc.base64ToUrlBase64 = function(b64) { + return b64 + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=/g, ''); +}; + +Enc.bufToUrlBase64 = function(buf) { + return Enc.base64ToUrlBase64(Enc.bufToBase64(buf)); +}; + +Enc.strToUrlBase64 = function(str) { + return Enc.bufToUrlBase64(Enc.strToBuf(str)); +}; + +module.exports = Enc; diff --git a/browser/bytes.js b/browser/bytes.js new file mode 100644 index 0000000..baeb25d --- /dev/null +++ b/browser/bytes.js @@ -0,0 +1,70 @@ +'use strict'; + +var Enc = module.exports; + +// to Binary String + +Enc.bufToBin = function(buf) { + var bin = ''; + // cannot use .map() because Uint8Array would return only 0s + buf.forEach(function(ch) { + bin += String.fromCharCode(ch); + }); + return bin; +}; + +Enc.strToBin = function(str) { + // Note: TextEncoder might be faster (or it might be slower, I don't know), + // but it doesn't solve the double-utf8 problem and MS Edge still has users without it + var escstr = encodeURIComponent(str); + // replaces any uri escape sequence, such as %0A, + // with binary escape, such as 0x0A + var binstr = escstr.replace(/%([0-9A-F]{2})/g, function(_, p1) { + return String.fromCharCode('0x' + p1); + }); + return binstr; +}; + +// to Buffer + +Enc.binToBuf = function(bin) { + var arr = bin.split('').map(function(ch) { + return ch.charCodeAt(0); + }); + return 'undefined' !== typeof Uint8Array ? new Uint8Array(arr) : arr; +}; + +Enc.strToBuf = function(str) { + return Enc.binToBuf(Enc.strToBin(str)); +}; + +// to Unicode String + +Enc.binToStr = function(binstr) { + var escstr = binstr.replace(/(.)/g, function(m, p) { + var code = p + .charCodeAt(0) + .toString(16) + .toUpperCase(); + if (code.length < 2) { + code = '0' + code; + } + return '%' + code; + }); + + return decodeURIComponent(escstr); +}; + +Enc.bufToStr = function(buf) { + return Enc.binToStr(Enc.bufToBin(buf)); +}; + +// Base64 + Hex + +Enc.base64ToHex = function(b64) { + return Enc.bufToHex(Enc.base64ToBuf(b64)); +}; + +Enc.hexToBase64 = function(hex) { + return btoa(Enc._hexToBin(hex)); +}; diff --git a/browser/hex.js b/browser/hex.js new file mode 100644 index 0000000..397810e --- /dev/null +++ b/browser/hex.js @@ -0,0 +1,70 @@ +'use strict'; + +var Enc = require('./bytes.js'); + +// To Hex + +Enc.bufToHex = function(u8) { + var hex = []; + var i, h; + var len = u8.byteLength || u8.length; + + for (i = 0; i < len; i += 1) { + h = u8[i].toString(16); + if (2 !== h.length) { + h = '0' + h; + } + hex.push(h); + } + + return hex.join('').toLowerCase(); +}; + +Enc.numToHex = function(d) { + d = d.toString(16); // .padStart(2, '0'); + if (d.length % 2) { + return '0' + d; + } + return d; +}; + +Enc.strToHex = function(str) { + return Enc._binToHex(Enc.strToBin(str)); +}; + +Enc._binToHex = function(bin) { + return bin + .split('') + .map(function(ch) { + var h = ch.charCodeAt(0).toString(16); + if (2 !== h.length) { + h = '0' + h; + } + return h; + }) + .join(''); +}; + +// From Hex + +Enc.hexToBuf = function(hex) { + var arr = []; + hex.match(/.{2}/g).forEach(function(h) { + arr.push(parseInt(h, 16)); + }); + return 'undefined' !== typeof Uint8Array ? new Uint8Array(arr) : arr; +}; + +Enc.hexToStr = function(hex) { + return Enc.binToStr(_hexToBin(hex)); +}; + +function _hexToBin(hex) { + return hex.replace(/([0-9A-F]{2})/gi, function(_, p1) { + return String.fromCharCode('0x' + p1); + }); +} + +Enc._hexToBin = _hexToBin; + +module.exports = Enc; diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..40e1716 --- /dev/null +++ b/build.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +# TODO convert to JS +cat browser/base64.js browser/hex.js browser/bytes.js encoding.js > all.tmp.js +sed -i '' '/use strict/d' all.tmp.js +sed -i '' '/require/d' all.tmp.js +sed -i '' '/exports/d' all.tmp.js +echo ';(function () {' > all.js +echo "'use strict';" >> all.js +echo "var Enc = window.Encoding = {};" >> all.js +cat all.tmp.js >> all.js +rm all.tmp.js +echo '}());' >> all.js + +mv all.js dist/encoding.all.js +uglifyjs dist/encoding.all.js > dist/encoding.all.min.js diff --git a/bytes.js b/bytes.js new file mode 100644 index 0000000..91f75e0 --- /dev/null +++ b/bytes.js @@ -0,0 +1,50 @@ +'use strict'; + +var Enc = module.exports; + +/* +Enc.bufToUint8 = function bufToUint8(buf) { + return new Uint8Array(buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength)); +}; +*/ + +// from Binary Strings + +Enc.binToBuf = function(bin) { + return Buffer.from(bin, 'binary'); +}; + +Enc.binToStr = function(bin) { + return Enc.binToBuf(bin).toString('utf8'); +}; + +// from Buffer + +Enc.bufToBin = function(buf) { + return Buffer.from(buf).toString('binary'); +}; + +Enc.bufToStr = function(buf) { + return Buffer.from(buf).toString('utf8'); +}; + +// from Unicode Strings + +Enc.strToBin = function(str) { + return Buffer.from(str).toString('binary'); +}; + +Enc.strToBuf = function(str) { + // default is 'utf8' + return Buffer.from(str); +}; + +// Base64 and Hex + +Enc.base64ToHex = function(b64) { + return Buffer.from(b64, 'base64').toString('hex'); +}; + +Enc.hexToBase64 = function(hex) { + return Buffer.from(hex, 'hex').toString('base64'); +}; diff --git a/dist/encoding.all.js b/dist/encoding.all.js new file mode 100644 index 0000000..bb1a404 --- /dev/null +++ b/dist/encoding.all.js @@ -0,0 +1,199 @@ +;(function () { +'use strict'; +var Enc = window.Encoding = {}; + + +// To Base64 + +Enc.bufToBase64 = function(u8) { + var bin = ''; + u8.forEach(function(i) { + bin += String.fromCharCode(i); + }); + return btoa(bin); +}; + +Enc.strToBase64 = function(str) { + return btoa(Enc.strToBin(str)); +}; + +// From Base64 + +function _base64ToBin(b64) { + return atob(Enc.urlBase64ToBase64(b64)); +} + +Enc._base64ToBin = _base64ToBin; + +Enc.base64ToBuf = function(b64) { + return Enc.binToBuf(_base64ToBin(b64)); +}; + +Enc.base64ToStr = function(b64) { + return Enc.binToStr(_base64ToBin(b64)); +}; + +// URL Safe Base64 + +Enc.urlBase64ToBase64 = function(u64) { + var r = u64 % 4; + if (2 === r) { + u64 += '=='; + } else if (3 === r) { + u64 += '='; + } + return u64.replace(/-/g, '+').replace(/_/g, '/'); +}; + +Enc.base64ToUrlBase64 = function(b64) { + return b64 + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=/g, ''); +}; + +Enc.bufToUrlBase64 = function(buf) { + return Enc.base64ToUrlBase64(Enc.bufToBase64(buf)); +}; + +Enc.strToUrlBase64 = function(str) { + return Enc.bufToUrlBase64(Enc.strToBuf(str)); +}; + + + +// To Hex + +Enc.bufToHex = function(u8) { + var hex = []; + var i, h; + var len = u8.byteLength || u8.length; + + for (i = 0; i < len; i += 1) { + h = u8[i].toString(16); + if (2 !== h.length) { + h = '0' + h; + } + hex.push(h); + } + + return hex.join('').toLowerCase(); +}; + +Enc.numToHex = function(d) { + d = d.toString(16); // .padStart(2, '0'); + if (d.length % 2) { + return '0' + d; + } + return d; +}; + +Enc.strToHex = function(str) { + return Enc._binToHex(Enc.strToBin(str)); +}; + +Enc._binToHex = function(bin) { + return bin + .split('') + .map(function(ch) { + var h = ch.charCodeAt(0).toString(16); + if (2 !== h.length) { + h = '0' + h; + } + return h; + }) + .join(''); +}; + +// From Hex + +Enc.hexToBuf = function(hex) { + var arr = []; + hex.match(/.{2}/g).forEach(function(h) { + arr.push(parseInt(h, 16)); + }); + return 'undefined' !== typeof Uint8Array ? new Uint8Array(arr) : arr; +}; + +Enc.hexToStr = function(hex) { + return Enc.binToStr(_hexToBin(hex)); +}; + +function _hexToBin(hex) { + return hex.replace(/([0-9A-F]{2})/gi, function(_, p1) { + return String.fromCharCode('0x' + p1); + }); +} + +Enc._hexToBin = _hexToBin; + + + +// to Binary String + +Enc.bufToBin = function(buf) { + var bin = ''; + // cannot use .map() because Uint8Array would return only 0s + buf.forEach(function(ch) { + bin += String.fromCharCode(ch); + }); + return bin; +}; + +Enc.strToBin = function(str) { + // Note: TextEncoder might be faster (or it might be slower, I don't know), + // but it doesn't solve the double-utf8 problem and MS Edge still has users without it + var escstr = encodeURIComponent(str); + // replaces any uri escape sequence, such as %0A, + // with binary escape, such as 0x0A + var binstr = escstr.replace(/%([0-9A-F]{2})/g, function(_, p1) { + return String.fromCharCode('0x' + p1); + }); + return binstr; +}; + +// to Buffer + +Enc.binToBuf = function(bin) { + var arr = bin.split('').map(function(ch) { + return ch.charCodeAt(0); + }); + return 'undefined' !== typeof Uint8Array ? new Uint8Array(arr) : arr; +}; + +Enc.strToBuf = function(str) { + return Enc.binToBuf(Enc.strToBin(str)); +}; + +// to Unicode String + +Enc.binToStr = function(binstr) { + var escstr = binstr.replace(/(.)/g, function(m, p) { + var code = p + .charCodeAt(0) + .toString(16) + .toUpperCase(); + if (code.length < 2) { + code = '0' + code; + } + return '%' + code; + }); + + return decodeURIComponent(escstr); +}; + +Enc.bufToStr = function(buf) { + return Enc.binToStr(Enc.bufToBin(buf)); +}; + +// Base64 + Hex + +Enc.base64ToHex = function(b64) { + return Enc.bufToHex(Enc.base64ToBuf(b64)); +}; + +Enc.hexToBase64 = function(hex) { + return btoa(Enc._hexToBin(hex)); +}; + +}()); diff --git a/dist/encoding.all.min.js b/dist/encoding.all.min.js new file mode 100644 index 0000000..3c430be --- /dev/null +++ b/dist/encoding.all.min.js @@ -0,0 +1 @@ +(function(){"use strict";var Enc=window.Encoding={};Enc.bufToBase64=function(u8){var bin="";u8.forEach(function(i){bin+=String.fromCharCode(i)});return btoa(bin)};Enc.strToBase64=function(str){return btoa(Enc.strToBin(str))};function _base64ToBin(b64){return atob(Enc.urlBase64ToBase64(b64))}Enc._base64ToBin=_base64ToBin;Enc.base64ToBuf=function(b64){return Enc.binToBuf(_base64ToBin(b64))};Enc.base64ToStr=function(b64){return Enc.binToStr(_base64ToBin(b64))};Enc.urlBase64ToBase64=function(u64){var r=u64%4;if(2===r){u64+="=="}else if(3===r){u64+="="}return u64.replace(/-/g,"+").replace(/_/g,"/")};Enc.base64ToUrlBase64=function(b64){return b64.replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")};Enc.bufToUrlBase64=function(buf){return Enc.base64ToUrlBase64(Enc.bufToBase64(buf))};Enc.strToUrlBase64=function(str){return Enc.bufToUrlBase64(Enc.strToBuf(str))};Enc.bufToHex=function(u8){var hex=[];var i,h;var len=u8.byteLength||u8.length;for(i=0;i (https://coolaj86.com/)", + "license": "MPL-2.0" +} diff --git a/tests/index.html b/tests/index.html new file mode 100644 index 0000000..485cdd4 --- /dev/null +++ b/tests/index.html @@ -0,0 +1,5 @@ + + + + + diff --git a/tests/index.js b/tests/index.js new file mode 100644 index 0000000..f8b0a13 --- /dev/null +++ b/tests/index.js @@ -0,0 +1,216 @@ +'use strict'; + +var Enc = + ('undefined' === typeof window ? {} : window).Encoding || require('../'); + +//UTF-8 +var pass = 0; +var references = { + string: 'I ½ ♥ 𩶘', + array: [73, 32, 194, 189, 32, 226, 153, 165, 32, 240, 169, 182, 152], + hex: [ + '49', + '20', + 'c2', + 'bd', + '20', + 'e2', + '99', + 'a5', + '20', + 'f0', + 'a9', + 'b6', + '98' + ].join(''), + base64: 'SSDCvSDimaUg8Km2mA==', + urlBase64: 'SSDCvSDimaUg8Km2mA', + base32: 'JEQMFPJA4KM2KIHQVG3JQ===' +}; +references.bin = references.array + .map(function(n) { + return String.fromCharCode(n); + }) + .join(''); +references.buffer = Uint8Array.from(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=', + urlBase64: '_-Ia84bOk2s' +}; +binrefs.buffer = new Uint8Array(binrefs.array); + +var str = references.string; +var buf = Enc.strToBuf(references.string); +var base64 = Enc.bufToBase64(references.buffer); +var hex = Enc.bufToHex(references.buffer); +//var b32 = Enc.bufToBase32(references.buffer); + +function buffersAreEqual(buf1, buf2) { + if (buf1.length !== buf2.length) { + return false; + } + return Array.prototype.every.call(buf1, function(byte, i) { + if (byte === buf2[i]) { + return true; + } + }); +} + +// To Binary String +if (Enc.strToBin(references.string) === references.bin) { + pass += 1; +} else { + console.error('[FAIL] str -> bin'); +} +if (Enc.bufToBin(references.array) !== references.bin) { + console.error('[FAIL] buf -> bin'); +} else { + pass += 1; +} + +// To Byte Array +if (!buffersAreEqual(Enc.strToBuf(references.string), references.array)) { + console.error('[FAIL] utf8 -> buf'); +} else { + pass += 1; +} +if (!buffersAreEqual(Enc.base64ToBuf(references.base64), references.array)) { + console.error('[FAIL] base64 -> buf'); +} else { + pass += 1; +} +if (!buffersAreEqual(Enc.hexToBuf(references.hex), references.array)) { + console.error('[FAIL] hex -> buf'); +} else { + pass += 1; +} +if (!buffersAreEqual(Enc.binToBuf(references.bin), references.array)) { + console.error('[FAIL] bin -> buf'); +} else { + pass += 1; +} + +// To Unicode String +if (Enc.bufToStr(references.array) !== references.string) { + console.error('[FAIL] buf -> str'); +} else { + pass += 1; +} +if (Enc.base64ToStr(references.base64) !== references.string) { + console.error('[FAIL] base64 -> str'); +} else { + pass += 1; +} +if (Enc.base64ToStr(references.urlBase64) !== references.string) { + console.error('[FAIL] url base64 -> str'); +} else { + pass += 1; +} +if (Enc.hexToStr(references.hex) !== references.string) { + console.error('[FAIL] hex -> str'); +} else { + pass += 1; +} +if (Enc.binToStr(references.bin) !== references.string) { + console.error('[FAIL] bin -> str'); +} else { + pass += 1; +} + +// To Base64 +if (Enc.bufToBase64(references.array) !== references.base64) { + console.error('[FAIL] buf -> base64'); +} else { + pass += 1; +} +if (Enc.bufToUrlBase64(references.array) !== references.urlBase64) { + console.error('[FAIL] buf -> url base64'); +} else { + pass += 1; +} +if (Enc.strToBase64(references.string) !== references.base64) { + console.error('[FAIL] str -> base64'); +} else { + pass += 1; +} +if (Enc.strToUrlBase64(references.string) !== references.urlBase64) { + console.error('[FAIL] str -> url base64'); +} else { + pass += 1; +} +if (Enc.hexToBase64(references.hex) !== references.base64) { + console.error('[FAIL] hex -> base64'); +} else { + pass += 1; +} + +// To Hex +if (Enc.bufToHex(references.array) !== references.hex) { + console.error('[FAIL] buf -> hex'); +} else { + pass += 1; +} +if (Enc.strToHex(references.string) !== references.hex) { + console.error('[FAIL] str -> hex', Enc.strToHex(references.string)); +} else { + pass += 1; +} +if (Enc.base64ToHex(references.base64) !== references.hex) { + console.error('[FAIL] base64 -> hex'); +} else { + pass += 1; +} + +// Raw Binary +if (Enc.bufToUrlBase64(binrefs.array) !== binrefs.urlBase64) { + console.error('[FAIL] buf -> url base64'); +} else { + pass += 1; +} + +var bytes = binrefs.array; +buf = new Uint8Array(bytes); +str = Enc.bufToBin(buf); +base64 = Enc.bufToBase64(buf); +hex = Enc.bufToHex(buf); + +// This can't be properly tested because binary strings can't be parsed +// if (str !== "ÿâó†Î“k") { +// pass += 1; +// console.log('[FAIL] binary -> str', str); +// } +if (binrefs.base64 !== base64) { + console.error('[FAIL] binary -> base64', base64); +} else { + pass += 1; +} +if (binrefs.hex !== hex) { + console.error('[FAIL] binary -> hex', hex); +} else { + pass += 1; +} + +// +// Base32 +// +/* + b32 = Enc.bufToBase32(references.buffer); + if (references.base32 !== b32) { + pass += 1; + console.error('[FAIL] binary -> base32', references.base32, '!==', b32); + } + buf = Enc.base32ToBuffer(references.base32); + if (!buffersAreEqual(buf, references.buffer)) { + pass += 1 + console.error('[FAIL] base32 -> binary', references.buffer, '!==', buf); + } + */ + +if (22 === pass) { + console.info('[PASS] ' + pass + ' tests passed'); +} else { + console.error('[FAIL] ' + (22 - pass) + ' of 22 tests failed'); +}