<|start_filename|>tests/assets/login.html<|end_filename|>
<html>

<head>
  <title>CKP - Sample Entry</title>
  <style>
    input {
      display: none;
    }
    legend {
      cursor: pointer;
    }
  </style>
  <script src="jquery.min.js"></script>
</head>

<body>
  <form action="#">
    <fieldset>
      <legend>
        Simple login
      </legend>
      <input type="text" placeholder="username" class="show">
      <input type="password" class="show">
    </fieldset>
    <fieldset>
      <legend>
        Registration form
      </legend>
      <input type="text" placeholder="username" class="show">
      <input type="password" class="show">
      <input type="password" placeholder="<PASSWORD>" class="show">
    </fieldset>
    <fieldset>
      <legend>
        Login with non-sibling inputs
      </legend>
      <div>
        <input type="text" placeholder="username" class="show">
      </div>
      <div>
        <input type="password" class="show">
      </div>
    </fieldset>
    <fieldset>
      <legend>
        Orphaned username field
      </legend>
      <div>
        <input type="text" placeholder="username" class="show">
      </div>
    </fieldset>
    <fieldset>
      <legend>
        email login
      </legend>
      <input type="email" placeholder="email" class="show">
      <input type="password" class="show">
    </fieldset>
    <fieldset>
      <legend>
        Non-specified input type
      </legend>
      <input placeholder="username" class="show">
      <input type="password" class="show">
    </fieldset>
    <fieldset>
      <legend>
        Visible password. We don't fill in "shown" passwords, they have to be a true password type.
      </legend>
      <input placeholder="username" class="show">
      <div class="uk-form-password">
        <input type="text" class="show">
      </div>
    </fieldset>
    <fieldset>
      <legend>
        Hidden password field. (Should not fill in)
      </legend>
      <input type="text" placeholder="username" class="show">
      <input type="password" style="display:none;">
    </fieldset>
    <fieldset>
      <legend>
        Hidden username field. (Should fill in, but not guaranteed in all scenarios)
      </legend>
      <input type="text" style="display:none;" placeholder="username">
      <input type="password" class="show">
    </fieldset>
    <fieldset>
      <legend>
        Lonely password field
      </legend>
      <input type="password" class="show">
    </fieldset>
    <fieldset>
      <legend>
        Detects filled in (turns yellow). So far, no way I can find that a content script can trigger this.
      </legend>
      <input type="text" placeholder="username" class="show js-detect-yellow">
      <input type="password" class="show detect-yellow">
    </fieldset>
    <fieldset>
      <legend>
        iframe - should fill out since it is in the same origin, but won't trigger the action icon, because it is not https
      </legend>
      <iframe src="iframecontent.html" height="60">
      </iframe>
    </fieldset>
  </form>

  <script type="text/javascript">
    $('legend').on('click', function() {
      $(this).closest('fieldset').find('input.show').toggle();
    });

    function turnYellow(input) {
      $(this).css({
        'background': 'yellow'
      });
    }

    //$('input.js-detect-yellow').on('keypress', turnYellow);
    $('input.js-detect-yellow').on('keydown', turnYellow);
    //$('input.js-detect-yellow').on('keyup', turnYellow);
  </script>
</body>

</html>


<|start_filename|>tests/test.streamCipher.js<|end_filename|>
describe('StreamCipher', function () {

	var cipher;

	beforeEach(inject(function ($injector) {
		cipher = new StreamCipher()
		cipher.setKey(new Uint8Array([0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 
			0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 
			0x20, 0x21]))
	}));

	afterEach(function() {
		
	});

	describe('EncryptString', function () {

		it('should advance position after encrypting a string', function () {
			cipher.position.should.equal(0);
			let enc = cipher.encryptString('bla')
			cipher.position.should.equal(3);
		});

		it('should decrypt to same value', function () {
			cipher.position.should.equal(0);
			let enc = cipher.encryptString('bla')
			let dec = cipher.getDecryptedFieldValue({protectedData: {
				password: {data: enc, position: 0}
			}}, 'password')

			dec.should.equal('bla')
		});

		it('should decrypt to same value after initial position', function () {
			cipher.position.should.equal(0);
			cipher.encryptString('foo')
			let pos = cipher.position
			let enc = cipher.encryptString('bar')
			let dec = cipher.getDecryptedFieldValue({protectedData: {
				password: {data: enc, position: pos}
			}}, 'password')

			dec.should.equal('bar')
		});
	});
});


<|start_filename|>options/controllers/startup.js<|end_filename|>
"use strict";

function StartupController($scope, settings) {

  settings.getCurrentDatabaseChoice().then(function(choice) {
    $scope.alreadyChoseDb = (choice == null ? false : true);
  }).then(function() {
    $scope.$apply();
  });
}


<|start_filename|>services/sampleDatabaseFileManager.js<|end_filename|>
/**

The MIT License (MIT)

Copyright (c) 2015 <NAME>.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

 */

"use strict";

function SampleDatabaseFileManager($http) {
  var exports = {
    key: 'sample',
    routePath: '/sample-database',
    listDatabases: listDatabases,
    getDatabaseChoiceData: getDatabaseChoiceData,
    getChosenDatabaseFile: getChosenDatabaseFile,
    supportedFeatures: ['ingognito', 'listDatabases'],
    title: 'Sample',
    icon: 'icon-flask',
    chooseTitle: 'Sample Database',
    chooseDescription: 'Sample database that you can use to try out the functionality.  The master password is <PASSWORD>.',
    getActive: getActive,
    setActive: setActive
  };

  function listDatabases() {
    return getActive().then(function(flag) {
      if (flag) {
        return [{
          title: 'Sample.kdbx - password is <PASSWORD>'
        }];
      } else {
        return [];
      }
    });
  }

  //get the minimum information needed to identify this file for future retrieval
  function getDatabaseChoiceData(dbInfo) {
    return {
      title: dbInfo.title
    }
  }

  //given minimal file information, retrieve the actual file
  function getChosenDatabaseFile(dbInfo) {
    return $http({
      method: 'GET',
      url: chrome.extension.getURL('/assets/Sample123.kdbx'),
      responseType: 'arraybuffer',
      cache: true
    }).then(function(response) {
      return response.data;
    });
  }

  function setActive(flag) {
    if (flag)
      return chrome.p.storage.local.set({'useSampleDatabase': true});
    else
      return chrome.p.storage.local.remove('useSampleDatabase');
  }

  function getActive() {
    return chrome.p.storage.local.get('useSampleDatabase').then(function(results) {
      return !!results.useSampleDatabase;
    });
  }

  return exports;
}


<|start_filename|>manifest.json<|end_filename|>
{
	"name": "CKP - KeePass integration for Chrome™",
	"short_name": "CKP Extension",
	"version": "2017.5.30",
	"manifest_version": 2,
	"minimum_chrome_version": "48",

	"description": "Readonly KeePass password database integration for Chrome™",
	"icons": {
		"16": "assets/icons/logo_16.png",
		"48": "assets/icons/logo_48.png"
	},
	"incognito": "split",
	"key": "<KEY>",
	"oauth2": {
		"client_id": "252911279862-spv4ks48i07fqbjvd3isorei5au254b5.apps.googleusercontent.com",
		"scopes": [
			"https://www.googleapis.com/auth/drive.readonly",
			"https://www.googleapis.com/auth/chromewebstore.readonly"
		]
	},

	"page_action": {
		"default_icon": {
			"19": "assets/icons/logo_19.png",
			"38": "assets/icons/logo_38.png"
		},
		"default_title": "Input field detected.  Click here or press Ctrl-Shift-Space to access your KeePass passwords.",
		"default_popup": "popups/popup.html"
	},
	"options_page": "options/options.html",
	"options_ui": {
		"page": "options/options.html",
		"chrome_style": true
	},
	"background": {
		"scripts": [
			"lib/base64.js", "lib/chrome-api-promise.js",
			"services/settings.js", "services/protectedMemory.js", "background/background.js"
		],
		"persistent": false
	},
	"commands": {
		"_execute_page_action": {
			"suggested_key": {
				"windows": "Ctrl+Shift+Space",
				"mac": "Command+Shift+Space",
				"chromeos": "Ctrl+Shift+Space",
				"linux": "Ctrl+Shift+Space"
			}
		}
	},
	"permissions": [
		"activeTab",
		"storage",
		"clipboardWrite",
		"identity",
		"declarativeContent",
		"alarms",
		"notifications"
	],
	"optional_permissions": [
		"https://*/",
		"http://*/",
		"file:///*/"
	]

}


<|start_filename|>options/partials/sample-database.html<|end_filename|>

If you don't have a KeePass database already, or if you prefer to try out CKP
with a sample database, check the box below to make the sample available.
<pageInfo>
  <input id="useSample" type="checkbox" ng-model="useSample" ng-change="updateSampleFlag()"> <label for="useSample">Use sample database, for testing only.</label>
</pageInfo>

<successMessage ng-show="useSample">
  Try it out now - click <a href="http://keepass.info/help/kb/testform.html" target="_blank">http://keepass.info/help/kb/testform.html</a>.
  On that page, look for the KeePass icon in the right hand side of the address bar, and click it.
  The password is "<PASSWORD>".
</successMessage>


<|start_filename|>lib/salsa20.js<|end_filename|>
var Salsa20 = (function () {

    function Salsa20(key, nonce) {
        // Constants.
        this.rounds = 20; // number of Salsa rounds
        this.sigmaWords = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574];

        // State.
        this.keyWords = [];           // key words
        this.nonceWords = [0, 0];     // nonce words
        this.counterWords = [0, 0];   // block counter words

        // Output buffer.
        this.block = [];        // output block of 64 bytes
        this.blockUsed = 64;     // number of block bytes used

        this.setKey(key);
        this.setNonce(nonce);
    }

    // setKey sets the key to the given 32-byte array.
    Salsa20.prototype.setKey = function(key) {
        for (var i = 0, j = 0; i < 8; i++, j += 4) {
            this.keyWords[i] = (key[j] & 0xff)        |
                              ((key[j+1] & 0xff)<<8)  |
                              ((key[j+2] & 0xff)<<16) |
                              ((key[j+3] & 0xff)<<24);
        }
        this.reset();
    };

    // setNonce sets the nonce to the given 8-byte array.
    Salsa20.prototype.setNonce = function(nonce) {
        this.nonceWords[0] = (nonce[0] & 0xff)      |
                            ((nonce[1] & 0xff)<<8)  |
                            ((nonce[2] & 0xff)<<16) |
                            ((nonce[3] & 0xff)<<24);
        this.nonceWords[1] = (nonce[4] & 0xff)      |
                            ((nonce[5] & 0xff)<<8)  |
                            ((nonce[6] & 0xff)<<16) |
                            ((nonce[7] & 0xff)<<24);
        this.reset();
    };

    // getBytes returns the next numberOfBytes bytes of stream.
    Salsa20.prototype.getBytes = function(numberOfBytes) {
        var out = new Array(numberOfBytes);
        for (var i = 0; i < numberOfBytes; i++) {
            if (this.blockUsed == 64) {
                this._generateBlock();
                this._incrementCounter();
                this.blockUsed = 0;
            }
            out[i] = this.block[this.blockUsed];
            this.blockUsed++;
        }
        return out;
    };

    Salsa20.prototype.decrypt = function(inBytes) {
      var outBytes = new Array(inBytes.length);
      var keyBytes = this.getBytes(inBytes.length);
      for(var i=0; i<inBytes.length; i++) {
        outBytes[i] = keyBytes[i] ^ inBytes[i];
      }

      return outBytes;
    };

    Salsa20.prototype.encrypt = Salsa20.prototype.decrypt;  //yep, its the same both ways due to the xor operation

    Salsa20.prototype.getHexString = function(numberOfBytes) {
        var hex=['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'];
        var out = [];
        var bytes = this.getBytes(numberOfBytes);
        for(var i = 0; i < bytes.length; i++) {
            out.push(hex[(bytes[i] >> 4) & 15]);
            out.push(hex[bytes[i] & 15]);
        }
        return out.join('');
    };

    Salsa20.prototype.reset = function() {
        this.counterWords[0] = 0;
        this.counterWords[1] = 0;
        this.blockUsed = 64;
    };

    // Private methods.

    // _incrementCounter increments block counter.
    Salsa20.prototype._incrementCounter = function() {
        // Note: maximum 2^64 blocks.
        this.counterWords[0] = (this.counterWords[0] + 1) & 0xffffffff;
        if (this.counterWords[0] == 0) {
            this.counterWords[1] = (this.counterWords[1] + 1) & 0xffffffff;
        }
    };

    // _generateBlock generates 64 bytes from key, nonce, and counter,
    // and puts the result into this.block.
    Salsa20.prototype._generateBlock = function() {
        var j0 = this.sigmaWords[0],
            j1 = this.keyWords[0],
            j2 = this.keyWords[1],
            j3 = this.keyWords[2],
            j4 = this.keyWords[3],
            j5 = this.sigmaWords[1],
            j6 = this.nonceWords[0],
            j7 = this.nonceWords[1],
            j8 = this.counterWords[0],
            j9 = this.counterWords[1],
            j10 = this.sigmaWords[2],
            j11 = this.keyWords[4],
            j12 = this.keyWords[5],
            j13 = this.keyWords[6],
            j14 = this.keyWords[7],
            j15 = this.sigmaWords[3];

            var x0 = j0, x1 = j1, x2 = j2, x3 = j3, x4 = j4, x5 = j5, x6 = j6, x7 = j7,
                x8 = j8, x9 = j9, x10 = j10, x11 = j11, x12 = j12, x13 = j13, x14 = j14, x15 = j15;

            var u;

            for (var i = 0; i < this.rounds; i += 2) {
                u = x0 + x12;
                x4 ^= (u<<7) | (u>>>(32-7));
                u = x4 + x0;
                x8 ^= (u<<9) | (u>>>(32-9));
                u = x8 + x4;
                x12 ^= (u<<13) | (u>>>(32-13));
                u = x12 + x8;
                x0 ^= (u<<18) | (u>>>(32-18));

                u = x5 + x1;
                x9 ^= (u<<7) | (u>>>(32-7));
                u = x9 + x5;
                x13 ^= (u<<9) | (u>>>(32-9));
                u = x13 + x9;
                x1 ^= (u<<13) | (u>>>(32-13));
                u = x1 + x13;
                x5 ^= (u<<18) | (u>>>(32-18));

                u = x10 + x6;
                x14 ^= (u<<7) | (u>>>(32-7));
                u = x14 + x10;
                x2 ^= (u<<9) | (u>>>(32-9));
                u = x2 + x14;
                x6 ^= (u<<13) | (u>>>(32-13));
                u = x6 + x2;
                x10 ^= (u<<18) | (u>>>(32-18));

                u = x15 + x11;
                x3 ^= (u<<7) | (u>>>(32-7));
                u = x3 + x15;
                x7 ^= (u<<9) | (u>>>(32-9));
                u = x7 + x3;
                x11 ^= (u<<13) | (u>>>(32-13));
                u = x11 + x7;
                x15 ^= (u<<18) | (u>>>(32-18));

                u = x0 + x3;
                x1 ^= (u<<7) | (u>>>(32-7));
                u = x1 + x0;
                x2 ^= (u<<9) | (u>>>(32-9));
                u = x2 + x1;
                x3 ^= (u<<13) | (u>>>(32-13));
                u = x3 + x2;
                x0 ^= (u<<18) | (u>>>(32-18));

                u = x5 + x4;
                x6 ^= (u<<7) | (u>>>(32-7));
                u = x6 + x5;
                x7 ^= (u<<9) | (u>>>(32-9));
                u = x7 + x6;
                x4 ^= (u<<13) | (u>>>(32-13));
                u = x4 + x7;
                x5 ^= (u<<18) | (u>>>(32-18));

                u = x10 + x9;
                x11 ^= (u<<7) | (u>>>(32-7));
                u = x11 + x10;
                x8 ^= (u<<9) | (u>>>(32-9));
                u = x8 + x11;
                x9 ^= (u<<13) | (u>>>(32-13));
                u = x9 + x8;
                x10 ^= (u<<18) | (u>>>(32-18));

                u = x15 + x14;
                x12 ^= (u<<7) | (u>>>(32-7));
                u = x12 + x15;
                x13 ^= (u<<9) | (u>>>(32-9));
                u = x13 + x12;
                x14 ^= (u<<13) | (u>>>(32-13));
                u = x14 + x13;
                x15 ^= (u<<18) | (u>>>(32-18));
            }

            x0 += j0;
            x1 += j1;
            x2 += j2;
            x3 += j3;
            x4 += j4;
            x5 += j5;
            x6 += j6;
            x7 += j7;
            x8 += j8;
            x9 += j9;
            x10 += j10;
            x11 += j11;
            x12 += j12;
            x13 += j13;
            x14 += j14;
            x15 += j15;

            this.block[ 0] = ( x0 >>>  0) & 0xff; this.block[ 1] = ( x0 >>>  8) & 0xff;
            this.block[ 2] = ( x0 >>> 16) & 0xff; this.block[ 3] = ( x0 >>> 24) & 0xff;
            this.block[ 4] = ( x1 >>>  0) & 0xff; this.block[ 5] = ( x1 >>>  8) & 0xff;
            this.block[ 6] = ( x1 >>> 16) & 0xff; this.block[ 7] = ( x1 >>> 24) & 0xff;
            this.block[ 8] = ( x2 >>>  0) & 0xff; this.block[ 9] = ( x2 >>>  8) & 0xff;
            this.block[10] = ( x2 >>> 16) & 0xff; this.block[11] = ( x2 >>> 24) & 0xff;
            this.block[12] = ( x3 >>>  0) & 0xff; this.block[13] = ( x3 >>>  8) & 0xff;
            this.block[14] = ( x3 >>> 16) & 0xff; this.block[15] = ( x3 >>> 24) & 0xff;
            this.block[16] = ( x4 >>>  0) & 0xff; this.block[17] = ( x4 >>>  8) & 0xff;
            this.block[18] = ( x4 >>> 16) & 0xff; this.block[19] = ( x4 >>> 24) & 0xff;
            this.block[20] = ( x5 >>>  0) & 0xff; this.block[21] = ( x5 >>>  8) & 0xff;
            this.block[22] = ( x5 >>> 16) & 0xff; this.block[23] = ( x5 >>> 24) & 0xff;
            this.block[24] = ( x6 >>>  0) & 0xff; this.block[25] = ( x6 >>>  8) & 0xff;
            this.block[26] = ( x6 >>> 16) & 0xff; this.block[27] = ( x6 >>> 24) & 0xff;
            this.block[28] = ( x7 >>>  0) & 0xff; this.block[29] = ( x7 >>>  8) & 0xff;
            this.block[30] = ( x7 >>> 16) & 0xff; this.block[31] = ( x7 >>> 24) & 0xff;
            this.block[32] = ( x8 >>>  0) & 0xff; this.block[33] = ( x8 >>>  8) & 0xff;
            this.block[34] = ( x8 >>> 16) & 0xff; this.block[35] = ( x8 >>> 24) & 0xff;
            this.block[36] = ( x9 >>>  0) & 0xff; this.block[37] = ( x9 >>>  8) & 0xff;
            this.block[38] = ( x9 >>> 16) & 0xff; this.block[39] = ( x9 >>> 24) & 0xff;
            this.block[40] = (x10 >>>  0) & 0xff; this.block[41] = (x10 >>>  8) & 0xff;
            this.block[42] = (x10 >>> 16) & 0xff; this.block[43] = (x10 >>> 24) & 0xff;
            this.block[44] = (x11 >>>  0) & 0xff; this.block[45] = (x11 >>>  8) & 0xff;
            this.block[46] = (x11 >>> 16) & 0xff; this.block[47] = (x11 >>> 24) & 0xff;
            this.block[48] = (x12 >>>  0) & 0xff; this.block[49] = (x12 >>>  8) & 0xff;
            this.block[50] = (x12 >>> 16) & 0xff; this.block[51] = (x12 >>> 24) & 0xff;
            this.block[52] = (x13 >>>  0) & 0xff; this.block[53] = (x13 >>>  8) & 0xff;
            this.block[54] = (x13 >>> 16) & 0xff; this.block[55] = (x13 >>> 24) & 0xff;
            this.block[56] = (x14 >>>  0) & 0xff; this.block[57] = (x14 >>>  8) & 0xff;
            this.block[58] = (x14 >>> 16) & 0xff; this.block[59] = (x14 >>> 24) & 0xff;
            this.block[60] = (x15 >>>  0) & 0xff; this.block[61] = (x15 >>>  8) & 0xff;
            this.block[62] = (x15 >>> 16) & 0xff; this.block[63] = (x15 >>> 24) & 0xff;
    };


    return Salsa20;
})();



<|start_filename|>popups/controllers/masterPasswordController.js<|end_filename|>
function MasterPasswordController($scope, $routeParams, $location, keepass, unlockedState, secureCache, settings, optionsLink, streamCipher) {
	"use strict";

  $scope.masterPassword = "";
  $scope.busy = false;
  $scope.fileName = decodeURIComponent($routeParams.fileTitle);
  $scope.providerKey = $routeParams.providerKey;
  $scope.selectedKeyFile = null;
  $scope.unlockedState = unlockedState;
  $scope.os = {};
  var passwordKey;
  $scope.slider_options = [
    {time: 0,  text: "Do not remember"},
    {time: 30, text: "Remember for 30 min."},
    {time: 120,  text: "Remember for 2 hours."},
    {time: 240,  text: "Remember for 4 hours."},
    {time: 480,  text: "Remember for 8 hours."},
    {time: 1440, text: "Remember for 24 hours."},
    {time: undefined, text: "Remember forever."}
  ];

  chrome.runtime.getPlatformInfo(function(info) {
    $scope.$apply(function() {
      $scope.os[info.os] = true;
    })
  });

  $scope.setSliderInt = function(time) {
    
    for (var i=0; i<$scope.slider_options.length;i++){
      if ($scope.slider_options[i].time === time)
        return i;
    }
    return 0;
  }
  $scope.setRememberPeriod = function() {
    var slider_option_index = parseInt($scope.slider_int);
    if (slider_option_index < $scope.slider_options.length){
      if (slider_option_index ==  $scope.slider_options.length - 1){
        $scope.rememberPassword = true;
        $scope.rememberPeriod = undefined;
      } else if (slider_option_index > 0){
        $scope.rememberPassword = true;
        $scope.rememberPeriod =  $scope.slider_options[slider_option_index].time;
      } else {
        $scope.rememberPassword = false;
        $scope.rememberPeriod = 0;
      }
      $scope.rememberPeriodText =  $scope.slider_options[slider_option_index].text;
    }
  }
  $scope.slider_int = 0;
  $scope.setRememberPeriod();

  settings.getKeyFiles().then(keyFiles => {
    $scope.keyFiles = keyFiles;
  }).then( () => {
	  return settings.getDefaultRememberOptions();
  }).then( rememberOptions => {
  	$scope.rememberPassword = rememberOptions.rememberPassword;
  	if (rememberOptions.rememberPassword) {
      $scope.rememberPeriod = rememberOptions.rememberPeriod;
      $scope.slider_int = $scope.setSliderInt($scope.rememberPeriod);
      $scope.setRememberPeriod();
  	}
  }).then(function() {
    return settings.getCurrentDatabaseUsage();
  }).then( usage => {
    //tweak UI based on what we know about the database file
    $scope.hidePassword = (usage.requiresPassword === false);
    $scope.hideKeyFile = (usage.requiresKeyfile === false);
    passwordKey = usage.passwordKey ? Base64.decode(usage.passwordKey): undefined;
    $scope.rememberedPassword = <PASSWORD>;
    if ($scope.rememberedPassword) {
	    $scope.rememberPassword = true;
	    $scope.rememberPeriod = usage.rememberPeriod;
      $scope.slider_int = $scope.setSliderInt($scope.rememberPeriod);
      $scope.setRememberPeriod();
    }

    if ($scope.rememberedPassword) {
    	// remembered password - autologin
    	$scope.enterMasterPassword()
    } else if (usage.keyFileName) {
    	// get matched key file
      var matches = $scope.keyFiles.filter(function(keyFile) {
        return keyFile.name == usage.keyFileName;
      })

      if (matches.length) {
        $scope.selectedKeyFile = matches[0];
        if ($scope.hidePassword) {
        	//auto-login
        	$scope.enterMasterPassword();
        }
      }
    }
  }).then(function() {
    $scope.$apply();
  });

  //determine current tab info:
  unlockedState.getTabDetails().then(function() {
    $scope.$apply();
  });

  //get entries from secure cache
  secureCache.get('entries').then(function(entries) {
    if (entries && entries.length) {
      return secureCache.get('streamKey').then(function(streamKey) {
        showResults(entries, streamKey);
        $scope.$apply();
      })
    }
  }).catch(function(err) {
    //this is fine - it just means the cache expired.  Clear the cache to be sure.
    secureCache.clear('entries');
    secureCache.clear('streamKey');
  });

  $scope.forgetPassword = function() {
 		settings.saveCurrentDatabaseUsage({
      
    }).then(function() {
			secureCache.clear('entries');
			secureCache.clear('streamKey');
			unlockedState.clearBackgroundState();
    	window.close();
    }); 	
  }

  //go to the options page to manage key files
  $scope.manageKeyFiles = function() {
    optionsLink.go();
  }

  $scope.chooseAnotherFile = function() {
    unlockedState.clearBackgroundState();
    secureCache.clear('entries');
    secureCache.clear('streamKey');
    $location.path('/choose-file');
  }

  $scope.enterMasterPassword = function() {
    $scope.clearMessages();
    $scope.busy = true;

    var passwordKeyPromise;
    if (!passwordKey) {
    	passwordKeyPromise = keepass.getMasterKey($scope.masterPassword, $scope.selectedKeyFile)
    } else {
			passwordKeyPromise = Promise.resolve(passwordKey);
		}

		passwordKeyPromise.then(function(newPasswordKey) {
			passwordKey = newPasswordKey;
			return keepass.getPasswords(passwordKey);
		}).then(function(entries) {
      //remember usage for next time:
      settings.saveCurrentDatabaseUsage({
        requiresPassword: $scope.masterPassword ? true : false,
        requiresKeyfile: $scope.selectedKeyFile ? true : false,
        passwordKey: $scope.rememberPassword ? Base64.encode(passwordKey) : undefined,
        keyFileName: $scope.selectedKeyFile ? $scope.selectedKeyFile.name : "",
        rememberPeriod: $scope.rememberPeriod
      });
      settings.saveDefaultRememberOptions($scope.rememberPassword, $scope.rememberPeriod);

      if ($scope.rememberPeriod) {
        var check_time = 60000*$scope.rememberPeriod; // milliseconds per min
      	settings.setForgetTime('forgetPassword', (Date.now() + check_time));
      } else {
      	//don't clear passwords
      	settings.clearForgetTimes(['forgetPassword']);
      }

      //show results:
      showResults(entries, streamCipher.getKey());

      $scope.busy = false;
    }).catch(function(err) {
      $scope.errorMessage = err.message || "Incorrect password or key file";
      $scope.busy = false;
      passwordKey = null;
    }).then(function() {
      $scope.$apply();
    });
  };

  function showResults(entries, streamKey) {
    streamCipher.setKey(streamKey);

    var siteUrl = parseUrl(unlockedState.url);
    var siteTokens = getValidTokens(siteUrl.hostname + "." + unlockedState.title);
    entries.forEach(function(entry) {
      //apply a ranking algorithm to find the best matches
      var entryHostName = parseUrl(entry.url).hostname || "";

      if (entryHostName && entryHostName == siteUrl.hostname)
        entry.matchRank = 100; //exact url match
      else
        entry.matchRank = 0;

      entry.matchRank += (entry.title && unlockedState.title && entry.title.toLowerCase() == unlockedState.title.toLowerCase()) ? 1 : 0;
      entry.matchRank += (entry.title && entry.title.toLowerCase() === siteUrl.hostname.toLowerCase()) ? 1 : 0;
      entry.matchRank += (entry.url && siteUrl.hostname.indexOf(entry.url.toLowerCase()) > -1) ? 0.9 : 0;
      entry.matchRank += (entry.title && siteUrl.hostname.indexOf(entry.title.toLowerCase()) > -1) ? 0.9 : 0;

      var entryTokens = getValidTokens(entryHostName + "." + entry.title);
      for (var i = 0; i < entryTokens.length; i++) {
        var token1 = entryTokens[i];
        for (var j = 0; j < siteTokens.length; j++) {
          var token2 = siteTokens[j];

          entry.matchRank += (token1 === token2) ? 0.2 : 0;
        }
      }
    });

    //save short term (in-memory) filtered results
    unlockedState.entries = entries.filter(function(entry) {
      return (entry.matchRank >= 100)
    });
    if (unlockedState.entries.length == 0) {
      unlockedState.entries = entries.filter(function(entry) {
        return (entry.matchRank > 0.8 && !entry.URL); //a good match for an entry without a url
      });
    }
    if (unlockedState.entries.length == 0) {
      unlockedState.entries = entries.filter(function(entry) {
        return (entry.matchRank >= 0.4);
      });

      if (unlockedState.entries.length) {
        $scope.partialMatchMessage = "No close matches, showing " + unlockedState.entries.length + " partial matches.";
      }
    }
    if (unlockedState.entries.length == 0) {
      $scope.errorMessage = "No matches found for this site."
    }

    //save longer term (in encrypted storage)
    secureCache.save('entries', entries);
    secureCache.save('streamKey', streamKey);
  }

  $scope.findManually = function() {
    $location.path('/find-entry');
  }

  $scope.clearMessages = function() {
    $scope.errorMessage = "";
    $scope.successMessage = "";
    $scope.partialMatchMessage = "";
  }

  function getValidTokens(tokenString) {
    if (!tokenString) return [];

    return tokenString.toLowerCase().split(/\.|\s|\//).filter(function(token) {
      return (token && token !== "com" && token !== "www" && token.length > 1);
    });
  }

  function parseUrl(url) {
    if (url && !url.indexOf('http') == 0)
      url = 'http://' + url;

    //from https://gist.github.com/jlong/2428561
    var parser = document.createElement('a');
    parser.href = url;

    /*
    parser.protocol; // => "http:"
    parser.hostname; // => "example.com"
    parser.port;     // => "3000"
    parser.pathname; // => "/pathname/"
    parser.search;   // => "?search=test"
    parser.hash;     // => "#hash"
    parser.host;     // => "example.com:3000"
    */

    return parser;
  }
}


<|start_filename|>options/partials/choose-dropbox-file.html<|end_filename|>

<infoMessage ng-if="busy">Fetching list of files...</infoMessage>
<fieldset ng-if="state.loggedIn && files && files.length > 0">
	<infoMessage >The following files are now accessible, and you will be able to choose one later (when logging into a website).  Unless you have a key file to load, CKP is setup and ready to use!</infoMessage>
	<files ng-if="state.loggedIn">
		<filelist>
		  <ul>
		    <li ng-repeat="file in files" class="not-selectable">
		      {{file.title}}
		    </li>
		  </ul>
		</filelist>
	</files>	
</fieldset>
<login ng-if="!state.loggedIn && !busy">
	<a class="pure-button" ng-click="login()">Login to DropBox</a>
</login>
<logout ng-if="state.loggedIn && !busy">
	<a class="pure-button" ng-click="logout()">Logout of Dropbox</a>
</logout>


<|start_filename|>popups/partials/startup.html<|end_filename|>

<span ng-hide="ready">Initializing...</span>
<div ng-show="ready">
CKP is an extension that uses your existing KeePass database files to autofill passwords on websites.
In order to continue, you must add your KeePass database file(s).
<br><br>
<a href="" ng-click="openOptionsPage()"><strong>Click here</strong></a> to go to the settings page for CKP,
where you can do so.
<br><br>
Once you have added a database, return here and you will be able to continue.
</div>


<|start_filename|>popups/controllers/chooseFileController.js<|end_filename|>
function ChooseFileController($scope, $location, passwordFileStoreRegistry, settings) {
	"use strict";

  $scope.errorMessage = "";
  $scope.successMessage = "";
  $scope.databases = [];

  passwordFileStoreRegistry.listFileManagers('listDatabases').forEach(function(provider) {
    provider.listDatabases().then(function(databases) {
    	if (databases && databases.length) {
	      databases.forEach(function(database) {
	        database.provider = provider;
	      });
	      $scope.databases = $scope.databases.concat(databases);
    	}
    }).then(function() {
    	if(!$scope.$$phase) {
    		//HACK: some providers are outside of digest
	      $scope.$apply();
    	}
    });
  });

  $scope.chooseDatabase = function(database) {
    var info = database.provider.getDatabaseChoiceData(database);
    settings.saveCurrentDatabaseChoice(info, database.provider).then(function() {
      $location.path('/enter-password/' + database.provider.key + '/' + encodeURIComponent(database.title));
      $scope.$apply();
    });
  }
}


<|start_filename|>services/oneDriveFileManager.js<|end_filename|>
/**

The MIT License (MIT)

Copyright (c) 2015 <NAME>.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

 */

"use strict";

function OneDriveFileManager ($http, $q, settings) {
  var accessTokenType = 'onedrive';

  var exports = {
    key: 'onedrive',
    routePath: '/onedrive',
    listDatabases: listDatabases,
    getDatabaseChoiceData: getDatabaseChoiceData,
    getChosenDatabaseFile: getChosenDatabaseFile,
    supportedFeatures: ['listDatabases'],
    title: 'OneDrive',
    icon: 'icon-onedrive',
    chooseTitle: 'OneDrive',
    chooseDescription: 'Access password files stored on OneDrive.  Files will be retrieved from OneDrive each time they are used.',
    authorize: authorize,
    revokeAuth: revokeAuth,
    isAuthorized: isAuthorized
  };

  function isAuthorized () {
    return settings.getAccessToken(accessTokenType).then(function (token) {
      return token != null;
    });
  }

  function authorize () {
    var promise = $q.defer();

    var url = 'https://login.live.com/oauth20_authorize.srf' +
              '?client_id=000000004817355D' +
              '&scope=' + encodeURIComponent('onedrive.readonly') +
              '&response_type=token' +
              '&redirect_uri=' + encodeURIComponent(chrome.identity.getRedirectURL('onedrive'));

    var promise = chrome.p.identity.launchWebAuthFlow({url: url, interactive: true}).then(function (redirectUrl) {
      var authInfo = parseAuthInfoFromUrl(redirectUrl);
      if (authInfo === null) {
        return Promise.reject('Failed to extract authentication information from redirect url');
      }

      settings.saveAccessToken(accessTokenType, authInfo.access_token);
      return Promise.resolve();
    });
    return $q.when(promise);
  }

  function listDatabases () {
    var promise = getToken()
      .then(searchFiles)
      .then(filterFiles)
      .catch(function (response) {
        // token expired
        if (response.status && response.status == 401) {
          return authorize().then(listDatabases);
        } else {
        	return [];
        }
      });

    return $q.when(promise);
  }

  function searchFiles (token) {
    // there is no proper way of searching for file-extensions right now (?), so we search for files containing kdb and filter ourselves afterwards
    var query = encodeURIComponent('kdb');
    var filter = encodeURIComponent('file ne null');
    var url = 'https://api.onedrive.com/v1.0/drive/root/view.search?q=' + query + '&filter=' + filter;
    return $http({ method: 'GET', url: url, headers: { Authorization: 'Bearer ' + token }});
  }

  function filterFiles (response) {
    if (!response.data.value) {
      return Promise.reject('Unexpected response from OneDrive API');
    }

    // only return files that have a .kdb or .kdbx extension
    var files = response.data.value.filter(function (file) {
      return file.name && /\.kdbx?$/.exec(file.name);
    });

    return files.map(transformFile);
  }

  function parseAuthInfoFromUrl (url) {
    var hash = /#(.+)$/.exec(url);
    if (!hash) {
      return null;
    }

    hash = hash[1];
    return JSON.parse('{"' + hash.replace(/&/g, '","').replace(/=/g, '":"') + '"}', function(key, value) {
      return key === "" ? value : decodeURIComponent(value);
    });
  }

  function transformFile (file) {
    var path = "";
    if (file.parentReference) {
      // path will be e.g. "/drive/root:/Documents"
      path = file.parentReference.path;

      // extract the part after the colon, so "/Documents"
      var split = /:(.+)$/.exec(path);
      if (split) {
        path = split[1];
      }

      if (!/\/$/.exec(path)) {
        // append trailing slash, if there was none
        path += "/";
      }
    }

    return {
      url: file['@content.downloadUrl'],
      title: path + file.name
    }
  }

  //get the minimum information needed to identify this file for future retrieval
  function getDatabaseChoiceData(dbInfo) {
    return {
      url: dbInfo.url,
      title: dbInfo.title
    };
  }

  //given minimal file information, retrieve the actual file
  function getChosenDatabaseFile(dbInfo) {
    return getToken()
      .then(function (token) {
        return loadFile(dbInfo, token);
      })
      .then(function (response) {
        return response.data;
      })
      .catch(function (response) {
        // token expired
        if (response.status && response.status == 401) {
          return authorize().then(function () {
            return getChosenDatabaseFile(dbInfo);
          });
        }
      });
  }

  function loadFile (dbInfo, token) {
    var id = encodeURIComponent(dbInfo.id);
    return $http({
      method: 'GET',
      url: dbInfo.url,
      responseType: 'arraybuffer',
      headers: {
        Authorization: 'Bearer ' + token
      }
    });
  }

  function revokeAuth () {
    settings.saveAccessToken(accessTokenType, null);
    var promise = chrome.p.identity.launchWebAuthFlow({url: 'https://login.live.com/oauth20_logout.srf'});
    return $q.when(promise);
  }

  function getToken () {
    return settings.getAccessToken(accessTokenType).then(function (token) {
      if (!token) {
        return Promise.reject('No access token available. Did you authorize yet?');
      }
      return token;
    });
  }

  return exports;
}


<|start_filename|>services/unlockedState.js<|end_filename|>
/**

The MIT License (MIT)

Copyright (c) 2015 <NAME>.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

*/

"use strict";

/**
 * Shared state and methods for an unlocked password file.
 */
function UnlockedState($interval, $location, keepassReference, protectedMemory, settings) {
	var my = {
		tabId: "", //tab id of current tab
		url: "", //url of current tab
		title: "", //window title of current tab
		origin: "", //url of current tab without path or querystring
		sitePermission: false, //true if the extension already has rights to autofill the password
		entries: null, //filtered password database entries
		clipboardStatus: "" //status message about clipboard, used when copying password to the clipboard
	};
	var copyEntry;

	//determine current url:
	my.getTabDetails = function() {
		return new Promise(function(resolve, reject) {
			chrome.tabs.query({
				active: true,
				currentWindow: true
			}, function(tabs) {
				if (tabs && tabs.length) {
					my.tabId = tabs[0].id;
					var url = tabs[0].url.split('?');
					my.url = url[0];
					my.title = tabs[0].title;

					var parsedUrl = parseUrl(tabs[0].url);
					my.origin = parsedUrl.protocol + '//' + parsedUrl.hostname + '/';

					chrome.p.permissions.contains({
						origins: [my.origin]
					})
						.then(function() {
							my.sitePermission = true;
						})
						.catch(function(err) {
							my.sitePermission = false;
						})
						.then(function() {
							resolve();
						})
				} else {
					reject(new Error("Unable to determine tab details"));
				}
			});
		});
	};

	my.clearBackgroundState = function() {
		my.entries = null;
		my.clipboardStatus = "";
	}
	$interval(my.clearBackgroundState, 60000, 1);  //clear backgroundstate after 10 minutes live - we should never be alive that long

	my.autofill = function(entry) {
		settings.getUseCredentialApiFlag().then(useCredentialApi => {
			chrome.runtime.sendMessage({
				m: "requestPermission",
				perms: {
					origins: [my.origin]
				},
				then: {
					m: "autofill",
					tabId: my.tabId,
					u: entry.userName,
					p: getPassword(entry),
					o: my.origin,
					uca: useCredentialApi
				}
			});

			window.close(); //close the popup
		})
	}

	//get clear-text password from entry
	function getPassword(entry) {
		return keepassReference.getFieldValue(entry, 'password', my.entries);
	}

	my.copyPassword = function(entry) {
		copyEntry = entry;
		document.execCommand('copy');
	}

	my.gotoDetails = function(entry) {
		$location.path('/entry-details/' + entry.id);
	}

	my.getDecryptedAttribute = function(entry, attributeName) {
		return keepassReference.getFieldValue(entry, attributeName, my.entries);
	}

	//listens for the copy event and does the copy
	document.addEventListener('copy', function(e) {
		if (!copyEntry) {
			return; //listener can get registered multiple times
		}

		var textToPutOnClipboard = getPassword(copyEntry);
		copyEntry = null;
		e.clipboardData.setData('text/plain', textToPutOnClipboard);
		e.preventDefault();

		settings.setForgetTime('clearClipboard', Date.now() + 1 * 60000)
		chrome.alarms.clear("forgetStuff", function() {
			//reset alarm timer so that it fires about 1 minute from now
			chrome.alarms.create("forgetStuff", {
				delayInMinutes: 1,
				periodInMinutes: 10
			});
		})

		chrome.runtime.sendMessage({
			m: "showMessage",
			text: 'Password copied to clipboard.  Clipboard will clear in 60 seconds.'
		});

		window.close(); //close the popup
	});

	function parseUrl(url) {
		//from https://gist.github.com/jlong/2428561
		var parser = document.createElement('a');
		parser.href = url;

		/*
    parser.protocol; // => "http:"
    parser.hostname; // => "example.com"
    parser.port;     // => "3000"
    parser.pathname; // => "/pathname/"
    parser.search;   // => "?search=test"
    parser.hash;     // => "#hash"
    parser.host;     // => "example.com:3000"
    */

		return parser;
	}

	return my;
}


<|start_filename|>options/partials/manageKeyFiles.html<|end_filename|>
<fieldset>
  <!--
  <legend>
    <icon p="icon-key2"></icon> Key Files
  </legend>
-->
  Key files are an optional authentication method.
  More info on key files is available on the <a href="http://keepass.info/help/base/keys.html#keyfiles" target="_blank">KeePass site</a>
  <br /><br />
  CKP can store your key files locally in Chrome storage, and apply them when opening your password database.
  <br />
  <br />Websites and other Chrome extensions do not have access to these files.  However, they are stored
  unencrypted in your local Chrome profile and someone with access to your device could read them.

  <keyfiles>
    <ul>
      <li ng-repeat="keyFile in keyFiles">
        <!--<icon p="icon-key2"></icon>-->{{keyFile.name}}
        <a class="removeFile" title="Remove" ng-click="removeKeyFile(keyFile)">
          <icon p="icon-times-circle"></icon>
        </a>
      </li>
    </ul>
  </keyfiles>
  <errorMessage ng-show="errorMessage">{{errorMessage}}</errorMessage>
  <input type="file" style="display:none;" id="file" name='file' file-change="handleKeyFile" />
  <a class="pure-button fill pure-button-primary" ng-click="selectFile()">
    <!--<icon p="icon-key2"></icon>-->Add Key File
  </a>

</fieldset>


<|start_filename|>options/controllers/dragDropController.js<|end_filename|>
"use strict";

function DragDropController($scope, localChromePasswordFileManager) {
  $scope.files = [];
  $scope.acknowledgedLimitations = false;

  $scope.selectFile = function() {
    document.getElementById('file').click();
  };

  $scope.acknowledgeLimitations = function() {
  	$scope.acknowledgedLimitations = true;
  }

  $scope.handleDrop = function(filePromises) {
    $scope.errorMessage = "";
    $scope.loadedFiles = 0;
    filePromises.forEach(function(filePromise) {
      filePromise.then(function(info) {
        if (info.file.name.indexOf('.kdb') < 0 || info.file.size < 70) {
          $scope.errorMessage += info.file.name + " is not a valid KeePass file. "
          return;
        }

        var fi = {
          title: info.file.name,
          lastModified: info.file.lastModified,
          lastModifiedDate: info.file.lastModifiedDate,
          size: info.file.size,
          type: info.file.type,
          data: Base64.encode(info.data)
        }

        var existingIndex = null;
        $scope.files.forEach(function(existingFi, index) {
          if (existingFi.title == fi.title) {
            existingIndex = index;
          }
        });

        if (existingIndex == null) {
          //add
          $scope.files.push(fi);
        } else {
          //replace
          $scope.files[existingIndex] = fi;
        }

        $scope.loadedFiles += 1;

        return fi;
      }).then(function(fi) {
        return localChromePasswordFileManager.saveDatabase({
          title: fi.title,
          data: fi.data,
          lastModified: fi.lastModified
        });
      })
    });

    Promise.all(filePromises).then(function() {
      $scope.$apply();
    });
  };

  $scope.removePasswordFile = function(fi) {
    localChromePasswordFileManager.deleteDatabase(fi).then(function() {
      return localChromePasswordFileManager.listDatabases();
    }).then(function(files) {
      $scope.loadedFiles = 0;
      $scope.files = files;
      $scope.$apply();
    })
  };

  localChromePasswordFileManager.listDatabases().then(function(files) {
    $scope.files = files;
    $scope.$apply();
  });
}


<|start_filename|>services/keepassHeader.js<|end_filename|>
/**

The MIT License (MIT)

Copyright (c) 2015 <NAME>.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

*/

"use strict";

function KeepassHeader() {
  var my = {
    readHeader: readHeader
  };

  var AES_CIPHER_UUID = new Uint8Array([0x31,0xc1,0xf2,0xe6,0xbf,0x71,0x43,0x50,0xbe,0x58,0x05,0x21,0x6a,0xfc,0x5a,0xff]);
  var littleEndian = (function() {
    var buffer = new ArrayBuffer(2);
    new DataView(buffer).setInt16(0, 256, true);
    return new Int16Array(buffer)[0] === 256;
  })();

  function readHeader(buf) {
    var sigHeader = new DataView(buf, 0, 8)
    var h = {
      sigKeePass: sigHeader.getUint32(0, littleEndian),
      sigKeePassType: sigHeader.getUint32(4, littleEndian)
    };

    var DBSIG_KEEPASS = 0x9AA2D903;
    var DBSIG_KDBX = 0xB54BFB67,
      DBSIG_KDBX_ALPHA = 0xB54BFB66,
      DBSIG_KDB = 0xB54BFB55,
      DBSIG_KDB_NEW = 0xB54BFB65;
    var VERSION_KDBX = 3;
    if (h.sigKeePass != DBSIG_KEEPASS || (h.sigKeePassType != DBSIG_KDBX && h.sigKeePassType != DBSIG_KDBX_ALPHA && h.sigKeePassType != DBSIG_KDB && h.sigKeePassType != DBSIG_KDB_NEW)) {
      //fail
      console.log("Signature fail.  sig 1:" + h.sigKeePass.toString(16) + ", sig2:" + h.sigKeePassType.toString(16));
      throw new Error('This is not a valid KeePass file - file signature is not correct.')
    }

    if (h.sigKeePassType == DBSIG_KDBX || h.sigKeePassType == DBSIG_KDBX_ALPHA) {
      readKdbxHeader(buf, 8, h);
    } else {
      readKdbHeader(buf, 8, h);
    }

    //console.log(h);
    //console.log("version: " + h.version.toString(16) + ", keyRounds: " + h.keyRounds);
    return h;
  }

  function readKdbHeader(buf, position, h) {
    var FLAG_SHA2             = 1;
    var FLAG_RIJNDAEL         = 2;
    var FLAG_ARCFOUR          = 4;
    var FLAG_TWOFISH          = 8;

    var dv = new DataView(buf, position, 116);
    var flags = dv.getUint32(0, littleEndian);
    if (flags & FLAG_RIJNDAEL != FLAG_RIJNDAEL) {
      throw new Error('We only support AES (aka Rijndael) encryption on KeePass KDB files.  This file is using something else.');
    }

    try {
	    h.cipher = AES_CIPHER_UUID;
	    h.majorVersion = dv.getUint16(4, littleEndian);
	    h.minorVersion = dv.getUint16(6, littleEndian);
	    h.masterSeed = new Uint8Array(buf, position + 8, 16);
	    h.iv = new Uint8Array(buf, position + 24, 16);
	    h.numberOfGroups = dv.getUint32(40, littleEndian);
	    h.numberOfEntries = dv.getUint32(44, littleEndian);
	    h.contentsHash = new Uint8Array(buf, position + 48, 32);
	    h.transformSeed = new Uint8Array(buf, position + 80, 32);
	    h.keyRounds = dv.getUint32(112, littleEndian);

	    //constants for KDB:
	    h.keyRounds2 = 0;
	    h.compressionFlags = 0;
	    h.protectedStreamKey = window.crypto.getRandomValues(new Uint8Array(16));  //KDB does not have this, but we will create in order to protect the passwords
	    h.innerRandomStreamId = 0;
	    h.streamStartBytes = null;
	    h.kdb = true;

	    h.dataStart = position + 116;  //=124 - the size of the KDB header
    } catch (err) {
    	throw new Error('Failed to parse KDB file header - file is corrupt or format not supported');
    }
  }

  function readKdbxHeader(buf, position, h) {
    try {
	    var version = new DataView(buf, position, 4)
	    h.majorVersion = version.getUint16(0, littleEndian);
	    h.minorVersion = version.getUint16(2, littleEndian);
	    position += 4;

	    var done = false;
	    while (!done) {
	      var descriptor = new DataView(buf, position, 3);
	      var fieldId = descriptor.getUint8(0, littleEndian);
	      var len = descriptor.getUint16(1, littleEndian);

	      var dv = new DataView(buf, position + 3, len);
	      //console.log("fieldid " + fieldId + " found at " + position);
	      position += 3;
	      switch (fieldId) {
	        case 0: //end of header
	          done = true;
	          break;
	        case 2: //cipherid, 16 bytes
	          h.cipher = new Uint8Array(buf, position, len);
	          break;
	        case 3: //compression flags, 4 bytes
	          h.compressionFlags = dv.getUint32(0, littleEndian);
	          break;
	        case 4: //master seed
	          h.masterSeed = new Uint8Array(buf, position, len);
	          break;
	        case 5: //transform seed
	          h.transformSeed = new Uint8Array(buf, position, len);
	          break;
	        case 6: //transform rounds, 8 bytes
	          h.keyRounds = dv.getUint32(0, littleEndian);
	          h.keyRounds2 = dv.getUint32(4, littleEndian);
	          break;
	        case 7: //iv
	          h.iv = new Uint8Array(buf, position, len);
	          break;
	        case 8: //protected stream key
	          h.protectedStreamKey = new Uint8Array(buf, position, len);
	          break;
	        case 9:
	          h.streamStartBytes = new Uint8Array(buf, position, len);
	          break;
	        case 10:
	          h.innerRandomStreamId = dv.getUint32(0, littleEndian);
	          break;
	        default:
	          break;
	      }

	      position += len;
	    }

	    h.kdbx = true;
	    h.dataStart = position;
    } catch (err) {
    	throw new Error('Failed to parse KDBX file header - file is corrupt or format not supported');
    }
  }

  return my;
}


<|start_filename|>services/streamCipher.js<|end_filename|>
/**

The MIT License (MIT)

Copyright (c) 2015 <NAME>.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
 */

/*
 * Encapsulates decoding and encoding of protected data using the Salsa20 stream cipher.
 */
function StreamCipher() {
	"use strict";

	var my = {
		setKey: setKey,
		getKey: getKey,
		encryptString: encryptString,
		getDecryptedFieldValue: getDecryptedFieldValue,
		clear: clear,
		position: 0
	};

	var salsa, plainKey;

	/*
	 * Init the cipher with the given 32 byte key (Array or UInt8Array or ArrayBuffer)
	 */
	function setKey(key) {
		plainKey = key;
		var streamKey = new Uint8Array(key);
    var iv = [0xE8, 0x30, 0x09, 0x4B, 0x97, 0x20, 0x5D, 0x2A];
    salsa = new Salsa20(streamKey, iv);
    my.position = 0;
	}

	function getKey() {
		return plainKey;
	}

	function encryptString(plainText) {
 		var encoder = new TextEncoder();
    var bytes = encoder.encode(plainText);
    var encBytes = salsa.encrypt(new Uint8Array(bytes));

    my.position += encBytes.length;  //advance the stream position

    return encBytes;
	}

	function clear() {
		salsa = null;
		my.position = 0;
		plainKey = null;
	}

	/*
	 * Return the unencrypted data for this field
	 */
  function getDecryptedFieldValue(currentEntry, fieldName) {
  	if (currentEntry.protectedData === undefined || !currentEntry.protectedData[fieldName])
  		return currentEntry[fieldName] || "";  //not an encrypted field

 		var decoder = new TextDecoder();
  	var protectedData = currentEntry.protectedData[fieldName];
  	salsa.reset();
    salsa.getBytes(protectedData.position);
    var decryptedBytes = new Uint8Array(salsa.decrypt(protectedData.data));
    var result = decoder.decode(decryptedBytes);

    return result;
  }

	return my;
}


<|start_filename|>options/controllers/storedData.js<|end_filename|>
"use strict";

function StoredDataController($scope) {
  Promise.all([refreshLocalStorage(), refreshSyncStorage(), refreshPermissions()]).then(function() {
    $scope.$apply();
  })

  function refreshLocalStorage() {
    $scope.localData = [];
    return chrome.p.storage.local.get(null).then(function(items) {
      for (var name in items) {
        var entry = {
          key: name,
          data: items[name]
        };

        $scope.localData.push(entry);
      }
    });
  }

  function refreshSyncStorage() {
    $scope.syncData = [];
    return chrome.p.storage.sync.get(null).then(function(items) {
      for (var name in items) {
        var entry = {
          key: name,
          data: items[name]
        };

        $scope.syncData.push(entry);
      }
    });
  }

  function refreshPermissions() {
    $scope.permissions = {};
    return chrome.p.permissions.getAll().then(function(perms) {
      $scope.permissions = perms;
    });
  }

  $scope.deleteLocalData = function(key) {
    chrome.p.storage.local.remove(key).then(function() {
      return refreshLocalStorage();
    }).then(function() {
      $scope.$apply();
    });
  }

  $scope.deleteOriginPermission = function(origin) {
    chrome.p.permissions.remove({
      "origins": [origin]
    }).then(function() {
      return refreshPermissions();
    }).then(function() {
      $scope.$apply();
    })
  }
}


<|start_filename|>options/controllers/settingsController.js<|end_filename|>
'use strict';

function SettingsController($scope, settings) {
    $scope.settings = {};

    settings.getPasswordListIconOption().then(function(option) {
        $scope.settings.PasswordListIconOption = option;
    });    
    
    settings.getPasswordListGroupOption().then(function(option) {
        $scope.settings.PasswordListGroupOption = option;
    });

    $scope.saveSettings = function() {
  	    Object.keys($scope.settings).forEach(function(setting) {
            if (typeof settings['set' + setting] === 'function') {
                settings['set' + setting]($scope.settings[setting]);
            }
        });
    }
}


<|start_filename|>options/partials/startup.html<|end_filename|>
<fieldset>
  CKP is an extension that uses your existing KeePass database files to autofill passwords on websites.
  <br>
  <br> The extension is only visible when you go to a web page that has input fields. When it activates, it shows a small KeePass icon on the right hand side of the browser address bar. Clicking on the icon will give you options to connect to your KeePass
  database, unlock it, and then autofill in the password on the website.
  <br>
  <br>
  <pageInfo ng-hide="alreadyChoseDb">
    To continue, you should...
    <ol>
      <li>
        <a href="#/databases">Click on the "KeePass Databases" menu</a> to show it where to find a KeePass database.
      </li>
      <li>
        Go to a website with a login page, and try it out. (<a href="http://keepass.info/help/kb/testform.html" target="_blank">for example</a>)
      </li>
    </ol>
  </pageInfo>
  If your database requires a key file then you should also <a href="#/keyFiles">click on the "Key Files" menu</a> to load your key file.
</fieldset>


<|start_filename|>services/protectedMemory.js<|end_filename|>
/**

The MIT License (MIT)

Copyright (c) 2015 <NAME>.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

*/

"use strict";

/**
 * Storage in RAM, just not in the clear.  Purpose is to prevent seeing the
 * contents in a casual scan of RAM.  Does not prevent an attacker with direct
 * access to the code from reading the contents.
 */
function ProtectedMemory() {
  var my = {
    getData: getData,
    setData: setData,
    clearData: clearData,
    serialize: serialize,      //not encrypted
    deserialize: deserialize,  //not encrypted
    hydrate: deserialize       //not encrypted
  }

  var enckey, keyPromise;
  var dataMap = {};
  var AES = {
    name: "AES-CBC",
    iv: window.crypto.getRandomValues(new Uint8Array(16))
  };

  keyPromise = initNewKey();
  function initNewKey() {
    return window.crypto.subtle.generateKey({name: "AES-CBC", length: 256}, false, ["encrypt", "decrypt"]).then(function(k) {
      enckey = k;
    });
  }

  function getData(key) {
    var encData = dataMap[key];
    if (encData === undefined || typeof(encData) !== 'string')
      return Promise.resolve(undefined);

    return keyPromise.then(function() {
      var encBytes = Base64.decode(encData);
      return window.crypto.subtle.decrypt(AES, enckey, encBytes);
    }).then(function(data) {
      var decoder = new TextDecoder();
      var decoded = decoder.decode(new Uint8Array(data));
      var parsed = JSON.parse(decoded)
      return dePrepData(parsed);
    });
  }

  function setData(key, data) {
    var preppedData = prepData(data);
    var encoder = new TextEncoder();
    var dataBytes = encoder.encode(JSON.stringify(preppedData));
    return keyPromise.then(function() {
      return window.crypto.subtle.encrypt(AES, enckey, dataBytes);
    }).then(function(encData) {
      var dataString = Base64.encode(encData);
      dataMap[key] = dataString;
      return Promise.resolve();
    });
  }

  function clearData() {
    dataMap = {};
    keyPromise = initNewKey();
    return keyPromise
  }

  function serialize(data) {
    var preppedData = prepData(data);
    var encoder = new TextEncoder();
    var dataBytes = encoder.encode(JSON.stringify(preppedData));
    return Base64.encode(dataBytes);
  }

  function deserialize(serializedData) {
    if (serializedData === undefined || typeof(serializedData) !== 'string')
      return undefined;

    var dataBytes = Base64.decode(serializedData);
    var decoder = new TextDecoder();
    var decoded = decoder.decode(new Uint8Array(dataBytes));
    var parsed = JSON.parse(decoded)
    return dePrepData(parsed);
  }

  /**
  * Prep data for serializing by converting ArrayBuffer properties to base64 properties
  * Also makes a deep copy, so what is returned is not the original.
  */
  var randomString = "Ựៅ" // Base64.encode(window.crypto.getRandomValues(new Uint8Array(4)));
  function prepData(data) {
    if (data === null || data === undefined || typeof (data) !== 'object')
      return data;

    if (data.constructor == ArrayBuffer || data.constructor == Uint8Array) {
      return randomString + Base64.encode(data);
    } else if (data.constructor == Array) {
      var newArray = new Array(data.length);
      for(var i=0; i<data.length; i++) {
        newArray[i] = prepData(data[i]);
      }
      return newArray;
    } else {
      var newObject = {};
      for(var prop in data) {
        newObject[prop] = prepData(data[prop]);
      }
      return newObject;
    }
  }

  function dePrepData(data) {
    if (data === null || data === undefined || (typeof (data) !== 'object' && typeof(data) !== 'string'))
      return data;

    if (typeof(data) === "string") {
      if (data.indexOf(randomString) == 0) {
        data = data.slice(randomString.length);
        return new Uint8Array(Base64.decode(data));
      } else {
        return data;
      }
    } else if (data.constructor == Array) {
      for(var i=0; i<data.length; i++) {
        data[i] = dePrepData(data[i]);
      }
    } else {
      for(var prop in data) {
        data[prop] = dePrepData(data[prop]);
      }
    }

    return data;
  }

  return my;
}


<|start_filename|>options/controllers/navController.js<|end_filename|>
"use strict";

function NavController($scope, $location) {
  $scope.isActive = function (viewLocation) {
    return viewLocation === $location.path();
  };
}


<|start_filename|>options/controllers/sample-database.js<|end_filename|>
"use strict";

function SampleDatabaseController($scope, sampleDatabaseFileManager) {

  $scope.useSample = false;
  sampleDatabaseFileManager.getActive().then(function(isActive) {
    $scope.useSample = isActive;
    $scope.$apply();
  });

  $scope.updateSampleFlag = function() {
    sampleDatabaseFileManager.setActive($scope.useSample)
  }
}


<|start_filename|>lib/chrome-api-promise.js<|end_filename|>
/**

The MIT License (MIT)

Copyright (c) 2015 <NAME>.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

*/

chrome.p = (function() {
  my = {
    permissions: {
      "contains": permissionContains,
      "request": permissionRequest,
      "remove": permissionRemove,
      "getAll": permissionsGetAll
    },
    identity: {
    	"launchWebAuthFlow": launchWebAuthFlow,
    	"getAuthToken": getAuthToken,
    	"removeCachedAuthToken": removeCachedAuthToken
    },
    storage: {
      sync: {
        "set": storageSyncSet,
        "get": storageSyncGet,
        "remove": storageSyncRemove
      },
      local: {
        "set": storageLocalSet,
        "get": storageLocalGet,
        "remove": storageLocalRemove
      }
    }
  };

	function removeCachedAuthToken(details) {
  	return new Promise(function(resolve, reject) {
  		chrome.identity.removeCachedAuthToken(details, function() {
  			if (chrome.runtime.lastError) 
  				reject(new Error(chrome.runtime.lastError.message));
  			else
  				resolve();
  		});
  	});
	} 

  function getAuthToken(interactive) {
  	return new Promise(function(resolve, reject) {
  		chrome.identity.getAuthToken(interactive, function(token) {
  			if (chrome.runtime.lastError) 
  				reject(new Error(chrome.runtime.lastError.message));
  			else if (token) 
  				resolve(token);
  			else
  				reject(new Error("Failed to retrieve Google login token"));
  		});
  	});
  }

  function launchWebAuthFlow(params) {
  	return new Promise(function(resolve, reject) {
  		chrome.identity.launchWebAuthFlow(params, function(redirect_url) {
  			if (chrome.runtime.lastError) {
  				reject(new Error(chrome.runtime.lastError.message));
  			} else if (redirect_url) {
  				resolve(redirect_url);
  			} else {
  				reject(new Error("Unknown error in oauth2 web flow"));
  			}
  		});
  	});
  }

  function permissionsGetAll() {
    return new Promise(function(resolve, reject) {
      chrome.permissions.getAll(function(perms) {
        if (chrome.runtime.lastError) {
          reject(new Error(chrome.runtime.lastError.message));
        } else {
          resolve(perms);
        }
      });
    });
  }

  function permissionRemove(perms) {
    return new Promise(function(resolve, reject) {
      chrome.permissions.remove(perms, function(removed) {
        if (removed) {
          // The permissions have been removed.
          resolve();
        } else {
          // The permissions have not been removed (e.g., you tried to remove
          // required permissions).
          if (chrome.runtime.lastError) {
            reject(new Error(chrome.runtime.lastError.message));
          } else {
            reject(new Error("Failed to remove permission"));
          }
        }
      });
    });
  }

  function permissionContains(perms) {
    return new Promise(function(resolve, reject) {
      chrome.permissions.contains(perms, function(hasPermission) {
        if (hasPermission) {
          resolve();
        } else {
          if (chrome.runtime.lastError) {
            reject(new Error(chrome.runtime.lastError.message));
          } else {
            reject(new Error("Requested permission not present"));
          }
        }
      });
    });
  }

  function permissionRequest(perms) {
    return new Promise(function(resolve, reject) {
      chrome.permissions.request(perms, function(granted) {
        if (granted) {
          resolve();
        } else {
          if (chrome.runtime.lastError) {
            reject(new Error(chrome.runtime.lastError.message));
          } else {
            reject(new Error("Failed to grant permission"));
          }
        }
      });
    });
  }

  function storageLocalRemove(key) {
    return storageRemove(chrome.storage.local, key);
  }
  function storageSyncRemove(key) {
    return storageRemove(chrome.storage.sync, key);
  }

  function storageRemove(storageArea, key) {
    return new Promise(function(resolve, reject) {
      storageArea.remove(key, function() {
        if (chrome.runtime.lastError) {
          reject(new Error(chrome.runtime.lastError.message));
        } else {
          resolve();
        }
      });
    })
  }

  function storageSet(storageArea, data) {
    return new Promise(function(resolve, reject) {
    	storageArea.set(data, function() {
    	  if (chrome.runtime.lastError) {
    	    reject(new Error(chrome.runtime.lastError.message));
    	  } else {
    		  resolve();
    	  }
  		});
    })
  }

  function storageGet(storageArea, keys) {
    return new Promise(function(resolve, reject) {
      storageArea.get(keys, function(items) {
    	  if (chrome.runtime.lastError) {
    	    reject(new Error(chrome.runtime.lastError.message));
    	  } else {
    		  resolve(items);
    	  }
    	});
    });
  }

  function storageSyncSet(data) {
    return storageSet(chrome.storage.sync, data);
  }
  function storageSyncGet(keys) {
    return storageGet(chrome.storage.sync, keys);
  }
  function storageLocalSet(data) {
    return storageSet(chrome.storage.local, data);
  }
  function storageLocalGet(keys) {
    return storageGet(chrome.storage.local, keys);
  }

  return my;
})();


<|start_filename|>services/keepassReference.js<|end_filename|>
/**

The MIT License (MIT)

Copyright (c) 2015 <NAME>.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

 */

/**
 * Service for resolving keepass references
 */
function KeepassReference(streamCipher) {
	"use strict";

	var my = {};

	my.hasReferences = function(fieldValue) {
		return !!/\{.+\}/.test(fieldValue || '');
	}

	/*
	 * Process all references found in fieldValue to their final values
	 */
	my.processAllReferences = function(fieldValue, currentEntry, allEntries) {
		var re = /(\{[^\{\}]+\})/g;
		var expressions = re.exec(fieldValue || '');
		if (!expressions) return fieldValue;  //no references
		
		var result = '', lastIndex = 0;
		while (expressions) {
			if (expressions.index >= lastIndex) {
				result += fieldValue.substring(lastIndex, expressions.index);
			}
			result += resolveReference(expressions[1], currentEntry, allEntries);
			lastIndex = expressions.index + expressions[1].length;
			expressions = re.exec(fieldValue || '');
		}

		if (lastIndex < fieldValue.length) {
			result += fieldValue.substring(lastIndex, fieldValue.length);
		}
		
		return result;
	}

	my.getFieldValue = function(currentEntry, fieldName, allEntries) {
		var plainText = streamCipher.getDecryptedFieldValue(currentEntry, fieldName);
		return my.processAllReferences(plainText, currentEntry, allEntries);
	}

	function resolveReference(referenceText, currentEntry, allEntries) {
		var localParts = /^\{([a-zA-Z]+)\}$/.exec(referenceText)
		if (localParts) {
			// local field
			switch (localParts[1].toUpperCase()) {
				case 'TITLE': return currentEntry.title; 
				case 'USERNAME': return currentEntry.userName;
				case 'URL': return currentEntry.url;
				case 'NOTES': return currentEntry.notes;
				case 'PASSWORD': return currentEntry.password;
			}
		}

		var customLocalString = /^\{S:([a-zA-Z]+)\}$/.exec(referenceText)
		if (customLocalString) {
			var camelCase = Case.camel(customLocalString[1])
			return currentEntry[camelCase];
		}

		var refString = /^\{REF:(T|U|P|A|N|I)@(T|U|P|A|N|I|O):(.+)\}$/.exec(referenceText);
		if (refString) {
			var wantedField = getPropertyNameFromCode(refString[1]);
			var searchIn = getPropertyNameFromCode(refString[2]);
			var text = refString[3];

			var matches = allEntries.filter(function(e) {
				if (searchIn === '*') {
					var customFieldMatches = e.keys.filter(function(key) {
						return String(e[key] || '').indexOf(text) !== -1;
					});
					return customFieldMatches.length > 0;
				} else {
					return String(e[searchIn] || '').indexOf(text) !== -1;
				}
			});
			if (matches.length) {
				return streamCipher.getDecryptedFieldValue(matches[0], wantedField);
			}
		}

		return referenceText;
	}

	my.resolveReference = resolveReference;

	function getPropertyNameFromCode(code) {
		switch (code) {
			case 'T': return 'title';
			case 'U': return 'userName';
			case 'P': return 'password';
			case 'A': return 'url';
			case 'N': return 'notes';
			case 'I': return 'id';
			case 'O': return '*';
		}

		return '';
	}

	return my;
}


<|start_filename|>services/optionsLink.js<|end_filename|>
"use strict";

//simple service to link to the options page
function OptionsLink() {
  var exports = {
    go: go
  }

  function go() {
    chrome.runtime.openOptionsPage();
  }

  return exports;
}


<|start_filename|>popups/controllers/settingsLink.js<|end_filename|>

"use strict";

function SettingsLinkController($scope, $location, optionsLink) {
  $scope.showSettingsPage = function() {
    optionsLink.go();
  }
}


<|start_filename|>options/partials/choose-file.html<|end_filename|>
<docs ng-cloak>
  <div ng-hide="authorized() || refreshing">
    If you have already-existing KeePass (.kdbx or .kdb) files stored in your google drive, then you can access them.
    <br><br>
    Chrome will ask you to allow this extension to access the Google Drive websites, and Google Drive will ask you to allow this extension to read your files.
    <br><br>
  </div>
  <pageInfo ng-show="requestingUrl">CKP needs to access https://www.googleapis.com/ and https://accounts.google.com/ in order to continue.  Please press "Allow"" on the dialog.</pageInfo>
  <pageInfo ng-show="requestingDriveAccess">CKP needs to view the files in your Google Drive in order to continue.  Please press "Accept" on the dialog.</pageInfo>
  <a class="pure-button fill pure-button-primary" ng-hide="authorized() || refreshing" ng-click="authorize()">
    <icon p="icon-google"></icon>Authorize
  </a>
  <errorMessage ng-show="errorMessage">{{errorMessage}}</errorMessage>
  <fieldset ng-show="authorized() && docs && docs.length">
		<infoMessage>The following files are now accessible, and you will be able to choose one later (when logging into a website).  Unless you have a key file to load, CKP is setup and ready to use!</infoMessage>
    <filelist>
      <ul>
        <li ng-repeat="doc in docs" class="not-selectable">
          {{doc.title}}
        </li>
      </ul>
    </filelist>
  </fieldset>
  <errorMessage ng-show="!refreshing && authorized() && (docs == null || docs.length == 0)">CKP has accessed your drive but could not find any password files. Ensure your .kdbx or .kdb file is in your google drive.</errorMessage>
  <button class="pure-button" ng-click="refresh()" ng-show="authorized()" title="Refresh the list to see new files">
    <icon p="icon-google"></icon>Refresh
  </button>
  <button class="pure-button" ng-show="authorized()" ng-click="logout()" title="Remove CKP permission to access Google Drive.">
    <icon p="icon-lock"></icon>Deauthorize
  </button>
</docs>


<|start_filename|>options/partials/advanced.html<|end_filename|>
<hr />
<fieldset>
	<a href="https://w3c.github.io/webappsec-credential-management/">Chrome Credential API</a> allows JavaScript to interact in a limited way with the Chrome password manager.  If enabled in the browser, CKP can use this to encourage Chrome to remember the passwords you use so that they become available in your browser without needing to use CKP.  
	<br /><br />
	Chrome also syncs passwords between devices, so they should also become available on your mobile device or other computers.  

	<pageInfo ng-if="flagEnabled"><input id="useCredentialApi" type="checkbox" ng-model="flags.useCredentialApi" ng-change="updateUseCredentialApiFlag()"> <label for="useCredentialApi">Use the credential API to encourage Chrome to save your credentials when filling them</label></pageInfo>

	<pageInfo ng-if="!flagEnabled">Go to <i>chrome://flags/#enable-credential-manager-api</i> to enable the API.  Flag is currently DISABLED.  Come back here after it is enabled to enable CKP to use the API.</pageInfo>
</fieldset>
<hr />
<fieldset>
  Unless you use the "remember password" option, CKP can only keep your password file open in-memory for a very brief
  time, typically about 10 seconds after you close the popup.  This can be frustrating if you have a long
  password and you need to access several websites.
  <pageInfo>
    <input id="diskCache" type="checkbox" ng-model="flags.useDiskCache" ng-change="updateDiskCacheFlag()"> <label for="diskCache">Use encrypted cache to keep database open longer</label>
  </pageInfo>
  Check the box above to enable disk-caching.  With this
  option enabled, an unlocked database is encrypted and stored locally so that
  you don't have to re-type your password for each website.  The cache expires
  automatically after about 40 minutes.
  <br /><br />
  Technical information on this feature is <a href="https://github.com/perfectapi/ChromeKeePass/wiki/On-disk-caching-of-open-database" target="_blank">available on the CKP wiki</a>.
</fieldset>


<|start_filename|>tests/test.settings.js<|end_filename|>
describe('Settings', function () {
	"use strict";
	
	chrome.p = {
		storage: {
			local: {
				'set': function(storageObject) {
					return new Promise(function(resolve, reject) {
						for(let key in storageObject) {
							storage[key] = storageObject[key];
						}
						resolve(true);
					})
				},
				'get': function(key) {
					return new Promise(function(resolve, reject) {
						var result = {};
						if (Array.isArray(key)) {
							key.forEach(function(singleKey) {
								result[singleKey] = storage[singleKey];
							})
						} else {
							result[key] = storage[key];
						}

						resolve(result);
					})
				}
			}
		}
	}

	let storage = {};
	let settings = new Settings();

	afterEach(function() {
		storage = {};
	});

	describe('forget times', function() {
		it('should set a simple time', function() {
			return settings.setForgetTime(new Date());
		});

		it('should get back the time it sets', function() {
			var time = new Date();
			return settings.setForgetTime('test', time).then(function() {
				return settings.getForgetTime('test')
			}).then(function(returnedTime) {
				returnedTime.should.equal(time);
			});
		})

		it('should return undefined for invalidKey', function() {
			return settings.getForgetTime('test').then(function(value) {
				(value === undefined).should.be.true;
			})
		})

		it('should support update of remembered time', function() {
			let time1 = new Date();
			let time2 = new Date() + 1;
			return settings.setForgetTime('test', time1).then(function() {
				return settings.setForgetTime('test', time2)
			}).then(function() {
				return settings.getForgetTime('test')
			}).then(function(returnedTime) {
				returnedTime.should.equal(time2);
			});
		})

		it('should support multiple remembered times', function() {
			let time1 = new Date();
			let time2 = time1 + 1;
			return settings.setForgetTime('test1', time1).then(function() {
				return settings.setForgetTime('test2', time2);
			}).then(function() {
				return settings.getForgetTime('test2')
			}).then(function(returnedTime2) {
				returnedTime2.should.equal(time2);
			}).then(function() {
				return settings.getForgetTime('test1')
			}).then(function(returnedTime1) {
				returnedTime1.should.equal(time1);
			})
		})

		it('should support getting multiple at same time', function() {
			let time1 = new Date();
			let time2 = time1 + 1;
			return settings.setForgetTime('test1', time1).then(function() {
				return settings.setForgetTime('test2', time2);
			}).then(function() {
				return settings.getAllForgetTimes();
			}).then(function(allTimes) {
				allTimes.test1.should.equal(time1);
				allTimes.test2.should.equal(time2);
			})
		})

		it('should support deleting a key', function() {
			var time = new Date();
			return settings.setForgetTime('test', time).then(function() {
				return settings.clearForgetTimes(['test'])
			}).then(function() {
				return settings.getAllForgetTimes()
			}).then(function(allTimes) {
				(allTimes.test === undefined).should.be.true;
			});			
		})

		it('should not error on deleting invalid key', function() {
			return settings.clearForgetTimes(['invalidKey'])
		})
	})
	
});


<|start_filename|>options/partials/choose-onedrive-file.html<|end_filename|>

<login ng-if="!authorized && !busy">
    If you have already-existing KeePass (.kdbx or .kdb) files stored in your OneDrive, then you can access them.
    <br><br>
    Chrome will ask you to allow this extension to access your OneDrive, and OneDrive will ask you to allow this extension to read your files.
    <br><br>

    <a class="pure-button" ng-click="authorize()">Authorize</a>
</login>

<infoMessage ng-if="busy">Loading...</infoMessage>

<infoMessage ng-if="authorized && !busy && files.length == 0">No keepass databases found</infoMessage>

<fieldset ng-if="authorized && files && files.length > 0">
	<infoMessage ng-if="authorized && files && files.length > 0">The following files are now accessible, and you will be able to choose one later (when logging into a website).  Unless you have a key file to load, CKP is setup and ready to use!</infoMessage>

	<files>
	    <filelist>
	      <ul>
	        <li ng-repeat="file in files" class="not-selectable">
	          {{ file.title }}
	        </li>
	      </ul>
	    </filelist>
	</files>	
</fieldset>

<logout ng-if="authorized && !busy">
    <a class="pure-button" ng-click="deauthorize()">Deauthorize</a>
</logout>


<|start_filename|>services/secureCacheMemory.js<|end_filename|>
/**

The MIT License (MIT)

Copyright (c) 2015 <NAME>.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

*/

"use strict";

/**
 * Storage in background page memory.
 */
function SecureCacheMemory(protectedMemory) {
  var exports = {}

  var awaiting = [];
  var messageReceived;
  var notifyReady;
  var ready = new Promise(function(resolve) {
    notifyReady = resolve;
  });
  exports.ready = function() {
    return ready.then(function(port) {
      return true;
    });
  };

  var promise = new Promise(function(resolve, reject) {
    messageReceived = resolve;
  });

  //init.  get tabId and open a port
  chrome.tabs.query({
    active: true,
    currentWindow: true
  }, function(tabs) {
    if (tabs && tabs.length) {
      var port = chrome.runtime.connect({
        name: "tab" + tabs[0].id
      });

      notifyReady(port);
    }
  });

  ready.then(function(port) {
    port.onMessage.addListener(function(serializedSavedState) {
      //called from the background when we get a response, i.e. some saved state.
      var savedState = protectedMemory.hydrate(serializedSavedState);
      var notifier = awaiting.shift();
      notifier(savedState); //notify others
    });
  });

  //wake up the background page and get a pipe to send/receive messages:
  exports.get = function(key) {
    ready.then(function(port) {
      port.postMessage({
        action: 'get',
        key: key
      });
    });

    var p = new Promise(function(resolve) {
      awaiting.push(resolve);
    });

    return p; //will resolve when we get something
  }

  exports.clear = function() {
    return ready.then(function(port) {
      port.postMessage({
        action: 'clear'
      });
    })
  }

  exports.save = function(key, value) {
    return ready.then(function(port) {
      var serializedValue = protectedMemory.serialize(value);
      port.postMessage({
        action: 'save',
        key: key,
        value: serializedValue
      });
    })
  }

  return exports;
}


<|start_filename|>popups/controllers/passwordListController.js<|end_filename|>
'use strict';

function PasswordListController($scope, settings) {
    $scope.settings = {};

    settings.getPasswordListIconOption().then(function(option) {
        $scope.settings.PasswordListIconOption = option;
    });

    settings.getPasswordListGroupOption().then(function(option) {
        $scope.settings.PasswordListGroupOption = option;
    });
}

<|start_filename|>popups/popup.js<|end_filename|>
/**

The MIT License (MIT)

Copyright (c) 2015 <NAME>.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

 */

"use strict";

var keepassApp = angular.module('keepassApp', ['ngAnimate', 'ngRoute', 'ngSanitize']);

keepassApp.config(['$routeProvider', function($routeProvider) {
  $routeProvider.when('/choose-file', {
    templateUrl: chrome.extension.getURL('/popups/partials/choose-file.html'),
    controller: 'chooseFileController'
  }).when('/enter-password/:providerKey/:fileTitle', {
    templateUrl: chrome.extension.getURL('/popups/partials/enter-password.html'),
    controller: 'masterPasswordController'
  }).when('/find-entry', {
    templateUrl: chrome.extension.getURL('/popups/partials/find-entry.html'),
    controller: 'findEntryController'
  }).when('/startup', {
    templateUrl: chrome.extension.getURL('/popups/partials/startup.html'),
    controller: 'startupController'
  }).when('/entry-details/:entryId', {
    templateUrl: chrome.extension.getURL('/popups/partials/entry-details.html'),
    controller: 'entryDetailsController'
  }).otherwise({
    redirectTo: '/startup'
  });
}]);

keepassApp.factory('pako', function() {
  return pako;
});

keepassApp.factory('passwordFileStoreRegistry', ['localChromePasswordFileManager', 
  'dropboxFileManager', 
  'googleDrivePasswordFileManager', 
  'oneDriveFileManager',
  'sampleDatabaseFileManager', 
  function(localChromePasswordFileManager, 
    dropboxFileManager,
    googleDrivePasswordFileManager,
    oneDriveFileManager,
    sampleDatabaseFileManager) {
  return new PasswordFileStoreRegistry(localChromePasswordFileManager, dropboxFileManager, googleDrivePasswordFileManager, oneDriveFileManager, sampleDatabaseFileManager);
}]);

keepassApp.factory('sampleDatabaseFileManager', ['$http', function($http) {
	return new SampleDatabaseFileManager($http);
}]);

keepassApp.factory('dropboxFileManager', ['$http', 'settings', function($http, settings) {
	return new DropboxFileManager($http, settings);
}]);

keepassApp.factory('googleDrivePasswordFileManager', ['$http', '$timeout', function($http, $timeout) {
	return new GoogleDrivePasswordFileManager($http, $timeout);
}]);

keepassApp.factory('oneDriveFileManager', ['$http', '$q', 'settings', function($http, $q, settings) {
  return new OneDriveFileManager($http, $q, settings);
}]);

keepassApp.factory('localChromePasswordFileManager', [function() {
	return new LocalChromePasswordFileManager();
}]);

keepassApp.factory('optionsLink', [function() {
  return new OptionsLink();
}]);

keepassApp.factory('settings', [function() {
  return new Settings();
}]);

keepassApp.factory('protectedMemory', [function() {
  return new ProtectedMemory();
}]);

keepassApp.factory('keepassHeader', ['settings', function(settings) {
  return new KeepassHeader(pako, settings);
}]);

keepassApp.factory('streamCipher', [function() {
  return new StreamCipher();
}]);

keepassApp.factory('keepassReference', ['streamCipher', function(streamCipher) {
  return new KeepassReference(streamCipher);
}]);

keepassApp.factory('keepass', ['keepassHeader', 'pako', 'settings', 'passwordFileStoreRegistry', 'keepassReference', 'streamCipher', function(keepassHeader, pako, settings, passwordFileStoreRegistry, keepassReference, streamCipher) {
	return new Keepass(keepassHeader, pako, settings, passwordFileStoreRegistry, keepassReference, streamCipher);
}]);

keepassApp.factory('unlockedState', ['$interval', '$location', 'keepassReference', 'protectedMemory', 'settings', function($interval, $location, keepassReference, protectedMemory, settings) {
  return new UnlockedState($interval, $location, keepassReference, protectedMemory, settings);
}]);

keepassApp.factory('secureCacheMemory', ['protectedMemory', function(protectedMemory) {
  return new SecureCacheMemory(protectedMemory);
}])

keepassApp.factory('secureCacheDisk', ['protectedMemory', 'secureCacheMemory', 'settings', function(protectedMemory, secureCacheMemory, settings) {
  return new SecureCacheDisk(protectedMemory, secureCacheMemory, settings);
}])

keepassApp.controller('startupController', ['$scope', '$location', 'settings', 'optionsLink', 'passwordFileStoreRegistry', StartupController]);
keepassApp.controller('chooseFileController', ['$scope', '$location', 'passwordFileStoreRegistry', 'settings', ChooseFileController]);
keepassApp.controller('masterPasswordController', ['$scope', '$routeParams', '$location', 'keepass', 'unlockedState', 'secureCacheDisk', 'settings', 'optionsLink', 'streamCipher', MasterPasswordController]);
keepassApp.controller('findEntryController', ['$scope', 'unlockedState', 'secureCacheDisk', 'streamCipher', FindEntryController]);
keepassApp.controller('entryDetailsController', ['$scope', '$routeParams', '$location', 'unlockedState', EntryDetailsController]);
keepassApp.controller('settingsLinkController', ['$scope', '$location', 'optionsLink', SettingsLinkController]);
keepassApp.controller('passwordListController', ['$scope', 'settings', PasswordListController]);

keepassApp.directive('icon', function() {
  function link(scope, element, attrs) {
    function renderSVG() {
      var icon = element.scope()[attrs.p];  //evaluate as scope expression
      if (!icon)
        icon = attrs.p;
      var html = '<svg class="icon ' + icon + '"><use xlink:href="#' + icon + '"></use></svg>';
      element.replaceWith( html );
    }

   	renderSVG();
  }

  return {
    link: link,
    restrict: 'E'
  };
});

//quick and dirty directive for file upload, based on answers from
// http://stackoverflow.com/questions/17922557/angularjs-how-to-check-for-changes-in-file-input-fields
keepassApp.directive('fileChange', function() {
  return {
    restrict: "A",
    link: function (scope, element, attrs) {
      var onChangeFunc = element.scope()[attrs.fileChange];
      element.bind('change', function(e) {
        var files = e.target.files;
        var loadedFiles = [];
        for (var i = 0, f; f = files[i]; i++) {
          // Read the File objects in this FileList.
          var loadedFile = new Promise(function(resolve, reject) {
            var reader = new FileReader();

            reader.onloadend = (function(theFile) {
              return function(e) {
                resolve({data: e.target.result, file: theFile});
              };
            })(f);

            reader.onerror = reader.onabort = (function(theFile) {
              return function(e) {
                reject(new Error("File upload failed"));
              };
            })(f);

            reader.readAsArrayBuffer(f);
          });

          loadedFiles.push(loadedFile);
        }

        onChangeFunc(loadedFiles);
      });
    }
  };
});

//based on http://blog.parkji.co.uk/2013/08/11/native-drag-and-drop-in-angularjs.html
keepassApp.directive('droppable', function() {
  return {
    scope: {
      drop: '&',
      bin: '='
    },
    link: function(scope, element) {
      // again we need the native object
      var el = element[0];

      el.addEventListener(
        'dragover',
        function(e) {
          e.dataTransfer.dropEffect = 'copy';
          // allows us to drop
          if (e.preventDefault) e.preventDefault();
          this.classList.add('over');
          return false;
        },
        false
      );

      el.addEventListener(
        'dragenter',
        function(e) {
          this.classList.add('over');
          return false;
        },
        false
      );

      el.addEventListener(
        'dragleave',
        function(e) {
          this.classList.remove('over');
          return false;
        },
        false
      );

      el.addEventListener(
        'drop',
        function(e) {
          // Stops some browsers from redirecting.
          e.stopPropagation();
          e.preventDefault();

          this.classList.remove('over');

          var files = e.dataTransfer.files;
          var loadedFiles = [];
          for (var i = 0, f; f = files[i]; i++) {
            // Read the File objects in this FileList.
            var loadedFile = new Promise(function(resolve, reject) {
              var reader = new FileReader();

              reader.onloadend = (function(theFile) {
                return function(e) {
                  resolve({data: e.target.result, file: theFile});
                };
              })(f);

              reader.onerror = reader.onabort = (function(theFile) {
                return function(e) {
                  reject(new Error("File upload failed"));
                };
              })(f);

              reader.readAsArrayBuffer(f);
            });

            loadedFiles.push(loadedFile);
          }

          // call the passed drop function
          scope.$apply(function(scope) {
            var fn = scope.drop();
            if ('undefined' !== typeof fn) {
              fn(loadedFiles);
            }
          });

          return false;
        },
        false
      );
    }
  }
});

//autofocus, from an answer at http://stackoverflow.com/questions/14833326/how-to-set-focus-on-input-field
keepassApp.directive('autoFocus', function($timeout) {
    return {
        restrict: 'AC',
        link: function(_scope, _element) {
            $timeout(function(){
                _element[0].focus();
            });
        }
    };
});

//http://stackoverflow.com/questions/12393703/how-to-include-one-partials-into-other-without-creating-a-new-scope
keepassApp.directive('staticInclude', function($http, $templateCache, $compile) {
	return function(scope, element, attrs) {
		var templatePath = attrs.staticInclude;
		$http.get(templatePath, { cache: $templateCache }).success(function(response) {
			var contents = element.html(response).contents();
			$compile(contents)(scope);
		});
	};
});
