From d547c90168283da78b418c5b41c24ab8e17c7b96 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 16 Aug 2021 17:44:13 +0200 Subject: [PATCH 01/46] Fix SharedArrayBuffer cross-origin issues for X2T --- www/common/onlyoffice/inner.js | 327 ++++++++------------------------- www/common/onlyoffice/main.js | 16 ++ www/common/outer/x2t.js | 243 ++++++++++++++++++++++++ www/convert/inner.js | 133 ++------------ www/convert/main.js | 15 ++ 5 files changed, 360 insertions(+), 374 deletions(-) create mode 100644 www/common/outer/x2t.js diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index 2afef4978..2e4b8a43e 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -20,6 +20,7 @@ define([ '/common/onlyoffice/oodoc_base.js', '/common/onlyoffice/ooslide_base.js', '/common/outer/worker-channel.js', + '/common/outer/x2t.js', '/bower_components/file-saver/FileSaver.min.js', @@ -47,7 +48,8 @@ define([ EmptyCell, EmptyDoc, EmptySlide, - Channel) + Channel, + X2T) { var saveAs = window.saveAs; var Nacl = window.nacl; @@ -60,7 +62,7 @@ define([ var DISPLAY_RESTORE_BUTTON = false; var NEW_VERSION = 4; var PENDING_TIMEOUT = 30000; - var CURRENT_VERSION = 'v4'; + var CURRENT_VERSION = X2T.CURRENT_VERSION; //var READONLY_REFRESH_TO = 15000; var debug = function (x, type) { @@ -1699,239 +1701,81 @@ define([ makeChannel(); }; - var x2tReady = Util.mkEvent(true); - var fetchFonts = function (x2t) { - var path = '/common/onlyoffice/'+CURRENT_VERSION+'/fonts/'; + var x2tConvertData = function (data, fileName, format, cb) { + var sframeChan = common.getSframeChannel(); var e = getEditor(); var fonts = e.FontLoader.fontInfos; - var files = e.FontLoader.fontFiles; - var suffixes = { - indexR: '', - indexB: '_Bold', - indexBI: '_Bold_Italic', - indexI: '_Italic', - }; - nThen(function (waitFor) { - fonts.forEach(function (font) { - // Check if the font is already loaded - if (!font.NeedStyles) { return; } - // Pick the variants we need (regular, bold, italic) - ['indexR', 'indexB', 'indexI', 'indexBI'].forEach(function (k) { - if (typeof(font[k]) !== "number" || font[k] === -1) { return; } // No matching file - var file = files[font[k]]; - - var name = font.Name + suffixes[k] + '.ttf'; - Util.fetch(path + file.Id, waitFor(function (err, buffer) { - if (buffer) { - x2t.FS.writeFile('/working/fonts/' + name, buffer); - } - })); - }); - }); - }).nThen(function () { - x2tReady.fire(); + var files = e.FontLoader.fontFiles.map(function (f) { + return { 'Id': f.Id, }; }); - }; - - var x2tInitialized = false; - var x2tInit = function(x2t) { - debug("x2t mount"); - // x2t.FS.mount(x2t.MEMFS, {} , '/'); - x2t.FS.mkdir('/working'); - x2t.FS.mkdir('/working/media'); - x2t.FS.mkdir('/working/fonts'); - x2tInitialized = true; - fetchFonts(x2t); - debug("x2t mount done"); - }; - var getX2T = function (cb) { - // Perform the x2t conversion - require(['/common/onlyoffice/x2t/x2t.js'], function() { // FIXME why does this fail without an access-control-allow-origin header? - var x2t = window.Module; - x2t.run(); - if (x2tInitialized) { - debug("x2t runtime already initialized"); - return void x2tReady.reg(function () { - cb(x2t); - }); - } - - x2t.onRuntimeInitialized = function() { - debug("x2t in runtime initialized"); - // Init x2t js module - x2tInit(x2t); - x2tReady.reg(function () { - cb(x2t); - }); - }; - }); - }; - - - /* - Converting Data - - This function converts a data in a specific format to the outputformat - The filename extension needs to represent the input format - Example: fileName=cryptpad.bin outputFormat=xlsx - */ - var getFormatId = function (ext) { - // Sheets - if (ext === 'xlsx') { return 257; } - if (ext === 'xls') { return 258; } - if (ext === 'ods') { return 259; } - if (ext === 'csv') { return 260; } - if (ext === 'pdf') { return 513; } - return; - }; - var getFromId = function (ext) { - var id = getFormatId(ext); - if (!id) { return ''; } - return ''+id+''; - }; - var getToId = function (ext) { - var id = getFormatId(ext); - if (!id) { return ''; } - return ''+id+''; - }; - var x2tConvertDataInternal = function(x2t, data, fileName, outputFormat) { - debug("Converting Data for " + fileName + " to " + outputFormat); - - // PDF - var pdfData = ''; - if (outputFormat === "pdf" && typeof(data) === "object" && data.bin && data.buffer) { - // Add conversion rules - pdfData = "false" + - "/working/fonts/"; - // writing file to mounted working disk (in memory) - x2t.FS.writeFile('/working/' + fileName, data.bin); - x2t.FS.writeFile('/working/pdf.bin', data.buffer); - } else { - // writing file to mounted working disk (in memory) - x2t.FS.writeFile('/working/' + fileName, data); - } - - // Adding images - Object.keys(window.frames[0].AscCommon.g_oDocumentUrls.urls || {}).forEach(function (_mediaFileName) { - var mediaFileName = _mediaFileName.substring(6); - var mediasSources = getMediasSources(); - var mediaSource = mediasSources[mediaFileName]; - var mediaData = mediaSource ? mediasData[mediaSource.src] : undefined; - if (mediaData) { - debug("Writing media data " + mediaFileName); - debug("Data"); - var fileData = mediaData.content; - x2t.FS.writeFile('/working/media/' + mediaFileName, new Uint8Array(fileData)); - } else { - debug("Could not find media content for " + mediaFileName); + var type = common.getMetadataMgr().getPrivateData().ooType; + sframeChan.query('Q_OO_CONVERT', { + data: data, + type: type, + fileName: fileName, + outputFormat: format, + images: window.frames[0].AscCommon.g_oDocumentUrls.urls || {}, + fonts: fonts, + fonts_files: files, + mediasSources: getMediasSources(), + mediasData: mediasData + }, function (err, obj) { + if (err || !obj || !obj.data) { + UI.warn(Messages.error); + cb(); } + cb(obj.data, obj.images); + }, { + raw: true }); - - - var inputFormat = fileName.split('.').pop(); - - var params = "" - + "" - + "/working/" + fileName + "" - + "/working/" + fileName + "." + outputFormat + "" - + pdfData - + getFromId(inputFormat) - + getToId(outputFormat) - + "false" - + ""; - // writing params file to mounted working disk (in memory) - x2t.FS.writeFile('/working/params.xml', params); - // running conversion - x2t.ccall("runX2T", ["number"], ["string"], ["/working/params.xml"]); - // reading output file from working disk (in memory) - var result; - try { - result = x2t.FS.readFile('/working/' + fileName + "." + outputFormat); - } catch (e) { - debug("Failed reading converted file"); - UI.removeModals(); - UI.warn(Messages.error); - return ""; - } - return result; }; APP.printPdf = function (obj, cb) { - getX2T(function (x2t) { - //var e = getEditor(); - //var d = e.asc_nativePrint(undefined, undefined, 0x100 + opts.printType).ImData; - var bin = getContent(); - var xlsData = x2tConvertDataInternal(x2t, { - buffer: obj.data, - bin: bin - }, 'output.bin', 'pdf'); - if (xlsData) { - var md = common.getMetadataMgr().getMetadataLazy(); - var type = common.getMetadataMgr().getPrivateData().ooType; - var title = md.title || md.defaultTitle || type; - var blob = new Blob([xlsData], {type: "application/pdf"}); - //var url = URL.createObjectURL(blob, { type: "application/pdf" }); - saveAs(blob, title+'.pdf'); - //window.open(url); - cb({ - "type":"save", - "status":"ok", - //"data":url + "?disposition=inline&ooname=output.pdf" - }); - /* - ooChannel.send({ - "type":"documentOpen", - "data": { - "type":"save", - "status":"ok", - "data":url + "?disposition=inline&ooname=output.pdf" - } - }); - */ - } + var bin = getContent(); + x2tConvertData({ + buffer: obj.data, + bin: bin + }, 'output.bin', 'pdf', function (xlsData) { + if (!xlsData) { return; } + var md = common.getMetadataMgr().getMetadataLazy(); + var type = common.getMetadataMgr().getPrivateData().ooType; + var title = md.title || md.defaultTitle || type; + var blob = new Blob([xlsData], {type: "application/pdf"}); + saveAs(blob, title+'.pdf'); + cb({ + "type":"save", + "status":"ok", + }); }); }; - var x2tSaveAndConvertDataInternal = function(x2t, data, filename, extension, finalFilename) { + var x2tSaveAndConvertData = function(data, filename, extension, finalFilename) { var type = common.getMetadataMgr().getPrivateData().ooType; - var xlsData; // PDF if (type === "sheet" && extension === "pdf") { var e = getEditor(); var d = e.asc_nativePrint(undefined, undefined, 0x101).ImData; - xlsData = x2tConvertDataInternal(x2t, { + x2tConvertData({ buffer: d.data, bin: data - }, filename, extension); + }, filename, extension, function (res) { + if (res) { + var _blob = new Blob([res], {type: "application/bin;charset=utf-8"}); + UI.removeModals(); + saveAs(_blob, finalFilename); + } + }); + return; + } + x2tConvertData(data, filename, extension, function (xlsData) { if (xlsData) { - var _blob = new Blob([xlsData], {type: "application/bin;charset=utf-8"}); + var blob = new Blob([xlsData], {type: "application/bin;charset=utf-8"}); UI.removeModals(); - saveAs(_blob, finalFilename); + saveAs(blob, finalFilename); + return; } - return; - } - if (type === "sheet" && extension !== 'xlsx') { - xlsData = x2tConvertDataInternal(x2t, data, filename, 'xlsx'); - filename += '.xlsx'; - } else if (type === "presentation" && extension !== "pptx") { - xlsData = x2tConvertDataInternal(x2t, data, filename, 'pptx'); - filename += '.pptx'; - } else if (type === "doc" && extension !== "docx") { - xlsData = x2tConvertDataInternal(x2t, data, filename, 'docx'); - filename += '.docx'; - } - xlsData = x2tConvertDataInternal(x2t, data, filename, extension); - if (xlsData) { - var blob = new Blob([xlsData], {type: "application/bin;charset=utf-8"}); - UI.removeModals(); - saveAs(blob, finalFilename); - } - }; - - var x2tSaveAndConvertData = function(data, filename, extension, finalName) { - getX2T(function (x2t) { - x2tSaveAndConvertDataInternal(x2t, data, filename, extension, finalName); + UI.warn(Messages.error); }); }; @@ -2004,32 +1848,29 @@ define([ $select.find('button').addClass('btn'); }; - var x2tImportImagesInternal = function(x2t, images, i, callback) { + var x2tImportImagesInternal = function(images, i, callback) { if (i >= images.length) { callback(); } else { debug("Import image " + i); var handleFileData = { - name: images[i], + name: images[i].name, mediasSources: getMediasSources(), callback: function() { debug("next image"); - x2tImportImagesInternal(x2t, images, i+1, callback); + x2tImportImagesInternal(images, i+1, callback); }, }; - var filePath = "/working/media/" + images[i]; - debug("Import filename " + filePath); - var fileData = x2t.FS.readFile("/working/media/" + images[i], { encoding : "binary" }); - debug("Importing data"); + var fileData = images[i].data; debug("Buffer"); debug(fileData.buffer); var blob = new Blob([fileData.buffer], {type: 'image/png'}); - blob.name = images[i]; + blob.name = images[i].name; APP.FMImages.handleFile(blob, handleFileData); } }; - var x2tImportImages = function (x2t, callback) { + var x2tImportImages = function (images, callback) { if (!APP.FMImages) { var fmConfigImages = { noHandlers: true, @@ -2055,14 +1896,8 @@ define([ // Import Images debug("Import Images"); - var files = x2t.FS.readdir("/working/media/"); - var images = []; - files.forEach(function (file) { - if (file !== "." && file !== "..") { - images.push(file); - } - }); - x2tImportImagesInternal(x2t, images, 0, function() { + debug(images); + x2tImportImagesInternal(images, 0, function() { debug("Sync media sources elements"); debug(getMediasSources()); APP.onLocal(); @@ -2072,26 +1907,12 @@ define([ }; - var x2tConvertData = function (x2t, data, filename, extension, callback) { - var convertedContent; - // Convert from ODF format: - // first convert to Office format then to the selected extension - if (filename.endsWith(".ods")) { - console.log(x2t, data, filename, extension); - convertedContent = x2tConvertDataInternal(x2t, new Uint8Array(data), filename, "xlsx"); - console.log(convertedContent); - convertedContent = x2tConvertDataInternal(x2t, convertedContent, filename + ".xlsx", extension); - } else if (filename.endsWith(".odt")) { - convertedContent = x2tConvertDataInternal(x2t, new Uint8Array(data), filename, "docx"); - convertedContent = x2tConvertDataInternal(x2t, convertedContent, filename + ".docx", extension); - } else if (filename.endsWith(".odp")) { - convertedContent = x2tConvertDataInternal(x2t, new Uint8Array(data), filename, "pptx"); - convertedContent = x2tConvertDataInternal(x2t, convertedContent, filename + ".pptx", extension); - } else { - convertedContent = x2tConvertDataInternal(x2t, new Uint8Array(data), filename, extension); - } - x2tImportImages(x2t, function() { - callback(convertedContent); + var x2tImportData = function (data, filename, extension, callback) { + x2tConvertData(new Uint8Array(data), filename, extension, function (binData, images) { + if (!binData) { return void UI.warn(Messages.error); } + x2tImportImages(images, function() { + callback(binData); + }); }); }; @@ -2145,10 +1966,8 @@ define([ ]); UI.openCustomModal(UI.dialog.customModal(div, {buttons: []})); setTimeout(function () { - getX2T(function (x2t) { - x2tConvertData(x2t, new Uint8Array(content), filename.name, "bin", function(c) { - importFile(c); - }); + x2tImportData(new Uint8Array(content), filename.name, "bin", function(c) { + importFile(c); }); }, 100); }; diff --git a/www/common/onlyoffice/main.js b/www/common/onlyoffice/main.js index b7626a589..8b79474ae 100644 --- a/www/common/onlyoffice/main.js +++ b/www/common/onlyoffice/main.js @@ -143,6 +143,22 @@ define([ } sframeChan.event('EV_OO_EVENT', obj); }); + + // X2T + var x2t; + var onConvert = function (obj, cb) { + x2t.convert(obj, cb); + }; + sframeChan.on('Q_OO_CONVERT', function (obj, cb) { + if (x2t) { return void onConvert(obj, cb); } + require(['/common/outer/x2t.js'], function (X2T) { + x2t = X2T.start(); + onConvert(obj, cb); + }); + }); + + + }; SFCommonO.start({ hash: hash, diff --git a/www/common/outer/x2t.js b/www/common/outer/x2t.js new file mode 100644 index 000000000..85b84c0ae --- /dev/null +++ b/www/common/outer/x2t.js @@ -0,0 +1,243 @@ +define([ + '/bower_components/nthen/index.js', + '/common/common-util.js', +], function (nThen, Util) { + var X2T = {}; + + var CURRENT_VERSION = X2T.CURRENT_VERSION = 'v4'; + var debug = function (str) { + if (localStorage.CryptPad_dev !== "1") { return; } + console.debug(str); + }; + + X2T.start = function () { + var x2tReady = Util.mkEvent(true); + var fetchFonts = function (x2t, obj, cb) { + if (!obj.fonts) { return void cb(); } + var path = '/common/onlyoffice/'+CURRENT_VERSION+'/fonts/'; + var fonts = obj.fonts; + var files = obj.fonts_files; + var suffixes = { + indexR: '', + indexB: '_Bold', + indexBI: '_Bold_Italic', + indexI: '_Italic', + }; + nThen(function (waitFor) { + fonts.forEach(function (font) { + // Check if the font is already loaded + if (!font.NeedStyles) { return; } + // Pick the variants we need (regular, bold, italic) + ['indexR', 'indexB', 'indexI', 'indexBI'].forEach(function (k) { + if (typeof(font[k]) !== "number" || font[k] === -1) { return; } // No matching file + var file = files[font[k]]; + + var name = font.Name + suffixes[k] + '.ttf'; + Util.fetch(path + file.Id, waitFor(function (err, buffer) { + if (buffer) { + x2t.FS.writeFile('/working/fonts/' + name, buffer); + } + })); + }); + }); + }).nThen(function () { + cb(); + }); + }; + var x2tInitialized = false; + var x2tInit = function(x2t) { + debug("x2t mount"); + // x2t.FS.mount(x2t.MEMFS, {} , '/'); + x2t.FS.mkdir('/working'); + x2t.FS.mkdir('/working/media'); + x2t.FS.mkdir('/working/fonts'); + x2tInitialized = true; + x2tReady.fire(); + debug("x2t mount done"); + }; + var getX2T = function (cb) { + // Perform the x2t conversion + require(['/common/onlyoffice/x2t/x2t.js'], function() { // FIXME why does this fail without an access-control-allow-origin header? + var x2t = window.Module; + x2t.run(); + if (x2tInitialized) { + debug("x2t runtime already initialized"); + return void x2tReady.reg(function () { + cb(x2t); + }); + } + + x2t.onRuntimeInitialized = function() { + debug("x2t in runtime initialized"); + // Init x2t js module + x2tInit(x2t); + x2tReady.reg(function () { + cb(x2t); + }); + }; + }); + }; + + var getFormatId = function (ext) { + // Sheets + if (ext === 'xlsx') { return 257; } + if (ext === 'xls') { return 258; } + if (ext === 'ods') { return 259; } + if (ext === 'csv') { return 260; } + if (ext === 'pdf') { return 513; } + // Docs + if (ext === 'docx') { return 65; } + if (ext === 'doc') { return 66; } + if (ext === 'odt') { return 67; } + if (ext === 'txt') { return 69; } + if (ext === 'html') { return 70; } + + // Slides + if (ext === 'pptx') { return 129; } + if (ext === 'ppt') { return 130; } + if (ext === 'odp') { return 131; } + + return; + }; + var getFromId = function (ext) { + var id = getFormatId(ext); + if (!id) { return ''; } + return ''+id+''; + }; + var getToId = function (ext) { + var id = getFormatId(ext); + if (!id) { return ''; } + return ''+id+''; + }; + + var x2tConvertDataInternal = function(x2t, obj) { + var data = obj.data; + var fileName = obj.fileName; + var outputFormat = obj.outputFormat; + var images = obj.images; + debug("Converting Data for " + fileName + " to " + outputFormat); + + // PDF + var pdfData = ''; + if (outputFormat === "pdf" && typeof(data) === "object" && data.bin && data.buffer) { + // Add conversion rules + pdfData = "false" + + "/working/fonts/"; + // writing file to mounted working disk (in memory) + x2t.FS.writeFile('/working/' + fileName, data.bin); + x2t.FS.writeFile('/working/pdf.bin', data.buffer); + } else { + // writing file to mounted working disk (in memory) + x2t.FS.writeFile('/working/' + fileName, data); + } + + // Adding images + Object.keys(images || {}).forEach(function (_mediaFileName) { + var mediaFileName = _mediaFileName.substring(6); + var mediasSources = obj.mediasSources || {}; + var mediasData = obj.mediasData || {}; + var mediaSource = mediasSources[mediaFileName]; + var mediaData = mediaSource ? mediasData[mediaSource.src] : undefined; + if (mediaData) { + debug("Writing media data " + mediaFileName); + debug("Data"); + var fileData = mediaData.content; + x2t.FS.writeFile('/working/media/' + mediaFileName, new Uint8Array(fileData)); + } else { + debug("Could not find media content for " + mediaFileName); + } + }); + + + var inputFormat = fileName.split('.').pop(); + + var params = "" + + "" + + "/working/" + fileName + "" + + "/working/" + fileName + "." + outputFormat + "" + + pdfData + + getFromId(inputFormat) + + getToId(outputFormat) + + "false" + + ""; + // writing params file to mounted working disk (in memory) + x2t.FS.writeFile('/working/params.xml', params); + // running conversion + x2t.ccall("runX2T", ["number"], ["string"], ["/working/params.xml"]); + // reading output file from working disk (in memory) + var result; + try { + result = x2t.FS.readFile('/working/' + fileName + "." + outputFormat); + } catch (e) { + debug("Failed reading converted file"); + return ""; + } + return result; + }; + + var convert = function (obj, cb) { + getX2T(function (x2t) { + // Fonts + fetchFonts(x2t, obj, function () { + var o = obj.outputFormat; + + if (o !== 'pdf') { + // Add intermediary conversion to Microsoft Office format if needed + // (bin to pdf is allowed) + [ + // Import from Open Document + {source: '.ods', format: 'xlsx'}, + {source: '.odt', format: 'docx'}, + {source: '.odp', format: 'pptx'}, + // Export to non Microsoft Office + {source: '.bin', type: 'sheet', format: 'xlsx'}, + {source: '.bin', type: 'doc', format: 'docx'}, + {source: '.bin', type: 'presentation', format: 'pptx'}, + ].forEach(function (_step) { + if (obj.fileName.endsWith(_step.source) && obj.outputFormat !== _step.format && + (!_step.type || _step.type === obj.type)) { + obj.outputFormat = _step.format; + obj.data = x2tConvertDataInternal(x2t, obj); + obj.fileName += '.'+_step.format; + } + }); + obj.outputFormat = o; + } + + var data = x2tConvertDataInternal(x2t, obj); + + // Convert to bin -- Import + // We need to extract the images + var images; + if (o === 'bin') { + images = []; + var files = x2t.FS.readdir("/working/media/"); + files.forEach(function (file) { + if (file !== "." && file !== "..") { + var fileData = x2t.FS.readFile("/working/media/" + file, { + encoding : "binary" + }); + images.push({ + name: file, + data: fileData + }); + } + }); + + } + + cb({ + data: data, + images: images + }); + }); + }); + }; + + return { + convert: convert + }; + }; + + return X2T; +}); diff --git a/www/convert/inner.js b/www/convert/inner.js index ba9870ccf..a3b244a8a 100644 --- a/www/convert/inner.js +++ b/www/convert/inner.js @@ -8,7 +8,6 @@ define([ '/common/hyperscript.js', '/customize/messages.js', '/common/common-interface.js', - '/common/common-util.js', '/bower_components/file-saver/FileSaver.min.js', 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', @@ -23,8 +22,7 @@ define([ SFCommon, h, Messages, - UI, - Util + UI ) { var APP = {}; @@ -32,126 +30,21 @@ define([ var common; var sFrameChan; - var debug = console.debug; - - var x2tReady = Util.mkEvent(true); - var x2tInitialized = false; - var x2tInit = function(x2t) { - debug("x2t mount"); - // x2t.FS.mount(x2t.MEMFS, {} , '/'); - x2t.FS.mkdir('/working'); - x2t.FS.mkdir('/working/media'); - x2t.FS.mkdir('/working/fonts'); - x2tInitialized = true; - x2tReady.fire(); - //fetchFonts(x2t); - debug("x2t mount done"); - }; - var getX2t = function (cb) { - require(['/common/onlyoffice/x2t/x2t.js'], function() { // FIXME why does this fail without an access-control-allow-origin header? - var x2t = window.Module; - x2t.run(); - if (x2tInitialized) { - debug("x2t runtime already initialized"); - return void x2tReady.reg(function () { - cb(x2t); - }); - } - - x2t.onRuntimeInitialized = function() { - debug("x2t in runtime initialized"); - // Init x2t js module - x2tInit(x2t); - x2tReady.reg(function () { - cb(x2t); - }); - }; - }); - }; - /* - Converting Data - - This function converts a data in a specific format to the outputformat - The filename extension needs to represent the input format - Example: fileName=cryptpad.bin outputFormat=xlsx - */ - var getFormatId = function (ext) { - // Sheets - if (ext === 'xlsx') { return 257; } - if (ext === 'xls') { return 258; } - if (ext === 'ods') { return 259; } - if (ext === 'csv') { return 260; } - if (ext === 'pdf') { return 513; } - // Docs - if (ext === 'docx') { return 65; } - if (ext === 'doc') { return 66; } - if (ext === 'odt') { return 67; } - if (ext === 'txt') { return 69; } - if (ext === 'html') { return 70; } - - // Slides - if (ext === 'pptx') { return 129; } - if (ext === 'ppt') { return 130; } - if (ext === 'odp') { return 131; } - - return; - }; - var getFromId = function (ext) { - var id = getFormatId(ext); - if (!id) { return ''; } - return ''+id+''; - }; - var getToId = function (ext) { - var id = getFormatId(ext); - if (!id) { return ''; } - return ''+id+''; - }; - var x2tConvertDataInternal = function(x2t, data, fileName, outputFormat) { - debug("Converting Data for " + fileName + " to " + outputFormat); - - var inputFormat = fileName.split('.').pop(); - - x2t.FS.writeFile('/working/' + fileName, data); - var params = "" - + "" - + "/working/" + fileName + "" - + "/working/" + fileName + "." + outputFormat + "" - + getFromId(inputFormat) - + getToId(outputFormat) - + "false" - + ""; - // writing params file to mounted working disk (in memory) - x2t.FS.writeFile('/working/params.xml', params); - // running conversion - x2t.ccall("runX2T", ["number"], ["string"], ["/working/params.xml"]); - // reading output file from working disk (in memory) - var result; - try { - result = x2t.FS.readFile('/working/' + fileName + "." + outputFormat); - } catch (e) { - console.error(e, x2t.FS); - debug("Failed reading converted file"); - UI.warn(Messages.error); - return ""; - } - return result; - }; var x2tConverter = function (typeSrc, typeTarget) { return function (data, name, cb) { - getX2t(function (x2t) { - if (typeSrc === 'ods') { - data = x2tConvertDataInternal(x2t, data, name, 'xlsx'); - name += '.xlsx'; - } - if (typeSrc === 'odt') { - data = x2tConvertDataInternal(x2t, data, name, 'docx'); - name += '.docx'; - } - if (typeSrc === 'odp') { - data = x2tConvertDataInternal(x2t, data, name, 'pptx'); - name += '.pptx'; + var sframeChan = common.getSframeChannel(); + sframeChan.query('Q_OO_CONVERT', { + data: data, + fileName: name, + outputFormat: typeTarget, + }, function (err, obj) { + if (err || !obj || !obj.data) { + UI.warn(Messages.error); + cb(); } - cb(x2tConvertDataInternal(x2t, data, name, typeTarget)); + cb(obj.data, obj.images); + }, { + raw: true }); }; }; diff --git a/www/convert/main.js b/www/convert/main.js index 236290b13..1bef9f9cd 100644 --- a/www/convert/main.js +++ b/www/convert/main.js @@ -17,11 +17,26 @@ define([ category = window.location.hash.slice(1); window.location.hash = ''; } + var addRpc = function (sframeChan) { + // X2T + var x2t; + var onConvert = function (obj, cb) { + x2t.convert(obj, cb); + }; + sframeChan.on('Q_OO_CONVERT', function (obj, cb) { + if (x2t) { return void onConvert(obj, cb); } + require(['/common/outer/x2t.js'], function (X2T) { + x2t = X2T.start(); + onConvert(obj, cb); + }); + }); + }; var addData = function (obj) { if (category) { obj.category = category; } }; SFCommonO.start({ noRealtime: true, + addRpc: addRpc, addData: addData }); }); From aff9ffa40057a29aaec2e60e7cef9abc8bfbf750 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 17 Aug 2021 10:28:08 +0200 Subject: [PATCH 02/46] Fix double callback in onlyoffice x2t --- www/common/onlyoffice/inner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index 2e4b8a43e..f43404505 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -1722,7 +1722,7 @@ define([ }, function (err, obj) { if (err || !obj || !obj.data) { UI.warn(Messages.error); - cb(); + return void cb(); } cb(obj.data, obj.images); }, { From 99572cbb6e589424221000f137b1eb154bc238f0 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 17 Aug 2021 10:28:32 +0200 Subject: [PATCH 03/46] Load fonts from browser cache when converting office documents --- www/common/outer/x2t.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/www/common/outer/x2t.js b/www/common/outer/x2t.js index 85b84c0ae..55ce85f2c 100644 --- a/www/common/outer/x2t.js +++ b/www/common/outer/x2t.js @@ -1,7 +1,8 @@ define([ + '/api/config', '/bower_components/nthen/index.js', '/common/common-util.js', -], function (nThen, Util) { +], function (ApiConfig, nThen, Util) { var X2T = {}; var CURRENT_VERSION = X2T.CURRENT_VERSION = 'v4'; @@ -14,7 +15,8 @@ define([ var x2tReady = Util.mkEvent(true); var fetchFonts = function (x2t, obj, cb) { if (!obj.fonts) { return void cb(); } - var path = '/common/onlyoffice/'+CURRENT_VERSION+'/fonts/'; + var path = ApiConfig.httpSafeOrigin + '/common/onlyoffice/'+CURRENT_VERSION+'/fonts/'; + var ver = '?' + ApiConfig.requireConf.urlArgs; var fonts = obj.fonts; var files = obj.fonts_files; var suffixes = { @@ -33,7 +35,7 @@ define([ var file = files[font[k]]; var name = font.Name + suffixes[k] + '.ttf'; - Util.fetch(path + file.Id, waitFor(function (err, buffer) { + Util.fetch(path + file.Id + ver, waitFor(function (err, buffer) { if (buffer) { x2t.FS.writeFile('/working/fonts/' + name, buffer); } From 777de599c390176b65c40631b8de593b33e244e2 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 17 Aug 2021 16:05:49 +0200 Subject: [PATCH 04/46] Download spreadsheets as xlsx from the drive --- www/common/drive-ui.js | 2 +- www/common/make-backup.js | 22 ++-- www/common/onlyoffice/inner.js | 116 +++++++++++++++----- www/common/onlyoffice/ooiframe.js | 175 ++++++++++++++++++++++++++++++ www/common/sframe-common-outer.js | 123 ++++++++++++++------- www/settings/inner.js | 2 +- www/sheet/export.js | 23 ++++ www/teams/inner.js | 2 +- 8 files changed, 385 insertions(+), 80 deletions(-) create mode 100644 www/common/onlyoffice/ooiframe.js create mode 100644 www/sheet/export.js diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index cb70986a4..1fe900e52 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -1275,7 +1275,7 @@ define([ hide.push('preview'); } if ($element.is('.cp-border-color-sheet')) { - hide.push('download'); + //hide.push('download'); // XXX if we don't want to enable this feature yet } if ($element.is('.cp-app-drive-static')) { hide.push('access', 'hashtag', 'properties', 'download'); diff --git a/www/common/make-backup.js b/www/common/make-backup.js index 3f66c1310..bcabd9be6 100644 --- a/www/common/make-backup.js +++ b/www/common/make-backup.js @@ -28,7 +28,7 @@ define([ return n; }; - var transform = function (ctx, type, sjson, cb) { + var transform = function (ctx, type, sjson, cb, padData) { var result = { data: sjson, ext: '.json', @@ -45,7 +45,7 @@ define([ result.ext = Exporter.ext || ''; result.data = data; cb(result); - }); + }, null, ctx.sframeChan, padData); }, function () { cb(result); }); @@ -117,6 +117,10 @@ define([ var opts = { password: pData.password }; + var padData = { + hash: parsed.hash, + password: pData.password + }; var handler = ctx.sframeChan.on("EV_CRYPTGET_PROGRESS", function (data) { if (data.hash !== parsed.hash) { return; } updateProgress.progress(data.progress); @@ -136,14 +140,14 @@ define([ if (cancelled) { return; } if (!res.data) { return; } var dl = function () { - saveAs(res.data, Util.fixFileName(name)); + saveAs(res.data, Util.fixFileName(name)+(res.ext || '')); }; cb(null, { metadata: res.metadata, content: res.data, download: dl }); - }); + }, padData); }); return { cancel: cancel @@ -228,6 +232,9 @@ define([ zip.file(fileName, res.data, opts); console.log('DONE ---- ' + fileName); setTimeout(done, 500); + }, { + hash: parsed.hash, + password: fData.password }); }); }; @@ -292,7 +299,7 @@ define([ }; // Main function. Create the empty zip and fill it starting from drive.root - var create = function (data, getPad, fileHost, cb, progress, cache) { + var create = function (data, getPad, fileHost, cb, progress, cache, sframeChan) { if (!data || !data.uo || !data.uo.drive) { return void cb('EEMPTY'); } var sem = Saferphore.create(5); var ctx = { @@ -307,7 +314,8 @@ define([ updateProgress: progress, max: 0, done: 0, - cache: cache + cache: cache, + sframeChan: sframeChan }; var filesData = data.sharedFolderId && ctx.sf[data.sharedFolderId] ? ctx.sf[data.sharedFolderId].filesData : ctx.data.filesData; progress('reading', -1); // Msg.settings_export_reading @@ -358,7 +366,7 @@ define([ else if (state === "done") { updateProgress.folderProgress(3); } - }, ctx.cache); + }, ctx.cache, ctx.sframeChan); }; var createExportUI = function (origin) { diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index f43404505..26c5aa947 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -254,6 +254,10 @@ define([ var getFileType = function () { var type = common.getMetadataMgr().getPrivateData().ooType; var title = common.getMetadataMgr().getMetadataLazy().title; + if (APP.downloadType) { + type = APP.downloadType; + title = "download"; + } var file = {}; switch(type) { case 'doc': @@ -727,6 +731,7 @@ define([ var cp = hashes[cpId] || {}; var minor = Number(s[1]) + 1; + if (APP.isDownload) { minor = undefined; } var toHash = cp.hash || 'NONE'; var fromHash = nextCpId ? hashes[nextCpId].hash : 'NONE'; @@ -735,6 +740,7 @@ define([ channel: content.channel, lastKnownHash: fromHash, toHash: toHash, + isDownload: APP.isDownload }, function (err, data) { if (err) { console.error(err); return void UI.errorLoadingScreen(Messages.error); } if (!Array.isArray(data.messages)) { @@ -786,6 +792,7 @@ define([ } var file = getFileType(); var type = common.getMetadataMgr().getPrivateData().ooType; + if (APP.downloadType) { type = APP.downloadType; } var blob = loadInitDocument(type, true); ooChannel.queue = messages; resetData(blob, file); @@ -1345,6 +1352,35 @@ define([ }); }; + var x2tConvertData = function (data, fileName, format, cb) { + var sframeChan = common.getSframeChannel(); + var e = getEditor(); + var fonts = e.FontLoader.fontInfos; + var files = e.FontLoader.fontFiles.map(function (f) { + return { 'Id': f.Id, }; + }); + var type = common.getMetadataMgr().getPrivateData().ooType; + sframeChan.query('Q_OO_CONVERT', { + data: data, + type: type, + fileName: fileName, + outputFormat: format, + images: window.frames[0].AscCommon.g_oDocumentUrls.urls || {}, + fonts: fonts, + fonts_files: files, + mediasSources: getMediasSources(), + mediasData: mediasData + }, function (err, obj) { + if (err || !obj || !obj.data) { + UI.warn(Messages.error); + return void cb(); + } + cb(obj.data, obj.images); + }, { + raw: true + }); + }; + startOO = function (blob, file, force) { if (APP.ooconfig && !force) { return void console.error('already started'); } var url = URL.createObjectURL(blob); @@ -1425,6 +1461,13 @@ define([ }); } }, + "onError": function () { + console.error(arguments); + if (APP.isDownload) { + var sframeChan = common.getSframeChannel(); + sframeChan.event('EV_OOIFRAME_DONE', ''); + } + }, "onDocumentReady": function () { evOnSync.fire(); var onMigrateRdy = Util.mkEvent(); @@ -1504,6 +1547,16 @@ define([ } } + if (APP.isDownload) { + var bin = getContent(); + x2tConvertData(bin, 'filename.bin', file.type, function (xlsData) { + var sframeChan = common.getSframeChannel(); + sframeChan.event('EV_OOIFRAME_DONE', xlsData, {raw: true}); + }); + return; + } + + if (isLockedModal.modal && force) { isLockedModal.modal.closeModal(); delete isLockedModal.modal; @@ -1701,35 +1754,6 @@ define([ makeChannel(); }; - var x2tConvertData = function (data, fileName, format, cb) { - var sframeChan = common.getSframeChannel(); - var e = getEditor(); - var fonts = e.FontLoader.fontInfos; - var files = e.FontLoader.fontFiles.map(function (f) { - return { 'Id': f.Id, }; - }); - var type = common.getMetadataMgr().getPrivateData().ooType; - sframeChan.query('Q_OO_CONVERT', { - data: data, - type: type, - fileName: fileName, - outputFormat: format, - images: window.frames[0].AscCommon.g_oDocumentUrls.urls || {}, - fonts: fonts, - fonts_files: files, - mediasSources: getMediasSources(), - mediasData: mediasData - }, function (err, obj) { - if (err || !obj || !obj.data) { - UI.warn(Messages.error); - return void cb(); - } - cb(obj.data, obj.images); - }, { - raw: true - }); - }; - APP.printPdf = function (obj, cb) { var bin = getContent(); x2tConvertData({ @@ -2193,6 +2217,39 @@ define([ }); }; + sframeChan.on('EV_OOIFRAME_REFRESH', function (data) { + // We want to get the "bin" content of a sheet from its json in order to download + // something useful from a non-onlyoffice app (download from drive or settings). + // We don't want to initialize a full pad in async-store because we only need a + // static version, so we can use "openVersionHash" which is based on GET_HISTORY_RANGE + APP.isDownload = data.downloadId; + APP.downloadType = data.type; + var json = data && data.json; + if (!json || !json.content) { + return void sframeChan.event('EV_OOIFRAME_DONE', ''); + } + content = json.content; + readOnly = true; + var version = (!content.version || content.version === 1) ? 'v1/' : + (content.version <= 3 ? 'v2b/' : CURRENT_VERSION+'/'); + var s = h('script', { + type:'text/javascript', + src: '/common/onlyoffice/'+version+'web-apps/apps/api/documents/api.js' + }); + $('#cp-app-oo-editor').append(s); + + var hashes = content.hashes || {}; + var idx = sortCpIndex(hashes); + var lastIndex = idx[idx.length - 1]; + + // We're going to open using "openVersionHash" to avoid reimplementing existing code. + // To do so, we're using a version corresponding to the latest checkpoint with a + // minor version of 0. "openVersionHash" knows that it needs to give us the latest + // version when "APP.isDownload" is true. + var sheetVersion = lastIndex + '.0'; + openVersionHash(sheetVersion); + }); + config.onInit = function (info) { var privateData = metadataMgr.getPrivateData(); metadataMgr.setDegraded(false); // FIXME degraded moded unsupported (no cursor channel) @@ -2506,6 +2563,7 @@ define([ readOnly = true; } } + // NOTE: don't forget to also update the version in 'EV_OOIFRAME_REFRESH' // If the sheet is locked by an offline user, remove it if (content && content.saveLock && !isUserOnline(content.saveLock)) { diff --git a/www/common/onlyoffice/ooiframe.js b/www/common/onlyoffice/ooiframe.js new file mode 100644 index 000000000..87d5adfed --- /dev/null +++ b/www/common/onlyoffice/ooiframe.js @@ -0,0 +1,175 @@ +// Load #1, load as little as possible because we are in a race to get the loading screen up. +define([ + '/bower_components/nthen/index.js', + '/api/config', + 'jquery', + '/common/requireconfig.js', + '/customize/messages.js', +], function (nThen, ApiConfig, $, RequireConfig, Messages) { + var requireConfig = RequireConfig(); + + var ready = false; + var currentCb; + var queue = []; + + var create = function (config) { + // Loaded in load #2 + var sframeChan; + var refresh = function (data, cb) { + if (currentCb) { + queue.push({data: data, cb: cb}); + return; + } + if (!ready) { + ready = function () { + refresh(data, cb); + }; + return; + } + currentCb = cb; + sframeChan.event('EV_OOIFRAME_REFRESH', data); + }; + nThen(function (waitFor) { + $(waitFor()); + }).nThen(function (waitFor) { + var lang = Messages._languageUsed; + var themeKey = 'CRYPTPAD_STORE|colortheme'; + var req = { + cfg: requireConfig, + req: [ '/common/loading.js' ], + pfx: window.location.origin, + theme: localStorage[themeKey], + themeOS: localStorage[themeKey+'_default'], + lang: lang + }; + window.rc = requireConfig; + window.apiconf = ApiConfig; + $('#sbox-oo-iframe').attr('src', + ApiConfig.httpSafeOrigin + '/sheet/inner.html?' + requireConfig.urlArgs + + '#' + encodeURIComponent(JSON.stringify(req))); + + // This is a cheap trick to avoid loading sframe-channel in parallel with the + // loading screen setup. + var done = waitFor(); + var onMsg = function (msg) { + var data = typeof(msg.data) === "object" ? msg.data : JSON.parse(msg.data); + if (data.q !== 'READY') { return; } + window.removeEventListener('message', onMsg); + var _done = done; + done = function () { }; + _done(); + }; + window.addEventListener('message', onMsg); + }).nThen(function (/*waitFor*/) { + var Cryptpad = config.modules.Cryptpad; + var Utils = config.modules.Utils; + + nThen(function (waitFor) { + // The inner iframe tries to get some data from us every ms (cache, store...). + // It will send a "READY" message and wait for our answer with the correct txid. + // First, we have to answer to this message, otherwise we're going to block + // sframe-boot.js. Then we can start the channel. + var msgEv = Utils.Util.mkEvent(); + var iframe = $('#sbox-oo-iframe')[0].contentWindow; + var postMsg = function (data) { + iframe.postMessage(data, '*'); + }; + var w = waitFor(); + var whenReady = function (msg) { + if (msg.source !== iframe) { return; } + var data = JSON.parse(msg.data); + if (!data.txid) { return; } + // Remove the listener once we've received the READY message + window.removeEventListener('message', whenReady); + // Answer with the requested data + postMsg(JSON.stringify({ txid: data.txid, language: Cryptpad.getLanguage(), localStore: window.localStore, cache: window.cpCache })); + + // Then start the channel + window.addEventListener('message', function (msg) { + if (msg.source !== iframe) { return; } + msgEv.fire(msg); + }); + config.modules.SFrameChannel.create(msgEv, postMsg, waitFor(function (sfc) { + sframeChan = sfc; + })); + w(); + }; + window.addEventListener('message', whenReady); + }).nThen(function () { + var updateMeta = function () { + //console.log('EV_METADATA_UPDATE'); + var metaObj; + nThen(function (waitFor) { + Cryptpad.getMetadata(waitFor(function (err, n) { + if (err) { + waitFor.abort(); + return void console.log(err); + } + metaObj = n; + })); + }).nThen(function (/*waitFor*/) { + metaObj.doc = {}; + var additionalPriv = { + fileHost: ApiConfig.fileHost, + loggedIn: Utils.LocalStore.isLoggedIn(), + origin: window.location.origin, + pathname: window.location.pathname, + feedbackAllowed: Utils.Feedback.state, + secureIframe: true, + }; + for (var k in additionalPriv) { metaObj.priv[k] = additionalPriv[k]; } + + sframeChan.event('EV_METADATA_UPDATE', metaObj); + }); + }; + Cryptpad.onMetadataChanged(updateMeta); + sframeChan.onReg('EV_METADATA_UPDATE', updateMeta); + + config.addCommonRpc(sframeChan, true); + + Cryptpad.padRpc.onMetadataEvent.reg(function (data) { + sframeChan.event('EV_RT_METADATA', data); + }); + + sframeChan.on('EV_OOIFRAME_DONE', function (data) { + if (queue.length) { + setTimeout(function () { + var first = queue.shift(); + refresh(first.data, first.cb); + }); + } + if (!currentCb) { return; } + currentCb(data); + currentCb = undefined; + }); + + // X2T + var x2t; + var onConvert = function (obj, cb) { + x2t.convert(obj, cb); + }; + sframeChan.on('Q_OO_CONVERT', function (obj, cb) { + if (x2t) { return void onConvert(obj, cb); } + require(['/common/outer/x2t.js'], function (X2T) { + x2t = X2T.start(); + onConvert(obj, cb); + }); + }); + + sframeChan.onReady(function () { + if (ready === true) { return; } + if (typeof ready === "function") { + ready(); + } + ready = true; + }); + }); + }); + return { + refresh: refresh + }; + }; + return { + create: create + }; +}); diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 575971368..ca60c2243 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -72,6 +72,7 @@ define([ var SFrameChannel; var sframeChan; var SecureIframe; + var OOIframe; var Messaging; var Notifier; var Utils = { @@ -98,6 +99,7 @@ define([ '/common/cryptget.js', '/common/outer/worker-channel.js', '/secureiframe/main.js', + '/common/onlyoffice/ooiframe.js', '/common/common-messaging.js', '/common/common-notifier.js', '/common/common-hash.js', @@ -112,7 +114,7 @@ define([ '/common/test.js', '/common/userObject.js', ], waitFor(function (_CpNfOuter, _Cryptpad, _Crypto, _Cryptget, _SFrameChannel, - _SecureIframe, _Messaging, _Notifier, _Hash, _Util, _Realtime, _Notify, + _SecureIframe, _OOIframe, _Messaging, _Notifier, _Hash, _Util, _Realtime, _Notify, _Constants, _Feedback, _LocalStore, _Cache, _AppConfig, _Test, _UserObject) { CpNfOuter = _CpNfOuter; Cryptpad = _Cryptpad; @@ -120,6 +122,7 @@ define([ Cryptget = _Cryptget; SFrameChannel = _SFrameChannel; SecureIframe = _SecureIframe; + OOIframe = _OOIframe; Messaging = _Messaging; Notifier = _Notifier; Utils.Hash = _Hash; @@ -571,6 +574,7 @@ define([ var edPublic, curvePublic, notifications, isTemplate; var settings = {}; var isSafe = ['debug', 'profile', 'drive', 'teams', 'calendar', 'file'].indexOf(currentPad.app) !== -1; + var ooDownloadData = {}; var isDeleted = isNewFile && currentPad.hash.length > 0; if (isDeleted) { @@ -1031,6 +1035,51 @@ define([ } }, cb); }); + sframeChan.on('Q_GET_HISTORY_RANGE', function (data, cb) { + var nSecret = secret; + if (cfg.isDrive) { + // Shared folder or user hash or fs hash + var hash = Utils.LocalStore.getUserHash() || Utils.LocalStore.getFSHash(); + if (data.sharedFolder) { hash = data.sharedFolder.hash; } + if (hash) { + var password = (data.sharedFolder && data.sharedFolder.password) || undefined; + nSecret = Utils.Hash.getSecrets('drive', hash, password); + } + } + if (data.href) { + var _parsed = Utils.Hash.parsePadUrl(data.href); + nSecret = Utils.Hash.getSecrets(_parsed.type, _parsed.hash, data.password); + } + if (data.isDownload && ooDownloadData[data.isDownload]) { + var ooData = ooDownloadData[data.isDownload]; + delete ooDownloadData[data.isDownload]; + nSecret = Utils.Hash.getSecrets('sheet', ooData.hash, ooData.password); + } + var channel = nSecret.channel; + var validate = nSecret.keys.validateKey; + var crypto = Crypto.createEncryptor(nSecret.keys); + Cryptpad.getHistoryRange({ + channel: data.channel || channel, + validateKey: validate, + toHash: data.toHash, + lastKnownHash: data.lastKnownHash + }, function (data) { + cb({ + isFull: data.isFull, + messages: data.messages.map(function (obj) { + // The 3rd parameter "true" means we're going to skip signature validation. + // We don't need it since the message is already validated serverside by hk + return { + msg: crypto.decrypt(obj.msg, true, true), + serverHash: obj.serverHash, + author: obj.author, + time: obj.time + }; + }), + lastKnownHash: data.lastKnownHash + }); + }); + }); }; addCommonRpc(sframeChan, isSafe); @@ -1272,46 +1321,6 @@ define([ }); }); }); - sframeChan.on('Q_GET_HISTORY_RANGE', function (data, cb) { - var nSecret = secret; - if (cfg.isDrive) { - // Shared folder or user hash or fs hash - var hash = Utils.LocalStore.getUserHash() || Utils.LocalStore.getFSHash(); - if (data.sharedFolder) { hash = data.sharedFolder.hash; } - if (hash) { - var password = (data.sharedFolder && data.sharedFolder.password) || undefined; - nSecret = Utils.Hash.getSecrets('drive', hash, password); - } - } - if (data.href) { - var _parsed = Utils.Hash.parsePadUrl(data.href); - nSecret = Utils.Hash.getSecrets(_parsed.type, _parsed.hash, data.password); - } - var channel = nSecret.channel; - var validate = nSecret.keys.validateKey; - var crypto = Crypto.createEncryptor(nSecret.keys); - Cryptpad.getHistoryRange({ - channel: data.channel || channel, - validateKey: validate, - toHash: data.toHash, - lastKnownHash: data.lastKnownHash - }, function (data) { - cb({ - isFull: data.isFull, - messages: data.messages.map(function (obj) { - // The 3rd parameter "true" means we're going to skip signature validation. - // We don't need it since the message is already validated serverside by hk - return { - msg: crypto.decrypt(obj.msg, true, true), - serverHash: obj.serverHash, - author: obj.author, - time: obj.time - }; - }), - lastKnownHash: data.lastKnownHash - }); - }); - }); // Store sframeChan.on('Q_DRIVE_GETDELETED', function (data, cb) { @@ -1461,6 +1470,38 @@ define([ initSecureModal('share', data || {}); }); + // OO iframe + var OOIframeObject = {}; + var initOOIframe = function (cfg, cb) { + if (!OOIframeObject.$iframe) { + var config = {}; + config.addCommonRpc = addCommonRpc; + config.modules = { + Cryptpad: Cryptpad, + SFrameChannel: SFrameChannel, + Utils: Utils + }; + OOIframeObject.$iframe = $('