Big update:
* Move all core functions from mains.js to background.js * Use message interface for IPCs between main.js, background.js and popup.js * Add popup interface : * safest method to compute masterkey * Direct access to our own gPass server with auto URL and username fill * Add some specific menus : * Access to gPass settings * Allow to disable extension * Update gPass icon when a password field has focus and gPass is ready to work
This commit is contained in:
@@ -1 +0,0 @@
|
||||
../chrome_addon/lib/
|
381
firefox_webextension/lib/main.js
Normal file
381
firefox_webextension/lib/main.js
Normal file
@@ -0,0 +1,381 @@
|
||||
/*
|
||||
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];
|
||||
|
||||
for (var a=0; a<type_filters.length; a++)
|
||||
{
|
||||
if ((match && field.getAttribute("type") == type_filters[a]) ||
|
||||
(!match && field.getAttribute("type") != type_filters[a]))
|
||||
{
|
||||
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 on_focus(e)
|
||||
{
|
||||
if (gpass_enabled)
|
||||
{
|
||||
parameters = {
|
||||
type:"update_icon",
|
||||
icon_id:GPASS_ICON.ACTIVATED,
|
||||
};
|
||||
browser.runtime.sendMessage(parameters, {});
|
||||
}
|
||||
}
|
||||
|
||||
function on_blur(e)
|
||||
{
|
||||
if (gpass_enabled)
|
||||
{
|
||||
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");
|
||||
|
||||
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);
|
||||
|
||||
// Get all other fields except text, email and password
|
||||
type_filters.push("password");
|
||||
all_logins = try_get_name(fields, type_filters, 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)
|
||||
{
|
||||
block_url(form.action);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Request can be sent to another URL... */
|
||||
if (managed_forms.length && do_block)
|
||||
block_url("<all_urls>");
|
||||
}
|
||||
|
||||
function unblock_all_forms()
|
||||
{
|
||||
debug("unblock all forms");
|
||||
|
||||
on_blur(null);
|
||||
|
||||
for(var i=0; i<managed_forms.length; i++)
|
||||
{
|
||||
var form = managed_forms[i];
|
||||
|
||||
form.removeEventListener("submit", on_sumbit);
|
||||
unblock_url(form.action);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (managed_forms.length)
|
||||
unblock_url("<all_urls>");
|
||||
|
||||
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("");
|
295
firefox_webextension/lib/misc.js
Normal file
295
firefox_webextension/lib/misc.js
Normal file
@@ -0,0 +1,295 @@
|
||||
/*
|
||||
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 DEBUG = false;
|
||||
|
||||
SERVER = {OK : 0, FAILED : 1, RESTART_REQUEST : 2};
|
||||
GPASS_ICON = {NORMAL:0, DISABLED:1, ACTIVATED:2};
|
||||
|
||||
var default_preferences = {"pbkdf2_level": 1000,
|
||||
"account_url": "https://gpass-demo.soutade.fr/demo",
|
||||
"crypto_v1_compatible": true};
|
||||
|
||||
var browser = browser || chrome;
|
||||
var crypto = crypto || window.crypto;
|
||||
|
||||
function notify(text, data)
|
||||
{
|
||||
browser.runtime.sendMessage({type: "notification", options:{"message":text, "data":data}});
|
||||
}
|
||||
|
||||
function block_url(url)
|
||||
{
|
||||
debug("Block URL " + url);
|
||||
browser.runtime.sendMessage({type: "block_url", options:{"url":url}});
|
||||
}
|
||||
|
||||
function unblock_url(url)
|
||||
{
|
||||
debug("Unblock URL " + url);
|
||||
browser.runtime.sendMessage({type: "unblock_url", options:{"url":url}});
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/6965107/converting-between-strings-and-arraybuffers
|
||||
function ab2str(buf) {
|
||||
return String.fromCharCode.apply(null, new Uint8Array(buf));
|
||||
}
|
||||
|
||||
// https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
|
||||
function str2ab2(str) {
|
||||
var chars = []
|
||||
for (var i=0, strLen=str.length; i < strLen; i++) {
|
||||
chars.push(str.charCodeAt(i));
|
||||
}
|
||||
return new Uint8Array(chars);
|
||||
}
|
||||
|
||||
function str2ab(str) {
|
||||
var buf = new ArrayBuffer(str.length);
|
||||
// var buf = new ArrayBuffer(str.length*2); // 2 bytes for each char
|
||||
var bufView = new Uint8Array(buf);
|
||||
for (var i=0, strLen=str.length; i < strLen; i++) {
|
||||
bufView[i] = str.charCodeAt(i);
|
||||
}
|
||||
return bufView;
|
||||
}
|
||||
|
||||
function crypto_pbkdf2(mkey, salt, level)
|
||||
{
|
||||
AESCBC = {
|
||||
name: "AES-CBC",
|
||||
length: 256,
|
||||
}
|
||||
|
||||
var key = str2ab(mkey);
|
||||
return crypto.subtle.importKey("raw", key, {name: "PBKDF2"}, false, ["deriveBits", "deriveKey"])
|
||||
.then(function(key){
|
||||
//sha-256
|
||||
return crypto.subtle.deriveKey({
|
||||
name: "PBKDF2",
|
||||
salt: str2ab(salt),
|
||||
iterations: level,
|
||||
hash: "SHA-256",
|
||||
}, key, AESCBC, false, ["encrypt", "decrypt", "unwrapKey", "wrapKey"])
|
||||
.then(function(key) {
|
||||
return key;
|
||||
})
|
||||
.catch(function(err){
|
||||
console.log("Error derive key " + err);
|
||||
});
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.log("Error import key" + err);
|
||||
});
|
||||
}
|
||||
|
||||
function simple_pbkdf2(mkey, salt, level)
|
||||
{
|
||||
AESCBC = {
|
||||
name: "AES-CBC",
|
||||
length: 256,
|
||||
}
|
||||
|
||||
var key = str2ab(mkey);
|
||||
return crypto.subtle.importKey("raw", key, {name: "PBKDF2"}, false, ["deriveBits", "deriveKey"])
|
||||
.then(function(key){
|
||||
//sha-256
|
||||
return crypto.subtle.deriveKey({
|
||||
name: "PBKDF2",
|
||||
salt: str2ab(salt),
|
||||
iterations: level,
|
||||
hash: "SHA-256",
|
||||
}, key, AESCBC, true, ["unwrapKey", "wrapKey"])
|
||||
.then(function(key) {
|
||||
return crypto.subtle.exportKey("raw", key)
|
||||
.then(function (key) {
|
||||
return ab2str(key);
|
||||
});
|
||||
})
|
||||
.catch(function(err){
|
||||
console.log("Error derive key " + err);
|
||||
});
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.log("Error import key" + err);
|
||||
});
|
||||
}
|
||||
|
||||
function _encrypt(mkey, iv, data)
|
||||
{
|
||||
while ((data.length % 16))
|
||||
data += "\0";
|
||||
|
||||
data = str2ab(data);
|
||||
|
||||
promise = mkey.then(function(mkey){
|
||||
return crypto.subtle.encrypt({
|
||||
name: "AES-CBC",
|
||||
iv: iv
|
||||
}, mkey, data)})
|
||||
.then(function(encrypted) {
|
||||
return ab2str(encrypted);
|
||||
})
|
||||
.catch(function(encryption) {
|
||||
console.log("Encryption rejected " + encryption);
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
async function _decrypt(mkey, iv, data)
|
||||
{
|
||||
while ((data.length % 16))
|
||||
data += "\0";
|
||||
|
||||
nulliv = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
pkcs7_padding = new Uint8Array([16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16]);
|
||||
pkcs7_padding = await _encrypt(mkey, nulliv, ab2str(pkcs7_padding));
|
||||
|
||||
data = str2ab(data + pkcs7_padding);
|
||||
|
||||
nulliv = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
|
||||
promise = mkey.then(function(mkey){
|
||||
return crypto.subtle.decrypt({
|
||||
name: "AES-CBC",
|
||||
iv: iv
|
||||
}, mkey, data)})
|
||||
.then(function(decrypted) {
|
||||
return ab2str(decrypted);
|
||||
})
|
||||
.catch(function(decryption) {
|
||||
console.log("Decryption rejected " + decryption);
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
async function encrypt_ecb(mkey, data)
|
||||
{
|
||||
var result = "";
|
||||
|
||||
debug("Encrypt ECB " + data);
|
||||
|
||||
nulliv = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
|
||||
while (data.length > 16)
|
||||
{
|
||||
res = await _encrypt(mkey, nulliv, data.slice(0, 16));
|
||||
// Remove PKCS7 padding
|
||||
result += res.slice(0, 16);
|
||||
data = data.slice(16);
|
||||
}
|
||||
res = await _encrypt(mkey, nulliv, data);
|
||||
result += res.slice(0, 16);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async function decrypt_ecb(mkey, data)
|
||||
{
|
||||
var result = "";
|
||||
|
||||
debug("Decrypt ECB " + data);
|
||||
|
||||
nulliv = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
|
||||
while (data.length > 16)
|
||||
{
|
||||
res = await _decrypt(mkey, nulliv, data.slice(0, 16));
|
||||
// Remove PKCS7 padding
|
||||
result += res.slice(0, 16);
|
||||
data = data.slice(16);
|
||||
}
|
||||
res = await _decrypt(mkey, nulliv, data);
|
||||
result += res.slice(0, 16);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async function encrypt_cbc(mkey, iv, data)
|
||||
{
|
||||
debug("Encrypt CBC " + data);
|
||||
|
||||
var result = await _encrypt(mkey, str2ab(iv), data);
|
||||
|
||||
// Remove PKCS7 padding
|
||||
return result.slice(0, result.length-16);
|
||||
}
|
||||
|
||||
async function decrypt_cbc(mkey, iv, data)
|
||||
{
|
||||
debug("Decrypt CBC " + data);
|
||||
|
||||
var result = await _decrypt(mkey, str2ab(iv), data);
|
||||
|
||||
// Remove PKCS7 padding
|
||||
return result.slice(0, result.length-16);
|
||||
}
|
||||
|
||||
async function digest(data)
|
||||
{
|
||||
return crypto.subtle.digest("SHA-256", str2ab(data)).then(function (hash) {
|
||||
return ab2str(hash);
|
||||
});
|
||||
}
|
||||
|
||||
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<parts.length; i++)
|
||||
res += parts[i] + ".";
|
||||
return res.substr(0, res.length-1);
|
||||
}
|
||||
// Simple xxx.com
|
||||
else if (parts.length == 2)
|
||||
return "*." + domain;
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
// 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++)
|
||||
{
|
||||
var c = _str_.charCodeAt(i).toString(16);
|
||||
if (c.length == 1) c = "0" + c;
|
||||
hex += c;
|
||||
}
|
||||
return hex;
|
||||
}
|
||||
|
||||
function debug(s)
|
||||
{
|
||||
if (DEBUG)
|
||||
console.log(s);
|
||||
}
|
32
firefox_webextension/lib/parseuri.js
Normal file
32
firefox_webextension/lib/parseuri.js
Normal file
@@ -0,0 +1,32 @@
|
||||
// parseUri 1.2.2
|
||||
// (c) Steven Levithan <stevenlevithan.com>
|
||||
// MIT License
|
||||
|
||||
parseURI = {
|
||||
|
||||
parseUri : function (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
|
||||
},
|
||||
parser: {
|
||||
strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
|
||||
loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
|
||||
}},
|
||||
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;
|
||||
}
|
||||
};
|
Reference in New Issue
Block a user