diff --git a/cfg/conf.sample.php b/cfg/conf.sample.php index c629288b..09769486 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/js/privatebin.js b/js/privatebin.js index e15a787a..f43441c4 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(); @@ -2514,39 +2517,43 @@ 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 }); + + // Get Blob URL + const blobUrl = window.URL.createObjectURL(blob); + // 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', blobUrl); } if (typeof fileName !== 'undefined') { $attachmentLink.attr('download', fileName); } - me.handleAttachmentPreview($attachmentPreview, attachmentData); + me.handleBlobAttachmentPreview($attachmentPreview, blobUrl, mediaType); }; /** @@ -2739,7 +2746,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { attachmentData = dataURL; if (Editor.isPreview()) { - me.handleAttachmentPreview($attachmentPreview, dataURL); + me.setAttachment(dataURL, loadedFile.name || ''); $attachmentPreview.removeClass('hidden'); } }; @@ -2747,26 +2754,21 @@ jQuery.PrivateBin = (function($, RawDeflate) { } /** - * handle the preview of files that can either be an image, video, audio or pdf element + * handle the preview of files decoded to blob that can either be an image, video, audio or pdf element * - * @name AttachmentViewer.handleAttachmentPreview + * @name AttachmentViewer.handleBlobAttachmentPreview * @function * @argument {jQuery} $targetElement element where the preview should be appended - * @argument {string} file as a data URL + * @argument {string} file as a blob URL + * @argument {string} mime type */ - 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,') - ); - + me.handleBlobAttachmentPreview = function ($targetElement, blobUrl, mimeType) { + if (blobUrl) { attachmentHasPreview = true; if (mimeType.match(/image\//i)) { $targetElement.html( $(document.createElement('img')) - .attr('src', data) + .attr('src', blobUrl) .attr('class', 'img-thumbnail') ); } else if (mimeType.match(/video\//i)) { @@ -2778,7 +2780,7 @@ jQuery.PrivateBin = (function($, RawDeflate) { .append($(document.createElement('source')) .attr('type', mimeType) - .attr('src', data)) + .attr('src', blobUrl)) ); } else if (mimeType.match(/audio\//i)) { $targetElement.html( @@ -2788,26 +2790,15 @@ jQuery.PrivateBin = (function($, RawDeflate) { .append($(document.createElement('source')) .attr('type', mimeType) - .attr('src', data)) + .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 - const clientHeight = $(window).height(); + var clientHeight = $(window).height(); $targetElement.html( $(document.createElement('embed')) - .attr('src', data) + .attr('src', blobUrl) .attr('type', 'application/pdf') .attr('class', 'pdfPreview') .css('height', clientHeight) diff --git a/js/test/AttachmentViewer.js b/js/test/AttachmentViewer.js index c1495fb6..438b2f89 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() && diff --git a/lib/Configuration.php b/lib/Configuration.php index dc7212f4..b670f2eb 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( diff --git a/tpl/bootstrap.php b/tpl/bootstrap.php index 0e5d55bb..541c61f1 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 f8905ff5..129f38e6 100644 --- a/tpl/page.php +++ b/tpl/page.php @@ -50,7 +50,7 @@ if ($MARKDOWN): endif; ?> - +