├── .gitignore
├── assets
├── img
│ ├── save.png
│ ├── sgp.png
│ ├── clipboard.png
│ ├── loading.gif
│ ├── options.png
│ ├── sgp@57x57.png
│ ├── sgp@72x72.png
│ ├── sgp@112x112.png
│ ├── sgp@144x144.png
│ ├── save-checked.png
│ └── clipboard-checked.png
└── css
│ └── style.css
├── grunt
├── options
│ ├── clean.js
│ ├── qunit.js
│ ├── pkg.js
│ ├── browserify.js
│ ├── template.js
│ ├── uglify.js
│ ├── checksum.js
│ ├── staticinline.js
│ ├── cssmin.js
│ ├── bookmarklet.js
│ ├── manifest.js
│ └── jshint.js
└── tasks
│ ├── aliases.js
│ ├── bookmarklet.js
│ └── checksum.js
├── .codeclimate.yml
├── mobile
└── cache.manifest
├── Gruntfile.js
├── checksums.json
├── CHANGELOG
├── .travis.yml
├── 404.html
├── test
├── qunit
│ ├── index.html
│ └── mobile.js
└── intern
│ ├── main.js
│ └── functional
│ └── mobile.js
├── package.json
├── .jscsrc
├── .jshintrc
├── src
├── homepage
│ └── index.html.tmpl
├── mobile
│ ├── lib
│ │ ├── localstorage-polyfill.js
│ │ ├── shortcut.js
│ │ └── identicon5.js
│ ├── index.html
│ ├── sgp.mobile.js
│ └── sgp.mobile.css
└── bookmarklet
│ └── sgp.bookmarklet.js
├── README.md
├── bookmarklet
└── bookmarklet.min.js
├── index.html
└── LICENSE
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | build
4 |
--------------------------------------------------------------------------------
/assets/img/save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chriszarate/supergenpass/HEAD/assets/img/save.png
--------------------------------------------------------------------------------
/assets/img/sgp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chriszarate/supergenpass/HEAD/assets/img/sgp.png
--------------------------------------------------------------------------------
/assets/img/clipboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chriszarate/supergenpass/HEAD/assets/img/clipboard.png
--------------------------------------------------------------------------------
/assets/img/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chriszarate/supergenpass/HEAD/assets/img/loading.gif
--------------------------------------------------------------------------------
/assets/img/options.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chriszarate/supergenpass/HEAD/assets/img/options.png
--------------------------------------------------------------------------------
/assets/img/sgp@57x57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chriszarate/supergenpass/HEAD/assets/img/sgp@57x57.png
--------------------------------------------------------------------------------
/assets/img/sgp@72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chriszarate/supergenpass/HEAD/assets/img/sgp@72x72.png
--------------------------------------------------------------------------------
/grunt/options/clean.js:
--------------------------------------------------------------------------------
1 | /* grunt-contrib-clean */
2 |
3 | module.exports = {
4 | app: ['build']
5 | };
6 |
--------------------------------------------------------------------------------
/assets/img/sgp@112x112.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chriszarate/supergenpass/HEAD/assets/img/sgp@112x112.png
--------------------------------------------------------------------------------
/assets/img/sgp@144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chriszarate/supergenpass/HEAD/assets/img/sgp@144x144.png
--------------------------------------------------------------------------------
/assets/img/save-checked.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chriszarate/supergenpass/HEAD/assets/img/save-checked.png
--------------------------------------------------------------------------------
/grunt/options/qunit.js:
--------------------------------------------------------------------------------
1 | /* grunt-contrib-qunit */
2 |
3 | module.exports = {
4 | app: ['test/qunit/*.html']
5 | };
6 |
--------------------------------------------------------------------------------
/assets/img/clipboard-checked.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chriszarate/supergenpass/HEAD/assets/img/clipboard-checked.png
--------------------------------------------------------------------------------
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | exclude_paths:
2 | - bookmarklet/*
3 | - grunt/*
4 | - src/mobile/lib/*
5 | languages:
6 | JavaScript: true
7 |
--------------------------------------------------------------------------------
/grunt/options/pkg.js:
--------------------------------------------------------------------------------
1 | /* Load package.json */
2 |
3 | 'use strict';
4 |
5 | module.exports = function (grunt) {
6 | return grunt.file.readJSON('package.json');
7 | };
8 |
--------------------------------------------------------------------------------
/grunt/options/browserify.js:
--------------------------------------------------------------------------------
1 | /* grunt-browserify */
2 |
3 | module.exports = {
4 | dist: {
5 | files: {
6 | 'build/mobile.js': ['src/mobile/*.js']
7 | }
8 | }
9 | };
10 |
--------------------------------------------------------------------------------
/grunt/options/template.js:
--------------------------------------------------------------------------------
1 | /* grunt-template */
2 |
3 | module.exports = {
4 | app: {
5 | files: {
6 | 'index.html': ['src/homepage/index.html.tmpl']
7 | }
8 | }
9 | };
10 |
--------------------------------------------------------------------------------
/grunt/options/uglify.js:
--------------------------------------------------------------------------------
1 | /* grunt-contrib-uglify */
2 |
3 | module.exports = {
4 | app: {
5 | files: {
6 | 'build/mobile.min.js': [
7 | 'build/mobile.js'
8 | ]
9 | }
10 | }
11 | };
12 |
--------------------------------------------------------------------------------
/mobile/cache.manifest:
--------------------------------------------------------------------------------
1 | CACHE MANIFEST
2 | # This manifest was generated by grunt-manifest HTML5 Cache Manifest Generator
3 | # Time: Thu Aug 25 2016 20:55:39 GMT-0400 (EDT)
4 |
5 | CACHE:
6 | index.html
7 |
8 | NETWORK:
9 | *
10 |
--------------------------------------------------------------------------------
/grunt/options/checksum.js:
--------------------------------------------------------------------------------
1 | /* Checksum options */
2 |
3 | module.exports = {
4 | app: {
5 | src: [
6 | 'mobile/index.html',
7 | 'bookmarklet/bookmarklet.min.js'
8 | ],
9 | dest: 'checksums.json'
10 | }
11 | };
12 |
--------------------------------------------------------------------------------
/grunt/options/staticinline.js:
--------------------------------------------------------------------------------
1 | /* grunt-static-inline */
2 |
3 | module.exports = {
4 | app: {
5 | options: {
6 | basepath: './'
7 | },
8 | files: {
9 | 'mobile/index.html': 'src/mobile/index.html'
10 | }
11 | }
12 | };
13 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | /*
2 | Static Gruntfile
3 | - Load Grunt plugins with `load-grunt-tasks`.
4 | - Load Grunt options and aliases with `grunt-load-options`.
5 | */
6 |
7 | 'use strict';
8 |
9 | module.exports = function (grunt) {
10 | require('load-grunt-tasks')(grunt);
11 | };
12 |
--------------------------------------------------------------------------------
/checksums.json:
--------------------------------------------------------------------------------
1 | {"bookmarklet/bookmarklet.min.js":"c8c2d64724351a752c2feafab551e030c405f52a4006588c5813a5e9218b2a5a4a57a88f70b8771137bb434ede9ffdae9c6395dc091627c9634f3a173cdc64ba","mobile/index.html":"435c36cb2c8fb88513a4dafe9e90a303a64cf2891655d1fed74a1b2638c0648977f6fd82c19b904738722b87ccebaeabc9e507e386a1901493e8fc9f4f9fb778"}
--------------------------------------------------------------------------------
/grunt/options/cssmin.js:
--------------------------------------------------------------------------------
1 | /* grunt-contrib-cssmin */
2 |
3 | /*jshint camelcase: false*/
4 | /*jscs: disable requireCamelCaseOrUpperCaseIdentifiers */
5 |
6 | module.exports = {
7 | add_banner: {
8 | files: {
9 | 'build/mobile.min.css': [
10 | 'src/mobile/*.css'
11 | ]
12 | }
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/grunt/options/bookmarklet.js:
--------------------------------------------------------------------------------
1 | /* Bookmarklet options */
2 |
3 | module.exports = {
4 | app: {
5 | options: {
6 | anonymize: false,
7 | mangleVars: true,
8 | urlencode: true
9 | },
10 | files: {
11 | 'bookmarklet/bookmarklet.min.js': ['src/bookmarklet/sgp.bookmarklet.js']
12 | }
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/grunt/options/manifest.js:
--------------------------------------------------------------------------------
1 | /* grunt-manifest */
2 |
3 | module.exports = {
4 | generate: {
5 | options: {
6 | basePath: 'mobile/',
7 | network: ['*'],
8 | verbose: true,
9 | timestamp: true
10 | },
11 | src: [
12 | 'index.html'
13 | ],
14 | dest: 'mobile/cache.manifest'
15 | }
16 | };
17 |
--------------------------------------------------------------------------------
/grunt/options/jshint.js:
--------------------------------------------------------------------------------
1 | /* grunt-contrib-jshint */
2 |
3 | module.exports = {
4 | options: {
5 | ignores: [
6 | 'src/mobile/lib/**/*.js'
7 | ],
8 | jshintrc: true
9 | },
10 | app: [
11 | 'src/**/*.js'
12 | ],
13 | grunt: [
14 | 'grunt/**/*.js'
15 | ],
16 | tests: [
17 | 'test/**/*.js'
18 | ]
19 | };
20 |
--------------------------------------------------------------------------------
/CHANGELOG:
--------------------------------------------------------------------------------
1 | # CHANGELOG
2 |
3 | ## 0.3.6
4 |
5 | - BEHAVIOR CHANGE: SGP no longer automatically saves any changes to advanced
6 | options whenever you generate a password. There is a new "save" button,
7 | available when you are viewing the advanced options, that allows you to
8 | manually save changes to advanced options.
9 |
10 | - Added hover text (title attributes) to some elements to provide functional
11 | explanations.
12 |
--------------------------------------------------------------------------------
/grunt/tasks/aliases.js:
--------------------------------------------------------------------------------
1 | /* Grunt task aliases */
2 |
3 | 'use strict';
4 |
5 | module.exports = function (grunt) {
6 |
7 | grunt.registerTask(
8 | 'default',
9 | [
10 | 'jshint',
11 | 'browserify',
12 | 'uglify',
13 | 'cssmin',
14 | 'staticinline',
15 | 'bookmarklet',
16 | 'template',
17 | 'clean',
18 | 'qunit',
19 | 'manifest',
20 | 'checksum'
21 | ]
22 | );
23 |
24 | };
25 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | node_js:
4 | - "5"
5 | before_install: npm install -g grunt-cli
6 | env:
7 | global:
8 | - secure: EpYkviZ1vRrXesZp221s0boZnhaQCLNcj0nX0p8shLH9wgR94Alc57THAp5g+1n3rziY/FrezI5jSr8J+6u3lNqOdkfsZ+zvzBQAOgtCKg7o8I4b0o0C/xLg1+dxoVTjEdXZhi1QskHzrNBvtZraSX6fW+XTaT3/hi5lwnpq04k=
9 | - secure: U1xFgm37PbYC9H8g4J9uWSHHxqvv/0AluL+4muJ6H9d8HaQV34WUTR6iRBusWn34Zc5m/aVeCreMInobknbM/9El66Q5B3tMHj7qMudrVGAyzrC6oaTYp4RtJexQ3XFJ0qCe79/wHWcJ+NWkIzToX5renuuME7ULMIzcPqDGIPg=
10 |
--------------------------------------------------------------------------------
/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | 404 Not Found
12 |
13 |
14 |
15 |
16 |
17 | Page not found
18 |
19 | The requested page was not found. Sorry about that! You can probably find what you need on the homepage .
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/test/qunit/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | SGP QUnit tests
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "SuperGenPass",
3 | "version": "0.3.11",
4 | "dependencies": {
5 | "crypto-js": "^3.1.6",
6 | "jquery": "^3.1.0",
7 | "supergenpass-lib": "^3.0.1"
8 | },
9 | "devDependencies": {
10 | "bookmarkleter": "^0.2.2",
11 | "browserify": "^13.1.0",
12 | "grunt": "^1.0.1",
13 | "grunt-browserify": "^5.0.0",
14 | "grunt-contrib-clean": "^1.0.0",
15 | "grunt-contrib-concat": "^1.0.1",
16 | "grunt-contrib-cssmin": "^1.0.1",
17 | "grunt-contrib-jshint": "^1.0.0",
18 | "grunt-contrib-qunit": "^1.2.0",
19 | "grunt-contrib-uglify": "^2.0.0",
20 | "grunt-load-options": "^0.2.1",
21 | "grunt-manifest": "^0.4.4",
22 | "grunt-static-inline": "^0.1.9",
23 | "grunt-template": "^1.0.0",
24 | "intern": "^3.2.3",
25 | "load-grunt-tasks": "^3.5.0",
26 | "qunitjs": "^2.0.1"
27 | },
28 | "description": "A bookmarklet password generator.",
29 | "homepage": "https://chriszarate.github.io/supergenpass/",
30 | "main": "Gruntfile.js",
31 | "scripts": {
32 | "test": "grunt qunit && [ \"$TRAVIS_PULL_REQUEST\" != \"true\" ] && node_modules/.bin/intern-runner config=test/intern/main"
33 | },
34 | "repository": {
35 | "type": "git",
36 | "url": "https://github.com/chriszarate/supergenpass"
37 | },
38 | "author": {
39 | "name": "Chris Zarate",
40 | "url": "http://chris.zarate.org"
41 | },
42 | "license": "GPL-2.0"
43 | }
44 |
--------------------------------------------------------------------------------
/grunt/tasks/bookmarklet.js:
--------------------------------------------------------------------------------
1 | /* Bookmarklet task */
2 |
3 | 'use strict';
4 |
5 | var bookmarkleter = require('bookmarkleter');
6 |
7 | module.exports = function (grunt) {
8 |
9 | var description = 'Generate a bookmarklet from JavaScript code.';
10 |
11 | grunt.registerMultiTask('bookmarklet', description, function () {
12 |
13 | // Merge task-specific and/or target-specific options with these defaults.
14 | var options = this.options({
15 | anonymize: true,
16 | urlencode: true,
17 | mangleVars: true,
18 | jQuery: false
19 | });
20 |
21 | // File exists helper.
22 | var fileExists = function (filepath) {
23 | if (grunt.file.exists(filepath)) {
24 | return true;
25 | } else {
26 | grunt.log.warn('Source file "' + filepath + '" not found.');
27 | return false;
28 | }
29 | };
30 |
31 | // Read and return the file's source.
32 | var readFile = function (filepath) {
33 | return (fileExists(filepath)) ? grunt.file.read(filepath) : '';
34 | };
35 |
36 | this.files.forEach(function (file) {
37 |
38 | // Load files and create bookmarklet.
39 | var contents = file.src.map(readFile).join('');
40 | var bookmarklet = bookmarkleter(contents, options);
41 |
42 | // Write joined contents to destination filepath.
43 | grunt.file.write(file.dest, bookmarklet);
44 | grunt.log.writeln('Bookmarklet: ' + file.dest);
45 |
46 | });
47 |
48 | });
49 |
50 | };
51 |
--------------------------------------------------------------------------------
/.jscsrc:
--------------------------------------------------------------------------------
1 | {
2 | "disallowEmptyBlocks": true,
3 | "disallowSpacesInsideArrayBrackets": true,
4 | "disallowSpacesInsideParentheses": true,
5 | "disallowSpaceAfterObjectKeys": true,
6 | "disallowSpaceAfterPrefixUnaryOperators": true,
7 | "disallowSpaceBeforePostfixUnaryOperators": true,
8 | "disallowSpaceBeforeBinaryOperators": [
9 | ","
10 | ],
11 | "disallowMixedSpacesAndTabs": true,
12 | "disallowTrailingWhitespace": true,
13 | "disallowTrailingComma": true,
14 | "disallowYodaConditions": true,
15 | "disallowKeywords": [ "with" ],
16 | "disallowMultipleVarDecl": true,
17 | "requireSpaceBeforeBlockStatements": true,
18 | "requireParenthesesAroundIIFE": true,
19 | "requireSpacesInConditionalExpression": true,
20 | "requireBlocksOnNewline": 1,
21 | "requireCommaBeforeLineBreak": true,
22 | "requireSpaceBeforeBinaryOperators": true,
23 | "requireSpaceAfterBinaryOperators": true,
24 | "requireCamelCaseOrUpperCaseIdentifiers": true,
25 | "requireLineFeedAtFileEnd": true,
26 | "requireCapitalizedConstructors": true,
27 | "requireDotNotation": true,
28 | "requireSpacesInForStatement": true,
29 | "requireSpaceBetweenArguments": true,
30 | "requireCurlyBraces": [
31 | "function",
32 | "if",
33 | "else",
34 | "for",
35 | "while",
36 | "do",
37 | "switch",
38 | "try",
39 | "catch"
40 | ],
41 | "requireSpaceAfterKeywords": [
42 | "function",
43 | "if",
44 | "else",
45 | "for",
46 | "while",
47 | "do",
48 | "switch",
49 | "case",
50 | "return",
51 | "try",
52 | "catch",
53 | "typeof"
54 | ],
55 | "validateLineBreaks": "LF",
56 | "validateQuoteMarks": "'",
57 | "validateIndentation": 2
58 | }
59 |
--------------------------------------------------------------------------------
/test/intern/main.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /*global define*/
4 |
5 | // Learn more about configuring this file at .
6 | // These default settings work OK for most people. The options that *must* be changed below are the
7 | // packages, suites, excludeInstrumentation, and (if you want functional tests) functionalSuites.
8 | define({
9 | // The port on which the instrumenting proxy will listen
10 | proxyPort: 9000,
11 |
12 | // A fully qualified URL to the Intern proxy
13 | proxyUrl: 'http://localhost:9000/',
14 |
15 | // Browsers to run integration testing against. Note that version numbers must be strings if used with Sauce
16 | // OnDemand. Options that will be permutated are browserName, version, platform, and platformVersion; any other
17 | // capabilities options specified for an environment will be copied as-is
18 | environments: [
19 | { browserName: 'android', version: '4.4', platform: 'Linux' },
20 | { browserName: 'iphone', version: '9.2', platform: 'OS X 10.10' },
21 | { browserName: 'internet explorer', version: '11.0', platform: 'Windows 7', requireWindowFocus: 'true' },
22 | { browserName: 'firefox', version: '45.0', platform: 'Windows 7' },
23 | { browserName: 'chrome', version: '50.0', platform: 'Windows 7' },
24 | { browserName: 'safari', version: '8.0', platform: 'OS X 10.10' }
25 | ],
26 |
27 | // Maximum number of simultaneous integration tests that should be executed on the remote WebDriver service
28 | maxConcurrency: 2,
29 |
30 | // Name of the tunnel class to use for WebDriver tests
31 | tunnel: 'SauceLabsTunnel',
32 |
33 | // Functional test suite(s) to run in each browser once non-functional tests are completed
34 | functionalSuites: ['test/intern/functional/mobile'],
35 |
36 | // A regular expression matching URLs to files that should not be included in code coverage analysis
37 | excludeInstrumentation: /^bower_components|node_modules\//
38 | });
39 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 |
3 | "maxerr" : 50,
4 |
5 | "bitwise" : true,
6 | "camelcase" : true,
7 | "curly" : true,
8 | "eqeqeq" : true,
9 | "forin" : true,
10 | "freeze" : true,
11 | "immed" : true,
12 | "indent" : 2,
13 | "latedef" : true,
14 | "newcap" : true,
15 | "noarg" : true,
16 | "noempty" : true,
17 | "nonbsp" : true,
18 | "nonew" : true,
19 | "plusplus" : true,
20 | "quotmark" : true,
21 |
22 | "undef" : true,
23 | "unused" : true,
24 | "strict" : true,
25 | "maxparams" : false,
26 | "maxdepth" : false,
27 | "maxstatements" : false,
28 | "maxcomplexity" : false,
29 | "maxlen" : false,
30 |
31 | "asi" : false,
32 | "boss" : false,
33 | "debug" : false,
34 | "eqnull" : false,
35 | "es5" : false,
36 | "esnext" : false,
37 | "moz" : false,
38 |
39 | "evil" : false,
40 | "expr" : false,
41 | "funcscope" : false,
42 | "globalstrict" : false,
43 | "iterator" : false,
44 | "lastsemic" : false,
45 | "laxbreak" : false,
46 | "laxcomma" : false,
47 | "loopfunc" : false,
48 | "multistr" : false,
49 | "noyield" : false,
50 | "notypeof" : false,
51 | "proto" : false,
52 | "scripturl" : false,
53 | "shadow" : false,
54 | "sub" : false,
55 | "supernew" : false,
56 | "validthis" : false,
57 |
58 | "browser" : false,
59 | "couch" : false,
60 | "devel" : false,
61 | "dojo" : false,
62 | "jasmine" : false,
63 | "jquery" : false,
64 | "mocha" : false,
65 | "mootools" : false,
66 | "node" : true,
67 | "nonstandard" : false,
68 | "prototypejs" : false,
69 | "qunit" : false,
70 | "rhino" : false,
71 | "shelljs" : false,
72 | "worker" : false,
73 | "wsh" : false,
74 | "yui" : false,
75 |
76 | "globals" : {}
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/grunt/tasks/checksum.js:
--------------------------------------------------------------------------------
1 | /* Checksum task */
2 |
3 | 'use strict';
4 |
5 | module.exports = function (grunt) {
6 |
7 | var description = 'Generate checksums for specified files.';
8 |
9 | grunt.registerMultiTask('checksum', description, function () {
10 |
11 | var done = this.async();
12 | var crypto = require('crypto');
13 | var fs = require('fs');
14 |
15 | // Merge task-specific and/or target-specific options with these defaults.
16 | var options = this.options({
17 | algorithm: 'sha512',
18 | basepath: false
19 | });
20 |
21 | // Placeholder for checksums.
22 | var result = {};
23 |
24 | // Task counter.
25 | var tasks = this.files.length;
26 |
27 | // Check for basepath.
28 | var removeBasepath = function (filepath) {
29 | return (filepath.substring(0, options.basepath.length) === options.basepath) ? filepath.substring(options.basepath.length) : filepath;
30 | };
31 |
32 | // Generate checksum.
33 | var checkSum = function (filepath, dest) {
34 | var shasum = crypto.createHash(options.algorithm);
35 | var stream = fs.ReadStream(filepath);
36 | stream.on('data', function (data) {
37 | shasum.update(data);
38 | });
39 | stream.on('end', function () {
40 | var sum = shasum.digest('hex');
41 | writeSum(removeBasepath(filepath), dest, sum);
42 | });
43 | };
44 |
45 | // Write to checksum file.
46 | var writeSum = function (src, dest, sum) {
47 | result[dest].sums[src] = sum;
48 | if (Object.keys(result[dest].sums).length === result[dest].count) {
49 | grunt.file.write(dest, JSON.stringify(result[dest].sums));
50 | grunt.log.writeln('Checksums: ' + dest);
51 | if (!tasks) {
52 | done();
53 | }
54 | tasks = tasks - 1;
55 | }
56 | };
57 |
58 | // Process files.
59 | this.files.forEach(function (file) {
60 |
61 | // Stash checksum destination in scope.
62 | var dest = file.dest;
63 |
64 | // Create a placeholder record for this checksum task.
65 | result[dest] = {
66 | count: file.src.length,
67 | sums: {}
68 | };
69 |
70 | // Loop through each source file.
71 | file.src.forEach(function (src) {
72 | checkSum(src, dest);
73 | });
74 |
75 | });
76 |
77 | });
78 |
79 | };
80 |
--------------------------------------------------------------------------------
/src/homepage/index.html.tmpl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | SuperGenPass: A Free Bookmarklet Password Generator
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | SuperGenPass is a different kind of password solution. Instead of storing your passwords on your hard disk or online—where they are vulnerable to theft and data loss—SuperGenPass uses a hash algorithm to transform a master password into unique, complex passwords for the Web sites you visit.
24 |
25 | SuperGenPass is a bookmarklet and runs right in your Web browser. It never stores or transmits your passwords , so it’s ideal for use on multiple and public computers. It’s also completely free and open-sourced on GitHub .
26 |
27 |
28 |
29 |
30 |
SGP
31 |
Drag this to your bookmarks toolbar. (Or right-click and add to bookmarks.)
32 |
33 |
34 |
Mobile
35 |
Use on public computers and mobile devices. (Add it to your home screen!)
36 |
37 |
38 |
39 |
40 |
41 |
42 | Should I use SuperGenPass?
43 | Maybe! Do you like bookmarklets? Do you like not ever knowing what your passwords are? (That’s a good thing!) Do you like the idea of using a slightly quirky password solution? You do ?
44 |
45 | I have a question!
46 | Please take a look at the FAQ on the SGP wiki. If you still have questions, please open an issue on GitHub .
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SuperGenPass
2 |
3 | [![Build status][build-status]][travis-ci]
4 |
5 | [![SauceLabs status][saucelabs-status]][saucelabs]
6 |
7 | This is the official repository of [SuperGenPass][sgp]. It contains code for the
8 | bookmarklet and the mobile version. The underlying algorithm that is used by
9 | SuperGenPass to generate passwords has its own repository:
10 | [chriszarate/supergenpass-lib][sgp-lib].
11 |
12 | Please see the [homepage][sgp] to obtain the latest stable version of the
13 | bookmarklet and the mobile version. For questions, please see the [FAQ][faq].
14 |
15 | ## About
16 |
17 | SuperGenPass is a different kind of password solution. Instead of storing your
18 | passwords on your hard disk or online—where they are vulnerable to theft and
19 | data loss—SuperGenPass uses a hash algorithm to transform a master password
20 | into unique, complex passwords for the Web sites you visit.
21 |
22 | ```
23 | SuperGenPass("masterpassword:example1.com") // => zVNqyKdf7F
24 | SuperGenPass("masterpassword:example2.com") // => eYPtU3mfVw
25 | ```
26 |
27 | SuperGenPass is a bookmarklet and runs right in your Web browser. It *never
28 | stores or transmits your passwords*, so it’s ideal for use on multiple and
29 | public computers. It’s also completely free and open source.
30 |
31 | ## Should I use SuperGenPass?
32 |
33 | Maybe! Do you like bookmarklets? Do you like *not knowing* what your passwords
34 | are? Do you like the idea of using a slightly quirky password solution? You
35 | *do*?
36 |
37 | ## Develop locally
38 |
39 | SuperGenPass development requires [Grunt][grunt]:
40 |
41 | ```shell
42 | git clone https://github.com/chriszarate/supergenpass.git && cd supergenpass
43 | npm install
44 | grunt
45 | ```
46 |
47 | ## Looking for GenPass?
48 |
49 | GenPass is maintained in its own repository: [chriszarate/genpass][gp].
50 |
51 |
52 | ## License
53 |
54 | SuperGenPass is released under the [GNU General Public License version 2][gplv2].
55 |
56 |
57 | ## Other implementations
58 |
59 | Since SuperGenPass is open-source, others have made their own versions for
60 | various platforms and with additional functionality. You can find a list on the
61 | [implementations wiki page][implementations]. Please note that these projects
62 | are not reviewed and compatibility is not guaranteed.
63 |
64 |
65 | [sgp]: http://supergenpass.com
66 | [sgp-lib]: https://github.com/chriszarate/supergenpass-lib
67 | [build-status]: https://travis-ci.org/chriszarate/supergenpass.svg?branch=master
68 | [travis-ci]: https://travis-ci.org/chriszarate/supergenpass
69 | [saucelabs-status]: https://saucelabs.com/browser-matrix/supergenpass.svg
70 | [saucelabs]: https://saucelabs.com/u/supergenpass
71 | [faq]: https://github.com/chriszarate/supergenpass/wiki/FAQ
72 | [grunt]: http://gruntjs.com
73 | [gp]: https://github.com/chriszarate/genpass
74 | [gplv2]: http://www.gnu.org/licenses/gpl-2.0.html
75 | [implementations]: https://github.com/chriszarate/supergenpass/wiki/Implementations
76 |
--------------------------------------------------------------------------------
/assets/css/style.css:
--------------------------------------------------------------------------------
1 | /* Lato Web font */
2 |
3 | @font-face {
4 | font-family: 'Lato';
5 | font-style: normal;
6 | font-weight: 400;
7 | src: local('Lato Regular'), local('Lato-Regular'), url(https://themes.googleusercontent.com/static/fonts/lato/v6/qIIYRU-oROkIk8vfvxw6QvesZW2xOQ-xsNqO47m55DA.woff) format('woff');
8 | }
9 |
10 | @font-face {
11 | font-family: 'Lato';
12 | font-style: normal;
13 | font-weight: 700;
14 | src: local('Lato Bold'), local('Lato-Bold'), url(https://themes.googleusercontent.com/static/fonts/lato/v6/qdgUG4U09HnJwhYI-uK18wLUuEpTyoUstqEm5AMlJo4.woff) format('woff');
15 | }
16 |
17 |
18 | /* Document */
19 |
20 | body {
21 | max-width: 35em;
22 | margin: 0 auto 1em auto;
23 | padding: 2em 3em;
24 | padding-top: 0;
25 | font-family: 'Lato', Helvetica, Arial, sans-serif;
26 | font-size: 1.3em;
27 | background-color: #fff;
28 | line-height: 1.5em;
29 | }
30 |
31 | header {
32 | text-align: center;
33 | }
34 |
35 | h1, h2, h3 {
36 | margin: 2em 0 1em 0;
37 | line-height: 1.25em;
38 | }
39 |
40 | h1 {
41 | margin-top: 1.5em;
42 | }
43 |
44 | h4 {
45 | margin: 0.5em 0 0 0;
46 | }
47 |
48 | a {
49 | color: #369;
50 | font-weight: bold;
51 | text-decoration: none;
52 | }
53 |
54 | a:hover {
55 | text-decoration: underline;
56 | }
57 |
58 |
59 | /* Actions */
60 |
61 | #actions {
62 | margin: 2em 0;
63 | padding: 1em 0;
64 | border-top: solid 1px #ccc;
65 | border-bottom: solid 1px #ccc;
66 | }
67 |
68 | .well {
69 | width: 45%;
70 | }
71 |
72 | .bookmarklet {
73 | float: left;
74 | }
75 |
76 | .mobile {
77 | float: right;
78 | }
79 |
80 | .well p {
81 | color: #666;
82 | font-size: 0.9em;
83 | text-align: center;
84 | }
85 |
86 | .well a {
87 | display: inline-block;
88 | width: 6em;
89 | padding: 8px 0;
90 | color: #fff;
91 | background-color: #356;
92 | font-weight: bold;
93 | border-bottom: solid 2px #023;
94 | border-radius: 4px;
95 | }
96 |
97 | .well a:hover {
98 | text-decoration: none;
99 | background-color: #467;
100 | border-bottom: solid 2px #134;
101 | }
102 |
103 |
104 | /* Code */
105 |
106 | pre {
107 | float: left;
108 | margin: 0;
109 | padding: 0.25em 0.75em;
110 | font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
111 | font-size: 1em;
112 | line-height: 1.2em;
113 | color: #666;
114 | background-color: #f7f7f9;
115 | border: 1px solid #e1e1e8;
116 | }
117 |
118 | strong.function {
119 | color: #333;
120 | }
121 |
122 | span.string {
123 | color: #690;
124 | }
125 |
126 | span.comment {
127 | color: #708090;
128 | }
129 |
130 |
131 | /* Tablet-y */
132 |
133 | @media only screen and (max-width: 770px) {
134 |
135 | /* Tighten */
136 |
137 | body {
138 | padding: 0 2em 2em 2em;
139 | }
140 |
141 | h1, h2, h3 {
142 | margin: 1.5em 0 0.75em 0;
143 | }
144 |
145 | h1 {
146 | margin-top: 1em;
147 | }
148 |
149 | pre {
150 | font-size: 0.8em;
151 | }
152 |
153 | header img {
154 | width: 150px;
155 | height: 100px;
156 | }
157 |
158 | }
159 |
160 |
161 | /* Phone-y */
162 |
163 | @media only screen and (max-width: 550px) {
164 |
165 | /* Tighten */
166 |
167 | body {
168 | margin: 0;
169 | padding: 0 1em 1em 1em;
170 | font-size: 1.1em;
171 | }
172 |
173 | }
174 |
--------------------------------------------------------------------------------
/bookmarklet/bookmarklet.min.js:
--------------------------------------------------------------------------------
1 | javascript:%22use%20strict%22;void%20function(e){var%20t=20150216,n=%22https://chriszarate.github.io%22,o=%22https://chriszarate.github.io/supergenpass/mobile/%22,i=1e5,a=2,s=%22SGP%20may%20be%20blocked%20on%20this%20site%20by%20a%20security%20setting.%22,r=%22Would%20you%20like%20to%20open%20the%20mobile%20version%3F%22,c='You%20may%20wish%20to%20%3Ca%20href=%22'+o+'%22%20target=%22_blank%22%3Eopen%20the%20mobile%20version%3C/a%3E.',d=function(){var%20e=confirm(s+%22%20%22+r);e%26%26window.open(o)},p=function(e){var%20r=e(document),p=r,u=!1,f=0,l=function(){try{var%20e=%22_%22+(new%20Date).getTime(),t=this.contentWindow;if(t[e]=e,t[e]===e)return%20p.add(t.document),!0}catch(n){return!1}},h=function(){try{var%20t=e(this).height()*e(this).width();t%3Ef%26%26t%3Ei%26%26(r=e(this.contentWindow.document),f=t)}catch(n){}},g=function(){D.remove()},m=function(){D.html(s+%22%20%22+c)},v=function(){try{this.contentWindow.postMessage('{%22version%22:'+t+%22}%22,n)}catch(e){m()}},b=function(e){var%20t=e.originalEvent;t.origin===n%26%26%22undefined%22!=typeof%20t.data%26%26(g(),clearTimeout(M),y(JSON.parse(t.data)))},y=function(t){e.each(t,function(e,t){switch(e){case%22result%22:x(t);break;case%22height%22:w(Math.max(parseInt(t,10),167)+2)}})},x=function(t){e(%22input:password:visible%22,p).css(%22background%22,%22%239f9%22).val(t).trigger(%22change%20click%22).on(%22input%22,k).focus()},w=function(e){G.css(%22height%22,e)},k=function(){e(this).css(%22background%22,%22%23fff%22)},j=function(){L.remove()},z=%22font-family:sans-serif;font-size:18px;line-height:20px;%22,E=%22z-index:99999;position:absolute;top:0;right:5px;width:258px;margin:0;padding:0;box-sizing:content-box;%22+z,T=%22overflow:hidden;width:258px;height:20px;margin:0;padding:0;text-align:right;background-color:%23356;cursor:move;box-sizing:content-box;%22+z,S=%22padding:0%205px;color:%23fff;cursor:pointer;%22+z,W=%22position:absolute;width:258px;height:190px;padding:15px;color:%23333;background-color:%23fff;font-family:monospace;font-size:15px;text-align:center;%22,q=%22position:static;width:258px;height:190px;border:none;overflow:hidden;pointer-events:auto;%22,L=e(%22%3Cdiv/%3E%22,{style:E}),Y=e(%22%3Cdiv/%3E%22,{style:T}),C=e(%22%3Cspan/%3E%22,{style:S}).append(%22×%22),D=e(%22%3Cdiv/%3E%22,{style:W}).append(%22Loading%20SGP%20...%22),G=e(%22%3Ciframe/%3E%22,{src:o,scrolling:%22no%22,style:q});e(%22frame%22).filter(l).each(h),e(%22iframe%22,r).filter(l).each(h),r||d();var%20M=setTimeout(m,1e3*a);C.on(%22click%22,j),Y.on(%22dblclick%22,j),L.css(%22top%22,r.scrollTop()+%22px%22),e(document.activeElement).blur(),G.on(%22load%22,v),e(window).on(%22message%22,b),Y.append(C),L.append(Y,D,G).appendTo(e(%22body%22,r)),Y.on({mousedown:function(e){var%20t=L.offset();u=[e.pageX-t.left,e.pageY-t.top],G.css(%22pointer-events%22,%22none%22),e.preventDefault()},mouseup:function(){u=!1,G.css(%22pointer-events%22,%22auto%22)}}),r.on(%22mousemove%22,function(e){u%26%26L.css({left:e.pageX-u[0],top:e.pageY-u[1]})})};if(e%26%26e.fn%26%26parseFloat(e.fn.jquery)%3E=1.7)p(e);else{var%20u=document.createElement(%22script%22);u.src=%22//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js%22,u.onload=u.onreadystatechange=function(){var%20e=this.readyState;e%26%26%22loaded%22!==e%26%26%22complete%22!==e||p(jQuery.noConflict())},u.addEventListener(%22error%22,d),u.addEventListener(%22abort%22,d),document.getElementsByTagName(%22head%22)[0].appendChild(u)}}(window.jQuery);
--------------------------------------------------------------------------------
/test/qunit/mobile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /*jshint browser: true, jquery: true, latedef: false, qunit: true*/
4 |
5 | QUnit.module('SuperGenPass mobile version');
6 |
7 | // Enumerate jQuery selectors for caching.
8 | var $iframe = $('#SGP');
9 | var $el = {};
10 | var selectors =
11 | [
12 | 'Passwd',
13 | 'Secret',
14 | 'Domain',
15 | 'RemoveSubdomains',
16 | 'Len',
17 | 'MethodMD5',
18 | 'MethodSHA512',
19 | 'SaveDefaults',
20 | 'Generate',
21 | 'Output'
22 | ];
23 |
24 | // Clear local storage.
25 | localStorage.clear();
26 |
27 | // Create click event.
28 | var clickEvent = document.createEvent('Event');
29 | clickEvent.initEvent('click', true, true);
30 |
31 | // Populate selector cache.
32 | $.each(selectors, function (i, val) {
33 | $el[val] = $('#' + val, $iframe[0].contentWindow.document);
34 | });
35 |
36 | // Send click event.
37 | var sendClick = function () {
38 | $el.Generate[0].dispatchEvent(clickEvent);
39 | };
40 |
41 |
42 | /* Tests */
43 |
44 | var suite = [
45 | {
46 | name: 'initial input',
47 | setup: function () {
48 | $el.Passwd.val('test');
49 | $el.Secret.val('secret');
50 | $el.Domain.val('https://login.example.com');
51 | $el.Len.val('10');
52 | $el.MethodMD5.prop('checked', true);
53 | $el.RemoveSubdomains.prop('checked', true);
54 | },
55 | expect: 'iTbF7RViG5'
56 | },
57 | {
58 | name: 'remove secret',
59 | setup: function () {
60 | $el.Secret.val('');
61 | },
62 | expect: 'w9UbG0NEk7'
63 | },
64 | {
65 | name: 'using SHA512',
66 | setup: function () {
67 | $el.MethodSHA512.prop('checked', true);
68 | },
69 | expect: 'sJfoZg3nU8'
70 | },
71 | {
72 | name: 'change length to 2',
73 | setup: function () {
74 | $el.Len.val('2');
75 | },
76 | expect: 'aC81'
77 | },
78 | {
79 | name: 'change secret and length to 100',
80 | setup: function () {
81 | $el.Len.val('100');
82 | $el.Secret.val('ssshh');
83 | },
84 | expect: 'fd35Ng0Xwne2Pb8f3XFu8r8y'
85 | },
86 | {
87 | name: 'change domain and subdomain removal',
88 | setup: function () {
89 | $el.Domain.val('https://login.example.com');
90 | $el.RemoveSubdomains.prop('checked', false);
91 | },
92 | expect: 'alrcP2cLv1lDddHXjExlS0H9'
93 | },
94 | {
95 | name: 'check local storage',
96 | setup: function () {
97 | $el.SaveDefaults[0].dispatchEvent(clickEvent);
98 | },
99 | expect: function (assert, done) {
100 | assert.ok(localStorage.getItem('Len') === '24', 'Password length value stored.');
101 | assert.ok(localStorage.getItem('Salt') === 'ssshh', 'Secret password value stored.');
102 | assert.ok(localStorage.getItem('Method') === 'sha512', 'Hash method setting stored.');
103 | assert.ok(localStorage.getItem('DisableTLD') === 'true', 'Subdomain removal setting stored.');
104 | done();
105 | }
106 | }
107 | ];
108 |
109 | suite.forEach(function (test) {
110 | QUnit.test(test.name, function (assert) {
111 | var done = assert.async();
112 | test.setup();
113 |
114 | sendClick();
115 |
116 | if (typeof test.expect === 'function') {
117 | test.expect(assert, done);
118 | return;
119 | }
120 |
121 | setTimeout(function () {
122 | assert.equal($el.Output.text(), test.expect);
123 | done();
124 | }, 100);
125 | });
126 | });
127 |
--------------------------------------------------------------------------------
/src/mobile/lib/localstorage-polyfill.js:
--------------------------------------------------------------------------------
1 | /*
2 | * LocalStorage polyfill
3 | * Based on https://gist.github.com/remy/350433 by Remy Sharp
4 | * Improved by Chris Zarate
5 | */
6 |
7 |
8 | 'use strict';
9 |
10 | /* Wrap storage API tests in try/catch to avoid browser errors. */
11 |
12 | var LocalStorage, SessionStorage;
13 |
14 | try {
15 | LocalStorage = window.localStorage;
16 | LocalStorage.setItem('TEST', '1');
17 | LocalStorage.removeItem('TEST');
18 | } catch(e) {
19 | LocalStorage = false;
20 | }
21 |
22 | try {
23 | SessionStorage = window.sessionStorage;
24 | SessionStorage.setItem('TEST', '1');
25 | SessionStorage.removeItem('TEST');
26 | } catch(e) {
27 | SessionStorage = false;
28 | }
29 |
30 | /* Provide polyfill if native support is not available. */
31 |
32 | if(!LocalStorage || !SessionStorage) {
33 |
34 | var Storage = function (type) {
35 | function createCookie(name, value, days) {
36 | var date, expires;
37 |
38 | if (days) {
39 | date = new Date();
40 | date.setTime(date.getTime()+(days*24*60*60*1000));
41 | expires = "; expires="+date.toGMTString();
42 | } else {
43 | expires = "";
44 | }
45 | document.cookie = name+"="+value+expires+"; path=/";
46 | }
47 |
48 | function readCookie(name) {
49 | var nameEQ = name + "=",
50 | ca = document.cookie.split(';'),
51 | i, c;
52 |
53 | for (i=0; i < ca.length; i++) {
54 | c = ca[i];
55 | while (c.charAt(0) === ' ') {
56 | c = c.substring(1,c.length);
57 | }
58 |
59 | if (c.indexOf(nameEQ) === 0) {
60 | return c.substring(nameEQ.length,c.length);
61 | }
62 | }
63 | return null;
64 | }
65 |
66 | function setData(data) {
67 | data = JSON.stringify(data);
68 | if (type === 'session') {
69 | window.name = data;
70 | } else {
71 | createCookie('localStorage', data, 365);
72 | }
73 | }
74 |
75 | function clearData() {
76 | if (type === 'session') {
77 | window.name = '';
78 | } else {
79 | createCookie('localStorage', '', 365);
80 | }
81 | }
82 |
83 | function getData() {
84 | var data = type === 'session' ? window.name : readCookie('localStorage');
85 | return data ? JSON.parse(data) : {};
86 | }
87 |
88 |
89 | // initialise if there's already data
90 | var data = getData();
91 |
92 | return {
93 | length: 0,
94 | clear: function () {
95 | data = {};
96 | this.length = 0;
97 | clearData();
98 | },
99 | getItem: function (key) {
100 | return data[key] === undefined ? null : data[key];
101 | },
102 | key: function (i) {
103 | // not perfect, but works
104 | var ctr = 0;
105 | for (var k in data) {
106 | if (ctr === i) return k;
107 | else ctr++;
108 | }
109 | return null;
110 | },
111 | removeItem: function (key) {
112 | delete data[key];
113 | this.length--;
114 | setData(data);
115 | },
116 | setItem: function (key, value) {
117 | data[key] = value+''; // forces the value to a string
118 | this.length++;
119 | setData(data);
120 | }
121 | };
122 | };
123 |
124 | /*
125 | First try to provide polyfill on the window object. But remember that
126 | the browser's security policy may be standing in the way even when
127 | native support is available. So fall back to an imposter object.
128 | */
129 |
130 | try {
131 | if(!LocalStorage) {
132 | LocalStorage = window.localStorage = new Storage('local');
133 | }
134 | } catch(e) {
135 | LocalStorage = new Storage('local');
136 | }
137 |
138 | try {
139 | if(!SessionStorage) {
140 | SessionStorage = window.sessionStorage = new Storage('session');
141 | }
142 | } catch(e) {
143 | SessionStorage = new Storage('session');
144 | }
145 |
146 | }
147 |
148 | /* Exports */
149 |
150 | module.exports = {
151 | local: LocalStorage,
152 | session: SessionStorage
153 | };
154 |
--------------------------------------------------------------------------------
/src/mobile/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | SGP
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | SGP
42 |
43 |
44 |
45 |
46 |
Requires JavaScript!
47 |
48 |
49 |
50 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | SuperGenPass: A Free Bookmarklet Password Generator
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | SuperGenPass is a different kind of password solution. Instead of storing your passwords on your hard disk or online—where they are vulnerable to theft and data loss—SuperGenPass uses a hash algorithm to transform a master password into unique, complex passwords for the Web sites you visit.
24 |
25 | SuperGenPass is a bookmarklet and runs right in your Web browser. It never stores or transmits your passwords , so it’s ideal for use on multiple and public computers. It’s also completely free and open-sourced on GitHub .
26 |
27 |
28 |
29 |
30 |
SGP
31 |
Drag this to your bookmarks toolbar. (Or right-click and add to bookmarks.)
32 |
33 |
34 |
Mobile
35 |
Use on public computers and mobile devices. (Add it to your home screen!)
36 |
37 |
38 |
39 |
40 |
41 |
42 | Should I use SuperGenPass?
43 | Maybe! Do you like bookmarklets? Do you like not ever knowing what your passwords are? (That’s a good thing!) Do you like the idea of using a slightly quirky password solution? You do ?
44 |
45 | I have a question!
46 | Please take a look at the FAQ on the SGP wiki. If you still have questions, please open an issue on GitHub .
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/test/intern/functional/mobile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /*global define*/
4 |
5 | define([
6 | 'intern!object',
7 | 'intern/chai!assert',
8 | 'require'
9 | ], function (registerSuite, assert, require) {
10 |
11 | var url = '../../../mobile/index.html';
12 |
13 | registerSuite({
14 |
15 | name: 'SuperGenPass mobile version',
16 |
17 | 'load the mobile version': function () {
18 | return this.remote
19 | .get(require.toUrl(url))
20 | .findById('Generate').isDisplayed()
21 | .then(function (isDisplayed) {
22 | assert.ok(isDisplayed, 'Mobile version should display a generate button.');
23 | });
24 | },
25 |
26 | 'click advanced options': function () {
27 | return this.remote
28 | .findById('Options').click().end()
29 | .findById('Secret').isDisplayed()
30 | .then(function (isDisplayed) {
31 | assert.ok(isDisplayed, 'Advanced options should be visible.');
32 | });
33 | },
34 |
35 | 'enter master password': function () {
36 | var input = 'test';
37 | return this.remote
38 | .findById('Passwd').clearValue().click().pressKeys(input)
39 | .getProperty('value')
40 | .then(function (value) {
41 | assert.ok(value === input, 'User should be able to type a master password.');
42 | });
43 | },
44 |
45 | 'enter secret password': function () {
46 | var input = 'secret';
47 | return this.remote
48 | .findById('Secret').clearValue().click().pressKeys(input)
49 | .getProperty('value')
50 | .then(function (value) {
51 | assert.ok(value === input, 'User should be able to type a secret password.');
52 | });
53 | },
54 |
55 | 'enter domain': function () {
56 | var input = 'login.example.com';
57 | return this.remote
58 | .findById('Domain').clearValue().click().pressKeys(input)
59 | .getProperty('value')
60 | .then(function (value) {
61 | assert.ok(value === input, 'User should be able to type a domain.');
62 | });
63 | },
64 |
65 | 'enter length': function () {
66 | var input = '12';
67 | return this.remote
68 | .findById('Len').clearValue().click().pressKeys(input)
69 | .getProperty('value')
70 | .then(function (value) {
71 | assert.ok(value === input, 'User should be able to type a length.');
72 | });
73 | },
74 |
75 | 'set the hash method': function () {
76 | return this.remote
77 | .findByCssSelector('label[for="MethodSHA512"]').click().end()
78 | .findById('MethodSHA512').getProperty('checked')
79 | .then(function (isChecked) {
80 | assert.ok(isChecked, 'User should be able to set the hash method.');
81 | });
82 | },
83 |
84 | 'submit the form': function () {
85 | return this.remote
86 | .findById('Generate').click().end()
87 | .findById('MaskText').setFindTimeout(100).getVisibleText()
88 | .then(function (value) {
89 | assert.ok(value === '**************', 'User should be able to generate a password.');
90 | });
91 | },
92 |
93 | 'show the password': function () {
94 | return this.remote
95 | .findById('MaskText').click().end()
96 | .findById('Output').getVisibleText()
97 | .then(function (value) {
98 | assert.ok(value === 'cLZ1vg6U98L3', 'User should be able to retrieve the generated password.');
99 | });
100 | },
101 |
102 | 'remove the secret password': function () {
103 | return this.remote
104 | .findById('Secret').clearValue().end()
105 | .findById('Generate').isDisplayed()
106 | .then(function (isDisplayed) {
107 | assert.ok(isDisplayed, 'Changing a value should reset the form.');
108 | });
109 | },
110 |
111 | 'resubmit the form': function () {
112 | return this.remote
113 | .findById('Generate').click().end()
114 | .findById('MaskText').setFindTimeout(100).click().end()
115 | .findById('Output').getVisibleText()
116 | .then(function (value) {
117 | assert.ok(value === 'sJfoZg3nU8y3', 'Removing the secret password should change the generated password.');
118 | });
119 | },
120 |
121 | 'change the hash method': function () {
122 | return this.remote
123 | .findByCssSelector('label[for="MethodMD5"]').click().end()
124 | .findById('Generate').click().end()
125 | .findById('MaskText').setFindTimeout(100).click().end()
126 | .findById('Output').getVisibleText()
127 | .then(function (value) {
128 | assert.ok(value === 'vBKDNdjhhL6d', 'Changing the hash method should change the generated password.');
129 | });
130 | },
131 |
132 | 'change the length': function () {
133 | return this.remote
134 | .findById('Down').click().click().end()
135 | .findById('Generate').click().end()
136 | .findById('MaskText').setFindTimeout(100).click().end()
137 | .findById('Output').getVisibleText()
138 | .then(function (value) {
139 | assert.ok(value === 'w9UbG0NEk7', 'Changing the password length with the mouse should change the generated password.');
140 | });
141 | },
142 |
143 | 'change the length again': function () {
144 | return this.remote
145 | .findById('Len').clearValue().click().pressKeys('24').end()
146 | .findById('Generate').click().end()
147 | .findById('MaskText').setFindTimeout(100).click().end()
148 | .findById('Output').getVisibleText()
149 | .then(function (value) {
150 | assert.ok(value === 'vBKDNdjhhL6dBfgDSRxZxAAA', 'Changing the password length again should change the generated password.');
151 | });
152 | },
153 |
154 | 'change the subdomain option': function () {
155 | return this.remote
156 | .findByCssSelector('label[for="RemoveSubdomains"]').click().end()
157 | .findById('Domain').clearValue().click().pressKeys('login.example.com').end()
158 | .findById('Generate').click().end()
159 | .findById('MaskText').setFindTimeout(100).click().end()
160 | .findById('Output').getVisibleText()
161 | .then(function (value) {
162 | assert.ok(value === 'iNerPM9Zu79a8gUIcLzC1QAA', 'Changing the subdomain option should change the generated password.');
163 | });
164 | },
165 |
166 | 'click advanced options again': function () {
167 | return this.remote
168 | .findById('Options').click().end()
169 | .findById('Secret').isDisplayed()
170 | .then(function (isDisplayed) {
171 | assert.ok(!isDisplayed, 'Advanced options should be hidden.');
172 | });
173 | }
174 |
175 | });
176 |
177 | });
178 |
--------------------------------------------------------------------------------
/src/mobile/lib/shortcut.js:
--------------------------------------------------------------------------------
1 | /**
2 | * http://www.openjs.com/scripts/events/keyboard_shortcuts/
3 | * Version : 2.01.B
4 | * By Binny V A
5 | * License : BSD
6 | */
7 |
8 |
9 | 'use strict';
10 |
11 | var shortcut = {
12 | 'all_shortcuts':{},//All the shortcuts are stored in this array
13 | 'add': function(shortcut_combination,callback,opt) {
14 | //Provide a set of default options
15 | var default_options = {
16 | 'type':'keydown',
17 | 'propagate':false,
18 | 'disable_in_input':false,
19 | 'target':document,
20 | 'keycode':false
21 | };
22 | if(!opt) opt = default_options;
23 | else {
24 | for(var dfo in default_options) {
25 | if(typeof opt[dfo] == 'undefined') opt[dfo] = default_options[dfo];
26 | }
27 | }
28 |
29 | var ele = opt.target;
30 | if(typeof opt.target == 'string') ele = document.getElementById(opt.target);
31 | shortcut_combination = shortcut_combination.toLowerCase();
32 |
33 | //The function to be called at keypress
34 | var func = function(e) {
35 | e = e || window.event;
36 |
37 | if(opt.disable_in_input) { //Don't enable shortcut keys in Input, Textarea fields
38 | var element;
39 | if(e.target) element=e.target;
40 | else if(e.srcElement) element=e.srcElement;
41 | if(element.nodeType==3) element=element.parentNode;
42 |
43 | if(element.tagName == 'INPUT' || element.tagName == 'TEXTAREA') return;
44 | }
45 |
46 | //Find Which key is pressed
47 | var code;
48 | if (e.keyCode) code = e.keyCode;
49 | else if (e.which) code = e.which;
50 | var character = String.fromCharCode(code).toLowerCase();
51 |
52 | if(code == 188) character=","; //If the user presses , when the type is onkeydown
53 | if(code == 190) character="."; //If the user presses , when the type is onkeydown
54 |
55 | var keys = shortcut_combination.split("+");
56 | //Key Pressed - counts the number of valid keypresses - if it is same as the number of keys, the shortcut function is invoked
57 | var kp = 0;
58 |
59 | //Work around for stupid Shift key bug created by using lowercase - as a result the shift+num combination was broken
60 | var shift_nums = {
61 | "`":"~",
62 | "1":"!",
63 | "2":"@",
64 | "3":"#",
65 | "4":"$",
66 | "5":"%",
67 | "6":"^",
68 | "7":"&",
69 | "8":"*",
70 | "9":"(",
71 | "0":")",
72 | "-":"_",
73 | "=":"+",
74 | ";":":",
75 | "'":"\"",
76 | ",":"<",
77 | ".":">",
78 | "/":"?",
79 | "\\":"|"
80 | };
81 | //Special Keys - and their codes
82 | var special_keys = {
83 | 'esc':27,
84 | 'escape':27,
85 | 'tab':9,
86 | 'space':32,
87 | 'return':13,
88 | 'enter':13,
89 | 'backspace':8,
90 |
91 | 'scrolllock':145,
92 | 'scroll_lock':145,
93 | 'scroll':145,
94 | 'capslock':20,
95 | 'caps_lock':20,
96 | 'caps':20,
97 | 'numlock':144,
98 | 'num_lock':144,
99 | 'num':144,
100 |
101 | 'pause':19,
102 | 'break':19,
103 |
104 | 'insert':45,
105 | 'home':36,
106 | 'delete':46,
107 | 'end':35,
108 |
109 | 'pageup':33,
110 | 'page_up':33,
111 | 'pu':33,
112 |
113 | 'pagedown':34,
114 | 'page_down':34,
115 | 'pd':34,
116 |
117 | 'left':37,
118 | 'up':38,
119 | 'right':39,
120 | 'down':40,
121 |
122 | 'f1':112,
123 | 'f2':113,
124 | 'f3':114,
125 | 'f4':115,
126 | 'f5':116,
127 | 'f6':117,
128 | 'f7':118,
129 | 'f8':119,
130 | 'f9':120,
131 | 'f10':121,
132 | 'f11':122,
133 | 'f12':123
134 | };
135 |
136 | var modifiers = {
137 | shift: { wanted:false, pressed:false},
138 | ctrl : { wanted:false, pressed:false},
139 | alt : { wanted:false, pressed:false},
140 | meta : { wanted:false, pressed:false} //Meta is Mac specific
141 | };
142 |
143 | if(e.ctrlKey) modifiers.ctrl.pressed = true;
144 | if(e.shiftKey) modifiers.shift.pressed = true;
145 | if(e.altKey) modifiers.alt.pressed = true;
146 | if(e.metaKey) modifiers.meta.pressed = true;
147 |
148 | for(var i=0; i 1) { //If it is a special key
166 | if(special_keys[k] == code) kp++;
167 |
168 | } else if(opt.keycode) {
169 | if(opt.keycode == code) kp++;
170 |
171 | } else { //The special keys did not match
172 | if(character == k) kp++;
173 | else {
174 | if(shift_nums[character] && e.shiftKey) { //Stupid Shift key bug created by using lowercase
175 | character = shift_nums[character];
176 | if(character == k) kp++;
177 | }
178 | }
179 | }
180 | }
181 |
182 | if(kp == keys.length &&
183 | modifiers.ctrl.pressed == modifiers.ctrl.wanted &&
184 | modifiers.shift.pressed == modifiers.shift.wanted &&
185 | modifiers.alt.pressed == modifiers.alt.wanted &&
186 | modifiers.meta.pressed == modifiers.meta.wanted) {
187 | callback(e);
188 |
189 | if(!opt.propagate) { //Stop the event
190 | //e.cancelBubble is supported by IE - this will kill the bubbling process.
191 | e.cancelBubble = true;
192 | e.returnValue = false;
193 |
194 | //e.stopPropagation works in Firefox.
195 | if (e.stopPropagation) {
196 | e.stopPropagation();
197 | e.preventDefault();
198 | }
199 | return false;
200 | }
201 | }
202 | };
203 | this.all_shortcuts[shortcut_combination] = {
204 | 'callback':func,
205 | 'target':ele,
206 | 'event': opt.type
207 | };
208 | //Attach the function with the event
209 | if(ele.addEventListener) ele.addEventListener(opt.type, func, false);
210 | else if(ele.attachEvent) ele.attachEvent('on'+opt.type, func);
211 | else ele['on'+opt.type] = func;
212 | },
213 |
214 | //Remove the shortcut - just specify the shortcut and I will remove the binding
215 | 'remove':function(shortcut_combination) {
216 | shortcut_combination = shortcut_combination.toLowerCase();
217 | var binding = this.all_shortcuts[shortcut_combination];
218 | delete(this.all_shortcuts[shortcut_combination]);
219 | if(!binding) return;
220 | var type = binding.event;
221 | var ele = binding.target;
222 | var callback = binding.callback;
223 |
224 | if(ele.detachEvent) ele.detachEvent('on'+type, callback);
225 | else if(ele.removeEventListener) ele.removeEventListener(type, callback, false);
226 | else ele['on'+type] = false;
227 | }
228 | };
229 |
230 | // Require shim
231 | module.exports = shortcut;
232 |
--------------------------------------------------------------------------------
/src/bookmarklet/sgp.bookmarklet.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /*jshint browser: true, devel: true, jquery: true*/
4 |
5 | void (function ($) {
6 |
7 | // Configuration
8 | var version = 20150216;
9 | var domain = 'https://chriszarate.github.io';
10 | var mobile = 'https://chriszarate.github.io/supergenpass/mobile/';
11 | var minFrameArea = 100000;
12 | var loadTimeout = 2; // seconds
13 |
14 | // Messages
15 | var securityMessage = 'SGP may be blocked on this site by a security setting.';
16 | var offerQuestion = 'Would you like to open the mobile version?';
17 | var offerLink = 'You may wish to open the mobile version .';
18 |
19 | var offerMobileVersion = function () {
20 | var redirect = confirm(securityMessage + ' ' + offerQuestion);
21 | if (redirect) {
22 | window.open(mobile);
23 | }
24 | };
25 |
26 | // Main
27 | var loadSGP = function ($) {
28 |
29 | // Defaults
30 | var $target = $(document);
31 | var $localFrames = $target;
32 | var dragging = false;
33 | var maxArea = 0;
34 |
35 | // Functions
36 |
37 | /*
38 | Determine if frame is local (not cross-origin).
39 | Adapted from answer by Esailija:
40 | http://stackoverflow.com/questions/11872917/
41 | */
42 |
43 | var isLocalFrame = function () {
44 | // Expects frame element as context.
45 | // Try/catch helps avoid XSS freakouts.
46 | try {
47 | var key = '_' + new Date().getTime();
48 | var win = this.contentWindow;
49 | win[key] = key;
50 | if (win[key] === key) {
51 | $localFrames.add(win.document);
52 | return true;
53 | }
54 | }
55 | catch (e) {
56 | return false;
57 | }
58 | };
59 |
60 | var findBiggestFrame = function () {
61 | // Expects frame element as context.
62 | // Try/catch helps avoid XSS freakouts.
63 | try {
64 | var area = $(this).height() * $(this).width();
65 | if (area > maxArea && area > minFrameArea) {
66 | $target = $(this.contentWindow.document);
67 | maxArea = area;
68 | }
69 | }
70 | catch (e) {}
71 | };
72 |
73 | var removeLoadingIndicator = function () {
74 | $loadingIndicator.remove();
75 | };
76 |
77 | var linkMobileVersion = function () {
78 | $loadingIndicator.html(securityMessage + ' ' + offerLink);
79 | };
80 |
81 | var postMessage = function () {
82 | // Send current bookmarklet version to SGP generator. (Also communicates
83 | // current URL and opens channel for response.)
84 | try {
85 | this.contentWindow.postMessage('{"version":' + version + '}', domain);
86 | } catch (e) {
87 | linkMobileVersion();
88 | }
89 | };
90 |
91 | var receiveMessage = function (e) {
92 | var post = e.originalEvent;
93 | if (post.origin === domain && typeof post.data !== 'undefined') {
94 | removeLoadingIndicator();
95 | clearTimeout(loadTimeoutID);
96 | processMessage(JSON.parse(post.data));
97 | }
98 | };
99 |
100 | var processMessage = function (data) {
101 | $.each(data, function (key, value) {
102 | switch (key) {
103 | case 'result':
104 | populatePassword(value);
105 | break;
106 | case 'height':
107 | changeFrameHeight(Math.max(parseInt(value, 10), 167) + 2);
108 | break;
109 | }
110 | });
111 | };
112 |
113 | var populatePassword = function (password) {
114 | // Populate generated password into password fields.
115 | $('input:password:visible', $localFrames)
116 | .css('background', '#9f9')
117 | .val(password)
118 | .trigger('change click')
119 | .on('input', resetPasswordField)
120 | .focus();
121 | };
122 |
123 | var changeFrameHeight = function (height) {
124 | // Change iframe height to match SGP generator document height.
125 | $frame.css('height', height);
126 | };
127 |
128 | var resetPasswordField = function () {
129 | $(this).css('background', '#fff');
130 | };
131 |
132 | var closeWindow = function () {
133 | $box.remove();
134 | };
135 |
136 | // Define CSS properties.
137 | var fontStyle = 'font-family:sans-serif;font-size:18px;line-height:20px;';
138 | var boxStyle = 'z-index:99999;position:absolute;top:0;right:5px;width:258px;margin:0;padding:0;box-sizing:content-box;' + fontStyle;
139 | var titleBarStyle = 'overflow:hidden;width:258px;height:20px;margin:0;padding:0;text-align:right;background-color:#356;cursor:move;box-sizing:content-box;' + fontStyle;
140 | var closeLinkStyle = 'padding:0 5px;color:#fff;cursor:pointer;' + fontStyle;
141 | var loadingIndicatorStyle = 'position:absolute;width:258px;height:190px;padding:15px;color:#333;background-color:#fff;font-family:monospace;font-size:15px;text-align:center;';
142 | var frameStyle = 'position:static;width:258px;height:190px;border:none;overflow:hidden;pointer-events:auto;';
143 |
144 | // Create SGP elements.
145 | var $box = $('
', {style: boxStyle});
146 | var $titleBar = $('
', {style: titleBarStyle});
147 | var $closeLink = $(' ', {style: closeLinkStyle}).append('×');
148 | var $loadingIndicator = $('
', {style: loadingIndicatorStyle}).append('Loading SGP ...');
149 | var $frame = $('', {src: mobile, scrolling: 'no', style: frameStyle});
150 |
151 | // Find largest viewport, looping through frames if applicable.
152 | $('frame').filter(isLocalFrame).each(findBiggestFrame);
153 | $('iframe', $target).filter(isLocalFrame).each(findBiggestFrame);
154 |
155 | // If no target document is found, offer mobile version.
156 | if (!$target) {
157 | offerMobileVersion();
158 | }
159 |
160 | // If SGP doesn't load after a timeout, offer mobile version.
161 | var loadTimeoutID = setTimeout(linkMobileVersion, loadTimeout * 1000);
162 |
163 | // Provide "close window" feature.
164 | $closeLink.on('click', closeWindow);
165 | $titleBar.on('dblclick', closeWindow);
166 |
167 | // Apply scroll offset.
168 | $box.css('top', $target.scrollTop() + 'px');
169 |
170 | // Blur any active form fields.
171 | $(document.activeElement).blur();
172 |
173 | // Attach postMessage listener and responder.
174 | $frame.on('load', postMessage);
175 | $(window).on('message', receiveMessage);
176 |
177 | // Append SGP window to target document.
178 | $titleBar.append($closeLink);
179 | $box.append($titleBar, $loadingIndicator, $frame).appendTo($('body', $target));
180 |
181 | /*
182 | Start drag listener.
183 | Adapted from jQuery console bookmarklet:
184 | http://github.com/jaz303/jquery-console
185 | */
186 |
187 | $titleBar.on({
188 | mousedown: function (e) {
189 | var offset = $box.offset();
190 | dragging = [e.pageX - offset.left, e.pageY - offset.top];
191 | $frame.css('pointer-events', 'none');
192 | e.preventDefault();
193 | },
194 | mouseup: function () {
195 | dragging = false;
196 | $frame.css('pointer-events', 'auto');
197 | }
198 | });
199 |
200 | $target.on('mousemove', function (e) {
201 | if (dragging) {
202 | $box.css({
203 | left: e.pageX - dragging[0],
204 | top: e.pageY - dragging[1]
205 | });
206 | }
207 | });
208 |
209 | };
210 |
211 | /*
212 | Look for jQuery 1.7+ (for ".on") and load it if it can't be found.
213 | Adapted from Paul Irish's method:
214 | http://pastie.org/462639
215 | */
216 |
217 | if ($ && $.fn && parseFloat($.fn.jquery) >= 1.7) {
218 |
219 | loadSGP($);
220 |
221 | } else {
222 |
223 | var s = document.createElement('script');
224 | s.src = '//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js';
225 | s.onload = s.onreadystatechange = function () {
226 | var state = this.readyState;
227 | if (!state || state === 'loaded' || state === 'complete') {
228 | loadSGP(jQuery.noConflict());
229 | }
230 | };
231 | s.addEventListener('error', offerMobileVersion);
232 | s.addEventListener('abort', offerMobileVersion);
233 |
234 | document.getElementsByTagName('head')[0].appendChild(s);
235 |
236 | }
237 |
238 | })(window.jQuery);
239 |
--------------------------------------------------------------------------------
/src/mobile/lib/identicon5.js:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Plugin: Identicon5 v1.0.0
4 | Author: http://FrancisShanahan.com
5 | Description: Draws identicons using HTML5 Canvas.
6 |
7 | Attribution: Based off the PHP implementation of Don Park's original identicon
8 | code for visual representation of MD5 hash values.
9 |
10 | Reference: http://sourceforge.net/projects/identicons/
11 |
12 | */
13 |
14 | /*
15 |
16 | Modified for SuperGenPass:
17 | 1. Do not fallback to Gravatar images (prevents password hashes from appearing in foreign server log).
18 | 2. Require a canvas element to passed as a parameter.
19 | 3. Require a hash to be passed as a parameter.
20 | 4. Add support for high-dpi identicons.
21 | 5. Remove dependency on jQuery.
22 | 6. Remove test function.
23 | 7. Export function for require.
24 |
25 | */
26 |
27 |
28 | 'use strict';
29 |
30 | /* fills a polygon based on a path */
31 | var fillPoly = function (ctx, path) {
32 | if (path.length >= 2) {
33 | ctx.beginPath();
34 | ctx.moveTo(path[0], path[1]);
35 | for (var i = 2; i < path.length; i++) {
36 | ctx.lineTo(path[i], path[i + 1]);
37 | i++;
38 | }
39 | ctx.fill();
40 | }
41 | };
42 |
43 | /* generate sprite for corners and sides */
44 | var getsprite = function (shape, size) {
45 |
46 | switch (shape) {
47 | case 0: // triangle
48 | shape = [0.5, 1, 1, 0, 1, 1];
49 | break;
50 | case 1: // parallelogram
51 | shape = [0.5, 0, 1, 0, 0.5, 1, 0, 1];
52 | break;
53 | case 2: // mouse ears
54 | shape = [0.5, 0, 1, 0, 1, 1, 0.5, 1, 1, 0.5];
55 | break;
56 | case 3: // ribbon
57 | shape = [0, 0.5, 0.5, 0, 1, 0.5, 0.5, 1, 0.5, 0.5];
58 | break;
59 | case 4: // sails
60 | shape = [0, 0.5, 1, 0, 1, 1, 0, 1, 1, 0.5];
61 | break;
62 | case 5: // fins
63 | shape = [1, 0, 1, 1, 0.5, 1, 1, 0.5, 0.5, 0.5];
64 | break;
65 | case 6: // beak
66 | shape = [0, 0, 1, 0, 1, 0.5, 0, 0, 0.5, 1, 0, 1];
67 | break;
68 | case 7: // chevron
69 | shape = [0, 0, 0.5, 0, 1, 0.5, 0.5, 1, 0, 1, 0.5, 0.5];
70 | break;
71 | case 8: // fish
72 | shape = [0.5, 0, 0.5, 0.5, 1, 0.5, 1, 1, 0.5, 1, 0.5, 0.5, 0, 0.5];
73 | break;
74 | case 9: // kite
75 | shape = [0, 0, 1, 0, 0.5, 0.5, 1, 0.5, 0.5, 1, 0.5, 0.5, 0, 1];
76 | break;
77 | case 10: // trough
78 | shape = [0, 0.5, 0.5, 1, 1, 0.5, 0.5, 0, 1, 0, 1, 1, 0, 1];
79 | break;
80 | case 11: // rays
81 | shape = [0.5, 0, 1, 0, 1, 1, 0.5, 1, 1, 0.75, 0.5, 0.5, 1, 0.25];
82 | break;
83 | case 12: // double rhombus
84 | shape = [0, 0.5, 0.5, 0, 0.5, 0.5, 1, 0, 1, 0.5, 0.5, 1, 0.5, 0.5, 0, 1];
85 | break;
86 | case 13: // crown
87 | shape = [0, 0, 1, 0, 1, 1, 0, 1, 1, 0.5, 0.5, 0.25, 0.5, 0.75, 0, 0.5, 0.5, 0.25];
88 | break;
89 | case 14: // radioactive
90 | shape = [0, 0.5, 0.5, 0.5, 0.5, 0, 1, 0, 0.5, 0.5, 1, 0.5, 0.5, 1, 0.5, 0.5, 0, 1];
91 | break;
92 | default: // tiles
93 | shape = [0, 0, 1, 0, 0.5, 0.5, 0.5, 0, 0, 0.5, 1, 0.5, 0.5, 1, 0.5, 0.5, 0, 1];
94 | break;
95 | }
96 |
97 | /* scale up */
98 | for (var i = 0; i < shape.length; i++) {
99 | shape[i] = shape[i] * size;
100 | }
101 |
102 | return shape;
103 |
104 | };
105 |
106 | /* Draw a polygon at location x,y and rotated by angle */
107 | /* assumes polys are square */
108 | var drawRotatedPolygon = function (ctx, sprite, x, y, shapeangle, angle, size) {
109 |
110 | var halfSize = size / 2;
111 | ctx.save();
112 |
113 | ctx.translate(x, y);
114 | ctx.rotate(angle * Math.PI / 180);
115 | ctx.save();
116 | ctx.translate(halfSize, halfSize);
117 | var tmpSprite = [];
118 | for (var p = 0; p < sprite.length; p++) {
119 | tmpSprite[p] = sprite[p] - halfSize;
120 | }
121 | ctx.rotate(shapeangle);
122 | fillPoly(ctx, tmpSprite);
123 |
124 | ctx.restore();
125 | ctx.restore();
126 |
127 | };
128 |
129 | /* generate sprite for center block */
130 | var getcenter = function (shape, size) {
131 |
132 | switch (shape) {
133 | case 0: // empty
134 | shape = [];
135 | break;
136 | case 1: // fill
137 | shape = [0, 0, 1, 0, 1, 1, 0, 1];
138 | break;
139 | case 2: // diamond
140 | shape = [0.5, 0, 1, 0.5, 0.5, 1, 0, 0.5];
141 | break;
142 | case 3: // reverse diamond
143 | shape = [0, 0, 1, 0, 1, 1, 0, 1, 0, 0.5, 0.5, 1, 1, 0.5, 0.5, 0, 0, 0.5];
144 | break;
145 | case 4: // cross
146 | shape = [0.25, 0, 0.75, 0, 0.5, 0.5, 1, 0.25, 1, 0.75, 0.5, 0.5, 0.75, 1, 0.25, 1, 0.5, 0.5, 0, 0.75, 0, 0.25, 0.5, 0.5];
147 | break;
148 | case 5: // morning star
149 | shape = [0, 0, 0.5, 0.25, 1, 0, 0.75, 0.5, 1, 1, 0.5, 0.75, 0, 1, 0.25, 0.5];
150 | break;
151 | case 6: // small square
152 | shape = [0.33, 0.33, 0.67, 0.33, 0.67, 0.67, 0.33, 0.67];
153 | break;
154 | case 7: // checkerboard
155 | shape = [0, 0, 0.33, 0, 0.33, 0.33, 0.66, 0.33, 0.67, 0, 1, 0, 1, 0.33, 0.67, 0.33, 0.67, 0.67, 1, 0.67, 1, 1, 0.67, 1, 0.67, 0.67, 0.33, 0.67, 0.33, 1, 0, 1, 0, 0.67, 0.33, 0.67, 0.33, 0.33, 0, 0.33];
156 | break;
157 | default: // tiles
158 | shape = [0, 0, 1, 0, 0.5, 0.5, 0.5, 0, 0, 0.5, 1, 0.5, 0.5, 1, 0.5, 0.5, 0, 1];
159 | break;
160 | }
161 |
162 | /* apply ratios */
163 | for (var i = 0; i < shape.length; i++) {
164 | shape[i] = shape[i] * size;
165 | }
166 |
167 | return shape;
168 |
169 | };
170 |
171 | // main drawing function.
172 | // Draws a identicon, based off an MD5 hash value of size "width"
173 | // (identicons are always square)
174 | var draw = function (ctx, hash, width) {
175 |
176 | var csh = parseInt(hash.substr(0, 1), 16); // corner sprite shape
177 | var ssh = parseInt(hash.substr(1, 1), 16); // side sprite shape
178 | var xsh = parseInt(hash.substr(2, 1), 16) & 7; // center sprite shape
179 |
180 | var halfPi = Math.PI/2;
181 | var cro = halfPi * (parseInt(hash.substr(3, 1), 16) & 3); // corner sprite rotation
182 | var sro = halfPi * (parseInt(hash.substr(4, 1), 16) & 3); // side sprite rotation
183 | var xbg = parseInt(hash.substr(5, 1), 16) % 2; // center sprite background
184 |
185 | /* corner sprite foreground color */
186 | var cfr = parseInt(hash.substr(6, 2), 16);
187 | var cfg = parseInt(hash.substr(8, 2), 16);
188 | var cfb = parseInt(hash.substr(10, 2), 16);
189 |
190 | /* side sprite foreground color */
191 | var sfr = parseInt(hash.substr(12, 2), 16);
192 | var sfg = parseInt(hash.substr(14, 2), 16);
193 | var sfb = parseInt(hash.substr(16, 2), 16);
194 |
195 | /* size of each sprite */
196 | var size = width / 3;
197 | var totalsize = width;
198 |
199 | /* start with blank 3x3 identicon */
200 |
201 | /* generate corner sprites */
202 | var corner = getsprite(csh, size);
203 | ctx.fillStyle = "rgb(" + cfr + "," + cfg + "," + cfb + ")";
204 | drawRotatedPolygon(ctx, corner, 0, 0, cro, 0, size);
205 | drawRotatedPolygon(ctx, corner, totalsize, 0, cro, 90, size);
206 | drawRotatedPolygon(ctx, corner, totalsize, totalsize, cro, 180, size);
207 | drawRotatedPolygon(ctx, corner, 0, totalsize, cro, 270, size);
208 |
209 | /* draw sides */
210 | var side = getsprite(ssh, size);
211 | ctx.fillStyle = "rgb(" + sfr + "," + sfg + "," + sfb + ")";
212 | drawRotatedPolygon(ctx, side, 0, size, sro, 0, size);
213 | drawRotatedPolygon(ctx, side, 2 * size, 0, sro, 90, size);
214 | drawRotatedPolygon(ctx, side, 3 * size, 2 * size, sro, 180, size);
215 | drawRotatedPolygon(ctx, side, size, 3 * size, sro, 270, size);
216 |
217 | var center = getcenter(xsh, size);
218 |
219 | /* make sure there's enough contrast before we use background color of side sprite */
220 | if (xbg > 0 && (Math.abs(cfr - sfr) > 127 || Math.abs(cfg - sfg) > 127 || Math.abs(cfb - sfb) > 127)) {
221 | ctx.fillStyle = "rgb(" + sfr + "," + sfg + "," + sfb + ")";
222 | } else {
223 | ctx.fillStyle = "rgb(" + cfr + "," + cfg + "," + cfb + ")";
224 | }
225 |
226 | drawRotatedPolygon(ctx, center, size, size, 0, 0, size);
227 |
228 | };
229 |
230 | var identicon5 = function (canvas, hash, size) {
231 |
232 | if (hash && canvas && canvas.getContext) {
233 |
234 | // canvas is supported
235 | var ctx = canvas.getContext("2d");
236 |
237 | // support high-dpi
238 | if(window.devicePixelRatio && window.devicePixelRatio == 2) {
239 | canvas.width = canvas.height = size * 2;
240 | ctx.scale(2, 2);
241 | } else {
242 | canvas.width = canvas.height = size;
243 | }
244 |
245 | draw(ctx, hash, size);
246 |
247 | }
248 |
249 | };
250 |
251 | // Require shim
252 | module.exports = identicon5;
253 |
--------------------------------------------------------------------------------
/src/mobile/sgp.mobile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /*jshint browser: true, latedef: false*/
4 |
5 | var $ = require('jquery');
6 | var sgp = require('supergenpass-lib');
7 | var md5 = require('crypto-js/md5');
8 | var sha512 = require('crypto-js/sha512');
9 | var identicon = require('./lib/identicon5');
10 | var shortcut = require('./lib/shortcut');
11 | var storage = require('./lib/localstorage-polyfill');
12 |
13 | // Set default values.
14 | var messageOrigin = false;
15 | var messageSource = false;
16 | var language = location.search.substring(1);
17 | var latestBookmarklet = '../bookmarklet/bookmarklet.min.js';
18 | var latestVersion = 20150216;
19 |
20 | // Hostnames that should not be populated into the domain field on referral.
21 | var noReferral = [
22 | 'chriszarate.github.io',
23 | 'www.google.com',
24 | 'www.bing.com',
25 | 'duckduckgo.com',
26 | 'r.search.yahoo.com'
27 | ];
28 |
29 | var localizations = {
30 | 'en': ['Master password', 'Domain / URL', 'Generate'],
31 | 'es': ['Contraseña maestra', 'Dominio / URL', 'Enviar'],
32 | 'fr': ['Mot de passe principal', 'Domaine / URL', 'Soumettre'],
33 | 'de': ['Master Passwort', 'Domain / URL', 'Abschicken'],
34 | 'pt-br': ['Senha-mestra', 'Domínio / URL', 'Gerar'],
35 | 'zh-hk': ['主密碼', '域名 / URL', '提交'],
36 | 'hu': ['Mesterjelszó', 'Tartomány / Internetcím', 'OK'],
37 | 'ru': ['Мастер-пароль', 'Домена / URL', 'Подтвердить'],
38 | 'nl': ['Hoofd wachtwoord', 'Domein / URL', 'Genereer'],
39 | 'fy': ['Haad wachtwurd', 'Domein / URL', 'Ferwurkje']
40 | };
41 |
42 | // Enumerate jQuery selectors for caching.
43 | var $el = {};
44 | var selectors =
45 | [
46 | 'PasswdField',
47 | 'Passwd',
48 | 'PasswdLabel',
49 | 'Secret',
50 | 'DomainField',
51 | 'Domain',
52 | 'DomainLabel',
53 | 'RemoveSubdomains',
54 | 'Len',
55 | 'Result',
56 | 'Generate',
57 | 'MaskText',
58 | 'CopyButton',
59 | 'Output',
60 | 'Canvas',
61 | 'Options',
62 | 'SaveDefaults',
63 | 'Update',
64 | 'Bookmarklet'
65 | ];
66 |
67 | // Retrieve defaults from local storage.
68 | var defaults = {
69 | length: storage.local.getItem('Len') || 10,
70 | secret: storage.local.getItem('Salt') || '',
71 | method: storage.local.getItem('Method') || 'md5',
72 | removeSubdomains: !storage.local.getItem('DisableTLD') || false,
73 | advanced: storage.local.getItem('Advanced') || false
74 | };
75 |
76 | // Save current options to local storage as defaults.
77 | var saveCurrentOptionsAsDefaults = function (e) {
78 | var input = getCurrentFormInput();
79 | storage.local.setItem('Len', input.options.length);
80 | storage.local.setItem('Salt', input.options.secret);
81 | storage.local.setItem('Method', input.options.method);
82 | storage.local.setItem('DisableTLD', !input.options.removeSubdomains || '');
83 | showButtonSuccess(e);
84 | };
85 |
86 | var showUpdateNotification = function (data) {
87 | $el.Bookmarklet.attr('href', data);
88 | $el.Update.show();
89 | sendDocumentHeight();
90 | };
91 |
92 | // Populate domain with referrer, if available and not from the blacklist.
93 | var populateReferrer = function (referrer) {
94 | if (referrer) {
95 | referrer = sgp.hostname(referrer, {removeSubdomains: false});
96 | if (noReferral.indexOf(referrer) === -1) {
97 | $el.Domain.val(sgp.hostname(referrer, {removeSubdomains: defaults.removeSubdomains}));
98 | }
99 | }
100 | };
101 |
102 | // Listen for postMessage from bookmarklet.
103 | var listenForBookmarklet = function (event) {
104 |
105 | var post = event.originalEvent;
106 |
107 | if (post.origin !== window.location.origin) {
108 |
109 | // Save message source.
110 | messageSource = post.source;
111 | messageOrigin = post.origin;
112 |
113 | // Parse message.
114 | $.each(JSON.parse(post.data), function (key, value) {
115 | switch (key) {
116 | case 'version':
117 | if (value < latestVersion) {
118 | // Fetch latest bookmarklet.
119 | $.ajax({
120 | url: latestBookmarklet,
121 | success: showUpdateNotification,
122 | dataType: 'html'
123 | });
124 | }
125 | break;
126 | }
127 | });
128 |
129 | // Populate domain field and call back with the browser height.
130 | $el.Domain.val(sgp.hostname(messageOrigin, {removeSubdomains: defaults.removeSubdomains})).trigger('change');
131 | sendDocumentHeight();
132 |
133 | }
134 |
135 | };
136 |
137 | var sendDocumentHeight = function () {
138 | postMessageToBookmarklet({
139 | height: $el.Body.height()
140 | });
141 | };
142 |
143 | var sendGeneratedPassword = function (generatedPassword) {
144 | postMessageToBookmarklet({
145 | result: generatedPassword
146 | });
147 | };
148 |
149 | // Send message using HTML5 postMessage API. Only post a message if we are in
150 | // communication with the bookmarklet.
151 | var postMessageToBookmarklet = function (message) {
152 | if (messageSource && messageOrigin) {
153 | messageSource.postMessage(JSON.stringify(message), messageOrigin);
154 | }
155 | };
156 |
157 | var getCurrentFormInput = function () {
158 | var removeSubdomains = $el.RemoveSubdomains.is(':checked');
159 | return {
160 | password: $el.Passwd.val(),
161 | domain: getDomain(removeSubdomains),
162 | options: {
163 | secret: $el.Secret.val(),
164 | length: getPasswordLength(),
165 | method: getHashMethod(),
166 | removeSubdomains: removeSubdomains
167 | }
168 | };
169 | };
170 |
171 | // Get valid domain value and update form.
172 | var getDomain = function (removeSubdomains) {
173 | var domain = $el.Domain.val().replace(/ /g, '');
174 | if (domain) {
175 | domain = sgp.hostname(domain, {removeSubdomains: removeSubdomains});
176 | $el.Domain.val(domain);
177 | }
178 | return domain;
179 | };
180 |
181 | // Get valid password length and update form.
182 | var getPasswordLength = function () {
183 | var passwordLength = validatePasswordLength($el.Len.val());
184 | $el.Len.val(passwordLength);
185 | return passwordLength;
186 | };
187 |
188 | var validatePasswordLength = function (passwordLength) {
189 | passwordLength = parseInt(passwordLength, 10) || 10;
190 | return Math.max(4, Math.min(passwordLength, 24));
191 | };
192 |
193 | var getHashMethod = function () {
194 | return $('input:radio[name=Method]:checked').val() || 'md5';
195 | };
196 |
197 | // Generate hexadecimal hash for identicons.
198 | var generateIdenticonHash = function (seed, hashMethod) {
199 | var hashFunction = (hashMethod === 'sha512') ? sha512 : md5;
200 | for (var i = 0; i <= 4; i = i + 1) {
201 | seed = hashFunction(seed).toString();
202 | }
203 | return seed;
204 | };
205 |
206 | var generateIdenticon = function () {
207 |
208 | var input = getCurrentFormInput();
209 | var options = input.options;
210 |
211 | if (input.password || options.secret) {
212 | var identiconHash = generateIdenticonHash(input.password + options.secret, options.method);
213 | identicon($el.Canvas[0], identiconHash, 16);
214 | $el.Canvas.show();
215 | } else {
216 | $el.Canvas.hide();
217 | }
218 |
219 | };
220 |
221 | var generatePassword = function () {
222 |
223 | var input = getCurrentFormInput();
224 | var options = input.options;
225 |
226 | if (!input.password) {
227 | $el.PasswdField.addClass('Missing');
228 | }
229 |
230 | if (!input.domain) {
231 | $el.DomainField.addClass('Missing');
232 | }
233 |
234 | if (input.password && input.domain) {
235 | sgp.generate(input.password, input.domain, options, populateGeneratedPassword);
236 | }
237 |
238 | };
239 |
240 | var populateGeneratedPassword = function (generatedPassword) {
241 | sendGeneratedPassword(generatedPassword);
242 | $el.Inputs.trigger('blur');
243 | $el.Output.text(generatedPassword);
244 | $el.Result.addClass('Offer').removeClass('Reveal');
245 | shortcut.add('Ctrl+H', toggleGeneratedPassword);
246 | };
247 |
248 | var toggleGeneratedPassword = function () {
249 | $el.Result.toggleClass('Reveal');
250 | };
251 |
252 | var clearGeneratedPassword = function (event) {
253 |
254 | var key = event.which;
255 |
256 | // Test for input key codes.
257 | var group1 = ([8, 32].indexOf(key) !== -1);
258 | var group2 = (key > 45 && key < 91);
259 | var group3 = (key > 95 && key < 112);
260 | var group4 = (key > 185 && key < 223);
261 | var enterKey = (key === 13);
262 |
263 | // When user enters form input, reset form status.
264 | if (event.type === 'change' || group1 || group2 || group3 || group4) {
265 | $el.Output.text('');
266 | $el.Result.removeClass('Offer');
267 | $el.PasswdField.removeClass('Missing');
268 | $el.DomainField.removeClass('Missing');
269 | shortcut.remove('Ctrl+H');
270 | }
271 |
272 | // Submit form on enter key.
273 | if (enterKey) {
274 | $el.Generate.trigger('click');
275 | event.preventDefault();
276 | }
277 |
278 | };
279 |
280 | var adjustPasswordLength = function (event) {
281 | var increment = ($(this).attr('id') === 'Up') ? 1 : -1;
282 | var passwordLength = validatePasswordLength($el.Len.val());
283 | var newPasswordLength = validatePasswordLength(passwordLength + increment);
284 | $el.Len.val(newPasswordLength).trigger('change');
285 | event.preventDefault();
286 | };
287 |
288 | var toggleAdvancedOptions = function () {
289 | var advanced = !$el.Body.hasClass('Advanced');
290 | $el.Body.toggleClass('Advanced', advanced);
291 | storage.local.setItem('Advanced', advanced || '');
292 | sendDocumentHeight();
293 | };
294 |
295 | var toggleSubdomainIndicator = function () {
296 | var input = getCurrentFormInput();
297 | $el.DomainField.toggleClass('Advanced', !input.options.removeSubdomains);
298 | };
299 |
300 | // Update button to show a success indicator. Remove indicator after 5 seconds.
301 | var showButtonSuccess = function (e) {
302 | $(e.target).addClass('Success');
303 | setTimeout(function () {
304 | $(e.target).removeClass('Success');
305 | }, 5000);
306 | };
307 |
308 | // Populate selector cache.
309 | $el.Inputs = $('input');
310 | $el.Body = $(document.body);
311 | $.each(selectors, function (i, val) {
312 | $el[val] = $('#' + val);
313 | });
314 |
315 | // Load defaults into form.
316 | $('input:radio[value=' + defaults.method + ']').prop('checked', true);
317 | $el.Len.val(validatePasswordLength(defaults.length));
318 | $el.Secret.val(defaults.secret).trigger('change');
319 | $el.RemoveSubdomains.prop('checked', defaults.removeSubdomains).trigger('change');
320 | $el.Body.toggleClass('Advanced', defaults.advanced);
321 |
322 | // Perform localization, if requested.
323 | if (language && localizations.hasOwnProperty(language)) {
324 | $el.Passwd.attr('placeholder', localizations[language][0]);
325 | $el.Domain.attr('placeholder', localizations[language][1]);
326 | $el.PasswdLabel.text(localizations[language][0]);
327 | $el.DomainLabel.text(localizations[language][1]);
328 | $el.Generate.text(localizations[language][2]);
329 | }
330 |
331 | // Provide fake input placeholders if browser does not support them.
332 | if (!('placeholder' in document.createElement('input'))) {
333 | $('#Passwd, #Secret, #Domain').on('keyup change', function () {
334 | $('label[for=' + $(this).attr('id') + ']').toggle($(this).val() === '');
335 | }).trigger('change');
336 | }
337 |
338 | // Copy to clipboard if possible.
339 | // https://developers.google.com/web/updates/2015/04/cut-and-copy-commands?hl=en
340 | $el.CopyButton.on('click', function (e) {
341 | var range = document.createRange();
342 | var selection = window.getSelection();
343 | var success = false;
344 |
345 | range.selectNodeContents($el.Output.get(0));
346 | selection.removeAllRanges();
347 | selection.addRange(range);
348 |
349 | try {
350 | success = document.execCommand('copy');
351 | } catch (err) {}
352 |
353 | selection.removeAllRanges();
354 |
355 | if (success) {
356 | showButtonSuccess(e);
357 | $el.Result.removeClass('Reveal');
358 | return;
359 | }
360 |
361 | $el.CopyButton.hide();
362 | });
363 |
364 | // Bind to interaction events.
365 | $el.Generate.on('click', generatePassword);
366 | $el.MaskText.on('click', toggleGeneratedPassword);
367 | $el.Options.on('click', toggleAdvancedOptions);
368 | $el.SaveDefaults.on('click', saveCurrentOptionsAsDefaults);
369 | $('#Up, #Down').on('click', adjustPasswordLength);
370 |
371 | // Bind to form events.
372 | $el.RemoveSubdomains.on('change', toggleSubdomainIndicator);
373 | $el.Inputs.on('keydown change', clearGeneratedPassword);
374 | $('#Passwd, #Secret, #MethodField').on('keyup change', generateIdenticon);
375 |
376 | // Bind to hotkeys.
377 | shortcut.add('Ctrl+O', toggleAdvancedOptions);
378 | shortcut.add('Ctrl+G', generatePassword);
379 |
380 | // Populate domain with referrer, if available.
381 | populateReferrer(document.referrer);
382 |
383 | // Set focus on password field.
384 | $el.Passwd.trigger('focus').trigger('change');
385 |
386 | // Attach postMessage listener for bookmarklet.
387 | $(window).on('message', listenForBookmarklet);
388 |
--------------------------------------------------------------------------------
/src/mobile/sgp.mobile.css:
--------------------------------------------------------------------------------
1 | /* Basic */
2 |
3 | body {
4 | width: auto;
5 | max-width: 320px;
6 | margin: 0 auto;
7 | padding: 0;
8 | color: #333;
9 | background-color: #689;
10 | font-family: Helvetica, Arial, sans-serif;
11 | font-size: 14px;
12 | line-height: 16px;
13 | }
14 |
15 | a {
16 | text-decoration: none;
17 | }
18 |
19 | h1 {
20 | margin: 0;
21 | margin-bottom: 10px;
22 | font-size: 1.25em;
23 | }
24 |
25 | noscript {
26 | margin-top: 10px;
27 | color: #f33;
28 | }
29 |
30 | * {
31 | -webkit-tap-highlight-color: transparent;
32 | }
33 |
34 |
35 | /* Container */
36 |
37 | #main {
38 | margin-top: 50px;
39 | padding: 25px;
40 | background-color: #fff;
41 | border-radius: 4px;
42 | }
43 |
44 |
45 | /* Update notification */
46 |
47 | #Update {
48 | display: none;
49 | padding: 5px;
50 | background-color: #fcc;
51 | text-align: center;
52 | }
53 |
54 | #Update a {
55 | color: #311;
56 | text-decoration: underline;
57 | }
58 |
59 |
60 | /* Canvas */
61 |
62 | #Canvas {
63 | display: none;
64 | position: absolute;
65 | top: 11px;
66 | right: 10px;
67 | width: 16px;
68 | height: 16px;
69 | }
70 |
71 |
72 | /* Form elements */
73 |
74 | form {
75 | margin: 0;
76 | }
77 |
78 | fieldset {
79 | position: relative;
80 | margin: 0;
81 | margin-top: 10px;
82 | padding: 0;
83 | border: none;
84 | }
85 |
86 | input {
87 | background-color: #fff;
88 | border: solid 2px #ccc;
89 | border-radius: 4px;
90 | -moz-box-sizing: border-box;
91 | -webkit-box-sizing: border-box;
92 | box-sizing: border-box;
93 | -webkit-appearance: none;
94 | -moz-appearance: none;
95 | }
96 |
97 | input:focus {
98 | outline: none;
99 | border-color: #9bc;
100 | }
101 |
102 | label {
103 | font-size: 14px;
104 | -webkit-user-select: none;
105 | -khtml-user-select: none;
106 | -moz-user-select: none;
107 | -ms-user-select: none;
108 | user-select: none;
109 | }
110 |
111 |
112 | /* Placeholders */
113 |
114 | .placeholder {
115 | display: none;
116 | position: absolute;
117 | top: 11px;
118 | left: 12px;
119 | color: #ccc;
120 | }
121 |
122 | ::-webkit-input-placeholder {
123 | color: #ccc;
124 | font-family: Helvetica, Arial, sans-serif;
125 | }
126 |
127 | :-moz-placeholder {
128 | color: #ccc;
129 | font-family: Helvetica, Arial, sans-serif;
130 | }
131 |
132 | ::-moz-placeholder {
133 | color: #ccc;
134 | font-family: Helvetica, Arial, sans-serif;
135 | }
136 |
137 | :-ms-input-placeholder {
138 | color: #ccc;
139 | font-family: Helvetica, Arial, sans-serif;
140 | }
141 |
142 | .Missing label { color: #d66; }
143 | .Missing input::-webkit-input-placeholder { color: #d66; }
144 | .Missing input:::-moz-placeholder { color: #d66; }
145 | .Missing input::-moz-placeholder { color: #d66; }
146 | .Missing input:-ms-input-placeholder { color: #d66; }
147 | .Missing input { border-color: #d66; }
148 |
149 |
150 | /* Text fields */
151 |
152 | #Passwd, #Secret, #Domain {
153 | width: 100%;
154 | height: 38px;
155 | margin: 0;
156 | padding: 8px 10px;
157 | font-size: 15px;
158 | }
159 |
160 | #Passwd {
161 | padding-right: 30px;
162 | }
163 |
164 | #PasswdField, #DomainField {
165 | clear: both;
166 | }
167 |
168 |
169 | /* Length field */
170 |
171 | #LenField input {
172 | float: left;
173 | width: 38px;
174 | height: 31px;
175 | margin: 0;
176 | padding: 5px;
177 | font-size: 14px;
178 | border-radius: 4px 0 0 4px;
179 | }
180 |
181 | #LenField label {
182 | float: left;
183 | width: 24px;
184 | height: 16px;
185 | margin: 0;
186 | padding: 6px 0;
187 | padding-bottom: 7px;
188 | color: #999;
189 | background-color: #ccc;
190 | font-size: 14px;
191 | font-weight: bold;
192 | text-align: center;
193 | border-bottom: solid 2px #bbb;
194 | border-radius: 4px 0 0 4px;
195 | cursor: pointer;
196 | }
197 |
198 | #LenField label:hover {
199 | color: #000;
200 | }
201 |
202 | #LenField label:first-of-type {
203 | padding-left: 2px;
204 | border-radius: 0;
205 | }
206 |
207 | #LenField label:last-of-type {
208 | padding-right: 2px;
209 | border-radius: 0 4px 4px 0;
210 | }
211 |
212 |
213 | /* Method toggle */
214 |
215 | #MethodField {
216 | float: right;
217 | }
218 |
219 | input[type="radio"] {
220 | display: none;
221 | }
222 |
223 | input[type="radio"] + label {
224 | float: right;
225 | width: 48px;
226 | height: 16px;
227 | margin: 0;
228 | padding: 6px 0;
229 | color: #999;
230 | background-color: #ccc;
231 | text-align: center;
232 | border-bottom: solid 2px #999;
233 | cursor: pointer;
234 | }
235 |
236 | input[type="radio"] + label:hover {
237 | color: #000;
238 | }
239 |
240 | input[type="radio"]:checked + label {
241 | color: #fff;
242 | background-color: #356;
243 | font-weight: bold;
244 | border-bottom: solid 2px #023;
245 | }
246 |
247 | #MethodField label:first-of-type {
248 | border-radius: 0 4px 4px 0;
249 | }
250 |
251 | #MethodField label:last-of-type {
252 | border-radius: 4px 0 0 4px;
253 | }
254 |
255 |
256 | /* Subdomains toggle */
257 |
258 | #SubdomainsField {
259 | position: absolute;
260 | top: 0;
261 | right: 9px;
262 | }
263 |
264 | input[type="checkbox"] {
265 | display: none;
266 | }
267 |
268 | input[type="checkbox"] + label {
269 | padding: 2px 4px;
270 | color: #fff;
271 | background-color: #356;
272 | font-size: 12px;
273 | border: solid 1px #023;
274 | border-radius: 3px;
275 | text-decoration: line-through;
276 | cursor: pointer;
277 | }
278 |
279 | input[type="checkbox"] + label:hover {
280 | color: #fff;
281 | border-color: #023;
282 | text-decoration: line-through;
283 | }
284 |
285 | input[type="checkbox"]:checked + label {
286 | color: #999;
287 | background-color: #fff;
288 | border-color: #999;
289 | text-decoration: none;
290 | }
291 |
292 | input[type="checkbox"]:checked + label:hover {
293 | color: #333;
294 | border-color: #333;
295 | }
296 |
297 |
298 | /* Advanced options */
299 |
300 | #Options {
301 | float: right;
302 | width: 18px;
303 | height: 18px;
304 | margin-right: 2px;
305 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAQAAABLCVATAAACDElEQVRIx63Wv0okQRAG8N/M3qKJkWCmJneGa2Jg5h8QRPEZDEw02ETxcURfw1R8ChcEwcRME711Z6Yu2N5x13/HcdMNzfbsVx9V9VV1N+9HS4a83l8K4bLe58i0/GVkmNORyeRY9CyEZ4vIZTIdcwn5Lc2+e+EIU9gQSqWwkb4cCff2v6NqYV0ohNIeOBUKhXAK9pQJsZ4sPhk5lvVVCuHRKs6FgYFwjlWPQqHStzyRyTFv8uTsVR3MjWP3QqUS7h27qf+7Suh80qssrdPoCoPafHIOvw2ELqZru7Gg1nSSq4uehEqoDMbIRrtKeEoa0rE2CrCFA+HFta4FM3pC+cGb0SyFnhkLuq69CAejtM+6S0qER70vApqcPY8iWd2ZHfrUUaoUSeZJikHyrZwIMxJJoVApdUaiHypVSpHWEc3wd18/kVVjAVZpLR2OCiHHbp3iN5rw6tyWJUu2nHv9BPFk962eclNYcTsGrIQHW2OlwZaHd4hbK5gaEg2BP1I7DGpQZRttLblcSxvbdQEMkafJkoy2eZtOXKQqHmlxhvZE9bdxVus7rPYLJzbNa3Pp5RPBw86Hpmxh512ehvPFpaRBMSZvJfz260NT5ljSn8jTQJG0/ZLo578SNRZaY8luTP7GCrKxFmmwaRs6Rho72Bo8ahs6/Bu9jhq7IBu8sht9RDT2rGnwofUfT78/AU+1mnSH4ucAAAAASUVORK5CYII=');
306 | background-repeat: no-repeat;
307 | background-size: 18px 18px;
308 | opacity: 0.7;
309 | cursor: pointer;
310 | }
311 |
312 | #Options:hover {
313 | opacity: 1.0;
314 | }
315 |
316 | .Option {
317 | display: none;
318 | }
319 |
320 | .Advanced .Option {
321 | display: block;
322 | }
323 |
324 | .Advanced #Domain {
325 | padding-right: 45px;
326 | }
327 |
328 |
329 | /* Save button */
330 |
331 | #SaveDefaults {
332 | float: right;
333 | width: 18px;
334 | height: 18px;
335 | margin-right: 8px;
336 | background-color: #fff;
337 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgAgMAAAAOFJJnAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAJUExURUxpcQAAAAAAAD5+edwAAAACdFJOUwAH6PdYmwAAAD9JREFUGNPF0KsRACAMwNAcklE6GlMwSiXXKSmfojAoop4O2UYNRPEEzJFVakADLWDvgOLY3cDsE+TA9o60oB24XkzxWNPvZAAAAABJRU5ErkJggg==');
338 | background-repeat: no-repeat;
339 | background-position: center center;
340 | background-size: 16px 16px;
341 | -webkit-transition: background 0.5s linear;
342 | transition: background 0.5s linear;
343 | opacity: 0.7;
344 | cursor: pointer;
345 | }
346 |
347 | #SaveDefaults:hover {
348 | opacity: 1.0;
349 | }
350 |
351 | #SaveDefaults.Success {
352 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAB+UExURUxpcQoKCuPj48HBwQMDAwcHBxAQEAAAAAAAAA0NDeTk5ImJiR0dHXFxcTk5ORUVFXV1dVdXV5ycnIuLiykpKfDw8G1tbebm5n9/f/X19UlJSYaGhhcXF25ubunp6eDg4DIyMmlpaTU1NZubm9XV1VFRUYSEhI6OjuHh4aurq4zmssgAAAAIdFJOUwD///////8HZqwuHAAAAJZJREFUOMvVk8sSgjAMRYM8vEVURETBNz7A//9BFshMHSa3GzecVZue6aRJKiLQkB57bTNEMRK++yFMBTgFOAVMVeiZ/wo6fxJEQRUO+YYKd1T0hgduJROeXnSlOdT40CQbvOgrLsZrqXDGW63DMtrKPjzphVoBWYGjLszWMKHZkVL7AZDSXiyCJB7PlqubjoGw/q9y3gER0xJYR8gIqQAAAABJRU5ErkJggg==');
353 | -webkit-transition: background 0s linear;
354 | transition: background 0s linear;
355 | }
356 |
357 |
358 | /* Field style for non-input elements */
359 |
360 | .Field {
361 | height: 16px;
362 | padding: 10px 5px;
363 | font-size: 16px;
364 | text-align: center;
365 | border-radius: 4px;
366 | }
367 |
368 |
369 | /* Buttons */
370 |
371 | .Button:active {
372 | margin-top: 2px !important;
373 | border-bottom: 0 !important;
374 | }
375 |
376 |
377 | /* Result fields */
378 |
379 | #Result {
380 | padding-top: 10px;
381 | }
382 |
383 | .Offer #Generate {
384 | display: none;
385 | }
386 |
387 | .Offer #Mask,
388 | .Offer #Output {
389 | display: block;
390 | }
391 |
392 |
393 | /* Generate button */
394 |
395 | #Generate {
396 | padding-bottom: 8px;
397 | color: #fff;
398 | background-color: #356;
399 | font-weight: bold;
400 | border-bottom: solid 2px #023;
401 | cursor: pointer;
402 | }
403 |
404 | #Generate:hover {
405 | color: #fff;
406 | background-color: #467;
407 | border-bottom: solid 2px #134;
408 | }
409 |
410 |
411 | /* Password output fields */
412 |
413 | #Output {
414 | display: none;
415 | }
416 |
417 | #Mask {
418 | display: none;
419 | height: 36px;
420 | overflow: hidden;
421 | position: relative;
422 | }
423 |
424 | #Output, #MaskText {
425 | color: #fff;
426 | background-color: #666;
427 | border: none;
428 | border-bottom: solid 2px #666;
429 | font-family: monospace;
430 | height: 14px;
431 | word-wrap: break-word;
432 | overflow-wrap: break-word;
433 | }
434 |
435 | #MaskText {
436 | border-color: #444;
437 | cursor: pointer;
438 | }
439 |
440 | #Output::selection {
441 | background-color: #fff;
442 | color: #333;
443 | }
444 |
445 | #Output::-moz-selection {
446 | background-color: #fff;
447 | color: #333;
448 | }
449 |
450 | .Reveal #MaskText {
451 | display: none;
452 | }
453 |
454 | .Reveal #Output {
455 | display: block;
456 | }
457 |
458 |
459 | /* Copy-to-clipboard button */
460 |
461 | #CopyButton {
462 | float: right;
463 | width: 24px;
464 | height: 14px;
465 | margin-top: 0;
466 | background-color: #356;
467 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAZFJREFUeNrtm0FKAzEUhlvpUrHMqtADCC56AzdCb+CRPEwX3kDoRvAGeoJZdCUOzn78C1nZMElJXlLb74dHFk3SvG/evCRDMp0U1iCN/T6VSo7nanLhAgAA/O/pUraRtUNAvnf8mPqp7YewWufL0ptzPB3eqXiXNTEE/yatGCdTlPB/X7IHNf8MRcBzrPP/TI3zLRgB3ypuCzyR0hGwV6/mNyEAg+WAQvP8se1Tx8ssAIAL1yw1CXnUjSXRxCTZ584JFhGwNXxgr9lnldy7NTW/V/FmsJbwLmRSx589AtwAV7IX2S5DlzvX1yrkfJUI8NSfq0pn9C3hWsVPznWIRQ54NMwB6yJfbBJ3Yx+yxmBcjevbdDeaazu634I+yRYZHF+4vlqL7bhFDiiqU8wBLIUBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJ6ZZagel7/gQAQAAQN0cUPtMEBEAAACYA+jO2N8+BsD2jAEc3DfwnRS1Ou9fW3EXJw3O+9fW6H2DXwyV/bjOkCPhAAAAAElFTkSuQmCC');
468 | background-repeat: no-repeat;
469 | background-size: 16px 16px;
470 | background-position: 50% 50%;
471 | -webkit-transition: background 0.5s linear;
472 | transition: background 0.5s linear;
473 | border-left: solid 1px #999;
474 | border-bottom: solid 2px #023;
475 | border-top-left-radius: 0;
476 | border-bottom-left-radius: 0;
477 | cursor: pointer;
478 | }
479 |
480 | #CopyButton.Active {
481 | border-top: solid 2px #fff;
482 | border-bottom: 0;
483 | }
484 |
485 | #CopyButton.Success {
486 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAQAAAAAYLlVAAAAAXNSR0IArs4c6QAAAglJREFUaN7tmc1LW0EQwCehVIRiQRRDQy2UQjAXLxZy9x9QULzmWCFQqCcPFfwncu2tCNFcBC8eKsYvhF6UlN4bTFuQBpUipfLrwXWpyQvvI8lswDent8Pszu/x5s3MMiIRhSYRbYkBegBAmjVq7Q4mpLTdV2ONtJf7DOehDooKAHBOthWgFOGgqABQbgVoqAJc+gaV30FBg9Lv3BigfwGCBmnQIOscoBwKoNx9gKx3ogqaaDoGECFNibqv8zolr1QbGcAqnoaqK0/CBrE/wGwogNnuA1QZDux+mGr3AaDGHClf5ynm/i/q3QSIJA8U4IJj9t0A/GKF5yIiLLoA2LrLFuS41geoMGj2PONMPwZO7NsPcKQfhFdk7I4PLv6CvLV/6+I3tNWfaf7qA/xg1Ni+bO0ZNABmbAk+dZEJPxq7BBsuUvFPRozdeze1YMFYTTUHnw7AprF57PX1ew9wcVt2RFjVKscNtvluVwVjMckfHYAiQyK8NqsDkiIiPOKzTkOybjUHwDUTZrWs1REVrCYPLJnnibu633uAotUMsExCRIQkh3o94VePFvydblM63rTjFb91Ad7cs0+wo92Wf7pnP+/iXpCz2jG+uQD4wgsREUapuLoZXbLLHjfx3fAhAagNOGKAvgPotcQAfQjQUPXvMbQqqwJ4jO3CzAM6Fa/BZeB5QKfSNE/4B+7t6AYuE5oKAAAAAElFTkSuQmCC');
487 | -webkit-transition: background 0s linear;
488 | transition: background 0s linear;
489 | }
490 |
491 |
492 | /* Bookmarklet view */
493 |
494 | @media only screen and (max-width: 800px) {
495 |
496 | body {
497 | background-color: #fff;
498 | }
499 |
500 | #main {
501 | margin: 0;
502 | padding: 15px;
503 | border-radius: 0;
504 | }
505 |
506 | #Output {
507 | height: auto;
508 | }
509 |
510 | }
511 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.
5 | 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Library General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
--------------------------------------------------------------------------------