diff --git a/ChangeLog b/ChangeLog index 60d0b6d..683aec7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,18 @@ +**v1.2 :** + +Server + * Rework UI + * Display PHP parsed URL in new URL case, not raw URL + * Add a filter for unciphered passwords (supports regular expressions) + * Add a button to copy unciphered password into clipboard + * Don't clear URL and login when adding a new password + * Change alert() by button name update for "Update" and "Copy clipboard" functions + +Addon + * Update manifest to v3 (Chrome only) + +CLI + **v1.1 :** Server diff --git a/chrome_addon/background.js b/chrome_addon/background.js index 1b044b7..86bb22d 100644 --- a/chrome_addon/background.js +++ b/chrome_addon/background.js @@ -1,5 +1,5 @@ /* - Copyright (C) 2013-2020 Grégory Soutadé + Copyright (C) 2013-2022 Grégory Soutadé This file is part of gPass. @@ -17,6 +17,11 @@ along with gPass. If not, see . */ +import {parseUri} from "./lib/parseuri.js"; +import {SERVER, GPASS_ICON, wildcard_domain, simple_pbkdf2, crypto_pbkdf2, + encrypt_ecb, encrypt_cbc, decrypt_cbc, digest, a2hex, hex2a, debug} from "./lib/misc.js"; +import {get_preference, set_preference, delete_preference} from "./compat.js"; + var browser = browser || chrome; var protocol_version = 4; var account_url = null; @@ -26,16 +31,14 @@ function _notification(message, data) if (message !== data) message += data; - options = { + var options = { type: "basic", title : "gPass", message : message, - iconUrl:browser.extension.getURL("icons/gpass_icon_64.png") + iconUrl:"icons/gpass_icon_64.png" }; browser.notifications.create("gPass", options, function(){}); - - window.setTimeout(function() {browser.notifications.clear("gPass", function(){})}, 2000); } async function generate_request(domain, login, mkey, iv) @@ -44,34 +47,36 @@ async function generate_request(domain, login, mkey, iv) debug("will encrypt " + v); while ((v.length % 16)) v += "\0"; - hash = await digest(v); + var hash = await digest(crypto, v); v += hash.slice(8, 24); - enc = encrypt_cbc(mkey, iv, v); + var enc = encrypt_cbc(mkey, iv, v); return enc; } async function ask_server(logins, domain, wdomain, mkey, sendResponse, options) { - account_url = await get_preference("account_url"); + var account_url = await get_preference("account_url"); - var salt = parseURI.parseUri(account_url); + var salt = parseUri(account_url); salt = salt["host"] + salt["path"]; debug("salt " + salt); - pbkdf2_level = await get_preference("pbkdf2_level"); + var pbkdf2_level = await get_preference("pbkdf2_level"); - global_iv = await simple_pbkdf2(salt, mkey, pbkdf2_level); + var global_iv = await simple_pbkdf2(crypto, salt, mkey, pbkdf2_level); global_iv = global_iv.slice(0, 16); - mkey = crypto_pbkdf2(mkey, salt, pbkdf2_level); + var mkey = crypto_pbkdf2(crypto, mkey, salt, pbkdf2_level); debug("global_iv " + a2hex(global_iv)); - keys = ""; + var keys = ""; + var key_index; + var a; for(key_index=0, a=0; a { + debug(e); + _notification("Network error : check your server address", ""); + }); - var r = this.responseText.split("\n"); - debug("resp " + r); + if (response === undefined) + { + ret = SERVER.ERROR; + sendResponse({"value": ret, options:options}); + return; + } + + const responseText = await response.text(); - for(var a=0; a protocol_version) + { + _notification("Protocol version not supported, please upgrade your addon", ""); + ret = SERVER.FAILED; + } + else + { + switch (server_protocol_version) { - _notification("Error : It seems that it's not a gPass server", - this.responseText); - ret = SERVER.FAILED; + case 2: + server_pbkdf2_level = 1000; + break; + case 3: + // Version 3 : nothing special to do + case 4: + // Version 4 : nothing special to do break; } - - server_protocol_version = params[1].match(/\d+/)[0]; - - if (server_protocol_version > protocol_version) - { - _notification("Protocol version not supported, please upgrade your addon", ""); - ret = SERVER.FAILED; - } - else - { - switch (server_protocol_version) - { - case 2: - server_pbkdf2_level = 1000; - break; - case 3: - // Version 3 : nothing special to do - case 4: - // Version 4 : nothing special to do - break; - } - } - break; - case "matched_key": - matched_key = params[1]; - case "pass": - ciphered_password = params[1]; - break; - case "pkdbf2_level": - case "pbkdf2_level": - server_pbkdf2_level = parseInt(params[1].match(/\d+/)[0], 10); - if (server_pbkdf2_level != NaN && - server_pbkdf2_level != pbkdf2_level && - server_pbkdf2_level >= 1000) // Minimum level for PBKDF2 ! - { - debug("New pbkdf2 level " + server_pbkdf2_level); - pbkdf2_level = server_pbkdf2_level; - set_preference("pbkdf2_level", pbkdf2_level, null); - ret = SERVER.RESTART_REQUEST; - } - break; - case "": - break; - default: - debug("Unknown command " + params[0]); - - _notification("Error : It seems that it's not a gPass server", - this.responseText); - ret = SERVER.FAILED; - break; } - } - - if (ret != SERVER.OK) - { - sendResponse({"value": ret, options:options}); - return; - } - - if (ciphered_password != "") - { - debug("Ciphered password : " + ciphered_password); - clear_password = await decrypt_cbc(mkey, global_iv, hex2a(ciphered_password)); - clear_password = clear_password.replace(/\0*$/, ""); - clear_password = clear_password.substr(3, clear_password.length); - debug("Clear password " + clear_password); - } - else - { - debug("No password found"); + break; + case "matched_key": + matched_key = params[1]; + case "pass": + ciphered_password = params[1]; + break; + case "pkdbf2_level": + case "pbkdf2_level": + server_pbkdf2_level = parseInt(params[1].match(/\d+/)[0], 10); + if (server_pbkdf2_level != NaN && + server_pbkdf2_level != pbkdf2_level && + server_pbkdf2_level >= 1000) // Minimum level for PBKDF2 ! + { + debug("New pbkdf2 level " + server_pbkdf2_level); + pbkdf2_level = server_pbkdf2_level; + set_preference("pbkdf2_level", pbkdf2_level, null); + ret = SERVER.RESTART_REQUEST; + } + break; + case "": + break; + default: + debug("Unknown command " + params[0]); + _notification("Error : It seems that it's not a gPass server", + responseText); ret = SERVER.FAILED; - - _notification("No password found in database", "") + break; } + } + if (ret != SERVER.OK) + { + sendResponse({"value": ret, options:options}); + return; + } + + if (ciphered_password != "") + { + debug("Ciphered password : " + ciphered_password); + var clear_password = await decrypt_cbc(mkey, global_iv, hex2a(ciphered_password)); + clear_password = clear_password.replace(/\0*$/, ""); + clear_password = clear_password.substr(3, clear_password.length); + debug("Clear password " + clear_password); sendResponse({"value": ret, "password":clear_password, "options":options}); - }, false); - gPassRequest.addEventListener("error", function(evt) { - debug("error"); - ret = false; - _notification("Error"); - }, false); - debug("connect to " + account_url); - gPassRequest.open("POST", account_url, true); - gPassRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8'); - gPassRequest.send(keys); + } + else + { + debug("No password found"); - return true; + ret = SERVER.FAILED; + + _notification("No password found in database", "") + + sendResponse({"value": ret, "options":options}); + } + + return; } function update_gpass_icon(iconId, tabId) { debug("update_gpass_icon"); - icon_infos = {"tabId":tabId}; - icon_name = ""; + var icon_infos = {"tabId":tabId}; + var icon_name = ""; switch (iconId) { @@ -237,6 +246,8 @@ function update_gpass_icon(iconId, tabId) debug(icon_name); + var icon_infos = {}; + icon_infos["path"] = { 16:"icons/gpass" + icon_name + "_icon_16.png", 32:"icons/gpass" + icon_name + "_icon_32.png", @@ -244,7 +255,10 @@ function update_gpass_icon(iconId, tabId) 128:"icons/gpass" + icon_name + "_icon_128.png", }; - browser.browserAction.setIcon(icon_infos); + if (browser.browserAction) + browser.browserAction.setIcon(icon_infos); + else + browser.action.setIcon(icon_infos); } async function is_gpass_enabled(uri) @@ -262,7 +276,7 @@ async function is_gpass_enabled(uri) else { debug("Check for enable"); - var domain = parseURI.parseUri(uri); + var domain = parseUri(uri); domain = domain["host"]; debug("Is gpass enabled for " + domain + " ?"); @@ -274,10 +288,10 @@ async function is_gpass_enabled(uri) function save_gpass_enable_config(uri, enable) { - var domain = parseURI.parseUri(uri); + var domain = parseUri(uri); domain = domain["host"]; - key = "disable-" + domain; + var key = "disable-" + domain; if (enable) { debug("Enable gpass for " + domain); @@ -296,7 +310,7 @@ function _query_tabs_is_gpass_enabled(tabs, sendResponse) { is_gpass_enabled(tabs[0].url).then( function (key_present) { - enabled = (key_present == null); + var enabled = (key_present == null); update_gpass_icon((enabled)?GPASS_ICON.NORMAL:GPASS_ICON.DISABLED, tabs[0].id); sendResponse({"enabled":enabled}); } @@ -321,6 +335,8 @@ function _query_tabs_update_icon(tabs, iconId) function update_enable(enabled, tab, saveConfig) { + var parameters; + if (enabled) { parameters = {type:"blockForms"}; @@ -345,20 +361,54 @@ function gpass_switch_enable(tab) is_gpass_enabled(tab.url).then( function (key_present) { - enabled = (key_present == null); + var enabled = (key_present == null); // Do switch enabled = !enabled; update_enable(enabled, tab, true); }); } +function createMenus(browser) +{ + var title; + + debug("Create menus"); + + /* Not supported by Chrome */ + if (browser.menus.onShown) + title = 'Disable gPass for this website'; + else + title = 'Disable or enable gPass for this website'; + + /* Enable/disable */ + browser.menus.create({ + id: 'switch_enable', + title: title, + contexts: ['action'], + }, () => {console.log(chrome.runtime.lastError);}); + + /* Not supported by Chrome */ + if (browser.menus.onShown) + title = 'Disable gPass for ALL websites'; + else + title = 'Disable or enable gPass for ALL websites'; + + /* Always enable/disable */ + browser.menus.create({ + id: 'always_disable', + title: title, + contexts: ['action'] + }); + +} + function extension_load() { browser.runtime.onMessage.addListener( function(request, sender, sendResponse) { if (request.type == "password") { - var domain = parseURI.parseUri(request.domain); + var domain = parseUri(request.domain); domain = domain["host"]; var wdomain = wildcard_domain(domain); @@ -390,19 +440,19 @@ function extension_load() } else if (request.type == "is_gpass_enabled") { - browser.tabs.query({active:true, currentWindow:true}, - function cb(tabs) { - _query_tabs_is_gpass_enabled(tabs, sendResponse); - }); + browser.tabs.query({active:true, currentWindow:true}).then( (tabs) => + { + _query_tabs_is_gpass_enabled(tabs, sendResponse); + }); return true; } else if (request.type == "update_icon") { debug("update_icon"); - browser.tabs.query({active:true, currentWindow:true}, - function cb(tabs) { - _query_tabs_update_icon(tabs, request.icon_id); - }); + browser.tabs.query({active:true, currentWindow:true}).then( (tabs) => + { + _query_tabs_update_icon(tabs, request.icon_id); + }); } else { @@ -411,116 +461,88 @@ function extension_load() } ); + /* Chrome */ if (!browser.menus && browser.contextMenus) + { browser.menus = browser.contextMenus; + } - /* Settings */ - browser.menus.create({ - id: 'settings', - title: 'gPass Settings', - contexts: ['browser_action'] - }); - - /* Not supported by Chrome */ - if (browser.menus.onShown) - title = 'Disable gPass for this website'; - else - title = 'Disable or enable gPass for this website'; - - /* Enable/disable */ - browser.menus.create({ - id: 'switch_enable', - title: title, - contexts: ['browser_action'] - }); - - /* Not supported by Chrome */ - if (browser.menus.onShown) - title = 'Disable gPass for ALL websites'; - else - title = 'Disable or enable gPass for ALL websites'; - - /* Always enable/disable */ - browser.menus.create({ - id: 'always_disable', - title: title, - contexts: ['browser_action'] + browser.runtime.onInstalled.addListener(() => { + createMenus(browser) }); browser.menus.onClicked.addListener( - function(info, tab) { - switch (info.menuItemId) { - case 'settings': - browser.runtime.openOptionsPage(); - break; + function(info, tab) { + switch (info.menuItemId) { - case 'always_disable': - get_preference('always_disabled').then( - function (always_disabled) { + case 'always_disable': + get_preference('always_disabled').then( + function (always_disabled) { debug('Change always disable'); debug(always_disabled); - always_disabled = !always_disabled; + always_disabled = !always_disabled; set_preference('always_disabled', always_disabled, function(error) { - browser.tabs.query({}, - function cb(tabs) { - for (i=0; i { + for (var i=0; i { @@ -50,9 +50,9 @@ function get_preference(key) return promise; } -function set_preference(key, value, sendResponse) +export function set_preference(key, value, sendResponse) { - pref = {[key]:value}; + var pref = {[key]:value}; chrome.storage.local.set(pref, function (result) { if (chrome.runtime.lastError) alert(chrome.runtime.lastError); @@ -61,12 +61,13 @@ function set_preference(key, value, sendResponse) }); } -function delete_preference(key) +export function delete_preference(key) { chrome.storage.local.remove(key); } -function send_tab_message(tab_id, parameters, callback) +export function send_tab_message(tab_id, parameters, callback) { chrome.tabs.sendMessage(tab_id, parameters, {}, callback); } + diff --git a/chrome_addon/lib/main.js b/chrome_addon/lib/main.js index 29e8143..bac768f 100644 --- a/chrome_addon/lib/main.js +++ b/chrome_addon/lib/main.js @@ -1,5 +1,5 @@ /* - Copyright (C) 2013-2020 Grégory Soutadé + Copyright (C) 2013-2022 Grégory Soutadé This file is part of gPass. @@ -17,30 +17,27 @@ along with gPass. If not, see . */ -var gpass_enabled = true; +/* from misc.js */ +/* Can't directly add it, because it's now a module */ +var DEBUG = false; +var browser = browser || chrome; +const SERVER = {OK : 0, FAILED : 1, RESTART_REQUEST : 2}; +const GPASS_ICON = {NORMAL:0, DISABLED:1, ACTIVATED:2}; -function _notification(message, data) +function debug(s) { - if (message !== data) - message += data; - - options = { - type: "basic", - title : "gPass", - message : message, - iconUrl:browser.extension.getURL("icons/gpass_icon_64.png") - }; - - browser.notifications.create(options).then( - function created(notification_id) - { - window.setTimeout(function() { - browser.notifications.clear(notification_id); - }, 2000); - } - ); + if (DEBUG) + console.log(s); } +function notify(text, data) +{ + browser.runtime.sendMessage({type: "notification", options:{"message":text, "data":data}}); +} + + +var gpass_enabled = true; + function _add_name(logins, name) { for(var i=0; i && type_filters.push("text"); type_filters.push("email"); - logins = try_get_name(fields, type_filters, true); + var logins = try_get_name(fields, type_filters, true); if (logins.length == 1) sendResponse(logins[0]); @@ -347,12 +344,12 @@ browser.runtime.onMessage.addListener( var response = ""; if (managed_forms.length == 1) { - fields = managed_forms[0].getElementsByTagName("input"); - password_field = null; + var fields = managed_forms[0].getElementsByTagName("input"); + var password_field = null; - for (a=0; a 16) { @@ -186,13 +186,14 @@ async function encrypt_ecb(mkey, data) return result; } -async function decrypt_ecb(mkey, data) +export async function decrypt_ecb(mkey, data) { var result = ""; - + var res; + debug("Decrypt ECB " + data); - nulliv = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + var nulliv = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); while (data.length > 16) { @@ -207,7 +208,7 @@ async function decrypt_ecb(mkey, data) return result; } -async function encrypt_cbc(mkey, iv, data) +export async function encrypt_cbc(mkey, iv, data) { debug("Encrypt CBC " + data); @@ -217,7 +218,7 @@ async function encrypt_cbc(mkey, iv, data) return result.slice(0, result.length-16); } -async function decrypt_cbc(mkey, iv, data) +export async function decrypt_cbc(mkey, iv, data) { debug("Decrypt CBC " + data); @@ -227,22 +228,22 @@ async function decrypt_cbc(mkey, iv, data) return result.slice(0, result.length-16); } -async function digest(data) +export async function digest(crypto, data) { return crypto.subtle.digest("SHA-256", str2ab(data)).then(function (hash) { return ab2str(hash); }); } -function wildcard_domain(domain) +export function wildcard_domain(domain) { var parts = domain.split("."); // Standard root domain (zzz.xxx.com) or more if (parts.length > 2) { - res = "*."; - for (i=1; i // MIT License -parseURI = { - - parseUri : function (str) { - var o = { +export function parseUri (str) { + var o = { strictMode: false, key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], q: { - name: "queryKey", - parser: /(?:^|&)([^&=]*)=?([^&]*)/g + name: "queryKey", + parser: /(?:^|&)([^&=]*)=?([^&]*)/g }, parser: { - strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/, - loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/ + strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/, + loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/ }}, - m = o.parser[o.strictMode ? "strict" : "loose"].exec(str), - uri = {}, - i = 14; + m = o.parser[o.strictMode ? "strict" : "loose"].exec(str), + uri = {}, + i = 14; - while (i--) uri[o.key[i]] = m[i] || ""; + 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; - }); + 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; +} - return uri; - } -}; diff --git a/chrome_addon/manifest.json b/chrome_addon/manifest.json index e8a871f..c72b613 100644 --- a/chrome_addon/manifest.json +++ b/chrome_addon/manifest.json @@ -1,9 +1,9 @@ { - "manifest_version": 2, + "manifest_version": 3, "name": "gPass", "short_name": "gPass", - "version": "1.1", + "version": "1.2", "description": "gPass : global password manager", "icons" : {"16":"icons/gpass_icon_16.png", "32":"icons/gpass_icon_32.png", "64":"icons/gpass_icon_64.png", "128":"icons/gpass_icon_128.png"}, "author" : "Grégory Soutadé", @@ -12,30 +12,32 @@ "content_scripts": [ { "matches": [""], - "js": ["lib/misc.js", "lib/main.js"], + "js": ["lib/main.js"], "run_at" : "document_idle", "all_frames" : true } ], "background": { - "persistent": true, - "scripts": ["lib/parseuri.js", "lib/misc.js", "compat.js", "background.js"] + "service_worker": "background.js", + "type": "module" }, "options_page": "options.html", - "browser_action": { + "action": { "default_icon": {"32":"icons/gpass_icon_32.png"}, "default_title": "Get your password", "default_popup": "popup/popup.html" }, + "host_permissions": [ + "" + ], + "permissions": [ - "", "activeTab", "notifications", - "tabs", "storage", "clipboardWrite", "contextMenus" diff --git a/chrome_addon/popup/popup.html b/chrome_addon/popup/popup.html index a2e4dbd..6c2785c 100644 --- a/chrome_addon/popup/popup.html +++ b/chrome_addon/popup/popup.html @@ -9,8 +9,6 @@ Copy password into clipboard
Your server - - - + diff --git a/chrome_addon/popup/popup.js b/chrome_addon/popup/popup.js index fdbec67..3373037 100644 --- a/chrome_addon/popup/popup.js +++ b/chrome_addon/popup/popup.js @@ -1,5 +1,5 @@ /* - Copyright (C) 2020 Grégory Soutadé + Copyright (C) 2020-2022 Grégory Soutadé This file is part of gPass. @@ -17,10 +17,13 @@ along with gPass. If not, see . */ +import {SERVER, browser, notify, debug} from "./misc.js"; +import {get_preference, send_tab_message} from "./compat.js"; + var username_filled = false; function _server_response(response, tabs, do_submit, force_copy) -{ +{ debug("Get Response"); if (response.value == SERVER.OK) { @@ -29,17 +32,17 @@ function _server_response(response, tabs, do_submit, force_copy) { navigator.clipboard.writeText(response.password).then(function() { notify("Password pasted into clipboard", ""); - window.close(); + window.setTimeout(function() {window.close();}, 2000); }); return true; } /* Fill + optional copy */ - parameters = { + var parameters = { "type":"setPassword", "password":response.password, - "submit":do_submit + "submit":do_submit, }; send_tab_message(tabs[0].id, parameters, function(arg) @@ -49,13 +52,13 @@ function _server_response(response, tabs, do_submit, force_copy) { navigator.clipboard.writeText(response.password).then(function() { notify("Password pasted into clipboard", ""); - window.close(); + window.setTimeout(function() {window.close();}, 2000); }); } else { notify("Password filled", ""); - window.close(); + window.setTimeout(function() {window.close();}, 2000); } } ); @@ -91,7 +94,7 @@ function _query_tabs_get_password(tabs) var logins = new Array(); logins.push(username); - parameters = { + var parameters = { type:"password", logins:logins, domain:domain, @@ -115,12 +118,12 @@ function get_password(evt) evt.preventDefault(); - browser.tabs.query({active:true, currentWindow:true}, _query_tabs_get_password); + browser.tabs.query({active:true, currentWindow:true}).then(_query_tabs_get_password); return false; } -pform = document.getElementById("passwordForm"); +var pform = document.getElementById("passwordForm"); if (pform != null) pform.onsubmit = get_password; @@ -132,7 +135,7 @@ function _query_tabs_init(tabs) if (tabs.length != 1) return; /* Fill username */ - parameters = { + var parameters = { "type":"getUsername" }; @@ -166,20 +169,22 @@ function _query_tabs_init(tabs) }; browser.runtime.sendMessage(parameters, {}, - function (response) - { - url = response.value; - url = url.substring(0, url.lastIndexOf('/')); - url += '?'; - url += 'url=' + encodeURI(tabs[0].url.split("?")[0]); - url += '&user=' + document.getElementById("gPassUsername").value; - link = document.getElementById("serverLink"); - link.href = url; - - return true; - }); + function (response) + { + if (tabs[0].url === undefined) return true; + if (tabs[0].url.startsWith("chrome://newtab/")) tabs[0].url = ""; + var url = response.value; + url = url.substring(0, url.lastIndexOf('/')); + url += '?'; + url += 'url=' + encodeURI(tabs[0].url.split("?")[0]); + url += '&user=' + document.getElementById("gPassUsername").value; + var link = document.getElementById("serverLink"); + link.href = url; + + return true; + }); return true; } -browser.tabs.query({active:true, currentWindow:true}, _query_tabs_init); +browser.tabs.query({active:true, currentWindow:true}).then(_query_tabs_init);