// parseUri 1.2.2
// (c) Steven Levithan <stevenlevithan.com>
// MIT License
// http://blog.stevenlevithan.com/archives/parseuri
function parseUri (str) {
	var	o   = parseUri.options,
		m   = o.parser[o.strictMode ? "strict" : "loose"].exec(str),
		uri = {},
		i   = 14;

	while (i--) uri[o.key[i]] = m[i] || "";

	uri[o.q.name] = {};
	uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
		if ($1) uri[o.q.name][$1] = $2;
	});

	return uri;
};

parseUri.options = {
	strictMode: false,
	key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],
	q:   {
		name:   "queryKey",
		parser: /(?:^|&)([^&=]*)=?([^&]*)/g
	},
	parser: {
		strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
		loose:  /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
	}
};

if (!String.prototype.trim) {
    String.prototype.trim = function() {
	return this.replace(/^\s+|\s+$/g, "");
    };
}

// Array Remove - By John Resig (MIT Licensed)
// http://stackoverflow.com/questions/500606/javascript-array-delete-elements
Array.prototype.remove = function(from, to) {
    var rest = this.slice((to || from) + 1 || this.length);
    this.length = from < 0 ? this.length + from : from;
    return this.push.apply(this, rest);
};

Element.prototype.removeAllChilds = function() {
    while (this.hasChildNodes())
	this.removeChild(this.childNodes[0]);
};

function _generate_random(size, symbols)
{
    forbidden = new Array('\\');

    var res = "";
    while (res.length < size)
    {
	a = Math.floor(Math.random() * (symbols.length/2)) * 2;
	diff = symbols[a+1] - symbols[a];
	r = Math.floor(Math.random()*diff);
	if (isNaN(r+symbols[a]))
	    continue;
	character = String.fromCharCode(r + symbols[a]);
	forbid = false;
	for (var j=0; j<forbidden.length; j++)
	{
	    if (character == forbidden[j])
	    {
		forbid = true;
		break;
	    }
	}
	if (forbid) continue;
	res += character;
    }

    return res;
}
function generate_random(size, only_ascii)
{
    // symbols 32 - 47 / 58 - 64 / 91 - 96 / 123 - 126
    // numbers 48 - 57
    // upper 65 - 90
    // lower 97 - 122
    // Give priority to letters (65 - 122 duplicated)
    var symbols;
    if (only_ascii)
	symbols = new Array(32, 47, 48, 57, 58, 64, 91, 96, 123, 126, 65, 90, 97, 122, 65, 90, 97, 122, 48, 57);
    else
	symbols = new Array(1, 255);

    return _generate_random(size, symbols);
}

function generate_password()
{
    document.getElementById("new_password").value = generate_random(16, true);
}

function generate_simple_password()
{
    // ! ( ) * + - . _
    // numbers 48 - 57
    // upper 65 - 90
    // lower 97 - 122
    symbols = new Array(33, 33, 40, 43, 45, 46, 95, 95, 48, 57, 65, 90, 97, 122, 48, 57, 65, 90, 97, 122, 48, 57, 48, 57, 65, 90, 97, 122);
    document.getElementById("new_password").value = _generate_random(8, symbols);
}

function url_domain(data) {
    var uri = parseUri(data)
   return uri['host'];
}

// http://stackoverflow.com/questions/3745666/how-to-convert-from-hex-to-ascii-in-javascript
function hex2a(hex) {
    var str = '';
    for (var i = 0; i < hex.length; i += 2)
	str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
    return str;
}

function a2hex(str) {
    var hex = '';
    for (var i = 0; i < str.length; i++)
    {
	c = str.charCodeAt(i).toString(16);
	if (c.length == 1) c = "0" + c;
        hex += c;
    }
    return hex;
}

async function derive_mkey(user, mkey)
{
    url = url_domain(server_url) + "/" + user;
    global_iv = simple_pbkdf2(url, mkey, pbkdf2_level);
    return crypto_pbkdf2(mkey, url, pbkdf2_level);
}

var passwords = null;
var current_user = "";
var current_mkey = "";
var clearTimer = null;
var global_iv = null;
var server_url = document.documentURI;

function PasswordEntry (ciphered_login, ciphered_password, salt, shadow_login) {
    this.ciphered_login = ciphered_login;
    this.ciphered_password = ciphered_password;
    this.unciphered = false;
    this.clear_url = "";
    this.clear_login = "";
    this.clear_password = "";
    this.masterkey = null;
    this.salt = salt;
    this.shadow_login = shadow_login;
    this.access_token = "";

    this.reset = function()
    {
	this.unciphered = false;
	this.clear_url = "";
	this.clear_login = "";
	this.clear_password = "";
	this.masterkey = null;
	this.salt = salt;
    }

    this.reset_master_key = function()
    {
	this.masterkey = null;
    }
    
    this.encrypt = async function(masterkey)
    {
	if (masterkey == this.masterkey)
	    return true;

	if (masterkey == null || this.clear_url == "" || this.clear_login == "")
	    return false;

	var ciphered_login = this.clear_url + ";" + this.clear_login;
	while ((ciphered_login.length % 16))
	    ciphered_login += "\0";
	var computed_hash = await digest(ciphered_login);
	ciphered_login += computed_hash.slice(8, 24);
	var iv = await global_iv;
	iv = iv.slice(0, 16);

	// Add salt
	var ciphered_password = generate_random(3, false) + this.clear_password ;

	this.ciphered_login = a2hex(await encrypt_cbc(masterkey, iv, ciphered_login));
	this.ciphered_password = a2hex(await encrypt_cbc(masterkey, iv, ciphered_password));

	this.unciphered = true;
	this.masterkey = masterkey;

	if (use_shadow_logins)
	    await this.generate_access_token(masterkey);
    }

    this.decrypt = async function(masterkey)
    {
	if (masterkey == null)
	    return false;

	if (masterkey == this.masterkey)
	    return (this.unciphered == true);

	var old = false;
	var iv = await global_iv;
	iv = iv.slice(0, 16);
	var login = await decrypt_cbc(masterkey, iv, hex2a(this.ciphered_login));
	
	var computed_digest = await digest(login.slice(0, login.length-16))
	computed_digest = computed_digest.slice(8, 24);

	if (login.indexOf(computed_digest) == login.length-16)
	{
	    login = login.slice(0, login.length-16).replace(/\0*$/, "");
	}
	else if (CRYPTO_V1_COMPATIBLE)
	{
	    login = await decrypt_ecb(masterkey, hex2a(this.ciphered_login));
	    if (login.indexOf("@@") != 0)
	    {
		return false;
	    }
	    login = login.replace(/\0*$/, "");
	    // Remove @@
	    login = login.substring(2);
	    old = true;
	}
	else
	    return false;

	infos = login.split(";");
	this.clear_url = infos[0];
	this.clear_login = infos[1];
	if (old)
	{
	    this.clear_password = await decrypt_ecb(masterkey, hex2a(this.ciphered_password));
	    // Remove salt
	    this.clear_password = this.clear_password.replace(/\0*$/, "");
	    this.clear_password = this.clear_password.substr(0, this.clear_password.length-3);
	}
	else
	{
	    this.clear_password = await decrypt_cbc(masterkey, iv, hex2a(this.ciphered_password));
	    // Remove salt
	    this.clear_password = this.clear_password.replace(/\0*$/, "");
	    this.clear_password = this.clear_password.substr(3, this.clear_password.length);
	}
	this.unciphered = true;
	this.masterkey = masterkey;

	return true;
    }

    this.isUnciphered = function(masterkey)
    {
	return (this.unciphered == true && masterkey == this.masterkey && masterkey != null)
    }

    this.isCiphered = function(masterkey)
    {
	return !(this.isUnciphered(masterkey));
    }

    this.shadow_login_to_access_token = async function(masterkey)
    {
	this.access_token = await encrypt_ecb(masterkey, hex2a(this.shadow_login));
	this.access_token = a2hex(this.access_token);
    }

    this.generate_access_token = async function(masterkey)
    {
	this.salt = a2hex(generate_random(16, false));
	this.shadow_login = a2hex(generate_random(16, false));

	return await this.shadow_login_to_access_token(masterkey);
    }
}

function clearMasterKey()
{
    current_mkey = null;

    for(i=0; i<passwords.length; i++)
    {
	passwords[i].reset();
    }
}

function stopClearTimer()
{
    if (clearTimer)
    {
	clearTimeout(clearTimer);
	clearTimer = null;
    }
}

function startClearTimer()
{
    stopClearTimer();
    clearTimer = setTimeout(
	function()
	{
	    clearMasterKey();
	    change_master_key(false);
	    scrollToTop();
	}
	, CLEAR_TIME);
}

function list_all_entries(user)
{
    passwords = new Array();

    req = new XMLHttpRequest();
    req.addEventListener("load", function(evt) {
	j = JSON.parse(this.responseText);
	for(i=0; i<j.entries.length; i++)
	{
	    if (j.entries[i].hasOwnProperty('login'))
		p = new PasswordEntry(j.entries[i].login, j.entries[i].password, "", "");
	    else
		p = new PasswordEntry("", "", j.entries[i].salt, j.entries[i].shadow_login);
	    passwords.push(p);
	}
    }
			 , false);
    req.open("POST", server_url, false);
    req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
    req.send("get_passwords=1&user=" + user);
}

function update_stats()
{
    nb_ciphered_passwords = 0;
    nb_unciphered_passwords = 0;

    for(i=0; i<passwords.length; i++)
    {
	if (passwords[i].isUnciphered(current_mkey))
	    nb_unciphered_passwords++;
	else
	    nb_ciphered_passwords++;
    }

    div = document.getElementById("nb_unciphered_passwords");
    div.removeAllChilds();

    text = document.createElement("b");
    text.appendChild(document.createTextNode(nb_unciphered_passwords + " unciphered password(s)"));
    div.appendChild(text);
    div.appendChild(document.createElement("br"));
    div.appendChild(document.createElement("br"));

    div = document.getElementById("nb_ciphered_passwords");
    div.removeAllChilds();

    text = document.createElement("b");
    text.appendChild(document.createTextNode(nb_ciphered_passwords + " ciphered password(s)"));
    div.appendChild(text);
    div.appendChild(document.createElement("br"));
    div.appendChild(document.createElement("br"));
}

// Remove all password without credentials
async function put_ciphered_credentials(passwords, masterkey)
{
    for(var i=0; i<passwords.length; i++)
    {
	await passwords[i].generate_access_token(masterkey);
	remove_password_server(current_user, passwords[i].ciphered_login, '');
	add_password_server(current_user, passwords[i]);
    }
}

async function get_ciphered_credentials(masterkey)
{
    access_tokens = '';
    old_passwords = new Array();

    for(var i=0; i<passwords.length; i++)
    {
	// Already got
	if (passwords[i].ciphered_login.length)
	{
	    if (!passwords[i].access_token.length)
	    {
		res = await passwords[i].decrypt(masterkey);
		if(res)
		    old_passwords.push(passwords[i]);
	    }
	    continue;
	}
	
	await passwords[i].shadow_login_to_access_token(masterkey);
	if (access_tokens.length) access_tokens += ",";
	access_tokens += passwords[i].access_token;
    }

    if (old_passwords.length)
	await put_ciphered_credentials(old_passwords, masterkey);

    if (!access_tokens.length)
	return;

    req = new XMLHttpRequest();
    req.addEventListener("load", function(evt) {
	j = JSON.parse(this.responseText);
	for(i=0; i<j.entries.length; i++)
	{
	    for (k=0; k<passwords.length; k++)
	    {
		if (passwords[k].access_token == j.entries[i].access_token)
		{
		    passwords[k].ciphered_login = j.entries[i].login;
		    passwords[k].ciphered_password = j.entries[i].password;
		    break;
		}
	    }
	}
    }, false);
    req.open("POST", server_url, false);
    req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
    req.send("get_secure_passwords=1&user=" + current_user + "&access_tokens=" + access_tokens);
}

async function change_master_key(warning_unciphered)
{
    var nb_unciphered = 0;

    if (current_mkey && use_shadow_logins)
	await get_ciphered_credentials(current_mkey);

    for(i=0; i<passwords.length; i++)
    {
	if (await passwords[i].decrypt(current_mkey))
	    nb_unciphered++;
    }

    if (!nb_unciphered && warning_unciphered)
	alert("No password unciphered with this master key !");

    password_div = document.getElementById("passwords");
    password_div.removeAllChilds();
    
    div = document.createElement("div");
    div.setAttribute("id", "nb_unciphered_passwords");
    password_div.appendChild(div);

    for(i=0; i<passwords.length; i++)
    {
	if (passwords[i].isUnciphered(current_mkey))
	{
	    div = document.createElement("div");
	    div.setAttribute("id", "unciph_entry_" + i);
	    div.setAttribute("class", "password");

	    ciph_login = document.createElement("input");
	    ciph_login.setAttribute("name", "ciphered_login");
	    ciph_login.setAttribute("type", "hidden");
	    ciph_login.setAttribute("login", passwords[i].ciphered_login);
	    div.appendChild(ciph_login);

	    div.appendChild(document.createTextNode("URL"));
	    url = document.createElement("input");
	    url.setAttribute("type", "text");
	    url.setAttribute("name", "url");
	    url.setAttribute("value", passwords[i].clear_url);
	    div.appendChild(url);

	    div.appendChild(document.createTextNode("login"));
	    login = document.createElement("input");
	    login.setAttribute("type", "text");
	    login.setAttribute("name", "login");
	    login.setAttribute("value", passwords[i].clear_login);
	    div.appendChild(login);

	    div.appendChild(document.createTextNode("password"));
	    password = document.createElement("input");
	    password.setAttribute("type", "text");
	    password.setAttribute("name", "password");
	    password.setAttribute("value", passwords[i].clear_password);
	    div.appendChild(password);

	    delete_button = document.createElement("input");
	    delete_button.setAttribute("type", "button");
	    delete_button.setAttribute("value", "Delete");
	    delete_button.setAttribute("onclick", "delete_entry(\"unciph_entry_" + i + "\");");
	    div.appendChild(delete_button);

	    update_button = document.createElement("input");
	    update_button.setAttribute("type", "button");
	    update_button.setAttribute("value", "Update");
	    update_button.setAttribute("onclick", "update_entry(\"unciph_entry_" + i + "\");");
	    div.appendChild(update_button);

	    password_div.appendChild(div);
	}
    }

    div = document.createElement("div");
    div.setAttribute("id", "nb_ciphered_passwords");
    password_div.appendChild(div);

    for(i=0; i<passwords.length; i++)
    {
	if (passwords[i].isCiphered(current_mkey))
	{
	    div = document.createElement("div");
	    div.setAttribute("id", "ciph_entry_" + i);
	    div.setAttribute("class", "password");

	    ciph_login = document.createElement("input");
	    ciph_login.setAttribute("name", "ciphered_login");
	    ciph_login.setAttribute("type", "hidden");
	    div.appendChild(ciph_login);

	    div.appendChild(document.createTextNode("URL"));
	    url = document.createElement("input");
	    url.setAttribute("class", "hash");
	    url.setAttribute("type", "text");
	    url.setAttribute("name", "URL");
	    div.appendChild(url);

	    div.appendChild(document.createTextNode("password"));
	    password = document.createElement("input");
	    password.setAttribute("class", "hash");
	    password.setAttribute("type", "text");
	    password.setAttribute("name", "password");
	    div.appendChild(password);

	    delete_button = document.createElement("input");
	    delete_button.setAttribute("type", "button");
	    delete_button.setAttribute("value", "Delete");
	    delete_button.setAttribute("onclick", "delete_entry(\"ciph_entry_" + i + "\");");
	    div.appendChild(delete_button);

	    password_div.appendChild(div);

	    if (passwords[i].ciphered_login.length)
	    {
		ciph_login.setAttribute("login", passwords[i].ciphered_login);
		url.setAttribute("value", passwords[i].ciphered_login);
		password.setAttribute("value", passwords[i].ciphered_password);
	    }
	    else
	    {
		ciph_login.setAttribute("login", passwords[i].shadow_login);
		url.setAttribute("value", passwords[i].shadow_login);
		// password empty
	    }
	}
    }

    input = document.getElementById("master_key");
    input.value = "";

    update_stats();
}

function update_master_key(warning_unciphered)
{
    user = select_widget.options[select_widget.selectedIndex].value;

    if (user != current_user)
    {
	current_user = user;

	document.title = "gPass - " + current_user;

	list_all_entries(current_user);

	addon_address = document.getElementById("addon_address");
	addon_address.removeAllChilds();

	addon_address.appendChild(document.createTextNode("Current addon address is : " + server_url + current_user));

	warning_unciphered = false;
    }

    current_mkey = document.getElementById("master_key").value;

    if (current_mkey != "")
    {
	for(i=0; i<passwords.length; i++)
	{
	    passwords[i].reset_master_key();
	}
	current_mkey = derive_mkey(current_user, current_mkey);
	startClearTimer();
    }
    else
    {
	current_mkey = null;
	// Disable warning on empty master key (clear passwords from others)
	warning_unciphered = false;
	stopClearTimer();
	clearMasterKey();
    }

    change_master_key(warning_unciphered);
}

function start()
{
    select_widget = document.getElementById('selected_user') ; 
    
    if (select_widget == null) return;

    return update_master_key(false);
}

function add_password_server(user, pentry)
{
    var ok = false;
    req = new XMLHttpRequest();
    req.addEventListener("load", function(evt) {
	resp = this.responseText;
	if (resp == "OK")
	    ok = true;
	else
	    alert(resp);
    }, false);
    req.open("POST", server_url, false);
    req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
    req.send("add_entry=1&user=" + user + "&login=" + pentry.ciphered_login + "&password=" + pentry.ciphered_password + "&shadow_login=" + pentry.shadow_login + "&salt=" + pentry.salt + "&access_token=" + pentry.access_token);

    return ok;
}

async function construct_pentry(user, url, password, login, mkey, derive_masterkey)
{
    var ret = null;

    if (url == "")
    {
	alert("URL is empty");
	return ret;
    }

    if (login == "")
    {
	alert("Login is empty");
	return ret;
    }

    if (password == "")
    {
	alert("Password is empty");
	return ret;
    }

    if (mkey == "")
    {
	alert("Master key is empty");
	return ret;
    }

    if (derive_masterkey)
	mkey = derive_mkey(user, mkey);

    for(i=0; i<passwords.length; i++)
    {
	p = passwords[i];
	if (p.clear_url == url &&
	    p.clear_password == password &&
	    p.clear_login == login &&
	    p.masterkey == mkey)
	{
	    alert("Entry already exists");
	    return ret;
	}
    }

    pentry = new PasswordEntry("", "", "", "");
    pentry.clear_url = url;
    pentry.clear_login = login;
    pentry.clear_password = password;
    await pentry.encrypt(mkey);

    return pentry;
}

function remove_password_server(user, login, access_token)
{
    var ok = false;

    req = new XMLHttpRequest();
    req.addEventListener("load", function(evt) {
	resp = this.responseText;
	if (resp == "OK")
	    ok = true;
	else
	    alert(resp);
    }, false);
    req.open("POST", server_url, false);
    req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
    req.send("delete_entry=1&user=" + user + "&login=" + login + "&access_token=" + access_token);

    return ok;
}

function add_password()
{
    var url = "";
    var login = "";
    var password = "";
    var mkey = "";

    div = document.getElementById("add_new_password");
    
    inputs = div.getElementsByTagName("input");

    for(i=0; i<inputs.length; i++)
    {
	if (inputs[i].getAttribute("name") == "url")
	    url = url_domain(inputs[i].value);
	else if (inputs[i].getAttribute("name") == "login")
	    login = inputs[i].value.trim();
	else if (inputs[i].getAttribute("name") == "password")
	    password = inputs[i].value.trim();
	else if (inputs[i].getAttribute("name") == "mkey")
	    mkey = inputs[i].value;
    }

    construct_pentry(current_user, url, password, login, mkey, true).then(
	function (pentry) {
	    if (pentry == null) return false;

	    res = add_password_server(current_user, pentry);

	    if (!res) return false;

	    for(i=0; i<passwords.length; i++)
	    {
		passwords[i].reset_master_key();
	    }

	    passwords.push(pentry);

	    current_mkey = pentry.masterkey;
	    change_master_key(false);

	    for(i=0; i<inputs.length; i++)
	    {
		if (inputs[i].getAttribute("type") == "text" ||
		    inputs[i].getAttribute("type") == "password")
		    inputs[i].value = "";
	    }

	    startClearTimer();
	});

    return true;
}

function delete_entry(entry_number)
{
    startClearTimer();

    entry = document.getElementById(entry_number);

    if (entry == null) {
	alert(entry_number + " not found"); 
	return;
    }

    inputs = entry.getElementsByTagName("input");

    var ciphered_login = null;
    for(i=0; i<inputs.length; i++)
    {
	if (inputs[i].getAttribute("name") == "ciphered_login")
	{
	    ciphered_login = inputs[i];
	    break;
	}
    }

    if (ciphered_login == null)
    {
	alert("Widget not found"); 
	return;
    }

    var found = -1;
    for(i=0; i<passwords.length; i++)
    {
	if (passwords[i].ciphered_login == ciphered_login.getAttribute("login") ||
	    passwords[i].shadow_login == ciphered_login.getAttribute("login"))
	{
	    found = i;
	    break;
	}
    }

    if (found == -1)
    {
	alert("Password not found int database");
	return;
    }

    if(!confirm("Are you sure want to delete this entry ?"))
	return;

    ok = remove_password_server(current_user, ciphered_login.getAttribute("login"), passwords[i].access_token);

    if (!ok) return;

    parent = ciphered_login.parentNode;
    parent.removeAllChilds();

    passwords.remove(found);

    update_stats();
}

function update_entry(entry_number)
{
    var url = "";
    var login = "";
    var password = "";
    var mkey = "";
    var ciphered_login;

    startClearTimer();

    entry = document.getElementById(entry_number);

    if (entry == null) {
	alert(entry_number + " not found"); 
	return;
    }

    inputs = entry.getElementsByTagName("input");

    var ciphered_login = null;
    for(i=0; i<inputs.length; i++)
    {
	if (inputs[i].getAttribute("name") == "url")
	    url = url_domain(inputs[i].value);
	else if (inputs[i].getAttribute("name") == "login")
	    login = inputs[i].value.trim();
	else if (inputs[i].getAttribute("name") == "password")
	    password = inputs[i].value.trim();
	else if (inputs[i].getAttribute("name") == "ciphered_login")
	    ciphered_login = inputs[i];
    }

    var found = -1;
    for(i=0; i<passwords.length; i++)
    {
	if (passwords[i].ciphered_login == ciphered_login.getAttribute("login"))
	{
	    found = i;
	    break;
	}
    }

    if (found == -1)
    {
	alert("Password not found int database");
	return;
    }

    if(!confirm("Are you sure want to update this entry ?"))
	return;

    construct_pentry(current_user, url, password, login, current_mkey, false).then(
	function (pentry) {
	    if (pentry == null) return;

	    ok = remove_password_server(current_user, passwords[found].ciphered_login, passwords[found].access_token);
	    if (!ok) return;

	    ok = add_password_server(current_user, pentry);
	    if (!ok) return;

	    passwords[found]  = pentry;
	    ciphered_login.setAttribute("login", pentry.ciphered_login);

	    alert("Entry updated");
	});
}

async function update_masterkey()
{
    var url = "";
    var login = "";
    var password = "";
    var mkey = "";
    var ciphered_login;

    oldmkey = document.getElementById("oldmkey").value;
    newmkey = document.getElementById("newmkey").value;

    if (newmkey == "" || oldmkey == "")
    {
	alert("Cannot set an empty masterkey");
	return;
    }

    if(!confirm("Are you sure want to update the masterkey ?"))
	return;

    oldmkey = derive_mkey(current_user, oldmkey);
    old_global_iv = global_iv;
    current_mkey = derive_mkey(current_user, newmkey);
    new_global_iv = global_iv;

    var found = 0;
    for(i=0; i<passwords.length; i++)
    {
	global_iv = old_global_iv;
	if (await passwords[i].decrypt(oldmkey))
	{
	    ok = remove_password_server(current_user, passwords[i].ciphered_login, passwords[i].access_token);
	    if (!ok)
	    {
		alert("Error updating password");
		break;
	    }

	    if (use_shadow_logins)
		await passwords[i].generate_access_token(current_mkey);
	    
	    global_iv = new_global_iv;
	    await passwords[i].encrypt(current_mkey);
	    ok = add_password_server(current_user, passwords[i]);

	    if (!ok)
	    {
		alert("Error updating password");
		break;
	    }
	    found++;
	}
    }

    if (found == 0)
	alert("No password found with this masterkey");
    else
    {
	alert(found + " passwords updated");
	change_master_key(false);
    }
}

function makeText(text) {
    var data = new Blob([text], {type: 'application/xml'});

    textFile = window.URL.createObjectURL(data);

    // returns a URL you can use as a href
    return textFile;
};

var text_link = null;
function export_database()
{
    startClearTimer();

    link = document.getElementById("export_link");

    if (text_link != null) window.URL.revokeObjectURL(text_link);

    text = "<passwords user=\"" + current_user + "\" addon_address=\"" + server_url + current_user + "\">\n";
    for(i=0; i<passwords.length; i++)
    {
	if (!passwords[i].unciphered) continue;
	text += "\t<password_entry>\n"
	text += "\t\t<url value=\"" + passwords[i].clear_url + "\"/>\n";
	text += "\t\t<login value=\"" + passwords[i].clear_login + "\"/>\n";
	text += "\t\t<password><![CDATA[" + passwords[i].clear_password.replace("]]>", "]]\\>", "g") + "]]></password>\n";
	text += "\t</password_entry>\n"
    }
    text += "</passwords>\n";

    text_link = makeText(text);
    link.href = text_link;

    link.style.display = "inline";
    link.style.visibility = "visible";

    alert_msg = "Click on download link to get all current unciphered passwords\n\n";
    alert_msg += "\"]]>\" sequence has been replaced by \"]]\\>\"";
    alert(alert_msg);
}