Merge branch 'exportSheet' into exportForm

pull/1/head
yflory 3 years ago
commit 3f1480a847

@ -575,6 +575,26 @@
return false;
};
// Tell if a file is spreadsheet from its metadata={title, fileType}
Util.isSpreadsheet = function (type, name) {
return (type &&
(type === 'application/vnd.oasis.opendocument.spreadsheet' ||
type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'))
|| (name && (name.endsWith('.xlsx') || name.endsWith('.ods')));
};
Util.isOfficeDoc = function (type, name) {
return (type &&
(type === 'application/vnd.oasis.opendocument.text' ||
type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'))
|| (name && (name.endsWith('.docx') || name.endsWith('.odt')));
};
Util.isPresentation = function (type, name) {
return (type &&
(type === 'application/vnd.oasis.opendocument.presentation' ||
type === 'application/vnd.openxmlformats-officedocument.presentationml.presentation'))
|| (name && (name.endsWith('.pptx') || name.endsWith('.odp')));
};
Util.isValidURL = function (str) {
var pattern = new RegExp('^(https?:\\/\\/)'+ // protocol
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name
@ -618,6 +638,32 @@
getColor().toString(16);
};
/* Chrome 92 dropped support for SharedArrayBuffer in cross-origin contexts
where window.crossOriginIsolated is false.
Their blog (https://blog.chromium.org/2021/02/restriction-on-sharedarraybuffers.html)
isn't clear about why they're doing this, but since it's related to site-isolation
it seems they're trying to do vague security things.
In any case, there seems to be a workaround where you can still create them
by using `new WebAssembly.Memory({shared: true, ...})` instead of `new SharedArrayBuffer`.
This seems unreliable, but it's better than not being able to export, since
we actively rely on postMessage between iframes and therefore can't afford
to opt for full isolation.
*/
var supportsSharedArrayBuffers = function () {
try {
return Object.prototype.toString.call(new window.WebAssembly.Memory({shared: true, initial: 0, maximum: 0}).buffer) === '[object SharedArrayBuffer]';
} catch (err) {
console.error(err);
}
return false;
};
Util.supportsWasm = function () {
return !(typeof(Atomics) === "undefined" || !supportsSharedArrayBuffers() || typeof(WebAssembly) === 'undefined');
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = Util;
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {

@ -89,7 +89,10 @@ define([
var faShared = 'fa-shhare-alt';
var faReadOnly = 'fa-eye';
var faPreview = 'fa-eye';
var faOpenInCode = 'cptools-code';
var faOpenInCode = AppConfig.applicationsIcon.code;
var faOpenInSheet = AppConfig.applicationsIcon.sheet;
var faOpenInDoc = AppConfig.applicationsIcon.doc;
var faOpenInPresentation = AppConfig.applicationsIcon.presentation;
var faRename = 'fa-pencil';
var faColor = 'cptools-palette';
var faTrash = 'fa-trash';
@ -332,6 +335,9 @@ define([
};
Messages.fc_openInSheet = "Edit in Sheet"; // XXX
Messages.fc_openInDoc = "Edit in Document"; // XXX
Messages.fc_openInPresentation = "Edit in Presentation"; // XXX
var createContextMenu = function () {
var menu = h('div.cp-contextmenu.dropdown.cp-unselectable', [
h('ul.dropdown-menu', {
@ -356,6 +362,18 @@ define([
'tabindex': '-1',
'data-icon': faOpenInCode,
}, Messages.fc_openInCode)),
h('li', h('a.cp-app-drive-context-openinsheet.dropdown-item', {
'tabindex': '-1',
'data-icon': faOpenInSheet,
}, Messages.fc_openInSheet)),
h('li', h('a.cp-app-drive-context-openindoc.dropdown-item', {
'tabindex': '-1',
'data-icon': faOpenInDoc,
}, Messages.fc_openInDoc)),
h('li', h('a.cp-app-drive-context-openinpresentation.dropdown-item', {
'tabindex': '-1',
'data-icon': faOpenInPresentation,
}, Messages.fc_openInPresentation)),
h('li', h('a.cp-app-drive-context-savelocal.dropdown-item', {
'tabindex': '-1',
'data-icon': 'fa-cloud-upload',
@ -1275,7 +1293,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');
@ -1293,6 +1311,18 @@ define([
if (!metadata || !Util.isPlainTextFile(metadata.fileType, metadata.title)) {
hide.push('openincode');
}
if (!metadata || !Util.isSpreadsheet(metadata.fileType, metadata.title)
|| !priv.supportsWasm) {
hide.push('openinsheet');
}
if (!metadata || !Util.isOfficeDoc(metadata.fileType, metadata.title)
|| !priv.supportsWasm) {
hide.push('openindoc');
}
if (!metadata || !Util.isPresentation(metadata.fileType, metadata.title)
|| !priv.supportsWasm) {
hide.push('openinpresentation');
}
if (metadata.channel && metadata.channel.length < 48) {
hide.push('preview');
}
@ -1309,6 +1339,9 @@ define([
containsFolder = true;
hide.push('openro');
hide.push('openincode');
hide.push('openinsheet');
hide.push('openindoc');
hide.push('openinpresentation');
hide.push('hashtag');
//hide.push('delete');
hide.push('makeacopy');
@ -1324,6 +1357,9 @@ define([
hide.push('savelocal');
hide.push('openro');
hide.push('openincode');
hide.push('openinsheet');
hide.push('openindoc');
hide.push('openinpresentation');
hide.push('properties', 'access');
hide.push('hashtag');
hide.push('makeacopy');
@ -1355,7 +1391,8 @@ define([
hide.push('download');
hide.push('share');
hide.push('savelocal');
hide.push('openincode'); // can't because of race condition
//hide.push('openincode'); // can't because of race condition
//hide.push('openinsheet'); // can't because of race condition
hide.push('makeacopy');
hide.push('preview');
}
@ -1367,6 +1404,9 @@ define([
if (!APP.loggedIn) {
hide.push('openparent');
hide.push('rename');
hide.push('openinsheet');
hide.push('openindoc');
hide.push('openinpresentation');
}
filter = function ($el, className) {
@ -1380,11 +1420,12 @@ define([
break;
case 'tree':
show = ['open', 'openro', 'preview', 'openincode', 'expandall', 'collapseall',
'color', 'download', 'share', 'savelocal', 'rename', 'delete', 'makeacopy',
'color', 'download', 'share', 'savelocal', 'rename', 'delete',
'makeacopy', 'openinsheet', 'openindoc', 'openinpresentation',
'deleteowned', 'removesf', 'access', 'properties', 'hashtag'];
break;
case 'default':
show = ['open', 'openro', 'preview', 'openincode', 'share', 'download', 'openparent', 'delete', 'deleteowned', 'properties', 'access', 'hashtag', 'makeacopy', 'savelocal', 'rename'];
show = ['open', 'openro', 'preview', 'openincode', 'openinsheet', 'openindoc', 'openinpresentation', 'share', 'download', 'openparent', 'delete', 'deleteowned', 'properties', 'access', 'hashtag', 'makeacopy', 'savelocal', 'rename'];
break;
case 'trashtree': {
show = ['empty'];
@ -4349,6 +4390,21 @@ define([
});
};
var openInApp = function (paths, app) {
var p = paths[0];
var el = manager.find(p.path);
var path = currentPath;
if (path[0] !== ROOT) { path = [ROOT]; }
var _metadata = manager.getFileData(el);
var _simpleData = {
title: _metadata.filename || _metadata.title,
href: _metadata.href || _metadata.roHref,
fileType: _metadata.fileType,
password: _metadata.password,
channel: _metadata.channel,
};
openIn(app, path, APP.team, _simpleData);
};
$contextMenu.on("click", "a", function(e) {
e.stopPropagation();
@ -4436,20 +4492,19 @@ define([
}
else if ($this.hasClass('cp-app-drive-context-openincode')) {
if (paths.length !== 1) { return; }
var p = paths[0];
el = manager.find(p.path);
(function () {
var path = currentPath;
if (path[0] !== ROOT) { path = [ROOT]; }
var _metadata = manager.getFileData(el);
var _simpleData = {
title: _metadata.filename || _metadata.title,
href: _metadata.href || _metadata.roHref,
password: _metadata.password,
channel: _metadata.channel,
};
openIn('code', path, APP.team, _simpleData);
})();
openInApp(paths, 'code');
}
else if ($this.hasClass('cp-app-drive-context-openinsheet')) {
if (paths.length !== 1) { return; }
openInApp(paths, 'sheet');
}
else if ($this.hasClass('cp-app-drive-context-openindoc')) {
if (paths.length !== 1) { return; }
openInApp(paths, 'doc');
}
else if ($this.hasClass('cp-app-drive-context-openinpresentation')) {
if (paths.length !== 1) { return; }
openInApp(paths, 'presentation');
}
else if ($this.hasClass('cp-app-drive-context-expandall') ||
$this.hasClass('cp-app-drive-context-collapseall')) {

@ -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',
@ -41,11 +41,11 @@ define([
}
var path = '/' + type + '/export.js';
require([path], function (Exporter) {
Exporter.main(json, function (data) {
result.ext = Exporter.ext || '';
Exporter.main(json, function (data, _ext) {
result.ext = _ext || Exporter.ext || '';
result.data = data;
cb(result);
});
}, null, ctx.sframeChan, padData);
}, function () {
cb(result);
});
@ -117,12 +117,16 @@ 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);
if (data.progress === 1) {
handler.stop();
updateProgress.progress2(1);
updateProgress.progress2(2);
}
});
ctx.get({
@ -136,14 +140,15 @@ 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 || ''));
};
updateProgress.progress2(1);
cb(null, {
metadata: res.metadata,
content: res.data,
download: dl
});
});
}, padData);
});
return {
cancel: cancel
@ -195,9 +200,16 @@ define([
});
};
var timeout = 60000;
// OO pads can only be converted one at a time so we have to give them a
// bigger timeout value in case there are 5 of them in the current queue
if (['sheet', 'doc', 'presentation'].indexOf(parsed.type) !== -1) {
timeout = 180000;
}
to = setTimeout(function () {
error('TIMEOUT');
}, 60000);
}, timeout);
setTimeout(function () {
if (ctx.stop) { return; }
@ -228,6 +240,9 @@ define([
zip.file(fileName, res.data, opts);
console.log('DONE ---- ' + fileName);
setTimeout(done, 500);
}, {
hash: parsed.hash,
password: fData.password
});
});
};
@ -292,7 +307,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 +322,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 +374,7 @@ define([
else if (state === "done") {
updateProgress.folderProgress(3);
}
}, ctx.cache);
}, ctx.cache, ctx.sframeChan);
};
var createExportUI = function (origin) {

@ -119,7 +119,7 @@ define([
// The first "cp" in history is the empty doc. It doesn't include the first patch
// of the history
var initialCp = cpIndex === sortedCp.length;
var initialCp = cpIndex === sortedCp.length || !cp.hash;
var messages = (data.messages || []).slice(initialCp ? 0 : 1);

@ -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) {
@ -72,34 +74,6 @@ define([
return JSONSortify(obj);
};
/* Chrome 92 dropped support for SharedArrayBuffer in cross-origin contexts
where window.crossOriginIsolated is false.
Their blog (https://blog.chromium.org/2021/02/restriction-on-sharedarraybuffers.html)
isn't clear about why they're doing this, but since it's related to site-isolation
it seems they're trying to do vague security things.
In any case, there seems to be a workaround where you can still create them
by using `new WebAssembly.Memory({shared: true, ...})` instead of `new SharedArrayBuffer`.
This seems unreliable, but it's better than not being able to export, since
we actively rely on postMessage between iframes and therefore can't afford
to opt for full isolation.
*/
var supportsSharedArrayBuffers = function () {
try {
return Object.prototype.toString.call(new window.WebAssembly.Memory({shared: true, initial: 0, maximum: 0}).buffer) === '[object SharedArrayBuffer]';
} catch (err) {
console.error(err);
}
return false;
};
var supportsXLSX = function () {
return !(typeof(Atomics) === "undefined" || !supportsSharedArrayBuffers() /* || typeof (SharedArrayBuffer) === "undefined" */ || typeof(WebAssembly) === 'undefined');
};
var toolbar;
var cursor;
@ -136,6 +110,10 @@ define([
var startOO = function () {};
var supportsXLSX = function () {
return privateData.supportsWasm;
};
var getMediasSources = APP.getMediasSources = function() {
content.mediasSources = content.mediasSources || {};
return content.mediasSources;
@ -256,6 +234,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':
@ -499,10 +481,10 @@ define([
startOO(blob, type, true);
};
var saveToServer = function () {
var saveToServer = function (blob, title) {
if (APP.cantCheckpoint) { return; } // TOO_LARGE
var text = getContent();
if (!text) {
if (!text && !blob) {
setEditable(false, true);
sframeChan.query('Q_CLEAR_CACHE_CHANNELS', [
'chainpad',
@ -513,9 +495,9 @@ define([
});
return;
}
var blob = new Blob([text], {type: 'plain/text'});
blob = blob || new Blob([text], {type: 'plain/text'});
var file = getFileType();
blob.name = (metadataMgr.getMetadataLazy().title || file.doc) + '.' + file.type;
blob.name = title || (metadataMgr.getMetadataLazy().title || file.doc) + '.' + file.type;
var data = {
hash: (APP.history || APP.template) ? ooChannel.historyLastHash : ooChannel.lastHash,
index: (APP.history || APP.template) ? ooChannel.currentIndex : ooChannel.cpIndex
@ -729,6 +711,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';
@ -737,6 +720,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)) {
@ -746,7 +730,7 @@ define([
// The first "cp" in history is the empty doc. It doesn't include the first patch
// of the history
var initialCp = major === 0;
var initialCp = major === 0 || !cp.hash;
var messages = (data.messages || []).slice(initialCp ? 0 : 1, minor);
messages.forEach(function (obj) {
@ -788,6 +772,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);
@ -1422,6 +1407,39 @@ define([
});
};
var x2tConvertData = function (data, fileName, format, cb) {
var sframeChan = common.getSframeChannel();
var e = getEditor();
var fonts = e && e.FontLoader.fontInfos;
var files = e && 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: (e && 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
});
};
// When download a sheet from the drive, we must wait for all the images
// to be downloaded and decrypted before converting to xlsx
var downloadImages = {};
startOO = function (blob, file, force) {
if (APP.ooconfig && !force) { return void console.error('already started'); }
var url = URL.createObjectURL(blob);
@ -1502,6 +1520,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();
@ -1581,6 +1606,25 @@ define([
}
}
if (APP.isDownload) {
var bin = getContent();
if (!supportsXLSX()) {
return void sframeChan.event('EV_OOIFRAME_DONE', bin, {raw: true});
}
nThen(function (waitFor) {
// wait for all the images to be loaded before converting
Object.keys(downloadImages).forEach(function (name) {
downloadImages[name].reg(waitFor());
});
}).nThen(function () {
x2tConvertData(bin, 'filename.bin', file.type, function (xlsData) {
sframeChan.event('EV_OOIFRAME_DONE', xlsData, {raw: true});
});
});
return;
}
if (isLockedModal.modal && force) {
isLockedModal.modal.closeModal();
delete isLockedModal.modal;
@ -1720,6 +1764,7 @@ define([
var mediasSources = getMediasSources();
var data = mediasSources[name];
downloadImages[name] = Util.mkEvent(true);
if (typeof data === 'undefined') {
debug("CryptPad - could not find matching media for " + name);
@ -1757,6 +1802,7 @@ define([
reader.onloadend = function () {
debug("MediaData set");
mediaData.content = reader.result;
downloadImages[name].fire();
};
reader.readAsArrayBuffer(res.content);
debug("Adding CryptPad Image " + data.name + ": " + blobUrl);
@ -1778,239 +1824,52 @@ define([
makeChannel();
};
var x2tReady = Util.mkEvent(true);
var fetchFonts = function (x2t) {
var path = '/common/onlyoffice/'+CURRENT_VERSION+'/fonts/';
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 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 '<m_nFormatFrom>'+id+'</m_nFormatFrom>';
};
var getToId = function (ext) {
var id = getFormatId(ext);
if (!id) { return ''; }
return '<m_nFormatTo>'+id+'</m_nFormatTo>';
};
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 = "<m_bIsNoBase64>false</m_bIsNoBase64>" +
"<m_sFontDir>/working/fonts/</m_sFontDir>";
// 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 inputFormat = fileName.split('.').pop();
var params = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+ "<TaskQueueDataConvert xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">"
+ "<m_sFileFrom>/working/" + fileName + "</m_sFileFrom>"
+ "<m_sFileTo>/working/" + fileName + "." + outputFormat + "</m_sFileTo>"
+ pdfData
+ getFromId(inputFormat)
+ getToId(outputFormat)
+ "<m_bIsNoBase64>false</m_bIsNoBase64>"
+ "</TaskQueueDataConvert>";
// 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);
});
};
@ -2083,32 +1942,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,
@ -2134,14 +1990,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();
@ -2151,26 +2001,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);
});
});
};
@ -2224,10 +2060,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);
};
@ -2453,6 +2287,40 @@ 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;
downloadImages = {};
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)
@ -2766,6 +2634,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)) {
@ -2853,9 +2722,99 @@ define([
return;
}
loadDocument(newDoc, useNewDefault);
setEditable(!readOnly);
UI.removeLoadingScreen();
var next = function () {
loadDocument(newDoc, useNewDefault);
setEditable(!readOnly);
UI.removeLoadingScreen();
};
if (privateData.isNewFile && privateData.fromFileData) {
try {
(function () {
var data = privateData.fromFileData;
var type = data.fileType;
var title = data.title;
// Fix extension if the file was renamed
if (Util.isSpreadsheet(type) && !Util.isSpreadsheet(data.title)) {
if (type === 'application/vnd.oasis.opendocument.spreadsheet') {
data.title += '.ods';
}
if (type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') {
data.title += '.xlsx';
}
}
if (Util.isOfficeDoc(type) && !Util.isOfficeDoc(data.title)) {
if (type === 'application/vnd.oasis.opendocument.text') {
data.title += '.odt';
}
if (type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') {
data.title += '.docx';
}
}
if (Util.isPresentation(type) && !Util.isPresentation(data.title)) {
if (type === 'application/vnd.oasis.opendocument.presentation') {
data.title += '.odp';
}
if (type === 'application/vnd.openxmlformats-officedocument.presentationml.presentation') {
data.title += '.pptx';
}
}
var href = data.href;
var password = data.password;
var parsed = Hash.parsePadUrl(href);
var secret = Hash.getSecrets('file', parsed.hash, password);
var hexFileName = secret.channel;
var fileHost = privateData.fileHost || privateData.origin;
var src = fileHost + Hash.getBlobPathFromHex(hexFileName);
var key = secret.keys && secret.keys.cryptKey;
var xhr = new XMLHttpRequest();
xhr.open('GET', src, true);
xhr.responseType = 'arraybuffer';
xhr.onload = function () {
if (/^4/.test('' + this.status)) {
// fallback to empty sheet
console.error(this.status);
return void next();
}
var arrayBuffer = xhr.response;
if (arrayBuffer) {
var u8 = new Uint8Array(arrayBuffer);
FileCrypto.decrypt(u8, key, function (err, decrypted) {
if (err) {
// fallback to empty sheet
console.error(err);
return void next();
}
var blobXlsx = decrypted.content;
new Response(blobXlsx).arrayBuffer().then(function (buffer) {
var u8Xlsx = new Uint8Array(buffer);
x2tImportData(u8Xlsx, data.title, 'bin', function (bin) {
var blob = new Blob([bin], {type: 'text/plain'});
saveToServer(blob, data.title);
Title.updateTitle(title);
UI.removeLoadingScreen();
});
});
});
}
};
xhr.onerror = function (err) {
// fallback to empty sheet
console.error(err);
next();
};
xhr.send(null);
})();
} catch (e) {
console.error(e);
next();
}
return;
}
next();
});
};

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

@ -0,0 +1,176 @@
// 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,
supportsWasm: Utils.Util.supportsWasm()
};
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
};
});

@ -0,0 +1,245 @@
define([
'/api/config',
'/bower_components/nthen/index.js',
'/common/common-util.js',
], function (ApiConfig, 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 = ApiConfig.httpSafeOrigin + '/common/onlyoffice/'+CURRENT_VERSION+'/fonts/';
var ver = '?' + ApiConfig.requireConf.urlArgs;
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 + ver, 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 '<m_nFormatFrom>'+id+'</m_nFormatFrom>';
};
var getToId = function (ext) {
var id = getFormatId(ext);
if (!id) { return ''; }
return '<m_nFormatTo>'+id+'</m_nFormatTo>';
};
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 = "<m_bIsNoBase64>false</m_bIsNoBase64>" +
"<m_sFontDir>/working/fonts/</m_sFontDir>";
// 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 = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+ "<TaskQueueDataConvert xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">"
+ "<m_sFileFrom>/working/" + fileName + "</m_sFileFrom>"
+ "<m_sFileTo>/working/" + fileName + "." + outputFormat + "</m_sFileTo>"
+ pdfData
+ getFromId(inputFormat)
+ getToId(outputFormat)
+ "<m_bIsNoBase64>false</m_bIsNoBase64>"
+ "</TaskQueueDataConvert>";
// 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;
});

@ -660,6 +660,11 @@ define([
var updateDecryptProgress = function (progressValue) {
var text = Math.round(progressValue * 100) + '%';
text += progressValue === 1 ? '' : ' (' + Messages.download_step2 + '...)';
if (progressValue === 2) {
Messages.download_step3 = "Converting..."; // XXX
text = Messages.download_step3;
progressValue = 1;
}
$pv.text(text);
$pb.css({
width: (progressValue * 100) + '%'

@ -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,8 @@ define([
var edPublic, curvePublic, notifications, isTemplate;
var settings = {};
var isSafe = ['debug', 'profile', 'drive', 'teams', 'calendar', 'file'].indexOf(currentPad.app) !== -1;
var isOO = ['sheet', 'doc', 'presentation'].indexOf(parsed.type) !== -1;
var ooDownloadData = {};
var isDeleted = isNewFile && currentPad.hash.length > 0;
if (isDeleted) {
@ -631,11 +636,12 @@ define([
channel: secret.channel,
enableSF: localStorage.CryptPad_SF === "1", // TODO to remove when enabled by default
devMode: localStorage.CryptPad_dev === "1",
fromFileData: Cryptpad.fromFileData ? {
fromFileData: Cryptpad.fromFileData ? (isOO ? Cryptpad.fromFileData : {
title: Cryptpad.fromFileData.title
} : undefined,
}) : undefined,
burnAfterReading: burnAfterReading,
storeInTeam: Cryptpad.initialTeam || (Cryptpad.initialPath ? -1 : undefined)
storeInTeam: Cryptpad.initialTeam || (Cryptpad.initialPath ? -1 : undefined),
supportsWasm: Utils.Util.supportsWasm()
};
if (window.CryptPad_newSharedFolder) {
additionalPriv.newSharedFolder = window.CryptPad_newSharedFolder;
@ -1032,6 +1038,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);
@ -1273,46 +1324,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) {
@ -1462,6 +1473,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 = $('<iframe>', {id: 'sbox-oo-iframe'}).appendTo($('body')).hide();
OOIframeObject.modal = OOIframe.create(config);
}
OOIframeObject.modal.refresh(cfg, function (data) {
cb(data);
});
};
sframeChan.on('Q_OOIFRAME_OPEN', function (data, cb) {
if (!data) { return void cb(); }
// Extract unsafe data (href and password) before sending it to onlyoffice
var padData = data.padData;
delete data.padData;
var uid = Utils.Util.uid();
ooDownloadData[uid] = padData;
data.downloadId = uid;
initOOIframe(data || {}, cb);
});
sframeChan.on('Q_TEMPLATE_USE', function (data, cb) {
Cryptpad.useTemplate(data, Cryptget, cb);
});
@ -1991,7 +2034,7 @@ define([
return;
}
// if we open a new code from a file
if (Cryptpad.fromFileData) {
if (Cryptpad.fromFileData && !isOO) {
Cryptpad.useFile(Cryptget, function (err) {
if (err) {
// TODO: better messages in case of expired, deleted, etc.?

@ -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 '<m_nFormatFrom>'+id+'</m_nFormatFrom>';
};
var getToId = function (ext) {
var id = getFormatId(ext);
if (!id) { return ''; }
return '<m_nFormatTo>'+id+'</m_nFormatTo>';
};
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 = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+ "<TaskQueueDataConvert xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">"
+ "<m_sFileFrom>/working/" + fileName + "</m_sFileFrom>"
+ "<m_sFileTo>/working/" + fileName + "." + outputFormat + "</m_sFileTo>"
+ getFromId(inputFormat)
+ getToId(outputFormat)
+ "<m_bIsNoBase64>false</m_bIsNoBase64>"
+ "</TaskQueueDataConvert>";
// 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
});
};
};

@ -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
});
});

@ -1016,7 +1016,7 @@ define([
Feedback.send('FULL_DRIVE_EXPORT_COMPLETE');
saveAs(blob, filename);
}, errors);
}, ui.update, common.getCache());
}, ui.update, common.getCache(), common.getSframeChannel());
ui.onCancel(function() {
ui.close();
bu.stop();

@ -0,0 +1,26 @@
define([], function () {
var module = {
ext: '.xlsx', // default
exts: ['.xlsx']
};
module.main = function (userDoc, cb, ext, sframeChan, padData) {
sframeChan.query('Q_OOIFRAME_OPEN', {
json: userDoc,
type: 'sheet',
padData: padData
}, function (err, u8) {
if (!u8) { return void cb(''); }
var ext;
if (typeof(u8) === "string") { ext = '.bin'; } // x2t not supported
var blob = new Blob([u8], {type: "application/bin;charset=utf-8"});
cb(blob, ext);
}, {
timeout: 600000,
raw: true
});
};
return module;
});

@ -1137,7 +1137,7 @@ define([
Feedback.send('FULL_TEAMDRIVE_EXPORT_COMPLETE');
saveAs(blob, filename);
}, errors);
}, ui.update, common.getCache);
}, ui.update, common.getCache, common.getSframeChannel());
ui.onCancel(function() {
ui.close();
bu.stop();

Loading…
Cancel
Save