Fixes https://github.com/lesspass/lesspass/issues/540 Put the option in beta with a warning to test it livepull/612/head
@@ -6,4 +6,5 @@ node_modules | |||
package-lock.json | |||
default.nix | |||
.envrc | |||
sandbox/lesspass-passwordrules | |||
sandbox/lesspass-passwordrules | |||
sandbox/lesspass-tld/top-1m.csv |
@@ -1,6 +1,6 @@ | |||
{ | |||
"name": "lesspass-pure", | |||
"version": "9.4.4", | |||
"version": "9.4.5", | |||
"description": "LessPass web component", | |||
"license": "GPL-3.0", | |||
"author": "Guillaume Vincent <guillaume@oslab.fr>", | |||
@@ -1,6 +1,7 @@ | |||
"use strict"; | |||
import atob from "@oslab/atob"; | |||
import mostUsedTlds from "./mostUsedTlds.json"; | |||
export function cleanUrl(url) { | |||
if (!url) { | |||
@@ -10,6 +11,29 @@ export function cleanUrl(url) { | |||
return matchesDomainName && matchesDomainName[1] ? matchesDomainName[1] : ""; | |||
} | |||
export function removeSiteSubdomain(url) { | |||
let hostname = ""; | |||
try { | |||
hostname = new URL(url).hostname; | |||
} catch (error) { | |||
return ""; | |||
} | |||
for (let i = 0; i < mostUsedTlds.length; i++) { | |||
const tld = mostUsedTlds[i]; | |||
const tldWithDot = `.${tld}`; | |||
if (hostname.endsWith(tldWithDot)) { | |||
const domain = hostname | |||
.replace(tldWithDot, "") | |||
.split(".") | |||
.pop(); | |||
if (domain) { | |||
return domain + tldWithDot; | |||
} | |||
} | |||
} | |||
return hostname; | |||
} | |||
function isAnIpAddressWithPort(address) { | |||
return /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d{1,5})$/.test(address); | |||
} | |||
@@ -45,7 +69,7 @@ export function getSite() { | |||
typeof chrome.tabs.query !== "undefined" | |||
) { | |||
chrome.tabs.query({ active: true, currentWindow: true }, tabs => { | |||
resolve(cleanUrl(tabs[0].url)); | |||
resolve(tabs[0].url); | |||
}); | |||
} else { | |||
resolve(""); | |||
@@ -1,35 +1,78 @@ | |||
import * as urlParser from "./url-parser"; | |||
test("cleanUrl", () => { | |||
expect("lesspass.com").toBe(urlParser.cleanUrl("https://lesspass.com/#!/")); | |||
expect("lesspass.com").toBe(urlParser.cleanUrl("https://lesspass.com/api/")); | |||
expect("api.lesspass.com").toBe( | |||
urlParser.cleanUrl("https://api.lesspass.com/") | |||
expect(urlParser.cleanUrl("https://lesspass.com/#!/")).toBe("lesspass.com"); | |||
expect(urlParser.cleanUrl("https://lesspass.com/api/")).toBe("lesspass.com"); | |||
expect(urlParser.cleanUrl("https://api.lesspass.com/")).toBe( | |||
"api.lesspass.com" | |||
); | |||
expect("lesspass.com").toBe(urlParser.cleanUrl("http://lesspass.com")); | |||
expect("stackoverflow.com").toBe( | |||
expect(urlParser.cleanUrl("http://lesspass.com")).toBe("lesspass.com"); | |||
expect( | |||
urlParser.cleanUrl( | |||
"http://stackoverflow.com/questions/3689423/google-chrome-plugin-how-to-get-domain-from-url-tab-url" | |||
) | |||
); | |||
expect("v4-alpha.getbootstrap.com").toBe( | |||
).toBe("stackoverflow.com"); | |||
expect( | |||
urlParser.cleanUrl("http://v4-alpha.getbootstrap.com/components/buttons/") | |||
); | |||
expect("accounts.google.com").toBe( | |||
).toBe("v4-alpha.getbootstrap.com"); | |||
expect( | |||
urlParser.cleanUrl( | |||
"https://accounts.google.com/ServiceLogin?service=mail&passive=true&rm=false&continue=https://mail.google.com/mail/&ss=1&scc=1<mpl=default<mplcache=2&emr=1&osid=1#identifier" | |||
) | |||
).toBe("accounts.google.com"); | |||
expect(urlParser.cleanUrl("https://www.netflix.com/browse")).toBe( | |||
"www.netflix.com" | |||
); | |||
expect(urlParser.cleanUrl("https://www.bbc.co.uk")).toBe("www.bbc.co.uk"); | |||
expect(urlParser.cleanUrl("https://192.168.1.1:10443/webapp/")).toBe( | |||
"192.168.1.1:10443" | |||
); | |||
expect(urlParser.cleanUrl(undefined)).toBe(""); | |||
expect(urlParser.cleanUrl("chrome://extensions/")).toBe(""); | |||
}); | |||
test("cleanUrl beta", () => { | |||
expect(urlParser.removeSiteSubdomain("co.uk")).toBe(""); | |||
expect(urlParser.removeSiteSubdomain("https://lesspass.com/#!/")).toBe( | |||
"lesspass.com" | |||
); | |||
expect(urlParser.removeSiteSubdomain("https://lesspass.com/api/")).toBe( | |||
"lesspass.com" | |||
); | |||
expect(urlParser.removeSiteSubdomain("https://api.lesspass.com/")).toBe( | |||
"lesspass.com" | |||
); | |||
expect(urlParser.removeSiteSubdomain("http://lesspass.com")).toBe("lesspass.com"); | |||
expect(urlParser.removeSiteSubdomain("http://www.lesspass.com")).toBe( | |||
"lesspass.com" | |||
); | |||
expect( | |||
urlParser.removeSiteSubdomain( | |||
"http://stackoverflow.com/questions/3689423/google-chrome-plugin-how-to-get-domain-from-url-tab-url" | |||
) | |||
).toBe("stackoverflow.com"); | |||
expect( | |||
urlParser.removeSiteSubdomain( | |||
"http://v4-alpha.getbootstrap.com/components/buttons/" | |||
) | |||
).toBe("getbootstrap.com"); | |||
expect( | |||
urlParser.removeSiteSubdomain( | |||
"https://accounts.google.com/ServiceLogin?service=mail&passive=true&rm=false&continue=https://mail.google.com/mail/&ss=1&scc=1<mpl=default<mplcache=2&emr=1&osid=1#identifier" | |||
) | |||
).toBe("google.com"); | |||
expect(urlParser.removeSiteSubdomain("https://mail.google.com/mail/u/0/")).toBe( | |||
"google.com" | |||
); | |||
expect("www.netflix.com").toBe( | |||
urlParser.cleanUrl("https://www.netflix.com/browse") | |||
expect(urlParser.removeSiteSubdomain("https://www.netflix.com/browse")).toBe( | |||
"netflix.com" | |||
); | |||
expect("www.bbc.co.uk").toBe(urlParser.cleanUrl("https://www.bbc.co.uk")); | |||
expect("192.168.1.1:10443").toBe( | |||
urlParser.cleanUrl("https://192.168.1.1:10443/webapp/") | |||
expect(urlParser.removeSiteSubdomain("https://www.bbc.co.uk")).toBe("bbc.co.uk"); | |||
expect(urlParser.removeSiteSubdomain("https://192.168.1.1:10443/webapp/")).toBe( | |||
"192.168.1.1" | |||
); | |||
expect("").toBe(urlParser.cleanUrl(undefined)); | |||
expect("").toBe(urlParser.cleanUrl(undefined)); | |||
expect("").toBe(urlParser.cleanUrl("chrome://extensions/")); | |||
expect(urlParser.removeSiteSubdomain(undefined)).toBe(""); | |||
expect(urlParser.removeSiteSubdomain("chrome://extensions/")).toBe("extensions"); | |||
}); | |||
test("getSuggestions", () => { | |||
@@ -68,7 +111,7 @@ test("getSite", () => { | |||
} | |||
}; | |||
return urlParser.getSite().then(site => { | |||
expect(site).toBe("example.org"); | |||
expect(site).toBe("https://example.org"); | |||
}); | |||
}); | |||
@@ -11,3 +11,5 @@ export const passwordURL = state => { | |||
}; | |||
export const shouldAutoFillSite = state => !state.settings.noAutoFillSite; | |||
export const shouldRemoveSubdomain = state => state.settings.removeSiteSubdomain; |
@@ -70,3 +70,20 @@ test("shouldAutoFillSite", () => { | |||
}) | |||
).toBe(true); | |||
}); | |||
test("shouldRemoveSubdomain", () => { | |||
expect( | |||
getters.shouldRemoveSubdomain({ | |||
settings: { | |||
removeSiteSubdomain: true | |||
} | |||
}) | |||
).toBe(true); | |||
expect( | |||
getters.shouldRemoveSubdomain({ | |||
settings: { | |||
removeSiteSubdomain: false | |||
} | |||
}) | |||
).toBe(false); | |||
}); |
@@ -19,7 +19,8 @@ const state = { | |||
settings: { | |||
baseURL: defaultBaseURL, | |||
encryptMasterPassword: true, | |||
noAutoFillSite: false | |||
noAutoFillSite: false, | |||
removeSiteSubdomain: false | |||
} | |||
}; | |||
@@ -121,7 +121,7 @@ import InputSite from "../components/InputSite.vue"; | |||
import Options from "../components/Options.vue"; | |||
import { showTooltip, hideTooltip } from "../services/tooltip"; | |||
import message from "../services/message"; | |||
import * as urlParser from "../services/url-parser"; | |||
import { getSite, cleanUrl, removeSiteSubdomain } from "../services/url-parser"; | |||
export default { | |||
name: "password-generator-view", | |||
@@ -133,12 +133,19 @@ export default { | |||
}, | |||
computed: { | |||
...mapState(["password", "passwords"]), | |||
...mapGetters(["passwordURL", "shouldAutoFillSite"]) | |||
...mapGetters([ | |||
"passwordURL", | |||
"shouldAutoFillSite", | |||
"shouldRemoveSubdomain" | |||
]) | |||
}, | |||
beforeMount() { | |||
if (this.shouldAutoFillSite) { | |||
urlParser.getSite().then(site => { | |||
this.$store.dispatch("setSite", { site }); | |||
getSite().then(site => { | |||
const cleanedSite = this.shouldRemoveSubdomain | |||
? removeSiteSubdomain(site) | |||
: cleanUrl(site); | |||
this.$store.dispatch("setSite", { site: cleanedSite }); | |||
}); | |||
} | |||
this.$store.dispatch("getPasswordFromUrlQuery", { | |||
@@ -28,26 +28,49 @@ | |||
<div class="mb-3"> | |||
<h5>{{ $t("Other options") }}</h5> | |||
</div> | |||
<label for="noAutoFillSite">{{ $t("Site") }}</label> | |||
<div class="form-check mb-3"> | |||
<input | |||
id="noAutoFillSite" | |||
class="form-check-input" | |||
type="checkbox" | |||
v-model="settings.noAutoFillSite" | |||
/> | |||
<label class="form-check-label" for="noAutoFillSite"> | |||
<small> | |||
{{ | |||
$t( | |||
"noAutoFillSite", | |||
"Stop filling in the site field automatically" | |||
) | |||
}} | |||
</small> | |||
</label> | |||
<div class="mb-4"> | |||
<label for="noAutoFillSite">{{ $t("Site") }}</label> | |||
<div class="form-check mb-3"> | |||
<input | |||
id="noAutoFillSite" | |||
class="form-check-input" | |||
type="checkbox" | |||
v-model="settings.noAutoFillSite" | |||
/> | |||
<label class="form-check-label" for="noAutoFillSite"> | |||
<small> | |||
{{ | |||
$t( | |||
"noAutoFillSite", | |||
"Stop filling in the site field automatically" | |||
) | |||
}} | |||
</small> | |||
</label> | |||
</div> | |||
<div class="form-check"> | |||
<input | |||
id="removeSiteSubdomain" | |||
class="form-check-input" | |||
type="checkbox" | |||
v-model="settings.removeSiteSubdomain" | |||
/> | |||
<label class="form-check-label" for="removeSiteSubdomain"> | |||
<small> | |||
{{ $t("removeSiteSubdomain", "Remove subdomain from site") }} | |||
</small> | |||
</label> | |||
</div> | |||
<small class="form-text text-danger"> | |||
{{ | |||
$t( | |||
"DontUseItYet", | |||
"Backward compatibility not guaranteed. Dont use it yet!" | |||
) | |||
}} | |||
</small> | |||
</div> | |||
<div class="form-group has-validation"> | |||
<div class="form-group"> | |||
<label for="login">{{ $t("LessPass Database Url") }}</label> | |||
<div class="inner-addon left-addon"> | |||
<i class="fa fa-user"></i> | |||
@@ -66,7 +89,7 @@ | |||
</div> | |||
<div | |||
v-if="settings.baseURL !== defaultBaseURL" | |||
class="text-warning mt-1" | |||
class="text-danger mt-1" | |||
> | |||
<small> | |||
{{ $t("It is not recommended to change the default url.") }} | |||
@@ -0,0 +1,16 @@ | |||
import csv | |||
from tld import get_tld | |||
tlds = {} | |||
with open('top-1m.csv', newline='') as csvfile: | |||
lignes = csv.reader(csvfile) | |||
for ligne in lignes: | |||
tld = get_tld(ligne[1], fix_protocol=True) | |||
tlds[tld] = tlds.get(tld, 0) + 1 | |||
sorted_tlds = [] | |||
for w in sorted(tlds, key=tlds.get, reverse=True): | |||
if tlds[w] > 1 and "xn--" not in w: | |||
sorted_tlds.append(w) | |||
print(sorted_tlds) |