From a1ecb26e8dfed0b33056dec111d3806d3e981bad Mon Sep 17 00:00:00 2001 From: Mathias Bynens Date: Wed, 30 Apr 2014 10:47:54 +0200 Subject: [PATCH] Tag the v0.1.0 release --- .gitattributes | 2 + .gitignore | 17 +++++ .travis.yml | 18 +++++ Gruntfile.js | 61 +++++++++++++++ LICENSE-MIT.txt | 20 +++++ README.md | 188 +++++++++++++++++++++++++++++++++++++++++++++++ bacon.js | 123 +++++++++++++++++++++++++++++++ bin/bacon | 121 ++++++++++++++++++++++++++++++ bower.json | 16 ++++ component.json | 25 +++++++ man/bacon.1 | 66 +++++++++++++++++ package.json | 64 ++++++++++++++++ tests/index.html | 35 +++++++++ tests/tests.js | 130 ++++++++++++++++++++++++++++++++ 14 files changed, 886 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 Gruntfile.js create mode 100644 LICENSE-MIT.txt create mode 100644 README.md create mode 100644 bacon.js create mode 100755 bin/bacon create mode 100644 bower.json create mode 100644 component.json create mode 100644 man/bacon.1 create mode 100644 package.json create mode 100644 tests/index.html create mode 100644 tests/tests.js diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..0a91f75 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Automatically normalize line endings for all text-based files +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..444ec4d --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# Coverage report +coverage + +# Installed npm modules +node_modules + +# Folder view configuration files +.DS_Store +Desktop.ini + +# Thumbnail cache files +._* +Thumbs.db + +# Files that might appear on external disks +.Spotlight-V100 +.Trashes diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..0fc50ac --- /dev/null +++ b/.travis.yml @@ -0,0 +1,18 @@ +language: node_js +node_js: + - "0.10" +before_script: + - "npm install -g grunt-cli" + # Narwhal uses a hardcoded path to openjdk v6, so use that version + - "sudo apt-get update -qq" + - "sudo apt-get install -qq openjdk-6-jre" + - "PACKAGE=rhino1_7R3; wget http://ftp.mozilla.org/pub/mozilla.org/js/$PACKAGE.zip && sudo unzip $PACKAGE -d /opt/ && rm $PACKAGE.zip" + - "PACKAGE=rhino1_7R3; echo -e '#!/bin/sh\\njava -jar /opt/'$PACKAGE'/js.jar $@' | sudo tee /usr/local/bin/rhino && sudo chmod +x /usr/local/bin/rhino" + - "PACKAGE=ringojs-0.9; wget http://ringojs.org/downloads/$PACKAGE.zip && sudo unzip $PACKAGE -d /opt/ && rm $PACKAGE.zip" + - "PACKAGE=ringojs-0.9; sudo ln -s /opt/$PACKAGE/bin/ringo /usr/local/bin/ringo && sudo chmod +x /usr/local/bin/ringo" + - "PACKAGE=v0.3.2; wget https://github.com/280north/narwhal/archive/$PACKAGE.zip && sudo unzip $PACKAGE -d /opt/ && rm $PACKAGE.zip" + - "PACKAGE=narwhal-0.3.2; sudo ln -s /opt/$PACKAGE/bin/narwhal /usr/local/bin/narwhal && sudo chmod +x /usr/local/bin/narwhal" + # If the enviroment stores rt.jar in a different directory, find it and symlink the directory + - "PREFIX=/usr/lib/jvm; if [ ! -d $PREFIX/java-6-openjdk ]; then for d in $PREFIX/java-6-openjdk-*; do if [ -e $d/jre/lib/rt.jar ]; then sudo ln -s $d $PREFIX/java-6-openjdk; break; fi; done; fi" +script: + "grunt ci" diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..72ed4d9 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,61 @@ +module.exports = function(grunt) { + + grunt.initConfig({ + 'shell': { + 'options': { + 'stdout': true, + 'stderr': true, + 'failOnError': true + }, + 'cover': { + 'command': 'istanbul cover --report "html" --verbose --dir "coverage" "tests/tests.js"' + }, + 'test-narwhal': { + 'command': 'echo "Testing in Narwhal..."; export NARWHAL_OPTIMIZATION=-1; narwhal "tests/tests.js"' + }, + 'test-phantomjs': { + 'command': 'echo "Testing in PhantomJS..."; phantomjs "tests/tests.js"' + }, + // Rhino 1.7R4 has a bug that makes it impossible to test in. + // https://bugzilla.mozilla.org/show_bug.cgi?id=775566 + // To test, use Rhino 1.7R3, or wait (heh) for the 1.7R5 release. + 'test-rhino': { + 'command': 'echo "Testing in Rhino..."; rhino -opt -1 "tests.js"', + 'options': { + 'execOptions': { + 'cwd': 'tests' + } + } + }, + 'test-ringo': { + 'command': 'echo "Testing in Ringo..."; ringo -o -1 "tests/tests.js"' + }, + 'test-node': { + 'command': 'echo "Testing in Node..."; node "tests/tests.js"' + }, + 'test-browser': { + 'command': 'echo "Testing in a browser..."; open "tests/index.html"' + } + } + }); + + grunt.loadNpmTasks('grunt-shell'); + + grunt.registerTask('cover', 'shell:cover'); + grunt.registerTask('ci', [ + 'shell:test-narwhal', + 'shell:test-phantomjs', + 'shell:test-rhino', + 'shell:test-ringo', + 'shell:test-node', + ]); + grunt.registerTask('test', [ + 'ci', + 'shell:test-browser' + ]); + + grunt.registerTask('default', [ + 'shell:test-node' + ]); + +}; diff --git a/LICENSE-MIT.txt b/LICENSE-MIT.txt new file mode 100644 index 0000000..97067e5 --- /dev/null +++ b/LICENSE-MIT.txt @@ -0,0 +1,20 @@ +Copyright Mathias Bynens + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..116c325 --- /dev/null +++ b/README.md @@ -0,0 +1,188 @@ +# bacon-cipher [![Build status](https://travis-ci.org/mathiasbynens/bacon-cipher.svg?branch=master)](https://travis-ci.org/mathiasbynens/bacon-cipher) [![Dependency status](https://gemnasium.com/mathiasbynens/bacon-cipher.svg)](https://gemnasium.com/mathiasbynens/bacon-cipher) + +_bacon-cipher_ is a JavaScript implementation of [Bacon’s cipher, a.k.a. the Baconian cipher](http://en.wikipedia.org/wiki/Bacon's_cipher). It can be used to encode plaintext to Bacon-ciphertext, or the other way around (i.e. decoding). + +By default it uses the most common Bacon cipher alphabet, i.e. `ABCDEFGHIKLMNOPQRSTUWXYZ` (24 letters). This boils down to the following translations: + +``` +a AAAAA g AABBA n ABBAA t BAABA +b AAAAB h AABBB o ABBAB u-v BAABB +c AAABA i-j ABAAA p ABBBA w BABAA +d AAABB k ABAAB q ABBBB x BABAB +e AABAA l ABABA r BAAAA y BABBA +f AABAB m ABABB s BAAAB z BABBB +``` + +## Installation + +Via [npm](http://npmjs.org/): + +```bash +npm install bacon-cipher +``` + +Via [Bower](http://bower.io/): + +```bash +bower install bacon-cipher +``` + +Via [Component](https://github.com/component/component): + +```bash +component install mathiasbynens/bacon-cipher +``` + +In a browser: + +```html + +``` + +In [Narwhal](http://narwhaljs.org/), [Node.js](http://nodejs.org/), and [RingoJS](http://ringojs.org/): + +```js +var bacon = require('bacon-cipher'); +``` + +In [Rhino](http://www.mozilla.org/rhino/): + +```js +load('bacon.js'); +``` + +Using an AMD loader like [RequireJS](http://requirejs.org/): + +```js +require( + { + 'paths': { + 'bacon': 'path/to/bacon' + } + }, + ['bacon'], + function(bacon) { + console.log(bacon); + } +); +``` + +## API + +### `bacon.version` + +A string representing the semantic version number. + +### `bacon.encode(text, options)` + +This function takes a string of text (the `text` parameter) and encrypts it using Bacon’s cipher. + +```js +bacon.encode('steganography'); +// → 'BAAABBAABAAABAAAABBAAAAAAABBAAABBABAABBABAAAAAAAAAABBBAAABBBBABBA' +``` + +By default it uses the most common Bacon cipher alphabet, i.e. `ABCDEFGHIKLMNOPQRSTUWXYZ` (24 letters). In this case instances of `J` are replaced with `I`, and instances of `V` are replaced with `U` before further encoding the input string. + +```js +bacon.encode('James Vendetta'); +// → 'ABAAAAAAAAABABBAABAABAAAB BAABBAABAAABBAAAAABBAABAABAABABAABAAAAAA' +``` + +It’s possible to pass an (optional) `options` object with an `alphabet` property to override the cipher alphabet. Note that in that case, no preprocessing (like replacing `j` and `v`) is done. For example, to use the full 26-letter alphabet: + +```js +bacon.encode('James Vendetta', { + 'alphabet': 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +}); +// → 'ABAABAAAAAABBAAAABAABAABA BABABAABAAABBABAAABBAABAABAABBBAABBAAAAA' +``` + +### `bacon.decode(text, options)` + +This function takes a string of text (the `text` parameter) and decrypts it using Bacon’s cipher. + +By default it uses the most common Bacon cipher alphabet, i.e. `ABCDEFGHIKLMNOPQRSTUWXYZ` (24 letters). + +```js +bacon.decode('BAAABBAABAAABAAAABBAAAAAAABBAAABBABAABBABAAAAAAAAAABBBAAABBBBABBA'); +// → 'STEGANOGRAPHY' + +bacon.decode('ABAAAAAAAAABABBAABAABAAAB BAABBAABAAABBAAAAABBAABAABAABABAABAAAAAA'); +// → 'IAMES UENDETTA' +``` + +It’s possible to pass an (optional) `options` object with an `alphabet` property to override the cipher alphabet. For example, to use the full 26-letter alphabet: + +```js +bacon.decode('ABAABAAAAAABBAAAABAABAABA BABABAABAAABBABAAABBAABAABAABBBAABBAAAAA', { + 'alphabet': 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +}); +// → 'JAMES VENDETTA' +``` + +### Using the `bacon` binary + +To use the `bacon` binary in your shell, simply install _bacon-cipher_ globally using npm: + +```bash +npm install -g bacon-cipher +``` + +After that you will be able to use Bacon’s cipher from the command line: + +```bash +$ bacon --encode 'foo bar baz' +AABABABBABABBAB AAAABAAAAABAAAA AAAABAAAAABABBB + +$ bacon --decode 'AABABABBABABBAB AAAABAAAAABAAAA AAAABAAAAABABBB' +FOO BAR BAZ + +$ bacon --encode --alphabet=ABCDEFGHIJKLMNOPQRSTUVWXYZ 'Julius Caesar' +ABAABBABAAABABBABAAABABAABAABA AAABAAAAAAAABAABAABAAAAAABAAAB + +$ bacon --decode --alphabet=ABCDEFGHIJKLMNOPQRSTUVWXYZ 'ABAABBABAAABABBABAAABABAABAABA AAABAAAAAAAABAABAABAAAAAABAAAB' +JULIUS CAESAR +``` + +Read a local text file, encrypt it using Bacon’s cipher with a non-default cipher alphabet, and save the result to a new file: + +```bash +$ bacon --encode < foo.txt > foo-bacon.txt +``` + +Or do the same with an online text file: + +```bash +$ curl -sL 'http://mths.be/brh' | bacon --encode > bacon.txt +``` + +Or, the opposite — read a local file containing Bacon ciphertext, decode it back to plain text, and save the result to a new file: + +```bash +$ bacon --decode < bacon.txt > original.txt +``` + +See `bacon --help` for the full list of options. + +## Support + +_bacon_ is designed to work in at least Node.js v0.10.0, Narwhal 0.3.2, RingoJS 0.8-0.9, PhantomJS 1.9.0, Rhino 1.7RC4, as well as old and modern versions of Chrome, Firefox, Safari, Opera, and Internet Explorer. + +## Unit tests & code coverage + +After cloning this repository, run `npm install` to install the dependencies needed for development and testing. You may want to install Istanbul _globally_ using `npm install istanbul -g`. + +Once that’s done, you can run the unit tests in Node using `npm test` or `node tests/tests.js`. To run the tests in Rhino, Ringo, Narwhal, and web browsers as well, use `grunt test`. + +To generate the code coverage report, use `grunt cover`. + +## Author + +| [![twitter/mathias](https://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=70)](https://twitter.com/mathias "Follow @mathias on Twitter") | +|---| +| [Mathias Bynens](http://mathiasbynens.be/) | + +## License + +_bacon_ is available under the [MIT](http://mths.be/mit) license. diff --git a/bacon.js b/bacon.js new file mode 100644 index 0000000..1720bc8 --- /dev/null +++ b/bacon.js @@ -0,0 +1,123 @@ +/*! http://mths.be/bacon v0.1.0 by @mathias | MIT license */ +;(function(root) { + + // Detect free variables `exports` + var freeExports = typeof exports == 'object' && exports; + + // Detect free variable `module` + var freeModule = typeof module == 'object' && module && + module.exports == freeExports && module; + + // Detect free variable `global`, from Node.js or Browserified code, + // and use it as `root` + var freeGlobal = typeof global == 'object' && global; + if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) { + root = freeGlobal; + } + + /*--------------------------------------------------------------------------*/ + + var DEFAULT_ALPHABET = 'ABCDEFGHIKLMNOPQRSTUWXYZ'; + + var decode = function(ciphertext, options) { + ciphertext = ciphertext.toUpperCase(); + // Use the most common 24-letter Bacon alphabet by default. + var alphabet = options && options.alphabet != null ? + options.alphabet.toUpperCase() : + DEFAULT_ALPHABET; + var index = -1; + var length = ciphertext.length; + var space = ''; + var result = ''; + var buffer = []; + var symbol; + var alphabetIndex; + while (++index < length) { + symbol = ciphertext.charAt(index); + if (symbol == 'A' || symbol == 'B') { + buffer.push(symbol); + } else { + // Prepare a space to be added to the output. + space = ' '; + } + if (buffer.length == 5) { + alphabetIndex = ( + (buffer[0] == 'A' ? 0 : 0x10) + // 0b10000 + (buffer[1] == 'A' ? 0 : 0x08) + // 0b01000 + (buffer[2] == 'A' ? 0 : 0x04) + // 0b00100 + (buffer[3] == 'A' ? 0 : 0x02) + // 0b00010 + (buffer[4] == 'A' ? 0 : 0x01) // 0b00001 + ); + buffer = []; + result += (result.length ? space : '') + alphabet.charAt(alphabetIndex); + space = ''; + } + } + return result; + }; + + var encode = function(string, options) { + string = string.toUpperCase(); + var alphabet; + if (options && options.alphabet != null) { + alphabet = options.alphabet.toUpperCase(); + } else { + // Use the most common 24-letter Bacon alphabet by default. + alphabet = DEFAULT_ALPHABET; + string = string + .replace(/J/g, 'I') + .replace(/V/g, 'U'); + } + var index = -1; + var length = string.length; + var alphabetIndex; + var space = ''; + var result = ''; + while (++index < length) { + alphabetIndex = alphabet.indexOf(string.charAt(index)); + if (alphabetIndex > -1) { + result += space + ( + (alphabetIndex & 0x10 ? 'B' : 'A') + // 0b10000 + (alphabetIndex & 0x08 ? 'B' : 'A') + // 0b01000 + (alphabetIndex & 0x04 ? 'B' : 'A') + // 0b00100 + (alphabetIndex & 0x02 ? 'B' : 'A') + // 0b00010 + (alphabetIndex & 0x01 ? 'B' : 'A') // 0b00001 + ); + space = ''; + } else if (index) { + // Prepare a space to be added to the output, unless it’s leading space. + space = ' '; + } + } + return result; + }; + + var bacon = { + 'encode': encode, + 'decode': decode, + 'version': '0.1.0' + }; + + // Some AMD build optimizers, like r.js, check for specific condition patterns + // like the following: + if ( + typeof define == 'function' && + typeof define.amd == 'object' && + define.amd + ) { + define(function() { + return bacon; + }); + } else if (freeExports && !freeExports.nodeType) { + if (freeModule) { // in Node.js or RingoJS v0.8.0+ + freeModule.exports = bacon; + } else { // in Narwhal or RingoJS v0.7.0- + for (var key in bacon) { + bacon.hasOwnProperty(key) && (freeExports[key] = bacon[key]); + } + } + } else { // in Rhino or a web browser + root.bacon = bacon; + } + +}(this)); diff --git a/bin/bacon b/bin/bacon new file mode 100755 index 0000000..fe7625b --- /dev/null +++ b/bin/bacon @@ -0,0 +1,121 @@ +#!/usr/bin/env node +(function() { + + var fs = require('fs'); + var bacon = require('../bacon.js'); + var strings = process.argv.splice(2); + var stdin = process.stdin; + var data; + var timeout; + var action; + var options = {}; + var log = console.log; + + var main = function() { + var option = strings[0]; + var count = 0; + + if (/^(?:-h|--help|undefined)$/.test(option)) { + log( + 'bacon-cipher v%s - http://mths.be/bacon', + bacon.version + ); + log([ + '\nUsage:\n', + '\tbacon [-e | --encode] [--alphabet=string] string', + '\tbacon [-d | --decode] [--alphabet=string] string', + '\tbacon [-v | --version]', + '\tbacon [-h | --help]', + '\nExamples:\n', + '\tbacon --encode \'BACON\'', + '\techo \'AAAABAAAAAAAABAABBABABBAA\' | bacon --decode', + '\tbacon --encode --alphabet=\'ABCDEFGHIJKLMNOPQRSTUVWXYZ\' \'vendetta\'' + ].join('\n')); + return process.exit(1); + } + + if (/^(?:-v|--version)$/.test(option)) { + log('v%s', bacon.version); + return process.exit(1); + } + + strings.forEach(function(string) { + // Process options + if (string == '-e' || string == '--encode') { + action = 'encode'; + return; + } + if (string == '-d' || string == '--decode') { + action = 'decode'; + return; + } + var matches; + if (matches = string.match(/^--alphabet=(.+)/)) { + options.alphabet = matches[1]; + return; + } + if (string == '--strict') { + action = 'decode'; + options.strict = true; + return; + } + // Process string(s) + var result; + if (!action) { + log('Error: bacon requires at least one option and a string argument.'); + log('Try `bacon --help` for more information.'); + return process.exit(1); + } + try { + result = bacon[action](string, options); + log(result); + count++; + } catch (error) { + log(error.message + '\n'); + log('Error: failed to %s.', action); + log('If you think this is a bug in bacon-cipher, please report it:'); + log('https://github.com/mathiasbynens/bacon-cipher/issues/new'); + log('\nStack trace using bacon-cipher@%s:\n', bacon.version); + log(error.stack); + return process.exit(1); + } + }); + if (!count) { + log('Error: bacon requires a string argument.'); + log('Try `bacon --help` for more information.'); + return process.exit(1); + } + // Return with exit status 0 outside of the `forEach` loop, in case + // multiple strings were passed in. + return process.exit(0); + }; + + if (stdin.isTTY) { + // handle shell arguments + main(); + } else { + // Either the script is called from within a non-TTY context, or `stdin` + // content is being piped in. + if (!process.stdout.isTTY) { + // The script was called from a non-TTY context. This is a rather uncommon + // use case we don’t actively support. However, we don’t want the script + // to wait forever in such cases, so… + timeout = setTimeout(function() { + // …if no piped data arrived after a whole minute, handle shell + // arguments instead. + main(); + }, 60000); + } + data = ''; + stdin.on('data', function(chunk) { + clearTimeout(timeout); + data += chunk; + }); + stdin.on('end', function() { + strings.push(data.trim()); + main(); + }); + stdin.resume(); + } + +}()); diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..3894338 --- /dev/null +++ b/bower.json @@ -0,0 +1,16 @@ +{ + "name": "bacon-cipher", + "version": "0.1.0", + "main": "bacon.js", + "ignore": [ + "bin", + "coverage", + "man", + "tests", + ".*", + "component.json", + "Gruntfile.js", + "node_modules", + "package.json" + ] +} diff --git a/component.json b/component.json new file mode 100644 index 0000000..d776a0e --- /dev/null +++ b/component.json @@ -0,0 +1,25 @@ +{ + "name": "bacon-cipher", + "version": "0.1.0", + "description": "A robust JavaScript implementation of Bacon’s cipher, a.k.a. the Baconian cipher.", + "repo": "mathiasbynens/bacon-cipher", + "license": "MIT", + "scripts": [ + "bacon.js" + ], + "main": "bacon.js", + "keywords": [ + "string", + "bacon", + "baconian", + "cipher", + "crypto", + "cryptography", + "stegano", + "steganography", + "encode", + "encrypt", + "decode", + "decrypt" + ] +} diff --git a/man/bacon.1 b/man/bacon.1 new file mode 100644 index 0000000..d325895 --- /dev/null +++ b/man/bacon.1 @@ -0,0 +1,66 @@ +.Dd April 30, 2014 +.Dt bacon 1 +.Sh NAME +.Nm bacon +.Nd encode or decode messages using Bacon's cipher +.Sh SYNOPSIS +.Nm +.Op Fl -encode Ar string +.br +.Op Fl -encode Fl -alphabet=alphabet Ar string +.br +.Op Fl -decode Ar string +.br +.Op Fl -decode Fl -alphabet=alphabet Ar string +.br +.Op Fl v | -version +.br +.Op Fl h | -help +.Sh DESCRIPTION +.Nm +encode or decode messages using Bacon's cipher. +.Sh OPTIONS +.Bl -ohang -offset +.It Sy "--encode" +Encode a string of text using Bacon's cipher. +.It Sy "--encode --alphabet=alphabet" +Specify a custom cipher alphabet to be used during encoding. By default the common 24-letter Bacon alphabet is used, in which case instances of `J` and `V` in the input are replaced with `I` and `U` respectively before further encoding the message. +.It Sy "--decode" +Decode a string of ciphertext using Bacon's cipher. +.It Sy "--decode --alphabet=alphabet" +Specify a custom cipher alphabet to be used during decoding. By default the common 24-letter Bacon alphabet is used. +.It Sy "-v, --version" +Print bacon's version. +.It Sy "-h, --help" +Show the help screen. +.El +.Sh EXIT STATUS +The +.Nm bacon +utility exits with one of the following values: +.Pp +.Bl -tag -width flag -compact +.It Li 0 +.Nm +successfully encoded/decoded the input and printed the result. +.It Li 1 +.Nm +wasn't instructed to encode/decode anything (for example, the +.Ar --help +flag was set); or, an error occurred. +.El +.Sh EXAMPLES +.Bl -ohang -offset +.It Sy "bacon --encode 'foo bar baz'" +Print an encoded version of the given string. +.It Sy "bacon --decode 'AABABABBABABBAB AAAABAAAAABAAAA AAAABAAAAABABBB'" +Print the decoded version of the given ciphertext. +.It Sy "echo\ 'foo bar baz'\ |\ bacon --encode" +Print the encoded version of the string that gets piped in. +.El +.Sh BUGS +bacon's bug tracker is located at . +.Sh AUTHOR +Mathias Bynens +.Sh WWW + diff --git a/package.json b/package.json new file mode 100644 index 0000000..d671204 --- /dev/null +++ b/package.json @@ -0,0 +1,64 @@ +{ + "name": "bacon-cipher", + "version": "0.1.0", + "description": "A robust JavaScript implementation of Bacon’s cipher, a.k.a. the Baconian cipher.", + "homepage": "http://mths.be/bacon", + "main": "bacon.js", + "bin": { + "bacon": "bin/bacon" + }, + "man": "man/bacon.1", + "keywords": [ + "string", + "bacon", + "baconian", + "cipher", + "crypto", + "cryptography", + "stegano", + "steganography", + "encode", + "encrypt", + "decode", + "decrypt" + ], + "licenses": [ + { + "type": "MIT", + "url": "http://mths.be/mit" + } + ], + "author": { + "name": "Mathias Bynens", + "url": "http://mathiasbynens.be/" + }, + "repository": { + "type": "git", + "url": "https://github.com/mathiasbynens/bacon-cipher.git" + }, + "bugs": { + "url": "https://github.com/mathiasbynens/bacon-cipher/issues" + }, + "files": [ + "LICENSE-MIT.txt", + "bacon.js", + "bin/", + "man/" + ], + "directories": { + "bin": "bin", + "man": "man", + "test": "tests" + }, + "scripts": { + "test": "node tests/tests.js" + }, + "devDependencies": { + "grunt": "~0.4.4", + "grunt-shell": "~0.7.0", + "istanbul": "~0.2.7", + "qunit-extras": "~1.1.0", + "qunitjs": "~1.11.0", + "requirejs": "~2.1.11" + } +} diff --git a/tests/index.html b/tests/index.html new file mode 100644 index 0000000..543fb0a --- /dev/null +++ b/tests/index.html @@ -0,0 +1,35 @@ + + + + + bacon test suite + + + +
+ + + + + + diff --git a/tests/tests.js b/tests/tests.js new file mode 100644 index 0000000..1652a81 --- /dev/null +++ b/tests/tests.js @@ -0,0 +1,130 @@ +(function(root) { + 'use strict'; + + var noop = Function.prototype; + + var load = (typeof require == 'function' && !(root.define && define.amd)) ? + require : + (!root.document && root.java && root.load) || noop; + + var QUnit = (function() { + return root.QUnit || ( + root.addEventListener || (root.addEventListener = noop), + root.setTimeout || (root.setTimeout = noop), + root.QUnit = load('../node_modules/qunitjs/qunit/qunit.js') || root.QUnit, + addEventListener === noop && delete root.addEventListener, + root.QUnit + ); + }()); + + var qe = load('../node_modules/qunit-extras/qunit-extras.js'); + if (qe) { + qe.runInContext(root); + } + + // The `bacon` object to test + var bacon = root.bacon || (root.bacon = ( + bacon = load('../bacon.js') || root.bacon, + bacon = bacon.bacon || bacon + )); + + /*--------------------------------------------------------------------------*/ + + function forEach(array, fn) { + var index = -1; + var length = array.length; + while (++index < length) { + fn(array[index]); + } + } + + function forOwn(object, fn) { + for (var key in object) { + if (object.hasOwnProperty(key)) { + fn(key, object[key]); + } + } + } + + // `throws` is a reserved word in ES3; alias it to avoid errors + var raises = QUnit.assert['throws']; + + // explicitly call `QUnit.module()` instead of `module()` + // in case we are in a CLI environment + QUnit.module('bacon'); + + test('bacon.encode', function() { + equal( + bacon.encode('ABCDEFGHIJKLMNOPQRSTUVWXYZ'), + 'AAAAAAAAABAAABAAAABBAABAAAABABAABBAAABBBABAAAABAAAABAABABABAABABBABBAAABBABABBBAABBBBBAAAABAAABBAABABAABBBAABBBABAABABABBABBABABBB', + 'A-Z' + ); + equal( + bacon.encode('abcdefghijklmnopqrstuvwxyz'), + 'AAAAAAAAABAAABAAAABBAABAAAABABAABBAAABBBABAAAABAAAABAABABABAABABBABBAAABBABABBBAABBBBBAAAABAAABBAABABAABBBAABBBABAABABABBABBABABBB', + 'a-z' + ); + equal( + bacon.encode('foo bar'), + 'AABABABBABABBAB AAAABAAAAABAAAA', + 'single space character' + ); + equal( + bacon.encode('fOo bAr'), + 'AABABABBABABBAB AAAABAAAAABAAAA', + 'uppercase vs. lowercase doesn’t matter' + ); + equal( + bacon.encode('!"#$%&\'()*+,-./0123456789:;<=>?@[\]^_`{|}~'), + '', + '[^a-zA-Z] are ignored' + ); + equal( + bacon.encode('foo!"#$%&\'()*+,-./0123456789:;<=>?@[\]^_`{|}~bar'), + 'AABABABBABABBAB AAAABAAAAABAAAA', + '[^a-zA-Z] are ignored and become spaces if they occur between [a-zA-Z]' + ); + equal( + bacon.encode('ABCDEFGHIJKLMNOPQRSTUVWXYZ', { 'alphabet': 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' }), + 'AAAAAAAAABAAABAAAABBAABAAAABABAABBAAABBBABAAAABAABABABAABABBABBAAABBABABBBAABBBBBAAAABAAABBAABABAABBBABAABABABBABBABABBBBBAAABBAAB', + 'custom cipher alphabet' + ); + equal( + bacon.encode('ABCDEFGHIJKLMNOPQRSTUVWXYZ', { 'alphabet': 'abcdefghijklmnopqrstuvwxyz' }), + 'AAAAAAAAABAAABAAAABBAABAAAABABAABBAAABBBABAAAABAABABABAABABBABBAAABBABABBBAABBBBBAAAABAAABBAABABAABBBABAABABABBABBABABBBBBAAABBAAB', + 'custom cipher alphabet is uppercased before use' + ); + }); + + test('bacon.decode', function() { + equal( + bacon.decode('AAAAAAAAABAAABAAAABBAABAAAABABAABBAAABBBABAAAABAAAABAABABABAABABBABBAAABBABABBBAABBBBBAAAABAAABBAABABAABBBAABBBABAABABABBABBABABBB'), + 'ABCDEFGHIIKLMNOPQRSTUUWXYZ', + 'A-Z' + ); + equal( + bacon.decode('AA AA AA AAA BAAAB AAAABBA ABAAA.ABAB#AABBA@AABB+BABAA/AABAAAABAABABABAAB_ABBABBAAABBABABBB AABBBBBAAAABAAABBAABABAABBBABABBB'), + 'A B CD E F G H IIKL MNO PQRST UUWX YZ', + '[^a-zA-Z] are ignored and become spaces if they occur between [a-zA-Z]' + ); + equal( + bacon.decode('AAAAAAAAABAAABAAAABBAABAAAABABAABBAAABBBABAAAABAABABABAABABBABBAAABBABABBBAABBBBBAAAABAAABBAABABAABBBABAABABABBABBABABBBBBAAABBAAB', { 'alphabet': 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' }), + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + 'custom cipher alphabet' + ); + equal( + bacon.decode('AAAAAAAAABAAABAAAABBAABAAAABABAABBAAABBBABAAAABAABABABAABABBABBAAABBABABBBAABBBBBAAAABAAABBAABABAABBBABAABABABBABBABABBBBBAAABBAAB', { 'alphabet': 'abcdefghijklmnopqrstuvwxyz' }), + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + 'custom cipher alphabet is uppercased before use' + ); + }); + + /*--------------------------------------------------------------------------*/ + + // configure QUnit and call `QUnit.start()` for + // Narwhal, Node.js, PhantomJS, Rhino, and RingoJS + if (!root.document || root.phantom) { + QUnit.config.noglobals = true; + QUnit.start(); + } +}(typeof global == 'object' && global || this));