gPass/firefox_webextension/lib/main.js

407 lines
9.2 KiB
JavaScript

/*
Copyright (C) 2013-2020 Grégory Soutadé
This file is part of gPass.
gPass is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
gPass is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with gPass. If not, see <http://www.gnu.org/licenses/>.
*/
var gpass_enabled = true;
function _notification(message, data)
{
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);
}
);
}
function _add_name(logins, name)
{
for(var i=0; i<logins.length; i++)
if (logins[i] == name) return ;
logins.push(name);
}
function try_get_name(fields, type_filters, match)
{
var user = null;
var all_logins = new Array();
for (var i=0; i<fields.length; i++)
{
var field = fields[i];
var to_test = !match;
for (var a=0; a<type_filters.length; a++)
{
if (match && field.getAttribute("type") == type_filters[a])
{
to_test = true;
break;
}
if(!match && field.getAttribute("type") == type_filters[a])
{
to_test = false;
break;
}
}
if (to_test)
{
if (field.hasAttribute("name") && field.value != "")
{
name = field.getAttribute("name");
// Subset of common user field
if (name == "user") user = field.value;
else if (name == "usr") user = field.value;
else if (name == "username") user = field.value;
else if (name == "login") user = field.value;
_add_name(all_logins, field.value);
}
}
}
if (user != null)
return new Array(user);
else
return all_logins;
}
function get_logins(form, all_logins)
{
var fields = form.getElementsByTagName("input");
var logins = null;
debug("get_logins");
var type_filters = new Array();
// Get all <input type="text"> && <input type="email">
type_filters.push("text");
type_filters.push("email");
if (all_logins)
logins = try_get_name(fields, type_filters, true);
else
{
// Get all other fields except text, email and password
type_filters.push("password");
type_filters.push("hidden");
logins = try_get_name(fields, type_filters, false);
}
return logins;
}
function on_focus(e)
{
if (!gpass_enabled)
return;
var logins = get_logins(this.form, true);
var all_logins = get_logins(this.form, false);
if (logins.length || all_logins.length)
{
parameters = {
type:"update_icon",
icon_id:GPASS_ICON.ACTIVATED,
};
browser.runtime.sendMessage(parameters, {});
}
}
function on_blur(e)
{
if (!gpass_enabled)
return;
parameters = {
type:"update_icon",
icon_id:GPASS_ICON.NORMAL,
};
browser.runtime.sendMessage(parameters, {});
}
function on_sumbit(e)
{
var form = this;
var fields = form.getElementsByTagName("input");
var domain = form.ownerDocument.baseURI;
var password_computed = false;
debug("on_submit");
logins = get_logins(form, true);
all_logins = get_logins(form, false);
if (!logins.length)
logins = all_logins;
e.preventDefault();
// Look for <input type="password" value="@@...">
for (var i=0; i<fields.length; i++)
{
var field = fields[i];
if (field.getAttribute("type") == "password")
{
password = field.value;
if (!password.startsWith("@@") && !password.startsWith("@_"))
continue;
// Remove current value to limit master key stealing
field.value = "";
password_computed = true;
do_submit = !password.startsWith("@_");
mkey = password.substring(2);
parameters = {
type:"password",
logins:logins,
domain:domain,
mkey:mkey,
options:{field_id:i}
};
browser.runtime.sendMessage(parameters, {},
function (response) {
debug(response);
var field = fields[response.options.field_id];
switch(response.value)
{
case SERVER.OK:
set_password(form, field, response.password, do_submit)
notify("Password successfully replaced", "");
break;
case SERVER.FAILED:
if (logins.length != all_logins.length)
{
parameters[logins] = all_logins;
browser.runtime.sendMessage(parameters);
}
break;
case SERVER.RESTART_REQUEST:
i = -1; // Restart loop
break;
}
}
);
}
}
if (!password_computed)
{
debug("No password computed");
form.submit();
return true;
}
return false;
}
function set_password(form, field, password, do_submit)
{
field.value = password;
// Remove gPass event listener and submit again with clear password
unblock_all_forms();
if (do_submit)
{
// Propagate change
change_cb = field.onchange;
if (change_cb)
change_cb();
// Try to type "enter"
var evt = new KeyboardEvent("keydown");
delete evt.which;
evt.which = 13;
field.dispatchEvent(evt);
// Submit form
form.submit();
}
}
var managed_forms = new Array();
function block_all_forms(doc, do_block)
{
var first_time = (managed_forms.length == 0);
var cur_focused_element = document.activeElement;
debug("block all forms");
gpass_enabled = do_block;
// If there is a password in the form, add a "submit" listener
for(var i=0; i<doc.forms.length; i++)
{
var form = doc.forms[i];
var fields = form.getElementsByTagName("input");
for (var a=0; a<fields.length; a++)
{
var field = fields[a];
if (field.getAttribute("type") == "password")
{
if (do_block)
{
old_cb = form.onsubmit;
if (old_cb)
form.removeEventListener("submit", old_cb);
form.addEventListener("submit", on_sumbit);
if (old_cb)
form.addEventListener("submit", old_cb);
field.addEventListener("focus", on_focus);
field.addEventListener("blur", on_blur);
if (cur_focused_element === field)
on_focus(null);
}
if (first_time)
managed_forms.push(form);
break;
}
}
}
}
function unblock_all_forms()
{
debug("unblock all forms");
for(var i=0; i<managed_forms.length; i++)
{
var form = managed_forms[i];
form.removeEventListener("submit", on_sumbit);
var fields = form.getElementsByTagName("input");
for (var a=0; a<fields.length; a++)
{
var field = fields[a];
if (field.getAttribute("type") == "password")
{
field.removeEventListener("focus", on_focus);
field.removeEventListener("blur", on_blur);
break;
}
}
}
gpass_enabled = false;
}
browser.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.type == "getUsername")
{
debug("getUsername");
if (managed_forms.length == 1)
{
fields = managed_forms[0].getElementsByTagName("input");
type_filters = new Array();
// Get all <input type="text"> && <input type="email">
type_filters.push("text");
type_filters.push("email");
logins = try_get_name(fields, type_filters, true);
if (logins.length == 1)
sendResponse(logins[0]);
else
sendResponse("");
}
else
sendResponse("");
}
else if (request.type == "setPassword")
{
debug("setPassword");
var response = "";
if (managed_forms.length == 1)
{
fields = managed_forms[0].getElementsByTagName("input");
password_field = null;
for (a=0; a<fields.length; a++)
{
field = fields[a];
if (field.getAttribute("type") == "password")
{
if (password_field == null)
password_field = field;
else
{
// More than one password field : abort
password_field = null;
break;
}
}
}
if (password_field)
{
set_password(managed_forms[0], password_field,
request.password, request.submit);
response = "ok";
}
}
sendResponse(response);
return true;
}
else if (request.type == "blockForms")
{
block_all_forms(document, true);
}
else if (request.type == "unblockForms")
{
unblock_all_forms();
}
});
function document_loaded()
{
parameters = {
"type": "is_gpass_enabled",
};
browser.runtime.sendMessage(parameters, {},
function (response) {
if (response)
block_all_forms(document, response.enabled);
});
}
document_loaded();
console.log("Welcome to gPass web extension v0.9 !");
console.log("Privacy Policy can be found at http://indefero.soutade.fr/p/gpass/source/tree/master/PrivacyPolicy.md");
console.log("");