'use strict'; const Tests = { GENERAL: '#general-results', INPUT_FIELDS: '#input-field-results', TOTP_FIELDS: '#totp-field-results', SEARCH_FIELDS: '#search-field-results', PASSWORD_CHANGE: '#password-change-results', }; function createResult(card, res, text) { const icon = kpxcUI.createElement('i', res ? 'fa fa-check' : 'fa fa-close', { id: text }); const span = kpxcUI.createElement('span', '', '', text); const br = document.createElement('br'); document.querySelector(card).appendMultiple(icon, span, br); } // General (global.js) async function testGeneral() { const testCard = Tests.GENERAL; // General kpxcAssert(trimURL('https://test.com/path_to_somwhere?login=username'), 'https://test.com/path_to_somwhere', testCard, 'trimURL()'); assertRegex(slashNeededForUrl('https://test.com'), true, testCard, 'slashNeededForUrl()'); assertRegex(slashNeededForUrl('https://test.com/'), false, testCard, 'slashNeededForUrl()'); // URL matching (URL in Site Preferences, page URL, expected result). // Consider using slighly different URL's for the tests cases. const matches = [ [ 'https://example.com/*', 'https://example.com/login_page', true ], [ 'https://*.lexample.com/*', 'https://example.com/login_page', false ], [ 'https://example.com/*', 'https://example2.com/login_page', false ], [ 'https://example.com/*', 'https://subdomain.example.com/login_page', false ], [ 'https://example.com', 'https://subdomain.example.com/login_page', false ], [ 'https://*.example.com/*', 'https://example.com/login_page', true ], [ 'https://*.example.com/*', 'https://test.example.com/login_page', true ], [ 'https://test.example.com/*', 'https://subdomain.example.com/login_page', false ], [ 'https://test.example.com/page/*', 'https://test.example.com/page/login_page', true ], [ 'https://test.example.com/page/*', 'https://test.example.com/page/login_page?dontcare=aboutme', true ], [ 'https://test.example.com/page/another_page/*', 'https://test.example.com/page/login', false ], [ 'https://test.example.com/path/another/a/', 'https://test.example.com/path/another/a/', true ], [ 'https://test.example.com/path/another/a/', 'https://test.example.com/path/another/b/', false ], [ 'https://test.example.com/*/another/a/', 'https://test.example.com/path/another/a/', true ], [ 'https://test.example.com/path/*/a/', 'https://test.example.com/path/another/a/', true ], [ 'https://test.example.com/path2/*/a/', 'https://test.example.com/path/another/a/', false ], [ 'https://example.com:8448/', 'https://example.com/', false ], [ 'https://example.com:8448/', 'https://example.com:8448/', true ], [ 'https://example.com:8448/login/page', 'https://example.com/login/page', false ], [ 'https://example.com:8448/*', 'https://example.com:8448/login/page', true ], [ 'https://example.com/$/*', 'https://example.com/$/login_page', true ], // Special character in URL [ 'https://example.com/*/*', 'https://example.com/$/login_page', true ], [ 'https://example.com/*/*', 'https://example.com/login_page', false ], [ 'https://*.com/*', 'https://example.com/$/login_page', true ], [ 'https://*.com/*', 'https://example.org/$/login_page', false ], [ 'https://*.*/*', 'https://example.org/$/login_page', false ], // IP based URL's [ 'https://127.128.129.130:8448/', 'https://127.128.129.130:8448/', true ], [ 'https://127.128.129.*:8448/', 'https://127.128.129.130:8448/', true ], [ 'https://127.128.*/', 'https://127.128.129.130/', true ], [ 'https://127.128.*/', 'https://127.1.129.130/', false ], [ 'https://127.128.129.130/', 'https://127.128.129.130:8448/', false ], [ 'https://127.128.129.*/', 'https://127.128.129.130:8448/', false ], // Invalid URL's [ '', 'https://example.com', false ], [ 'abcdefgetc', 'https://example.com', false ], [ '{TOTP}\\no', 'https://example.com', false ], [ 'https://320.320.320.320', 'https://example.com', false ] ]; for (const m of matches) { assertRegex(siteMatch(m[0], m[1]), m[2], testCard, `siteMatch() for ${m[1]}`); } // TODO: Enable these tests later. Not sure how to tests cookies API which requires to be running in the // background script. // Top-Level-Domain tests /*const tldUrls = [ [ 'another.example.co.uk', 'co.uk' ], [ 'www.example.com', 'com' ], [ 'github.com', 'com' ], [ 'test.net', 'net' ], [ 'so.many.subdomains.co.jp', 'co.jp' ], [ '192.168.0.1', '192.168.0.1' ], [ 'www.nic.ar','ar' ], [ 'no.no.no', 'no' ], [ 'www.blogspot.com.ar', 'blogspot.com.ar' ], // blogspot.com.ar is a TLD [ 'jap.an.ide.kyoto.jp', 'ide.kyoto.jp' ], // ide.kyoto.jp is a TLD ]; for (const d of tldUrls) { kpxcAssert(page.getTopLevelDomainFromUrl(d[0]), d[1], testCard, 'getTopLevelDomainFromUrl() for ' + d[0]); } // Base domain tests const domains = [ [ 'another.example.co.uk', 'example.co.uk' ], [ 'www.example.com', 'example.com' ], [ 'test.net', 'test.net' ], [ 'so.many.subdomains.co.jp', 'subdomains.co.jp' ], [ '192.168.0.1', '192.168.0.1' ], [ 'www.nic.ar', 'nic.ar' ], [ 'www.blogspot.com.ar', 'www.blogspot.com.ar' ], // blogspot.com.ar is a TLD [ 'www.arpa', 'www.arpa' ], [ 'jap.an.ide.kyoto.jp', 'an.ide.kyoto.jp' ], // ide.kyoto.jp is a TLD [ 'kobe.jp', 'kobe.jp' ], ]; for (const d of domains) { kpxcAssert(page.getBaseDomainFromUrl(d[0]), d[1], testCard, 'getBaseDomainFromUrl() for ' + d[0]); }*/ } // Input field matching (keepassxc-browser.js) async function testInputFields() { // Div ID, expected fields, action element ID (a button to be clicked) const testDivs = [ [ 'basic1', 2 ], // Username/passwd fields [ 'basic2', 1 ], // Only username field [ 'basic3', 1 ], // Only password field [ 'basic4', 3 ], // Username/passwd/TOTP fields [ 'div1', 2, '#toggle1' ], // Fields are behind a button that must be pressed [ 'div2', 2, '#toggle2' ], // Fields are behind a button that must be pressed behind a JavaScript [ 'div3', 2, '#toggle3' ], // Fields are behind a button that must be pressed [ 'div4', 2, '#toggle4' ], // Fields are behind a button that must be pressed [ 'hiddenFields1', 0 ], // Two hidden fields [ 'hiddenFields2', 1 ], // Two hidden fields with one visible ]; for (const div of testDivs) { await assertInputFields(div[0], div[1], div[2]); } } // Search fields (kpxcFields async function testSearchFields() { const searchFields = [ [ '', { id: 'otp_field', name: 'otp', type: 'text', maxLength: '8' }, 'Generic 2FA field', false ], [ '', { placeholder: 'search', type: 'text', id: 'username' }, 'Placeholder only', true ], [ '', { ariaLabel: 'search', type: 'text', id: 'username' }, 'aria-label only', true ], ]; for (const field of searchFields) { assertSearchField(field[0], field[1], field[2], field[3]); } assertSearchForm({ id: 'username', type: 'text', }, 'Generic input field under search form', true); } // TOTP fields (kpxcTOTPIcons) async function testTotpFields() { const totpFields = [ [ '', { id: 'otp_field', name: 'otp', type: 'text', maxLength: '8' }, 'Generic 2FA field', true ], [ '', { id: '2fa', type: 'text', maxLength: '6' }, 'Generic 2FA field', true ], [ '', { id: '2fa', type: 'text', maxLength: '4' }, 'Ignore if field maxLength too small', false ], [ '', { id: '2fa', type: 'text', maxLength: '12' }, 'Ignore if field maxLength too long', false ], [ '', { id: '2fa', type: 'text', maxLength: '12', autocomplete: 'one-time-code' }, 'Accept if one-time-code', true ], [ '', { id: 'username', type: 'text', }, 'Ignore a generic input field', false ], [ '', { type: 'password', }, 'Ignore a password input field', false ], [ // Protonmail 'TwoFA-input ng-empty ng-invalid ng-invalid-required ng-valid-minlength ng-valid-maxlength ng-touched', { autocapitalize: 'off', autocorrect: 'off', id: 'twoFactorCode', type: 'text', placeholder: 'Two-factor passcode', name: 'twoFactorCode' }, 'Protonmail 2FA', true ], [ // Nextcloud '', { minlength: '6', maxLength: '10', name: 'challenge', placeholder: 'Authentication code', type: 'tel', }, 'Nextcloud 2FA', true ], [ // GMail 'whsOnd zHQkBf', { autocomplete: 'off', id: 'idvPin', tabindex: '0', name: 'idvPin', pattern: '[0-9 ]*', type: 'tel', spellcheck: 'false' }, 'GMail 2FA', true ], [ // Live.com 'form-control', { autocomplete: 'off', id: 'idTxtBx_SAOTCC_OTC', maxLength: '8', tabindex: '0', name: 'otc', placeholder: 'Code', type: 'tel' }, 'Live.com 2FA', true ], ]; for (const field of totpFields) { assertTOTPField(field[0], field[1], field[2], field[3]); } } // Password change async function testPasswordChange() { // Div ID, expected new password const localDivs = [ [ 'passwordChange1', 'newPassword' ], // Default order without form [ 'passwordChange2', 'newPassword' ], // Reversed order without form [ 'passwordChange3', 'newPassword' ], // Default order with form [ 'passwordChange4', 'newPassword' ], // Reversed order with form [ 'passwordChange5', 'newPassword' ], // Each field has own form ]; for (const div of localDivs) { await assertPasswordChangeFields(div[0], div[1]); } } // Run tests (async () => { await Promise.all([ await testGeneral(), await testInputFields(), await testSearchFields(), await testTotpFields(), await testPasswordChange(), ]); })();