From a80bd4e4ea1dd97889998b1dca08b511245c06a5 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Thu, 4 Jan 2024 23:08:17 +0100 Subject: [PATCH] fix url filter, IDN URL unit test --- js/common.js | 2 +- js/package-lock.json | 4 +-- js/privatebin.js | 60 ++++++++++++++++++++++++++---------------- js/test/PasteStatus.js | 45 +++++++++++++++++++++++++++++++ tpl/bootstrap.php | 2 +- tpl/page.php | 2 +- 6 files changed, 87 insertions(+), 28 deletions(-) diff --git a/js/common.js b/js/common.js index 7e406acb..295fd090 100644 --- a/js/common.js +++ b/js/common.js @@ -37,7 +37,7 @@ var a2zString = ['a','b','c','d','e','f','g','h','i','j','k','l','m', }) ), schemas = ['ftp','http','https'], - supportedLanguages = ['de', 'es', 'fr', 'it', 'no', 'pl', 'pt', 'oc', 'ru', 'sl', 'zh'], + supportedLanguages = ['ar', 'bg', 'ca', 'co', 'cs', 'de', 'el', 'es', 'et', 'fi', 'fr', 'he', 'hu', 'id', 'it', 'ja', 'jbo', 'lt', 'no', 'nl', 'pl', 'pt', 'oc', 'ru', 'sk', 'sl', 'th', 'tr', 'uk', 'zh'], mimeTypes = ['image/png', 'application/octet-stream'], formats = ['plaintext', 'markdown', 'syntaxhighlighting'], mimeFile = fs.createReadStream('/etc/mime.types'), diff --git a/js/package-lock.json b/js/package-lock.json index b7b29ab7..ed57b711 100644 --- a/js/package-lock.json +++ b/js/package-lock.json @@ -1,12 +1,12 @@ { "name": "privatebin", - "version": "1.5.2", + "version": "1.6.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "privatebin", - "version": "1.5.2", + "version": "1.6.2", "license": "zlib-acknowledgement", "devDependencies": { "@peculiar/webcrypto": "^1.1.1", diff --git a/js/privatebin.js b/js/privatebin.js index db85eef7..e63ab5fb 100644 --- a/js/privatebin.js +++ b/js/privatebin.js @@ -2035,29 +2035,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { xhrFields: { withCredentials: false }, - success: function(response) { - let responseString = response; - if (typeof responseString === 'object') { - responseString = JSON.stringify(responseString); - } - if (typeof responseString === 'string' && responseString.length > 0) { - const shortUrlMatcher = /https?:\/\/[^\s]+/g; - const shortUrl = (responseString.match(shortUrlMatcher) || []).filter(URL.canParse).sort(function(a, b) { - return a.length - b.length; - })[0]; - if (typeof shortUrl === 'string' && shortUrl.length > 0) { - // we disable the button to avoid calling shortener again - $shortenButton.addClass('buttondisabled'); - // update link - $pasteUrl.text(shortUrl); - $pasteUrl.prop('href', shortUrl); - // we pre-select the link so that the user only has to [Ctrl]+[c] the link - Helper.selectText($pasteUrl[0]); - return; - } - } - Alert.showError('Cannot parse response from URL shortener.'); - } + success: PasteStatus.extractUrl }) .fail(function(data, textStatus, errorThrown) { console.error(textStatus, errorThrown); @@ -2123,6 +2101,42 @@ jQuery.PrivateBin = (function($, RawDeflate) { Helper.selectText($pasteUrl[0]); }; + /** + * extracts URLs from given string + * + * if at least one is found, it disables the shortener button and + * replaces the paste URL + * + * @name PasteStatus.extractUrl + * @function + * @param {string} response + */ + me.extractUrl = function(response) + { + if (typeof response === 'object') { + response = JSON.stringify(response); + } + if (typeof response === 'string' && response.length > 0) { + const shortUrlMatcher = /https?:\/\/[^\s]+/g; + const shortUrl = (response.match(shortUrlMatcher) || []).filter(function(a) { + return URL.canParse(a); + }).sort(function(a, b) { + return a.length - b.length; + })[0]; + if (typeof shortUrl === 'string' && shortUrl.length > 0) { + // we disable the button to avoid calling shortener again + $shortenButton.addClass('buttondisabled'); + // update link + $pasteUrl.text(shortUrl); + $pasteUrl.prop('href', shortUrl); + // we pre-select the link so that the user only has to [Ctrl]+[c] the link + Helper.selectText($pasteUrl[0]); + return; + } + } + Alert.showError('Cannot parse response from URL shortener.'); + }; + /** * shows the remaining time * diff --git a/js/test/PasteStatus.js b/js/test/PasteStatus.js index cf1f7e0c..9a5c60d0 100644 --- a/js/test/PasteStatus.js +++ b/js/test/PasteStatus.js @@ -34,6 +34,51 @@ describe('PasteStatus', function () { ); }); + describe('extractUrl', function () { + this.timeout(30000); + + jsc.property( + 'extracts and updates URLs found in given response', + jsc.elements(['http','https']), + 'nestring', + jsc.nearray(common.jscA2zString()), + jsc.array(common.jscQueryString()), + jsc.array(common.jscAlnumString()), + 'string', + function (schema, domain, tld, query, shortid, fragment) { + domain = domain.replace(/\P{Letter}|[\u00AA-\u00BA]/gu,'').toLowerCase(); + if (domain.length === 0) { + domain = 'a'; + } + const expected = '.' + tld.join('') + '/' + (query.length > 0 ? + ('?' + encodeURI(query.join('').replace(/^&+|&+$/gm,'')) + + shortid.join('')) : '') + (fragment.length > 0 ? + ('#' + encodeURI(fragment)) : ''), + clean = jsdom(); + + // not available in node before v19.9.0, v18.17.0 + if (typeof URL.canParse !== 'function') { + URL.canParse = function(a) { + return true; + } + } + + $('body').html('
'); + $.PrivateBin.PasteStatus.init(); + $.PrivateBin.PasteStatus.createPasteNotification('', ''); + $.PrivateBin.PasteStatus.extractUrl(schema + '://' + domain + expected); + + const result = $('#pasteurl')[0].href; + clean(); + + return result.endsWith(expected) && ( + result.startsWith(schema + '://xn--') || + result.startsWith(schema + '://' + domain) + ); + } + ); + }); + describe('showRemainingTime', function () { this.timeout(30000); diff --git a/tpl/bootstrap.php b/tpl/bootstrap.php index 386705fa..3c72c00b 100644 --- a/tpl/bootstrap.php +++ b/tpl/bootstrap.php @@ -73,7 +73,7 @@ endif; ?> - + diff --git a/tpl/page.php b/tpl/page.php index 026b4ae1..c66c2ee5 100644 --- a/tpl/page.php +++ b/tpl/page.php @@ -51,7 +51,7 @@ endif; ?> - +