Merge branch 'master' into crowdin-translation

This commit is contained in:
El RIDO 2024-01-27 19:19:08 +01:00
commit 239f6da73c
No known key found for this signature in database
GPG key ID: 0F5C940A6BD81F92
12 changed files with 344 additions and 221 deletions

View file

@ -41,7 +41,7 @@ jobs:
key: ${{ runner.os }}-${{ env.extensions-cache-key }} key: ${{ runner.os }}-${{ env.extensions-cache-key }}
- name: Cache extensions - name: Cache extensions
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: ${{ steps.extcache.outputs.dir }} path: ${{ steps.extcache.outputs.dir }}
key: ${{ steps.extcache.outputs.key }} key: ${{ steps.extcache.outputs.key }}
@ -76,7 +76,7 @@ jobs:
shell: bash shell: bash
- name: Cache dependencies - name: Cache dependencies
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: ${{ steps.composer-cache.outputs.dir }} path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ steps.get-date.outputs.date }}-${{ hashFiles('**/composer.json') }} key: ${{ runner.os }}-composer-${{ steps.get-date.outputs.date }}-${{ hashFiles('**/composer.json') }}

View file

@ -4,6 +4,7 @@
* ADDED: Translations for Romanian * ADDED: Translations for Romanian
* 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)

66
composer.lock generated
View file

@ -316,16 +316,16 @@
}, },
{ {
"name": "nikic/php-parser", "name": "nikic/php-parser",
"version": "v4.17.1", "version": "v4.18.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/nikic/PHP-Parser.git", "url": "https://github.com/nikic/PHP-Parser.git",
"reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d" "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bcbb2179f97633e98bbbc87044ee2611c7d7999",
"reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -366,9 +366,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/nikic/PHP-Parser/issues", "issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v4.17.1" "source": "https://github.com/nikic/PHP-Parser/tree/v4.18.0"
}, },
"time": "2023-08-13T19:53:39+00:00" "time": "2023-12-10T21:03:43+00:00"
}, },
{ {
"name": "phar-io/manifest", "name": "phar-io/manifest",
@ -483,23 +483,23 @@
}, },
{ {
"name": "phpunit/php-code-coverage", "name": "phpunit/php-code-coverage",
"version": "9.2.29", "version": "9.2.30",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76" "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/6a3a87ac2bbe33b25042753df8195ba4aa534c76", "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca2bd87d2f9215904682a9cb9bb37dda98e76089",
"reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76", "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-dom": "*", "ext-dom": "*",
"ext-libxml": "*", "ext-libxml": "*",
"ext-xmlwriter": "*", "ext-xmlwriter": "*",
"nikic/php-parser": "^4.15", "nikic/php-parser": "^4.18 || ^5.0",
"php": ">=7.3", "php": ">=7.3",
"phpunit/php-file-iterator": "^3.0.3", "phpunit/php-file-iterator": "^3.0.3",
"phpunit/php-text-template": "^2.0.2", "phpunit/php-text-template": "^2.0.2",
@ -549,7 +549,7 @@
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.29" "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.30"
}, },
"funding": [ "funding": [
{ {
@ -557,7 +557,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2023-09-19T04:57:46+00:00" "time": "2023-12-22T06:47:57+00:00"
}, },
{ {
"name": "phpunit/php-file-iterator", "name": "phpunit/php-file-iterator",
@ -802,16 +802,16 @@
}, },
{ {
"name": "phpunit/phpunit", "name": "phpunit/phpunit",
"version": "9.6.15", "version": "9.6.16",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git", "url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "05017b80304e0eb3f31d90194a563fd53a6021f1" "reference": "3767b2c56ce02d01e3491046f33466a1ae60a37f"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/05017b80304e0eb3f31d90194a563fd53a6021f1", "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3767b2c56ce02d01e3491046f33466a1ae60a37f",
"reference": "05017b80304e0eb3f31d90194a563fd53a6021f1", "reference": "3767b2c56ce02d01e3491046f33466a1ae60a37f",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -885,7 +885,7 @@
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues", "issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy", "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.15" "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.16"
}, },
"funding": [ "funding": [
{ {
@ -901,7 +901,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-12-01T16:55:19+00:00" "time": "2024-01-19T07:03:14+00:00"
}, },
{ {
"name": "sebastian/cli-parser", "name": "sebastian/cli-parser",
@ -1146,20 +1146,20 @@
}, },
{ {
"name": "sebastian/complexity", "name": "sebastian/complexity",
"version": "2.0.2", "version": "2.0.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/complexity.git", "url": "https://github.com/sebastianbergmann/complexity.git",
"reference": "739b35e53379900cc9ac327b2147867b8b6efd88" "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a",
"reference": "739b35e53379900cc9ac327b2147867b8b6efd88", "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"nikic/php-parser": "^4.7", "nikic/php-parser": "^4.18 || ^5.0",
"php": ">=7.3" "php": ">=7.3"
}, },
"require-dev": { "require-dev": {
@ -1191,7 +1191,7 @@
"homepage": "https://github.com/sebastianbergmann/complexity", "homepage": "https://github.com/sebastianbergmann/complexity",
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/complexity/issues", "issues": "https://github.com/sebastianbergmann/complexity/issues",
"source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3"
}, },
"funding": [ "funding": [
{ {
@ -1199,7 +1199,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2020-10-26T15:52:27+00:00" "time": "2023-12-22T06:19:30+00:00"
}, },
{ {
"name": "sebastian/diff", "name": "sebastian/diff",
@ -1473,20 +1473,20 @@
}, },
{ {
"name": "sebastian/lines-of-code", "name": "sebastian/lines-of-code",
"version": "1.0.3", "version": "1.0.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/lines-of-code.git", "url": "https://github.com/sebastianbergmann/lines-of-code.git",
"reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5",
"reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"nikic/php-parser": "^4.6", "nikic/php-parser": "^4.18 || ^5.0",
"php": ">=7.3" "php": ">=7.3"
}, },
"require-dev": { "require-dev": {
@ -1518,7 +1518,7 @@
"homepage": "https://github.com/sebastianbergmann/lines-of-code", "homepage": "https://github.com/sebastianbergmann/lines-of-code",
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/lines-of-code/issues", "issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
"source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4"
}, },
"funding": [ "funding": [
{ {
@ -1526,7 +1526,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2020-11-28T06:42:11+00:00" "time": "2023-12-22T06:20:34+00:00"
}, },
{ {
"name": "sebastian/object-enumerator", "name": "sebastian/object-enumerator",

View file

@ -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'),
@ -113,8 +113,8 @@ exports.jscBase64String = function() {
}; };
// provides a random URL schema supported by the whatwg-url library // provides a random URL schema supported by the whatwg-url library
exports.jscSchemas = function() { exports.jscSchemas = function(withFtp = true) {
return jsc.elements(schemas); return jsc.elements(withFtp ? schemas : schemas.slice(1));
}; };
// provides a random supported language string // provides a random supported language string
@ -131,3 +131,24 @@ exports.jscMimeTypes = function() {
exports.jscFormats = function() { exports.jscFormats = function() {
return jsc.elements(formats); return jsc.elements(formats);
}; };
// provides random URLs
exports.jscUrl = function(withFragment = true, withQuery = true) {
let url = {
schema: exports.jscSchemas(),
address: jsc.nearray(exports.jscA2zString()),
};
if (withFragment) {
url.fragment = jsc.string;
}
if(withQuery) {
url.query = jsc.array(exports.jscQueryString());
}
return jsc.record(url);
};
exports.urlToString = function (url) {
return url.schema + '://' + url.address.join('') + '/' + (url.query ? '?' +
encodeURI(url.query.join('').replace(/^&+|&+$/gm,'')) : '') +
(url.fragment ? '#' + encodeURI(url.fragment) : '');
};

4
js/package-lock.json generated
View file

@ -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",

View file

@ -2037,29 +2037,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);
@ -2125,6 +2103,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; // JSON API will have URL in quotes, XML in tags
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; // shortest first
})[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
* *

View file

@ -96,36 +96,34 @@ describe('Helper', function () {
jsc.property( jsc.property(
'replaces URLs with anchors', 'replaces URLs with anchors',
'string', 'string',
jsc.elements(['http', 'https', 'ftp']), common.jscUrl(),
jsc.nearray(common.jscA2zString()),
jsc.array(common.jscQueryString()),
jsc.array(common.jscHashString()), jsc.array(common.jscHashString()),
'string', 'string',
function (prefix, schema, address, query, fragment, postfix) { function (prefix, url, fragment, postfix) {
query = query.join('');
fragment = fragment.join('');
prefix = prefix.replace(/\r|\f/g, '\n').replace(/\u0000/g, '').replace(/\u000b/g, ''); prefix = prefix.replace(/\r|\f/g, '\n').replace(/\u0000/g, '').replace(/\u000b/g, '');
postfix = ' ' + postfix.replace(/\r/g, '\n').replace(/\u0000/g, ''); postfix = ' ' + postfix.replace(/\r/g, '\n').replace(/\u0000/g, '');
let url = schema + '://' + address.join('') + '/?' + query + '#' + fragment, url.fragment = fragment.join('');
let urlString = common.urlToString(url),
clean = jsdom(); clean = jsdom();
$('body').html('<div id="foo"></div>'); $('body').html('<div id="foo"></div>');
let e = $('#foo'); let e = $('#foo');
// special cases: When the query string and fragment imply the beginning of an HTML entity, eg. &#0 or &#x // special cases: When the query string and fragment imply the beginning of an HTML entity, eg. &#0 or &#x
if ( if (
query.slice(-1) === '&' && url.query[-1] === '&' &&
(parseInt(fragment.substring(0, 1), 10) >= 0 || fragment.charAt(0) === 'x' ) (parseInt(url.fragment.charAt(0), 10) >= 0 || url.fragment.charAt(0) === 'x')
) ) {
{ url.query.pop();
url = schema + '://' + address.join('') + '/?' + query.substring(0, query.length - 1); urlString = common.urlToString(url);
postfix = ''; postfix = '';
} }
e.text(prefix + url + postfix); e.text(prefix + urlString + postfix);
$.PrivateBin.Helper.urls2links(e); $.PrivateBin.Helper.urls2links(e);
let result = e.html(); let result = e.html();
clean(); clean();
url = $('<div />').text(url).html(); urlString = $('<div />').text(urlString).html();
return $('<div />').text(prefix).html() + '<a href="' + url + '" target="_blank" rel="nofollow noopener noreferrer">' + url + '</a>' + $('<div />').text(postfix).html() === result; const expected = $('<div />').text(prefix).html() + '<a href="' + urlString + '" target="_blank" rel="nofollow noopener noreferrer">' + urlString + '</a>' + $('<div />').text(postfix).html();
return $('<div />').text(prefix).html() + '<a href="' + urlString + '" target="_blank" rel="nofollow noopener noreferrer">' + urlString + '</a>' + $('<div />').text(postfix).html() === result;
} }
); );
jsc.property( jsc.property(
@ -261,16 +259,16 @@ describe('Helper', function () {
this.timeout(30000); this.timeout(30000);
jsc.property( jsc.property(
'returns the URL without query & fragment', 'returns the URL without query & fragment',
jsc.elements(['http', 'https']), common.jscSchemas(false),
jsc.nearray(common.jscA2zString()), common.jscUrl(),
jsc.array(common.jscA2zString()), function (schema, url) {
jsc.array(common.jscQueryString()), url.schema = schema;
'string', const fullUrl = common.urlToString(url);
function (schema, address, path, query, fragment) { delete(url.query);
delete(url.fragment);
$.PrivateBin.Helper.reset(); $.PrivateBin.Helper.reset();
var path = path.join('') + (path.length > 0 ? '/' : ''), const expected = common.urlToString(url),
expected = schema + '://' + address.join('') + '/' + path, clean = jsdom('', {url: fullUrl}),
clean = jsdom('', {url: expected + '?' + query.join('') + '#' + fragment}),
result = $.PrivateBin.Helper.baseUri(); result = $.PrivateBin.Helper.baseUri();
clean(); clean();
return expected === result; return expected === result;

View file

@ -80,23 +80,22 @@ describe('Model', function () {
jsc.property( jsc.property(
'returns the query string without separator, if any', 'returns the query string without separator, if any',
jsc.nearray(common.jscA2zString()), common.jscUrl(true, false),
jsc.nearray(common.jscA2zString()),
jsc.tuple(new Array(16).fill(common.jscHexString)), jsc.tuple(new Array(16).fill(common.jscHexString)),
jsc.array(common.jscQueryString()), jsc.array(common.jscQueryString()),
jsc.array(common.jscQueryString()), jsc.array(common.jscQueryString()),
'string', function (url, pasteId, queryStart, queryEnd) {
function (schema, address, pasteId, queryStart, queryEnd, fragment) { if (queryStart.length > 0) {
var pasteIdString = pasteId.join(''), queryStart.push('&');
queryStartString = queryStart.join('') + (queryStart.length > 0 ? '&' : ''), }
queryEndString = (queryEnd.length > 0 ? '&' : '') + queryEnd.join(''), if (queryEnd.length > 0) {
queryString = queryStartString + pasteIdString + queryEndString, queryEnd.unshift('&');
clean = jsdom('', { }
url: schema.join('') + '://' + address.join('') + url.query = queryStart.concat(pasteId, queryEnd);
'/?' + queryString + '#' + fragment const pasteIdString = pasteId.join(''),
}); clean = jsdom('', {url: common.urlToString(url)});
global.URL = require('jsdom-url').URL; global.URL = require('jsdom-url').URL;
var result = $.PrivateBin.Model.getPasteId(); const result = $.PrivateBin.Model.getPasteId();
$.PrivateBin.Model.reset(); $.PrivateBin.Model.reset();
clean(); clean();
return pasteIdString === result; return pasteIdString === result;
@ -104,14 +103,9 @@ describe('Model', function () {
); );
jsc.property( jsc.property(
'throws exception on empty query string', 'throws exception on empty query string',
jsc.nearray(common.jscA2zString()), common.jscUrl(true, false),
jsc.nearray(common.jscA2zString()), function (url) {
'string', let clean = jsdom('', {url: common.urlToString(url)}),
function (schema, address, fragment) {
var clean = jsdom('', {
url: schema.join('') + '://' + address.join('') +
'/#' + fragment
}),
result = false; result = false;
global.URL = require('jsdom-url').URL; global.URL = require('jsdom-url').URL;
try { try {
@ -135,35 +129,24 @@ describe('Model', function () {
jsc.property( jsc.property(
'returns the fragment of a v1 URL', 'returns the fragment of a v1 URL',
jsc.nearray(common.jscA2zString()), common.jscUrl(),
jsc.nearray(common.jscA2zString()), function (url) {
jsc.array(common.jscQueryString()), url.fragment = common.btoa(url.fragment.padStart(32, '\u0000'));
'nestring', const clean = jsdom('', {url: common.urlToString(url)}),
function (schema, address, query, fragment) {
const fragmentString = common.btoa(fragment.padStart(32, '\u0000'));
let clean = jsdom('', {
url: schema.join('') + '://' + address.join('') +
'/?' + query.join('') + '#' + fragmentString
}),
result = $.PrivateBin.Model.getPasteKey(); result = $.PrivateBin.Model.getPasteKey();
$.PrivateBin.Model.reset(); $.PrivateBin.Model.reset();
clean(); clean();
return fragmentString === result; return url.fragment === result;
} }
); );
jsc.property( jsc.property(
'returns the v1 fragment stripped of trailing query parts', 'returns the v1 fragment stripped of trailing query parts',
jsc.nearray(common.jscA2zString()), common.jscUrl(),
jsc.nearray(common.jscA2zString()),
jsc.array(common.jscQueryString()),
'nestring',
jsc.array(common.jscHashString()), jsc.array(common.jscHashString()),
function (schema, address, query, fragment, trail) { function (url, trail) {
const fragmentString = common.btoa(fragment.padStart(32, '\u0000')); const fragmentString = common.btoa(url.fragment.padStart(32, '\u0000'));
let clean = jsdom('', { url.fragment = fragmentString + '&' + trail.join('');
url: schema.join('') + '://' + address.join('') + '/?' + const clean = jsdom('', {url: common.urlToString(url)}),
query.join('') + '#' + fragmentString + '&' + trail.join('')
}),
result = $.PrivateBin.Model.getPasteKey(); result = $.PrivateBin.Model.getPasteKey();
$.PrivateBin.Model.reset(); $.PrivateBin.Model.reset();
clean(); clean();
@ -172,18 +155,12 @@ describe('Model', function () {
); );
jsc.property( jsc.property(
'returns the fragment of a v2 URL', 'returns the fragment of a v2 URL',
jsc.nearray(common.jscA2zString()), common.jscUrl(),
jsc.nearray(common.jscA2zString()), function (url) {
jsc.array(common.jscQueryString()),
'nestring',
function (schema, address, query, fragment) {
// base58 strips leading NULL bytes, so the string is padded with these if not found // base58 strips leading NULL bytes, so the string is padded with these if not found
fragment = fragment.padStart(32, '\u0000'); const fragment = url.fragment.padStart(32, '\u0000');
let fragmentString = $.PrivateBin.CryptTool.base58encode(fragment), url.fragment = $.PrivateBin.CryptTool.base58encode(fragment);
clean = jsdom('', { const clean = jsdom('', {url: common.urlToString(url)}),
url: schema.join('') + '://' + address.join('') +
'/?' + query.join('') + '#' + fragmentString
}),
result = $.PrivateBin.Model.getPasteKey(); result = $.PrivateBin.Model.getPasteKey();
$.PrivateBin.Model.reset(); $.PrivateBin.Model.reset();
clean(); clean();
@ -192,19 +169,13 @@ describe('Model', function () {
); );
jsc.property( jsc.property(
'returns the v2 fragment stripped of trailing query parts', 'returns the v2 fragment stripped of trailing query parts',
jsc.nearray(common.jscA2zString()), common.jscUrl(),
jsc.nearray(common.jscA2zString()),
jsc.array(common.jscQueryString()),
'nestring',
jsc.array(common.jscHashString()), jsc.array(common.jscHashString()),
function (schema, address, query, fragment, trail) { function (url, trail) {
// base58 strips leading NULL bytes, so the string is padded with these if not found // base58 strips leading NULL bytes, so the string is padded with these if not found
fragment = fragment.padStart(32, '\u0000'); const fragment = url.fragment.padStart(32, '\u0000');
let fragmentString = $.PrivateBin.CryptTool.base58encode(fragment), url.fragment = $.PrivateBin.CryptTool.base58encode(fragment) + '&' + trail.join('');
clean = jsdom('', { const clean = jsdom('', {url: common.urlToString(url)}),
url: schema.join('') + '://' + address.join('') + '/?' +
query.join('') + '#' + fragmentString + '&' + trail.join('')
}),
result = $.PrivateBin.Model.getPasteKey(); result = $.PrivateBin.Model.getPasteKey();
$.PrivateBin.Model.reset(); $.PrivateBin.Model.reset();
clean(); clean();
@ -213,14 +184,9 @@ describe('Model', function () {
); );
jsc.property( jsc.property(
'throws exception on empty fragment of the URL', 'throws exception on empty fragment of the URL',
jsc.nearray(common.jscA2zString()), common.jscUrl(false),
jsc.nearray(common.jscA2zString()), function (url) {
jsc.array(common.jscQueryString()), let clean = jsdom('', {url: common.urlToString(url)}),
function (schema, address, query) {
var clean = jsdom('', {
url: schema.join('') + '://' + address.join('') +
'/?' + query.join('')
}),
result = false; result = false;
try { try {
$.PrivateBin.Model.getPasteKey(); $.PrivateBin.Model.getPasteKey();

View file

@ -1,32 +1,39 @@
'use strict'; 'use strict';
var common = require('../common'); var common = require('../common');
function urlStrings(schema, longUrl, shortUrl) {
longUrl.schema = schema;
shortUrl.schema = schema;
let longUrlString = common.urlToString(longUrl),
shortUrlString = common.urlToString(shortUrl);
// ensure the two random URLs actually are sorted as expected
if (longUrlString.length <= shortUrlString.length) {
if (longUrlString.length === shortUrlString.length) {
longUrl.address.unshift('a');
longUrlString = common.urlToString(longUrl);
} else {
[longUrlString, shortUrlString] = [shortUrlString, longUrlString];
}
}
return [longUrlString, shortUrlString];
}
describe('PasteStatus', function () { describe('PasteStatus', function () {
describe('createPasteNotification', function () { describe('createPasteNotification', function () {
this.timeout(30000); this.timeout(30000);
jsc.property( jsc.property(
'creates a notification after a successfull paste upload', 'creates a notification after a successfull paste upload',
common.jscSchemas(), common.jscUrl(),
jsc.nearray(common.jscA2zString()), common.jscUrl(false),
jsc.array(common.jscQueryString()), function (url1, url2) {
'string', const expected1 = common.urlToString(url1).replace(/&(gt|lt)$/, '&$1a'),
common.jscSchemas(), expected2 = common.urlToString(url2).replace(/&(gt|lt)$/, '&$1a'),
jsc.nearray(common.jscA2zString()),
jsc.array(common.jscQueryString()),
function (
schema1, address1, query1, fragment1,
schema2, address2, query2
) {
var expected1 = schema1 + '://' + address1.join('') + '/?' +
encodeURI(query1.join('').replace(/^&+|&+$/gm,'') + '#' + fragment1),
expected2 = schema2 + '://' + address2.join('') + '/?' +
encodeURI(query2.join('').replace(/^&+|&+$/gm,'')),
clean = jsdom(); clean = jsdom();
$('body').html('<div><div id="deletelink"></div><div id="pastelink"></div></div>'); $('body').html('<div><div id="deletelink"></div><div id="pastelink"></div></div>');
$.PrivateBin.PasteStatus.init(); $.PrivateBin.PasteStatus.init();
$.PrivateBin.PasteStatus.createPasteNotification(expected1, expected2); $.PrivateBin.PasteStatus.createPasteNotification(expected1, expected2);
var result1 = $('#pasteurl')[0].href, const result1 = $('#pasteurl')[0].href,
result2 = $('#deletelink a')[0].href; result2 = $('#deletelink a')[0].href;
clean(); clean();
return result1 === expected1 && result2 === expected2; return result1 === expected1 && result2 === expected2;
@ -34,6 +41,138 @@ describe('PasteStatus', function () {
); );
}); });
describe('extractUrl', function () {
this.timeout(30000);
jsc.property(
'extracts and updates IDN URLs found in given response',
common.jscSchemas(false),
'nestring',
common.jscUrl(),
function (schema, domain, url) {
domain = domain.replace(/\P{Letter}|[\u00AA-\u00BA]/gu, '').toLowerCase();
if (domain.length === 0) {
domain = 'a';
}
url.schema = schema;
url.address.unshift('.');
url.address = domain.split('').concat(url.address);
const urlString = common.urlToString(url),
expected = urlString.substring((schema + '://' + domain).length),
clean = jsdom();
$('body').html('<div><div id="pastelink"></div></div>');
$.PrivateBin.PasteStatus.init();
$.PrivateBin.PasteStatus.createPasteNotification('', '');
$.PrivateBin.PasteStatus.extractUrl(urlString);
const result = $('#pasteurl')[0].href;
clean();
return result.endsWith(expected) && (
result.startsWith(schema + '://xn--') ||
result.startsWith(schema + '://' + domain)
);
}
);
// YOURLS API samples from: https://yourls.org/readme.html#API;apireturn
jsc.property(
'extracts and updates URLs found in YOURLS API JSON response',
common.jscSchemas(false),
common.jscUrl(),
common.jscUrl(false),
function (schema, longUrl, shortUrl) {
const [longUrlString, shortUrlString] = urlStrings(schema, longUrl, shortUrl),
yourlsResponse = {
url: {
keyword: longUrl.address.join(''),
url: longUrlString,
title: "example title",
date: "2014-10-24 16:01:39",
ip: "127.0.0.1"
},
status: "success",
message: longUrlString + " added to database",
title: "example title",
shorturl: shortUrlString,
statusCode: 200
},
clean = jsdom();
$('body').html('<div><div id="pastelink"></div></div>');
$.PrivateBin.PasteStatus.init();
$.PrivateBin.PasteStatus.createPasteNotification('', '');
$.PrivateBin.PasteStatus.extractUrl(JSON.stringify(yourlsResponse, undefined, 4));
const result = $('#pasteurl')[0].href;
clean();
return result === shortUrlString;
}
);
jsc.property(
'extracts and updates URLs found in YOURLS API XML response',
common.jscSchemas(false),
common.jscUrl(),
common.jscUrl(false),
function (schema, longUrl, shortUrl) {
const [longUrlString, shortUrlString] = urlStrings(schema, longUrl, shortUrl),
yourlsResponse = '<result>\n' +
' <keyword>' + longUrl.address.join('') + '</keyword>\n' +
' <shorturl>' + shortUrlString + '</shorturl>\n' +
' <longurl>' + longUrlString + '</longurl>\n' +
' <message>success</message>\n' +
' <statusCode>200</statusCode>\n' +
'</result>',
clean = jsdom();
$('body').html('<div><div id="pastelink"></div></div>');
$.PrivateBin.PasteStatus.init();
$.PrivateBin.PasteStatus.createPasteNotification('', '');
$.PrivateBin.PasteStatus.extractUrl(yourlsResponse);
const result = $('#pasteurl')[0].href;
clean();
return result === shortUrlString;
}
);
jsc.property(
'extracts and updates URLs found in YOURLS proxy HTML response',
common.jscSchemas(false),
common.jscUrl(),
common.jscUrl(false),
function (schema, longUrl, shortUrl) {
const [longUrlString, shortUrlString] = urlStrings(schema, longUrl, shortUrl),
yourlsResponse = '<!DOCTYPE html>\n' +
'<html lang="en">\n' +
'\t<head>\n' +
'\t\t<meta charset="utf-8" />\n' +
'\t\t<meta http-equiv="Content-Security-Policy" content="default-src \'none\'; base-uri \'self\'; form-action \'none\'; manifest-src \'self\'; connect-src * blob:; script-src \'self\' \'unsafe-eval\'; style-src \'self\'; font-src \'self\'; frame-ancestors \'none\'; img-src \'self\' data: blob:; media-src blob:; object-src blob:; sandbox allow-same-origin allow-scripts allow-forms allow-popups allow-modals allow-downloads">\n' +
'\t\t<meta name="robots" content="noindex" />\n' +
'\t\t<meta name="google" content="notranslate">\n' +
'\t\t<title>PrivateBin</title>\n' +
'\t</head>\n' +
'\t<body>\n' +
'\t\t<p>Your paste is <a id="pasteurl" href="' + shortUrlString + '">' + shortUrlString + '</a> <span id="copyhint">(Hit [Ctrl]+[c] to copy)</span></p>\n' +
'\t</body>\n' +
'</html>',
clean = jsdom();
$('body').html('<div><div id="pastelink"></div></div>');
$.PrivateBin.PasteStatus.init();
$.PrivateBin.PasteStatus.createPasteNotification('', '');
$.PrivateBin.PasteStatus.extractUrl(yourlsResponse);
const result = $('#pasteurl')[0].href;
clean();
return result === shortUrlString;
}
);
});
describe('showRemainingTime', function () { describe('showRemainingTime', function () {
this.timeout(30000); this.timeout(30000);
@ -41,18 +180,9 @@ describe('PasteStatus', function () {
'shows burn after reading message or remaining time v1', 'shows burn after reading message or remaining time v1',
'bool', 'bool',
'nat', 'nat',
jsc.nearray(common.jscA2zString()), common.jscUrl(),
jsc.nearray(common.jscA2zString()), function (burnafterreading, remainingTime, url) {
jsc.nearray(common.jscQueryString()), let clean = jsdom('', {url: common.urlToString(url)}),
'string',
function (
burnafterreading, remainingTime,
schema, address, query, fragment
) {
var clean = jsdom('', {
url: schema.join('') + '://' + address.join('') +
'/?' + query.join('') + '#' + fragment
}),
result; result;
$('body').html('<div id="remainingtime" class="hidden"></div>'); $('body').html('<div id="remainingtime" class="hidden"></div>');
$.PrivateBin.PasteStatus.init(); $.PrivateBin.PasteStatus.init();
@ -79,18 +209,9 @@ describe('PasteStatus', function () {
'shows burn after reading message or remaining time v2', 'shows burn after reading message or remaining time v2',
'bool', 'bool',
'nat', 'nat',
jsc.nearray(common.jscA2zString()), common.jscUrl(),
jsc.nearray(common.jscA2zString()), function (burnafterreading, remainingTime, url) {
jsc.nearray(common.jscQueryString()), let clean = jsdom('', {url: common.urlToString(url)}),
'string',
function (
burnafterreading, remainingTime,
schema, address, query, fragment
) {
var clean = jsdom('', {
url: schema.join('') + '://' + address.join('') +
'/?' + query.join('') + '#' + fragment
}),
result; result;
$('body').html('<div id="remainingtime" class="hidden"></div>'); $('body').html('<div id="remainingtime" class="hidden"></div>');
$.PrivateBin.PasteStatus.init(); $.PrivateBin.PasteStatus.init();

View file

@ -13,10 +13,9 @@ describe('UiHelper', function () {
jsc.property( jsc.property(
'redirects to home, when the state is null', 'redirects to home, when the state is null',
common.jscSchemas(), common.jscUrl(false, false),
jsc.nearray(common.jscA2zString()), function (url) {
function (schema, address) { const expected = common.urlToString(url),
var expected = schema + '://' + address.join('') + '/',
clean = jsdom('', {url: expected}); clean = jsdom('', {url: expected});
// make window.location.href writable // make window.location.href writable
@ -34,13 +33,11 @@ describe('UiHelper', function () {
jsc.property( jsc.property(
'does not redirect to home, when a new paste is created', 'does not redirect to home, when a new paste is created',
common.jscSchemas(), common.jscUrl(false),
jsc.nearray(common.jscA2zString()),
jsc.array(common.jscQueryString()),
jsc.nearray(common.jscBase64String()), jsc.nearray(common.jscBase64String()),
function (schema, address, query, fragment) { function (url, fragment) {
var expected = schema + '://' + address.join('') + '/?' + url.fragment = fragment.join('');
query.join('') + '#' + fragment.join(''), const expected = common.urlToString(url),
clean = jsdom('', {url: expected}); clean = jsdom('', {url: expected});
// make window.location.href writable // make window.location.href writable
@ -67,15 +64,12 @@ describe('UiHelper', function () {
jsc.property( jsc.property(
'redirects to home', 'redirects to home',
common.jscSchemas(), common.jscUrl(),
jsc.nearray(common.jscA2zString()), function (url) {
jsc.array(common.jscQueryString()), const clean = jsdom('', {url: common.urlToString(url)});
jsc.nearray(common.jscBase64String()), delete(url.query);
function (schema, address, query, fragment) { delete(url.fragment);
var expected = schema + '://' + address.join('') + '/', const expected = common.urlToString(url);
clean = jsdom('', {
url: expected + '?' + query.join('') + '#' + fragment.join('')
});
// make window.location.href writable // make window.location.href writable
Object.defineProperty(window.location, 'href', { Object.defineProperty(window.location, 'href', {

View file

@ -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-Zy/z0iR0xK8/2xkYJOr7JD7GS46Z2EoWFKcnSWK2FkCKf3VxFKoL3l7Tc+ru2mqZfxgk6H/j8+5JLeDKcafqxw==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-cfRk8a/8RpvMb4g9Su9kcKNcs7+PyGioUxH6z6k9e4vcdZmtz+gM2HwIP/Gd/1r6h3qoxrkO4jodn2E7gtZ7EA==" 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" />

View file

@ -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-Zy/z0iR0xK8/2xkYJOr7JD7GS46Z2EoWFKcnSWK2FkCKf3VxFKoL3l7Tc+ru2mqZfxgk6H/j8+5JLeDKcafqxw==" crossorigin="anonymous"></script> <script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-cfRk8a/8RpvMb4g9Su9kcKNcs7+PyGioUxH6z6k9e4vcdZmtz+gM2HwIP/Gd/1r6h3qoxrkO4jodn2E7gtZ7EA==" 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" />