diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index dd5f59b6..730de26e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -41,7 +41,7 @@ jobs: key: ${{ runner.os }}-${{ env.extensions-cache-key }} - name: Cache extensions - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.extcache.outputs.dir }} key: ${{ steps.extcache.outputs.key }} @@ -76,7 +76,7 @@ jobs: shell: bash - name: Cache dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ steps.get-date.outputs.date }}-${{ hashFiles('**/composer.json') }} diff --git a/composer.lock b/composer.lock index bf8fa3fd..5f8d2963 100644 --- a/composer.lock +++ b/composer.lock @@ -316,16 +316,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.17.1", + "version": "v4.18.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d" + "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", - "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bcbb2179f97633e98bbbc87044ee2611c7d7999", + "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999", "shasum": "" }, "require": { @@ -366,9 +366,9 @@ ], "support": { "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", @@ -483,23 +483,23 @@ }, { "name": "phpunit/php-code-coverage", - "version": "9.2.29", + "version": "9.2.30", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76" + "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/6a3a87ac2bbe33b25042753df8195ba4aa534c76", - "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca2bd87d2f9215904682a9cb9bb37dda98e76089", + "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.15", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", @@ -549,7 +549,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "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": [ { @@ -557,7 +557,7 @@ "type": "github" } ], - "time": "2023-09-19T04:57:46+00:00" + "time": "2023-12-22T06:47:57+00:00" }, { "name": "phpunit/php-file-iterator", @@ -802,16 +802,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.15", + "version": "9.6.16", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "05017b80304e0eb3f31d90194a563fd53a6021f1" + "reference": "3767b2c56ce02d01e3491046f33466a1ae60a37f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/05017b80304e0eb3f31d90194a563fd53a6021f1", - "reference": "05017b80304e0eb3f31d90194a563fd53a6021f1", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3767b2c56ce02d01e3491046f33466a1ae60a37f", + "reference": "3767b2c56ce02d01e3491046f33466a1ae60a37f", "shasum": "" }, "require": { @@ -885,7 +885,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "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": [ { @@ -901,7 +901,7 @@ "type": "tidelift" } ], - "time": "2023-12-01T16:55:19+00:00" + "time": "2024-01-19T07:03:14+00:00" }, { "name": "sebastian/cli-parser", @@ -1146,20 +1146,20 @@ }, { "name": "sebastian/complexity", - "version": "2.0.2", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", "shasum": "" }, "require": { - "nikic/php-parser": "^4.7", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3" }, "require-dev": { @@ -1191,7 +1191,7 @@ "homepage": "https://github.com/sebastianbergmann/complexity", "support": { "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": [ { @@ -1199,7 +1199,7 @@ "type": "github" } ], - "time": "2020-10-26T15:52:27+00:00" + "time": "2023-12-22T06:19:30+00:00" }, { "name": "sebastian/diff", @@ -1473,20 +1473,20 @@ }, { "name": "sebastian/lines-of-code", - "version": "1.0.3", + "version": "1.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", "shasum": "" }, "require": { - "nikic/php-parser": "^4.6", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3" }, "require-dev": { @@ -1518,7 +1518,7 @@ "homepage": "https://github.com/sebastianbergmann/lines-of-code", "support": { "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": [ { @@ -1526,7 +1526,7 @@ "type": "github" } ], - "time": "2020-11-28T06:42:11+00:00" + "time": "2023-12-22T06:20:34+00:00" }, { "name": "sebastian/object-enumerator", diff --git a/js/common.js b/js/common.js index 295fd090..d3953d36 100644 --- a/js/common.js +++ b/js/common.js @@ -113,8 +113,8 @@ exports.jscBase64String = function() { }; // provides a random URL schema supported by the whatwg-url library -exports.jscSchemas = function() { - return jsc.elements(schemas); +exports.jscSchemas = function(withFtp = true) { + return jsc.elements(withFtp ? schemas : schemas.slice(1)); }; // provides a random supported language string @@ -131,3 +131,24 @@ exports.jscMimeTypes = function() { exports.jscFormats = function() { 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) : ''); +}; \ No newline at end of file diff --git a/js/privatebin.js b/js/privatebin.js index 83865562..2437e670 100644 --- a/js/privatebin.js +++ b/js/privatebin.js @@ -2128,7 +2128,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { response = JSON.stringify(response); } if (typeof response === 'string' && response.length > 0) { - const shortUrlMatcher = /https?:\/\/[^\s]+/g; + 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); @@ -2140,7 +2140,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { return false; } }).sort(function(a, b) { - return a.length - b.length; + return a.length - b.length; // shortest first })[0]; if (typeof shortUrl === 'string' && shortUrl.length > 0) { // we disable the button to avoid calling shortener again diff --git a/js/test/Helper.js b/js/test/Helper.js index 2cd5202f..95ae5709 100644 --- a/js/test/Helper.js +++ b/js/test/Helper.js @@ -96,36 +96,34 @@ describe('Helper', function () { jsc.property( 'replaces URLs with anchors', 'string', - jsc.elements(['http', 'https', 'ftp']), - jsc.nearray(common.jscA2zString()), - jsc.array(common.jscQueryString()), + common.jscUrl(), jsc.array(common.jscHashString()), 'string', - function (prefix, schema, address, query, fragment, postfix) { - query = query.join(''); - fragment = fragment.join(''); + function (prefix, url, fragment, postfix) { prefix = prefix.replace(/\r|\f/g, '\n').replace(/\u0000/g, '').replace(/\u000b/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(); $('body').html('
'); let e = $('#foo'); // special cases: When the query string and fragment imply the beginning of an HTML entity, eg. or if ( - query.slice(-1) === '&' && - (parseInt(fragment.substring(0, 1), 10) >= 0 || fragment.charAt(0) === 'x' ) - ) - { - url = schema + '://' + address.join('') + '/?' + query.substring(0, query.length - 1); + url.query[-1] === '&' && + (parseInt(url.fragment.charAt(0), 10) >= 0 || url.fragment.charAt(0) === 'x') + ) { + url.query.pop(); + urlString = common.urlToString(url); postfix = ''; } - e.text(prefix + url + postfix); + e.text(prefix + urlString + postfix); $.PrivateBin.Helper.urls2links(e); let result = e.html(); clean(); - url = $('').text(url).html(); - return $('').text(prefix).html() + '' + url + '' + $('').text(postfix).html() === result; + urlString = $('').text(urlString).html(); + const expected = $('').text(prefix).html() + '' + urlString + '' + $('').text(postfix).html(); + return $('').text(prefix).html() + '' + urlString + '' + $('').text(postfix).html() === result; } ); jsc.property( @@ -261,16 +259,16 @@ describe('Helper', function () { this.timeout(30000); jsc.property( 'returns the URL without query & fragment', - jsc.elements(['http', 'https']), - jsc.nearray(common.jscA2zString()), - jsc.array(common.jscA2zString()), - jsc.array(common.jscQueryString()), - 'string', - function (schema, address, path, query, fragment) { + common.jscSchemas(false), + common.jscUrl(), + function (schema, url) { + url.schema = schema; + const fullUrl = common.urlToString(url); + delete(url.query); + delete(url.fragment); $.PrivateBin.Helper.reset(); - var path = path.join('') + (path.length > 0 ? '/' : ''), - expected = schema + '://' + address.join('') + '/' + path, - clean = jsdom('', {url: expected + '?' + query.join('') + '#' + fragment}), + const expected = common.urlToString(url), + clean = jsdom('', {url: fullUrl}), result = $.PrivateBin.Helper.baseUri(); clean(); return expected === result; diff --git a/js/test/Model.js b/js/test/Model.js index 9ebc5472..db2198ba 100644 --- a/js/test/Model.js +++ b/js/test/Model.js @@ -80,23 +80,22 @@ describe('Model', function () { jsc.property( 'returns the query string without separator, if any', - jsc.nearray(common.jscA2zString()), - jsc.nearray(common.jscA2zString()), + common.jscUrl(true, false), jsc.tuple(new Array(16).fill(common.jscHexString)), jsc.array(common.jscQueryString()), jsc.array(common.jscQueryString()), - 'string', - function (schema, address, pasteId, queryStart, queryEnd, fragment) { - var pasteIdString = pasteId.join(''), - queryStartString = queryStart.join('') + (queryStart.length > 0 ? '&' : ''), - queryEndString = (queryEnd.length > 0 ? '&' : '') + queryEnd.join(''), - queryString = queryStartString + pasteIdString + queryEndString, - clean = jsdom('', { - url: schema.join('') + '://' + address.join('') + - '/?' + queryString + '#' + fragment - }); - global.URL = require('jsdom-url').URL; - var result = $.PrivateBin.Model.getPasteId(); + function (url, pasteId, queryStart, queryEnd) { + if (queryStart.length > 0) { + queryStart.push('&'); + } + if (queryEnd.length > 0) { + queryEnd.unshift('&'); + } + url.query = queryStart.concat(pasteId, queryEnd); + const pasteIdString = pasteId.join(''), + clean = jsdom('', {url: common.urlToString(url)}); + global.URL = require('jsdom-url').URL; + const result = $.PrivateBin.Model.getPasteId(); $.PrivateBin.Model.reset(); clean(); return pasteIdString === result; @@ -104,14 +103,9 @@ describe('Model', function () { ); jsc.property( 'throws exception on empty query string', - jsc.nearray(common.jscA2zString()), - jsc.nearray(common.jscA2zString()), - 'string', - function (schema, address, fragment) { - var clean = jsdom('', { - url: schema.join('') + '://' + address.join('') + - '/#' + fragment - }), + common.jscUrl(true, false), + function (url) { + let clean = jsdom('', {url: common.urlToString(url)}), result = false; global.URL = require('jsdom-url').URL; try { @@ -135,35 +129,24 @@ describe('Model', function () { jsc.property( 'returns the fragment of a v1 URL', - jsc.nearray(common.jscA2zString()), - jsc.nearray(common.jscA2zString()), - jsc.array(common.jscQueryString()), - 'nestring', - function (schema, address, query, fragment) { - const fragmentString = common.btoa(fragment.padStart(32, '\u0000')); - let clean = jsdom('', { - url: schema.join('') + '://' + address.join('') + - '/?' + query.join('') + '#' + fragmentString - }), + common.jscUrl(), + function (url) { + url.fragment = common.btoa(url.fragment.padStart(32, '\u0000')); + const clean = jsdom('', {url: common.urlToString(url)}), result = $.PrivateBin.Model.getPasteKey(); $.PrivateBin.Model.reset(); clean(); - return fragmentString === result; + return url.fragment === result; } ); jsc.property( 'returns the v1 fragment stripped of trailing query parts', - jsc.nearray(common.jscA2zString()), - jsc.nearray(common.jscA2zString()), - jsc.array(common.jscQueryString()), - 'nestring', + common.jscUrl(), jsc.array(common.jscHashString()), - function (schema, address, query, fragment, trail) { - const fragmentString = common.btoa(fragment.padStart(32, '\u0000')); - let clean = jsdom('', { - url: schema.join('') + '://' + address.join('') + '/?' + - query.join('') + '#' + fragmentString + '&' + trail.join('') - }), + function (url, trail) { + const fragmentString = common.btoa(url.fragment.padStart(32, '\u0000')); + url.fragment = fragmentString + '&' + trail.join(''); + const clean = jsdom('', {url: common.urlToString(url)}), result = $.PrivateBin.Model.getPasteKey(); $.PrivateBin.Model.reset(); clean(); @@ -172,18 +155,12 @@ describe('Model', function () { ); jsc.property( 'returns the fragment of a v2 URL', - jsc.nearray(common.jscA2zString()), - jsc.nearray(common.jscA2zString()), - jsc.array(common.jscQueryString()), - 'nestring', - function (schema, address, query, fragment) { + common.jscUrl(), + function (url) { // base58 strips leading NULL bytes, so the string is padded with these if not found - fragment = fragment.padStart(32, '\u0000'); - let fragmentString = $.PrivateBin.CryptTool.base58encode(fragment), - clean = jsdom('', { - url: schema.join('') + '://' + address.join('') + - '/?' + query.join('') + '#' + fragmentString - }), + const fragment = url.fragment.padStart(32, '\u0000'); + url.fragment = $.PrivateBin.CryptTool.base58encode(fragment); + const clean = jsdom('', {url: common.urlToString(url)}), result = $.PrivateBin.Model.getPasteKey(); $.PrivateBin.Model.reset(); clean(); @@ -192,19 +169,13 @@ describe('Model', function () { ); jsc.property( 'returns the v2 fragment stripped of trailing query parts', - jsc.nearray(common.jscA2zString()), - jsc.nearray(common.jscA2zString()), - jsc.array(common.jscQueryString()), - 'nestring', + common.jscUrl(), 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 - fragment = fragment.padStart(32, '\u0000'); - let fragmentString = $.PrivateBin.CryptTool.base58encode(fragment), - clean = jsdom('', { - url: schema.join('') + '://' + address.join('') + '/?' + - query.join('') + '#' + fragmentString + '&' + trail.join('') - }), + const fragment = url.fragment.padStart(32, '\u0000'); + url.fragment = $.PrivateBin.CryptTool.base58encode(fragment) + '&' + trail.join(''); + const clean = jsdom('', {url: common.urlToString(url)}), result = $.PrivateBin.Model.getPasteKey(); $.PrivateBin.Model.reset(); clean(); @@ -213,14 +184,9 @@ describe('Model', function () { ); jsc.property( 'throws exception on empty fragment of the URL', - jsc.nearray(common.jscA2zString()), - jsc.nearray(common.jscA2zString()), - jsc.array(common.jscQueryString()), - function (schema, address, query) { - var clean = jsdom('', { - url: schema.join('') + '://' + address.join('') + - '/?' + query.join('') - }), + common.jscUrl(false), + function (url) { + let clean = jsdom('', {url: common.urlToString(url)}), result = false; try { $.PrivateBin.Model.getPasteKey(); diff --git a/js/test/PasteStatus.js b/js/test/PasteStatus.js index baa6ab33..a233bb48 100644 --- a/js/test/PasteStatus.js +++ b/js/test/PasteStatus.js @@ -1,32 +1,39 @@ 'use strict'; 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('createPasteNotification', function () { this.timeout(30000); jsc.property( 'creates a notification after a successfull paste upload', - common.jscSchemas(), - jsc.nearray(common.jscA2zString()), - jsc.array(common.jscQueryString()), - 'string', - common.jscSchemas(), - 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,'')), + common.jscUrl(), + common.jscUrl(false), + function (url1, url2) { + const expected1 = common.urlToString(url1).replace(/&(gt|lt)$/, '&$1a'), + expected2 = common.urlToString(url2).replace(/&(gt|lt)$/, '&$1a'), clean = jsdom(); $('body').html('Your paste is ' + shortUrlString + ' (Hit [Ctrl]+[c] to copy)
\n' + + '\t\n' + + '', + clean = jsdom(); + + $('body').html('