From ff6b9bd8f96d868460161ea5d8ee9a467cfdc89c Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 12 Jun 2019 04:37:17 +0300 Subject: [PATCH 1/8] Use blob URI for saving attachments (#432) --- js/privatebin.js | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/js/privatebin.js b/js/privatebin.js index e15a787..dcd0b8a 100644 --- a/js/privatebin.js +++ b/js/privatebin.js @@ -2514,32 +2514,33 @@ jQuery.PrivateBin = (function($, RawDeflate) { */ me.setAttachment = function(attachmentData, fileName) { + // data URI format: data:[][;base64], + + // position in data URI string of where data begins + const base64Start = attachmentData.indexOf(',') + 1; + // position in data URI string of where mediaType ends + const mediaTypeEnd = attachmentData.indexOf(';'); + + // extract mediaType + const mediaType = attachmentData.substring(5, mediaTypeEnd); + // extract data and convert to binary + const decodedData = atob(attachmentData.substring(base64Start)); + + // Transform into a Blob + const buf = new Uint8Array(decodedData.length); + for (let i = 0; i < decodedData.length; ++i) { + buf[i] = decodedData.charCodeAt(i); + } + const blob = new window.Blob([ buf ], { type: mediaType }); + // IE does not support setting a data URI on an a element - // Convert dataURI to a Blob and use msSaveBlob to download + // Using msSaveBlob to download if (window.Blob && navigator.msSaveBlob) { $attachmentLink.off('click').on('click', function () { - // data URI format: data:[][;base64], - - // position in data URI string of where data begins - const base64Start = attachmentData.indexOf(',') + 1; - // position in data URI string of where mediaType ends - const mediaTypeEnd = attachmentData.indexOf(';'); - - // extract mediaType - const mediaType = attachmentData.substring(5, mediaTypeEnd); - // extract data and convert to binary - const decodedData = atob(attachmentData.substring(base64Start)); - - // Transform into a Blob - const buf = new Uint8Array(decodedData.length); - for (let i = 0; i < decodedData.length; ++i) { - buf[i] = decodedData.charCodeAt(i); - } - const blob = new window.Blob([ buf ], { type: mediaType }); navigator.msSaveBlob(blob, fileName); }); } else { - $attachmentLink.attr('href', attachmentData); + $attachmentLink.attr('href', window.URL.createObjectURL(blob)); } if (typeof fileName !== 'undefined') { From dcbefcc1c3933ab1c5da695a27574aaea1a599e6 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 12 Jun 2019 05:29:19 +0300 Subject: [PATCH 2/8] Use blob for previews --- js/privatebin.js | 70 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/js/privatebin.js b/js/privatebin.js index dcd0b8a..770bc8e 100644 --- a/js/privatebin.js +++ b/js/privatebin.js @@ -2547,7 +2547,8 @@ jQuery.PrivateBin = (function($, RawDeflate) { $attachmentLink.attr('download', fileName); } - me.handleAttachmentPreview($attachmentPreview, attachmentData); + //me.handleAttachmentPreview($attachmentPreview, attachmentData); + me.handleBlobAttachmentPreview($attachmentPreview, blobUrl, mediaType); }; /** @@ -2819,6 +2820,73 @@ jQuery.PrivateBin = (function($, RawDeflate) { } }; + /** + * handle the preview of files decoded to blob that can either be an image, video, audio or pdf element + * + * @name AttachmentViewer.handleBlobAttachmentPreview + * @function + * @argument {jQuery} $targetElement element where the preview should be appended + * @argument {string} file as a blob URL + * @argument {string} mime type + */ + me.handleBlobAttachmentPreview = function ($targetElement, blobUrl, mimeType) { + if (blobUrl) { + attachmentHasPreview = true; + if (mimeType.match(/image\//i)) { + $targetElement.html( + $(document.createElement('img')) + .attr('src', blobUrl) + .attr('class', 'img-thumbnail') + ); + } else if (mimeType.match(/video\//i)) { + $targetElement.html( + $(document.createElement('video')) + .attr('controls', 'true') + .attr('autoplay', 'true') + .attr('class', 'img-thumbnail') + + .append($(document.createElement('source')) + .attr('type', mimeType) + .attr('src', blobUrl)) + ); + } else if (mimeType.match(/audio\//i)) { + $targetElement.html( + $(document.createElement('audio')) + .attr('controls', 'true') + .attr('autoplay', 'true') + + .append($(document.createElement('source')) + .attr('type', mimeType) + .attr('src', blobUrl)) + ); + } else if (mimeType.match(/\/pdf/i)) { + // PDFs are only displayed if the filesize is smaller than about 1MB (after base64 encoding). + // Bigger filesizes currently cause crashes in various browsers. + // See also: https://code.google.com/p/chromium/issues/detail?id=69227 + + // Firefox crashes with files that are about 1.5MB + // The performance with 1MB files is bearable + if (data.length > 1398488) { + Alert.showError('File too large, to display a preview. Please download the attachment.'); //TODO: is this error really neccessary? + return; + } + + // Fallback for browsers, that don't support the vh unit + var clientHeight = $(window).height(); + + $targetElement.html( + $(document.createElement('embed')) + .attr('src', blobUrl) + .attr('type', 'application/pdf') + .attr('class', 'pdfPreview') + .css('height', clientHeight) + ); + } else { + attachmentHasPreview = false; + } + } + }; + /** * attaches the file attachment drag & drop handler to the page * From d3f9670bc20334d6128876b6a02922ea64250bf0 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 12 Jun 2019 06:05:55 +0300 Subject: [PATCH 3/8] Remove data length detection, because we work with URL --- js/privatebin.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/privatebin.js b/js/privatebin.js index 770bc8e..961fd9f 100644 --- a/js/privatebin.js +++ b/js/privatebin.js @@ -2866,10 +2866,10 @@ jQuery.PrivateBin = (function($, RawDeflate) { // Firefox crashes with files that are about 1.5MB // The performance with 1MB files is bearable - if (data.length > 1398488) { + /*if (data.length > 1398488) { Alert.showError('File too large, to display a preview. Please download the attachment.'); //TODO: is this error really neccessary? return; - } + }*/ // Fallback for browsers, that don't support the vh unit var clientHeight = $(window).height(); From abd71413c3cc3001070694d2e0066809129a33c4 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Wed, 12 Jun 2019 06:29:36 +0300 Subject: [PATCH 4/8] Store Blob URL in variable --- js/privatebin.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/js/privatebin.js b/js/privatebin.js index 961fd9f..c0bfb32 100644 --- a/js/privatebin.js +++ b/js/privatebin.js @@ -2533,6 +2533,9 @@ jQuery.PrivateBin = (function($, RawDeflate) { } const blob = new window.Blob([ buf ], { type: mediaType }); + // Get Blob URL + const blobUrl = window.URL.createObjectURL(blob); + // IE does not support setting a data URI on an a element // Using msSaveBlob to download if (window.Blob && navigator.msSaveBlob) { @@ -2540,14 +2543,13 @@ jQuery.PrivateBin = (function($, RawDeflate) { navigator.msSaveBlob(blob, fileName); }); } else { - $attachmentLink.attr('href', window.URL.createObjectURL(blob)); + $attachmentLink.attr('href', blobUrl); } if (typeof fileName !== 'undefined') { $attachmentLink.attr('download', fileName); } - //me.handleAttachmentPreview($attachmentPreview, attachmentData); me.handleBlobAttachmentPreview($attachmentPreview, blobUrl, mediaType); }; @@ -2800,7 +2802,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { // Firefox crashes with files that are about 1.5MB // The performance with 1MB files is bearable if (data.length > 1398488) { - Alert.showError('File too large, to display a preview. Please download the attachment.'); //TODO: is this error really neccessary? + Alert.showError('File too large, to display a preview. Please download the attachment.'); //TODO: is this error really necessary? return; } @@ -2867,7 +2869,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { // Firefox crashes with files that are about 1.5MB // The performance with 1MB files is bearable /*if (data.length > 1398488) { - Alert.showError('File too large, to display a preview. Please download the attachment.'); //TODO: is this error really neccessary? + Alert.showError('File too large, to display a preview. Please download the attachment.'); //TODO: is this error really necessary? return; }*/ From 6cf52f4cf3332b743fc22a90fcf442dfa2e31363 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 15 Jun 2019 08:56:47 +0200 Subject: [PATCH 5/8] mocking window.URL.createObjectURL to have tests working with blob URLs --- js/test/AttachmentViewer.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/js/test/AttachmentViewer.js b/js/test/AttachmentViewer.js index c1495fb..438b2f8 100644 --- a/js/test/AttachmentViewer.js +++ b/js/test/AttachmentViewer.js @@ -11,13 +11,13 @@ describe('AttachmentViewer', function () { jsc.property( 'displays & hides data as requested', common.jscMimeTypes(), - jsc.nearray(common.jscBase64String()), 'string', 'string', 'string', - function (mimeType, base64, filename, prefix, postfix) { + 'string', + function (mimeType, rawdata, filename, prefix, postfix) { var clean = jsdom(), - data = 'data:' + mimeType + ';base64,' + base64.join(''), + data = 'data:' + mimeType + ';base64,' + btoa(rawdata), previewSupported = ( mimeType.substring(0, 6) === 'image/' || mimeType.substring(0, 6) === 'audio/' || @@ -34,6 +34,16 @@ describe('AttachmentViewer', function () { 'Download attachment' ); + // mock createObjectURL for jsDOM + if (typeof window.URL.createObjectURL === 'undefined') { + Object.defineProperty( + window.URL, + 'createObjectURL', + {value: function(blob) { + return 'blob:' + location.origin + '/1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed'; + }} + ) + } $.PrivateBin.AttachmentViewer.init(); results.push( !$.PrivateBin.AttachmentViewer.hasAttachment() && @@ -45,6 +55,8 @@ describe('AttachmentViewer', function () { } else { $.PrivateBin.AttachmentViewer.setAttachment(data); } + // beyond this point we will get the blob URL instead of the data + data = window.URL.createObjectURL(data); var attachment = $.PrivateBin.AttachmentViewer.getAttachment(); results.push( $.PrivateBin.AttachmentViewer.hasAttachment() && From 451a4817c4465f30a3c8a311546f2cddbb6374ba Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 15 Jun 2019 09:35:26 +0200 Subject: [PATCH 6/8] replace data-URL method usage with blob-URL one --- js/privatebin.js | 81 ++++------------------------------------------- tpl/bootstrap.php | 2 +- tpl/page.php | 2 +- 3 files changed, 8 insertions(+), 77 deletions(-) diff --git a/js/privatebin.js b/js/privatebin.js index c0bfb32..461b62c 100644 --- a/js/privatebin.js +++ b/js/privatebin.js @@ -2101,8 +2101,11 @@ jQuery.PrivateBin = (function($, RawDeflate) { // show preview PasteViewer.setText($message.val()); if (AttachmentViewer.hasAttachmentData()) { - let attachmentData = AttachmentViewer.getAttachmentData() || AttachmentViewer.getAttachmentLink().attr('href'); - AttachmentViewer.handleAttachmentPreview(AttachmentViewer.getAttachmentPreview(), attachmentData); + const attachment = AttachmentViewer.getAttachment(); + AttachmentViewer.handleBlobAttachmentPreview( + AttachmentViewer.getAttachmentPreview(), + attachment[0], attachment[1] + ); } PasteViewer.run(); @@ -2743,85 +2746,13 @@ jQuery.PrivateBin = (function($, RawDeflate) { attachmentData = dataURL; if (Editor.isPreview()) { - me.handleAttachmentPreview($attachmentPreview, dataURL); + me.setAttachment(dataURL, loadedFile.name || ''); $attachmentPreview.removeClass('hidden'); } }; fileReader.readAsDataURL(loadedFile); } - /** - * handle the preview of files that can either be an image, video, audio or pdf element - * - * @name AttachmentViewer.handleAttachmentPreview - * @function - * @argument {jQuery} $targetElement element where the preview should be appended - * @argument {string} file as a data URL - */ - me.handleAttachmentPreview = function ($targetElement, data) { - if (data) { - // source: https://developer.mozilla.org/en-US/docs/Web/API/FileReader#readAsDataURL() - const mimeType = data.slice( - data.indexOf('data:') + 5, - data.indexOf(';base64,') - ); - - attachmentHasPreview = true; - if (mimeType.match(/image\//i)) { - $targetElement.html( - $(document.createElement('img')) - .attr('src', data) - .attr('class', 'img-thumbnail') - ); - } else if (mimeType.match(/video\//i)) { - $targetElement.html( - $(document.createElement('video')) - .attr('controls', 'true') - .attr('autoplay', 'true') - .attr('class', 'img-thumbnail') - - .append($(document.createElement('source')) - .attr('type', mimeType) - .attr('src', data)) - ); - } else if (mimeType.match(/audio\//i)) { - $targetElement.html( - $(document.createElement('audio')) - .attr('controls', 'true') - .attr('autoplay', 'true') - - .append($(document.createElement('source')) - .attr('type', mimeType) - .attr('src', data)) - ); - } else if (mimeType.match(/\/pdf/i)) { - // PDFs are only displayed if the filesize is smaller than about 1MB (after base64 encoding). - // Bigger filesizes currently cause crashes in various browsers. - // See also: https://code.google.com/p/chromium/issues/detail?id=69227 - - // Firefox crashes with files that are about 1.5MB - // The performance with 1MB files is bearable - if (data.length > 1398488) { - Alert.showError('File too large, to display a preview. Please download the attachment.'); //TODO: is this error really necessary? - return; - } - - // Fallback for browsers, that don't support the vh unit - const clientHeight = $(window).height(); - - $targetElement.html( - $(document.createElement('embed')) - .attr('src', data) - .attr('type', 'application/pdf') - .attr('class', 'pdfPreview') - .css('height', clientHeight) - ); - } else { - attachmentHasPreview = false; - } - } - }; - /** * handle the preview of files decoded to blob that can either be an image, video, audio or pdf element * diff --git a/tpl/bootstrap.php b/tpl/bootstrap.php index 0e5d55b..48001f1 100644 --- a/tpl/bootstrap.php +++ b/tpl/bootstrap.php @@ -72,7 +72,7 @@ if ($MARKDOWN): endif; ?> - + diff --git a/tpl/page.php b/tpl/page.php index f8905ff..512df89 100644 --- a/tpl/page.php +++ b/tpl/page.php @@ -50,7 +50,7 @@ if ($MARKDOWN): endif; ?> - + From f915af1a5a3226e13fab262ba658ca867f4d7259 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 15 Jun 2019 09:36:09 +0200 Subject: [PATCH 7/8] adjust CSP header to allow blob URLs --- cfg/conf.sample.php | 2 +- lib/Configuration.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cfg/conf.sample.php b/cfg/conf.sample.php index c629288..0976948 100644 --- a/cfg/conf.sample.php +++ b/cfg/conf.sample.php @@ -70,7 +70,7 @@ languageselection = false ; Check the documentation at https://content-security-policy.com/ ; Note: If you use a bootstrap theme, you can remove the allow-popups from the sandbox restrictions. ; By default this disallows to load images from third-party servers, e.g. when they are embedded in pastes. If you wish to allow that, you can adjust the policy here. See https://github.com/PrivateBin/PrivateBin/wiki/FAQ#why-does-not-it-load-embedded-images for details. -; cspheader = "default-src 'none'; manifest-src 'self'; connect-src *; script-src 'self' 'unsafe-eval'; style-src 'self'; font-src 'self'; img-src 'self' data:; media-src data:; object-src data:; Referrer-Policy: 'no-referrer'; sandbox allow-same-origin allow-scripts allow-forms allow-popups allow-modals" +; cspheader = "default-src 'none'; manifest-src 'self'; connect-src *; script-src 'self' 'unsafe-eval'; style-src 'self'; font-src 'self'; img-src 'self' blob:; media-src blob:; object-src blob:; Referrer-Policy: 'no-referrer'; sandbox allow-same-origin allow-scripts allow-forms allow-popups allow-modals" ; stay compatible with PrivateBin Alpha 0.19, less secure ; if enabled will use base64.js version 1.7 instead of 2.1.9 and sha1 instead of diff --git a/lib/Configuration.php b/lib/Configuration.php index dc7212f..b670f2e 100644 --- a/lib/Configuration.php +++ b/lib/Configuration.php @@ -53,7 +53,7 @@ class Configuration 'urlshortener' => '', 'qrcode' => true, 'icon' => 'identicon', - 'cspheader' => 'default-src \'none\'; manifest-src \'self\'; connect-src *; script-src \'self\' \'unsafe-eval\'; style-src \'self\'; font-src \'self\'; img-src \'self\' data:; media-src data:; object-src data:; Referrer-Policy: \'no-referrer\'; sandbox allow-same-origin allow-scripts allow-forms allow-popups allow-modals', + 'cspheader' => 'default-src \'none\'; manifest-src \'self\'; connect-src *; script-src \'self\' \'unsafe-eval\'; style-src \'self\'; font-src \'self\'; img-src \'self\' blob:; media-src blob:; object-src blob:; Referrer-Policy: \'no-referrer\'; sandbox allow-same-origin allow-scripts allow-forms allow-popups allow-modals', 'zerobincompatibility' => false, ), 'expire' => array( From b4ceb4078df0e3998cdaa8f674014f6d4142f640 Mon Sep 17 00:00:00 2001 From: El RIDO Date: Sat, 15 Jun 2019 09:47:55 +0200 Subject: [PATCH 8/8] removed obsolete code and comments, tested with a PDF of 9 MiB and it works fine in Firefox and Chrome --- js/privatebin.js | 11 ----------- tpl/bootstrap.php | 2 +- tpl/page.php | 2 +- 3 files changed, 2 insertions(+), 13 deletions(-) diff --git a/js/privatebin.js b/js/privatebin.js index 461b62c..f43441c 100644 --- a/js/privatebin.js +++ b/js/privatebin.js @@ -2793,17 +2793,6 @@ jQuery.PrivateBin = (function($, RawDeflate) { .attr('src', blobUrl)) ); } else if (mimeType.match(/\/pdf/i)) { - // PDFs are only displayed if the filesize is smaller than about 1MB (after base64 encoding). - // Bigger filesizes currently cause crashes in various browsers. - // See also: https://code.google.com/p/chromium/issues/detail?id=69227 - - // Firefox crashes with files that are about 1.5MB - // The performance with 1MB files is bearable - /*if (data.length > 1398488) { - Alert.showError('File too large, to display a preview. Please download the attachment.'); //TODO: is this error really necessary? - return; - }*/ - // Fallback for browsers, that don't support the vh unit var clientHeight = $(window).height(); diff --git a/tpl/bootstrap.php b/tpl/bootstrap.php index 48001f1..541c61f 100644 --- a/tpl/bootstrap.php +++ b/tpl/bootstrap.php @@ -72,7 +72,7 @@ if ($MARKDOWN): endif; ?> - + diff --git a/tpl/page.php b/tpl/page.php index 512df89..129f38e 100644 --- a/tpl/page.php +++ b/tpl/page.php @@ -50,7 +50,7 @@ if ($MARKDOWN): endif; ?> - +