Merge pull request #1227 from PrivateBin/shortener-idn
Shortener URL validation improvement
This commit is contained in:
commit
9fb7aee589
7 changed files with 89 additions and 28 deletions
|
@ -3,6 +3,7 @@
|
||||||
## 1.6.3 (not yet released)
|
## 1.6.3 (not yet released)
|
||||||
* ADDED: Detect and report on damaged pastes (#1218)
|
* ADDED: Detect and report on damaged pastes (#1218)
|
||||||
* CHANGED: Upgrading libraries to: zlib 1.3
|
* CHANGED: Upgrading libraries to: zlib 1.3
|
||||||
|
* FIXED: Support more types of valid URLs for shorteners, incl. IDN ones (#1224)
|
||||||
|
|
||||||
## 1.6.2 (2023-12-15)
|
## 1.6.2 (2023-12-15)
|
||||||
* FIXED: English not selectable when `languageselection` enabled (#1208)
|
* FIXED: English not selectable when `languageselection` enabled (#1208)
|
||||||
|
|
|
@ -37,7 +37,7 @@ var a2zString = ['a','b','c','d','e','f','g','h','i','j','k','l','m',
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
schemas = ['ftp','http','https'],
|
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'],
|
mimeTypes = ['image/png', 'application/octet-stream'],
|
||||||
formats = ['plaintext', 'markdown', 'syntaxhighlighting'],
|
formats = ['plaintext', 'markdown', 'syntaxhighlighting'],
|
||||||
mimeFile = fs.createReadStream('/etc/mime.types'),
|
mimeFile = fs.createReadStream('/etc/mime.types'),
|
||||||
|
|
4
js/package-lock.json
generated
4
js/package-lock.json
generated
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "privatebin",
|
"name": "privatebin",
|
||||||
"version": "1.5.2",
|
"version": "1.6.2",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "privatebin",
|
"name": "privatebin",
|
||||||
"version": "1.5.2",
|
"version": "1.6.2",
|
||||||
"license": "zlib-acknowledgement",
|
"license": "zlib-acknowledgement",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@peculiar/webcrypto": "^1.1.1",
|
"@peculiar/webcrypto": "^1.1.1",
|
||||||
|
|
|
@ -2035,29 +2035,7 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
||||||
xhrFields: {
|
xhrFields: {
|
||||||
withCredentials: false
|
withCredentials: false
|
||||||
},
|
},
|
||||||
success: function(response) {
|
success: PasteStatus.extractUrl
|
||||||
let responseString = response;
|
|
||||||
if (typeof responseString === 'object') {
|
|
||||||
responseString = JSON.stringify(responseString);
|
|
||||||
}
|
|
||||||
if (typeof responseString === 'string' && responseString.length > 0) {
|
|
||||||
const shortUrlMatcher = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g;
|
|
||||||
const shortUrl = (responseString.match(shortUrlMatcher) || []).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.');
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.fail(function(data, textStatus, errorThrown) {
|
.fail(function(data, textStatus, errorThrown) {
|
||||||
console.error(textStatus, errorThrown);
|
console.error(textStatus, errorThrown);
|
||||||
|
@ -2123,6 +2101,50 @@ jQuery.PrivateBin = (function($, RawDeflate) {
|
||||||
Helper.selectText($pasteUrl[0]);
|
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(urlRegExMatch) {
|
||||||
|
if (typeof URL.canParse === 'function') {
|
||||||
|
return URL.canParse(urlRegExMatch);
|
||||||
|
}
|
||||||
|
// polyfill for older browsers (< 120) & node (< 19.9 & < 18.17)
|
||||||
|
try {
|
||||||
|
return !!new URL(urlRegExMatch);
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}).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
|
* shows the remaining time
|
||||||
*
|
*
|
||||||
|
|
|
@ -34,6 +34,44 @@ 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();
|
||||||
|
|
||||||
|
$('body').html('<div><div id="pastelink"></div></div>');
|
||||||
|
$.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 () {
|
describe('showRemainingTime', function () {
|
||||||
this.timeout(30000);
|
this.timeout(30000);
|
||||||
|
|
||||||
|
|
|
@ -73,7 +73,7 @@ endif;
|
||||||
?>
|
?>
|
||||||
<script type="text/javascript" data-cfasync="false" src="js/purify-3.0.6.js" integrity="sha512-N3y6/HOk3pbsw3lFh4O8CKKEVwu1B2CF8kinhjURf8Yqa5OfSUt+/arozxFW+TUPOPw3TsDCRT/0u7BGRTEVUw==" crossorigin="anonymous"></script>
|
<script type="text/javascript" data-cfasync="false" src="js/purify-3.0.6.js" integrity="sha512-N3y6/HOk3pbsw3lFh4O8CKKEVwu1B2CF8kinhjURf8Yqa5OfSUt+/arozxFW+TUPOPw3TsDCRT/0u7BGRTEVUw==" crossorigin="anonymous"></script>
|
||||||
<script type="text/javascript" data-cfasync="false" src="js/legacy.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-LYos+qXHIRqFf5ZPNphvtTB0cgzHUizu2wwcOwcwz/VIpRv9lpcBgPYz4uq6jx0INwCAj6Fbnl5HoKiLufS2jg==" crossorigin="anonymous"></script>
|
<script type="text/javascript" data-cfasync="false" src="js/legacy.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-LYos+qXHIRqFf5ZPNphvtTB0cgzHUizu2wwcOwcwz/VIpRv9lpcBgPYz4uq6jx0INwCAj6Fbnl5HoKiLufS2jg==" crossorigin="anonymous"></script>
|
||||||
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-wkyB+e2HinOE+hZOEfBtNVlg2lneE09dnrDjZKd77dHowLEIxrys1La7neVov1HWcAQlwhqxnZF4yr19kDuunw==" crossorigin="anonymous"></script>
|
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-m/JO/NGsP+FEHPACdYe60BFkURk+NVCBaMsIUOUC5J1EnVE2XgVKF6Z+YSDd+9M4ISu0SivYxCZBEl8qW8u1Jg==" crossorigin="anonymous"></script>
|
||||||
<!-- icon -->
|
<!-- icon -->
|
||||||
<link rel="apple-touch-icon" href="<?php echo I18n::encode($BASEPATH); ?>img/apple-touch-icon.png" sizes="180x180" />
|
<link rel="apple-touch-icon" href="<?php echo I18n::encode($BASEPATH); ?>img/apple-touch-icon.png" sizes="180x180" />
|
||||||
<link rel="icon" type="image/png" href="img/favicon-32x32.png" sizes="32x32" />
|
<link rel="icon" type="image/png" href="img/favicon-32x32.png" sizes="32x32" />
|
||||||
|
|
|
@ -51,7 +51,7 @@ endif;
|
||||||
?>
|
?>
|
||||||
<script type="text/javascript" data-cfasync="false" src="js/purify-3.0.6.js" integrity="sha512-N3y6/HOk3pbsw3lFh4O8CKKEVwu1B2CF8kinhjURf8Yqa5OfSUt+/arozxFW+TUPOPw3TsDCRT/0u7BGRTEVUw==" crossorigin="anonymous"></script>
|
<script type="text/javascript" data-cfasync="false" src="js/purify-3.0.6.js" integrity="sha512-N3y6/HOk3pbsw3lFh4O8CKKEVwu1B2CF8kinhjURf8Yqa5OfSUt+/arozxFW+TUPOPw3TsDCRT/0u7BGRTEVUw==" crossorigin="anonymous"></script>
|
||||||
<script type="text/javascript" data-cfasync="false" src="js/legacy.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-LYos+qXHIRqFf5ZPNphvtTB0cgzHUizu2wwcOwcwz/VIpRv9lpcBgPYz4uq6jx0INwCAj6Fbnl5HoKiLufS2jg==" crossorigin="anonymous"></script>
|
<script type="text/javascript" data-cfasync="false" src="js/legacy.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-LYos+qXHIRqFf5ZPNphvtTB0cgzHUizu2wwcOwcwz/VIpRv9lpcBgPYz4uq6jx0INwCAj6Fbnl5HoKiLufS2jg==" crossorigin="anonymous"></script>
|
||||||
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-wkyB+e2HinOE+hZOEfBtNVlg2lneE09dnrDjZKd77dHowLEIxrys1La7neVov1HWcAQlwhqxnZF4yr19kDuunw==" crossorigin="anonymous"></script>
|
<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-m/JO/NGsP+FEHPACdYe60BFkURk+NVCBaMsIUOUC5J1EnVE2XgVKF6Z+YSDd+9M4ISu0SivYxCZBEl8qW8u1Jg==" crossorigin="anonymous"></script>
|
||||||
<!-- icon -->
|
<!-- icon -->
|
||||||
<link rel="apple-touch-icon" href="img/apple-touch-icon.png?<?php echo rawurlencode($VERSION); ?>" sizes="180x180" />
|
<link rel="apple-touch-icon" href="img/apple-touch-icon.png?<?php echo rawurlencode($VERSION); ?>" sizes="180x180" />
|
||||||
<link rel="icon" type="image/png" href="img/favicon-32x32.png?<?php echo rawurlencode($VERSION); ?>" sizes="32x32" />
|
<link rel="icon" type="image/png" href="img/favicon-32x32.png?<?php echo rawurlencode($VERSION); ?>" sizes="32x32" />
|
||||||
|
|
Loading…
Reference in a new issue