Compare commits

..

7 Commits

15 changed files with 334 additions and 274 deletions

3
.gitmodules vendored
View File

@ -2,6 +2,9 @@
path = web/documentserver-example/nodejs/public/assets/document-templates
url = https://github.com/ONLYOFFICE/document-templates
branch = main/default
[submodule "web/documentserver-example/nodejs/public/assets/document-formats"]
path = web/documentserver-example/nodejs/public/assets/document-formats
url = https://github.com/ONLYOFFICE/document-formats
[submodule "web/documentserver-example/nodejs/public/assets/plugin-aiautofill"]
path = web/documentserver-example/nodejs/public/assets/plugin-aiautofill
url = https://github.com/ONLYOFFICE/plugin-aiautofill.git

View File

@ -1,6 +1,5 @@
# Change Log
- nodejs: use formats, languages, maxFileSize, urls to services, jwt header and prefix from document server meta
- nodejs: wopi CopyPasteRestrictions for anonymous
- update insertImage formats
- new mobile index page view

View File

@ -29,7 +29,6 @@ const config = require('config');
const mime = require('mime');
const urllib = require('urllib');
const urlModule = require('url');
const { emitWarning } = require('process');
const DocManager = require('./helpers/docManager');
const documentService = require('./helpers/documentService');
const fileUtility = require('./helpers/fileUtility');
@ -42,6 +41,8 @@ const siteUrl = configServer.get('siteUrl');
const enableForgotten = configServer.get('enableForgotten');
const cfgSignatureEnable = configServer.get('token.enable');
const cfgSignatureUseForRequest = configServer.get('token.useforrequest');
const cfgSignatureAuthorizationHeader = configServer.get('token.authorizationHeader');
const cfgSignatureAuthorizationHeaderPrefix = configServer.get('token.authorizationHeaderPrefix');
const cfgSignatureSecretExpiresIn = configServer.get('token.expiresIn');
const cfgSignatureSecret = configServer.get('token.secret');
const verifyPeerOff = configServer.get('verify_peer_off');
@ -87,16 +88,16 @@ app.use(favicon(`${__dirname}/public/images/favicon.ico`)); // use favicon
app.use(bodyParser.json()); // connect middleware that parses json
app.use(bodyParser.urlencoded({ extended: false })); // connect middleware that parses urlencoded bodies
app.get('/', async (req, res) => { // define a handler for default page
app.get('/', (req, res) => { // define a handler for default page
try {
req.DocManager = new DocManager(req, res);
res.render('index', { // render index template with the parameters specified
preloaderUrl: siteUrl + configServer.get('preloaderUrl'),
storedFiles: await req.DocManager.getStoredFiles(),
storedFiles: req.DocManager.getStoredFiles(),
params: req.DocManager.getCustomParams(),
users,
languages: (await documentService.config()).langObject,
languages: configServer.get('languages'),
serverVersion: config.get('version'),
enableForgotten,
});
@ -133,14 +134,14 @@ app.get('/forgotten', async (req, res) => {
function getForgottenFile(key) {
return new Promise((resolve, reject) => {
documentService.commandRequest('getForgotten', key, async (err, data) => {
documentService.commandRequest('getForgotten', key, (err, data) => {
if (err) {
reject(err);
} else {
const parsedData = JSON.parse(data);
resolve({
name: parsedData.key,
documentType: await fileUtility.getFileType(parsedData.url),
documentType: fileUtility.getFileType(parsedData.url),
url: parsedData.url,
});
}
@ -181,7 +182,7 @@ app.delete('/forgotten', (req, res) => { // define a handler for removing forgot
}
});
app.get('/download', async (req, res) => { // define a handler for downloading files
app.get('/download', (req, res) => { // define a handler for downloading files
req.DocManager = new DocManager(req, res);
const fileName = fileUtility.getFileName(req.query.fileName);
@ -190,10 +191,9 @@ app.get('/download', async (req, res) => { // define a handler for downloading f
if (!!userAddress
&& cfgSignatureEnable && cfgSignatureUseForRequest) {
const authorization = req.get((await documentService.config()).authorization.header);
const authorizationPrefix = (await documentService.config()).authorization.prefix;
if (authorization && authorization.startsWith(authorizationPrefix)) {
token = authorization.substring(authorizationPrefix.length);
const authorization = req.get(cfgSignatureAuthorizationHeader);
if (authorization && authorization.startsWith(cfgSignatureAuthorizationHeaderPrefix)) {
token = authorization.substring(cfgSignatureAuthorizationHeaderPrefix.length);
}
try {
@ -233,13 +233,12 @@ app.get('/data', (req, res) => { // define a handler for getting sample ai form
});
});
app.get('/history', async (req, res) => {
app.get('/history', (req, res) => {
req.DocManager = new DocManager(req, res);
if (cfgSignatureEnable && cfgSignatureUseForRequest) {
const authorization = req.get((await documentService.config()).authorization.header);
const authorizationPrefix = (await documentService.config()).authorization.prefix;
if (authorization && authorization.startsWith(authorizationPrefix)) {
const token = authorization.substring(authorizationPrefix.length);
const authorization = req.get(cfgSignatureAuthorizationHeader);
if (authorization && authorization.startsWith(cfgSignatureAuthorizationHeaderPrefix)) {
const token = authorization.substring(cfgSignatureAuthorizationHeaderPrefix.length);
try {
jwt.verify(token, cfgSignatureSecret);
} catch (err) {
@ -277,7 +276,7 @@ app.get('/history', async (req, res) => {
filestream.pipe(res); // send file information to the response by streams
});
app.post('/upload', async (req, res) => { // define a handler for uploading files
app.post('/upload', (req, res) => { // define a handler for uploading files
req.DocManager = new DocManager(req, res);
req.DocManager.storagePath(''); // mkdir if not exist
@ -286,13 +285,13 @@ app.post('/upload', async (req, res) => { // define a handler for uploading file
const uploadDirTmp = path.join(uploadDir, 'tmp'); // and create directory for temporary files if it doesn't exist
req.DocManager.createDirectory(uploadDirTmp);
const fileSizeLimit = (await documentService.config()).limits.maxFileSize;
const fileSizeLimit = configServer.get('maxFileSize');
// create a new incoming form
const form = new formidable.IncomingForm({ maxFileSize: fileSizeLimit, maxTotalFileSize: fileSizeLimit });
form.uploadDir = uploadDirTmp; // and write there all the necessary parameters
form.keepExtensions = true;
form.parse(req, async (err, fields, files) => { // parse this form
form.parse(req, (err, fields, files) => { // parse this form
if (err) { // if an error occurs
// DocManager.cleanFolderRecursive(uploadDirTmp, true); // clean the folder with temporary files
res.writeHead(200, { 'Content-Type': 'application/json' });
@ -321,11 +320,11 @@ app.post('/upload', async (req, res) => { // define a handler for uploading file
return;
}
const exts = await fileUtility.getSuppotredExtensions(); // all the supported file extensions
const exts = fileUtility.getSuppotredExtensions(); // all the supported file extensions
const curExt = fileUtility.getFileExtension(file.originalFilename, true);
const documentType = await fileUtility.getFileType(file.originalFilename);
const documentType = fileUtility.getFileType(file.originalFilename);
if (exts.indexOf(curExt) === -1 || (await fileUtility.getFormatActions(curExt)).length === 0) {
if (exts.indexOf(curExt) === -1 || fileUtility.getFormatActions(curExt).length === 0) {
// DocManager.cleanFolderRecursive(uploadDirTmp, true); // if not, clean the folder with temporary files
res.writeHead(200, { 'Content-Type': 'application/json' });
res.write('{ "error": "File type is not supported" }');
@ -376,19 +375,19 @@ app.post('/create', (req, res) => {
const userAddress = req.DocManager.curUserHostAddress();
req.DocManager.historyPath(fileName, userAddress, true);
urllib.request(fileUrl, { method: 'GET' }, async (err, data) => {
urllib.request(fileUrl, { method: 'GET' }, (err, data) => {
// check if the file size exceeds the maximum file size
if ((await documentService.config()).limits.maxFileSize < data.length || data.length <= 0) {
if (configServer.get('maxFileSize') < data.length || data.length <= 0) {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.write(JSON.stringify({ error: 'File size is incorrect' }));
res.end();
return;
}
const exts = await fileUtility.getSuppotredExtensions(); // all the supported file extensions
const exts = fileUtility.getSuppotredExtensions(); // all the supported file extensions
const curExt = fileUtility.getFileExtension(fileName, true);
if (exts.indexOf(curExt) === -1 || (await fileUtility.getFormatActions(curExt)).length === 0) {
if (exts.indexOf(curExt) === -1 || fileUtility.getFormatActions(curExt).length === 0) {
// and write the error status and message to the response
res.writeHead(200, { 'Content-Type': 'application/json' });
res.write(JSON.stringify({ error: 'File type is not supported' }));
@ -413,7 +412,7 @@ app.post('/create', (req, res) => {
}
});
app.post('/convert', async (req, res) => { // define a handler for converting files
app.post('/convert', (req, res) => { // define a handler for converting files
req.DocManager = new DocManager(req, res);
const fileName = fileUtility.getFileName(req.body.filename);
@ -471,7 +470,7 @@ app.post('/convert', async (req, res) => { // define a handler for converting fi
if (status !== 200) throw new Error(`Conversion service returned status: ${status}`);
// write a file with a new extension, but with the content from the origin file
if ((await fileUtility.getFileType(correctName)) !== null) {
if (fileUtility.getFileType(correctName) !== null) {
fileSystem.writeFileSync(req.DocManager.storagePath(correctName), data);
} else {
writeResult(newFileUri.replace('http://localhost/', siteUrl), result, 'FileTypeIsNotSupported');
@ -507,7 +506,7 @@ app.post('/convert', async (req, res) => { // define a handler for converting fi
try {
// check if the file with such an extension can be converted
if (((await fileUtility.getConvertExtensions()).indexOf(fileExt) !== -1) || ('fileExt' in req.body)) {
if ((fileUtility.getConvertExtensions().indexOf(fileExt) !== -1) || ('fileExt' in req.body)) {
const storagePath = req.DocManager.storagePath(fileName);
const stat = fileSystem.statSync(storagePath);
let key = fileUri + stat.mtime.getTime();
@ -525,11 +524,11 @@ app.post('/convert', async (req, res) => { // define a handler for converting fi
}
});
app.get('/files', async (req, res) => { // define a handler for getting files information
app.get('/files', (req, res) => { // define a handler for getting files information
try {
req.DocManager = new DocManager(req, res);
// get the information about the files from the storage path
const filesInDirectoryInfo = await req.DocManager.getFilesInfo();
const filesInDirectoryInfo = req.DocManager.getFilesInfo();
res.setHeader('Content-Type', 'application/json');
res.write(JSON.stringify(filesInDirectoryInfo)); // transform files information into the json string
} catch (ex) {
@ -540,12 +539,12 @@ app.get('/files', async (req, res) => { // define a handler for getting files in
res.end();
});
app.get('/files/file/:fileId', async (req, res) => { // define a handler for getting file information by its id
app.get('/files/file/:fileId', (req, res) => { // define a handler for getting file information by its id
try {
req.DocManager = new DocManager(req, res);
const { fileId } = req.params;
// get the information about the file specified by a file id
const fileInfoById = await req.DocManager.getFilesInfo(fileId);
const fileInfoById = req.DocManager.getFilesInfo(fileId);
res.setHeader('Content-Type', 'application/json');
res.write(JSON.stringify(fileInfoById));
} catch (ex) {
@ -721,12 +720,38 @@ app.put('/restore', async (req, res) => { // define a handler for restore file v
res.end();
});
app.post('/forcesave', async (req, res) => {
req.DocManager = new DocManager(req, res);
const uAddress = req.query.useraddress;
const fName = fileUtility.getFileName(req.query.filename);
if (req.headers['content-type'] === 'application/octet-stream') {
const form = new formidable.IncomingForm();
form.parse(req, async (err, fields, files) => {
if (err) {
res.write('{"error":1, "message":"document data is empty"}');
res.end();
return;
}
const filePath = files.file.filepath || files.file[0].filepath;
const data = fileSystem.readFileSync(filePath);
await req.DocManager.forcesaveFile(data, fName, fileUtility.getFileExtension(fName), uAddress);
fileSystem.unlinkSync(filePath);
res.write('{"error":0}');
res.end();
});
} else {
res.write('{"error":0}');
res.end();
}
});
app.post('/track', async (req, res) => { // define a handler for tracking file changes
req.DocManager = new DocManager(req, res);
let uAddress = req.query.useraddress;
let fName = fileUtility.getFileName(req.query.filename);
let version = 0;
// track file changes
const processTrack = async function processTrack(response, bodyTrack, fileNameTrack, userAddressTrack) {
@ -751,56 +776,7 @@ app.post('/track', async (req, res) => { // define a handler for tracking file c
if (status !== 200) throw new Error(`Document editing service returned status: ${status}`);
const storagePath = req.DocManager.storagePath(newFileName, userAddress);
let historyPath = req.DocManager.historyPath(newFileName, userAddress); // get the path to the history data
if (historyPath === '') { // if the history path doesn't exist
historyPath = req.DocManager.historyPath(newFileName, userAddress, true); // create it
req.DocManager.createDirectory(historyPath); // and create a directory for the history data
}
const countVersion = req.DocManager.countVersion(historyPath); // get the next file version number
version = countVersion + 1;
// get the path to the specified file version
const versionPath = req.DocManager.versionPath(newFileName, userAddress, version);
req.DocManager.createDirectory(versionPath); // create a directory to the specified file version
const downloadZip = body.changesurl;
if (downloadZip) {
// get the path to the file with document versions differences
const pathChanges = req.DocManager.diffPath(newFileName, userAddress, version);
const zip = await urllib.request(downloadZip, { method: 'GET' });
const statusZip = zip.status;
const dataZip = zip.data;
if (statusZip === 200) {
fileSystem.writeFileSync(pathChanges, dataZip); // write the document version differences to the archive
} else {
emitWarning(`Document editing service returned status: ${statusZip}`);
}
}
const changeshistory = JSON.stringify(body.history);
if (changeshistory) {
// get the path to the file with document changes
const pathChangesJson = req.DocManager.changesPath(newFileName, userAddress, version);
fileSystem.writeFileSync(pathChangesJson, changeshistory); // and write this data to the path in json format
}
const pathKey = req.DocManager.keyPath(newFileName, userAddress, version); // get the path to the key.txt file
fileSystem.writeFileSync(pathKey, body.key); // write the key value to the key.txt file
// get the path to the previous file version
const pathPrev = path.join(versionPath, `prev${fileUtility.getFileExtension(fileName)}`);
// and write it to the current path
fileSystem.renameSync(req.DocManager.storagePath(fileName, userAddress), pathPrev);
fileSystem.writeFileSync(storagePath, data);
// get the path to the forcesaved file
const forcesavePath = req.DocManager.forcesavePath(newFileName, userAddress, false);
if (forcesavePath !== '') { // if this path is empty
fileSystem.unlinkSync(forcesavePath); // remove it
}
await req.DocManager.saveFile(fileName, newFileName, userAddress, body, data);
} catch (ex) {
console.log(ex);
response.setHeader('Content-Type', 'application/json');
@ -870,64 +846,8 @@ app.post('/track', async (req, res) => { // define a handler for tracking file c
const downloadExt = `.${body.fileType}`;
const isSubmitForm = body.forcesavetype === 3; // SubmitForm
let correctName = fileName;
let forcesavePath = '';
if (isSubmitForm) {
// new file
if (newFileName) {
correctName = req.DocManager.getCorrectName(
`${fileUtility.getFileName(fileName, true)}-form${downloadExt}`,
userAddress,
);
} else {
const ext = fileUtility.getFileExtension(fileName);
correctName = req.DocManager.getCorrectName(
`${fileUtility.getFileName(fileName, true)}-form${ext}`,
userAddress,
);
}
forcesavePath = req.DocManager.storagePath(correctName, userAddress);
} else {
if (newFileName) {
correctName = req.DocManager.getCorrectName(fileUtility.getFileName(
fileName,
true,
) + downloadExt, userAddress);
}
// create forcesave path if it doesn't exist
forcesavePath = req.DocManager.forcesavePath(correctName, userAddress, false);
if (forcesavePath === '') {
forcesavePath = req.DocManager.forcesavePath(correctName, userAddress, true);
}
}
fileSystem.writeFileSync(forcesavePath, data);
if (isSubmitForm) {
const uid = body.actions[0].userid;
req.DocManager.saveFileData(correctName, uid, 'Filling Form', userAddress);
const { formsdataurl } = body;
if (formsdataurl) {
const formsdataName = req.DocManager.getCorrectName(
`${fileUtility.getFileName(correctName, true)}.txt`,
userAddress,
);
// get the path to the file with forms data
const formsdataPath = req.DocManager.storagePath(formsdataName, userAddress);
const formsdata = await urllib.request(formsdataurl, { method: 'GET' });
const statusFormsdata = formsdata.status;
const dataFormsdata = formsdata.data;
if (statusFormsdata === 200) {
fileSystem.writeFileSync(formsdataPath, dataFormsdata); // write the forms data
} else {
emitWarning(`Document editing service returned status: ${statusFormsdata}`);
}
} else {
emitWarning('Document editing service do not returned formsdataurl');
}
}
await req.DocManager.forcesaveFile(data, fileName, downloadExt, userAddress, body, newFileName, isSubmitForm);
} catch (ex) {
console.log(ex);
response.write(`{ "error":1, "message":${JSON.stringify(ex)} }`);
@ -989,6 +909,9 @@ app.post('/track', async (req, res) => { // define a handler for tracking file c
}
}
}
if (bodyTrack.users.length <= 0 && req.DocManager.forcesavePath(fName, uAddress, false)) {
await req.DocManager.saveFile(fName, fName, uAddress, bodyTrack);
}
} else if (bodyTrack.status === 2 || bodyTrack.status === 3) { // MustSave, Corrupted
await processSave(bodyTrack.url, bodyTrack, fileNameTrack, userAddressTrack); // save file
return;
@ -1020,7 +943,7 @@ app.post('/track', async (req, res) => { // define a handler for tracking file c
if (Object.hasOwn(req.body, 'token')) { // if request body has its own token
body = documentService.readToken(req.body.token); // read and verify it
} else {
const checkJwtHeaderRes = await documentService.checkJwtHeader(req); // otherwise, check jwt token headers
const checkJwtHeaderRes = documentService.checkJwtHeader(req); // otherwise, check jwt token headers
if (checkJwtHeaderRes) { // if they exist
if (checkJwtHeaderRes.payload) {
body = checkJwtHeaderRes.payload; // get the payload object
@ -1102,11 +1025,12 @@ app.get('/config', async (req, res) => {
res.end();
});
app.get('/editor', async (req, res) => { // define a handler for editing document
app.get('/editor', (req, res) => { // define a handler for editing document
try {
req.DocManager = new DocManager(req, res);
let { fileExt } = req.query;
const fromBuffer = req.query.fromBuffer === 'true';
const user = users.getUser(req.query.userid);
const userid = user.id;
const { name } = user;
@ -1144,7 +1068,7 @@ app.get('/editor', async (req, res) => { // define a handler for editing documen
type = 'desktop';
}
const fileType = await fileUtility.getFileType(fileName);
const fileType = fileUtility.getFileType(fileName);
const templatesImageUrl = req.DocManager.getTemplateImageUrl(fileType);
const createUrl = req.DocManager.getCreateUrl(fileType, userid, type, lang);
let templates = null;
@ -1197,10 +1121,9 @@ app.get('/editor', async (req, res) => { // define a handler for editing documen
const directUrl = req.DocManager.getDownloadUrl(fileName);
let mode = req.query.mode || 'edit'; // mode: view/edit/review/comment/fillForms/embedded
// check if this file can be edited
let canEdit = (await fileUtility.getEditExtensions()).indexOf(fileExt.slice(1)) !== -1;
let canEdit = fileUtility.getEditExtensions().indexOf(fileExt.slice(1)) !== -1; // check if this file can be edited
if (((!canEdit && mode === 'edit') || mode === 'fillForms')
&& (await fileUtility.getFillExtensions()).indexOf(fileExt.slice(1)) !== -1) {
&& fileUtility.getFillExtensions().indexOf(fileExt.slice(1)) !== -1) {
mode = 'fillForms';
canEdit = true;
}
@ -1251,11 +1174,12 @@ app.get('/editor', async (req, res) => { // define a handler for editing documen
// file config data
const argss = {
apiUrl: siteUrl + (await documentService.config()).urls.api.replace('/', ''),
bufferCallback: req.DocManager.getCallback(fileName, fromBuffer),
apiUrl: siteUrl + configServer.get('apiUrl'),
file: {
name: fileName,
ext: fileUtility.getFileExtension(fileName, true),
uri: url,
uri: fromBuffer ? '_data_' : url,
directUrl: !userDirectUrl ? null : directUrl,
uriUser: directUrl,
created: new Date().toDateString(),
@ -1409,9 +1333,9 @@ app.post('/historyObj', (req, res) => {
res.end();
});
app.get('/formats', async (req, res) => {
app.get('/formats', (req, res) => {
try {
const formats = await documentService.formats();
const formats = fileUtility.getFormats();
res.json({
formats,
});

View File

@ -17,10 +17,14 @@
"wopi": {
"discovery": "hosting/discovery"
},
"commandUrl": "command",
"converterUrl": "converter",
"apiUrl": "web-apps/apps/api/documents/api.js",
"preloaderUrl": "web-apps/apps/api/documents/preload.html",
"exampleUrl": null,
"storageFolder": "./files",
"storagePath": "/files",
"maxFileSize": 1073741824,
"maxNameLength": 50,
"enableForgotten": true,
"mobileRegEx": "android|avantgo|playbook|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od|ad)|iris|kindle|lge |maemo|midp|mmp|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\\/|plucker|pocket|psp|symbian|treo|up\\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino",
@ -28,10 +32,61 @@
"enable": false,
"useforrequest": true,
"algorithmRequest": "HS256",
"authorizationHeader": "Authorization",
"authorizationHeaderPrefix": "Bearer ",
"secret": "secret",
"expiresIn": "5m"
},
"verify_peer_off": true
"verify_peer_off": true,
"languages": {
"en": "English",
"sq-AL": "Albanian (Albania)",
"ar": "Arabic",
"hy": "Armenian",
"az": "Azerbaijani",
"eu": "Basque",
"be": "Belarusian",
"bg": "Bulgarian",
"ca": "Catalan",
"zh": "Chinese (Simplified)",
"zh-TW": "Chinese (Traditional)",
"cs": "Czech",
"da": "Danish",
"nl": "Dutch",
"en-GB": "English (United Kingdom)",
"fi": "Finnish",
"fr": "French",
"gl": "Galego",
"de": "German",
"el": "Greek",
"he-IL": "Hebrew (Israel)",
"hu": "Hungarian",
"id": "Indonesian",
"it": "Italian",
"ja": "Japanese",
"ko": "Korean",
"lo": "Lao",
"lv": "Latvian",
"ms": "Malay (Malaysia)",
"no": "Norwegian",
"pl": "Polish",
"pt": "Portuguese (Brazil)",
"pt-PT": "Portuguese (Portugal)",
"ro": "Romanian",
"ru": "Russian",
"sr-Cyrl-RS": "Serbian (Cyrillic)",
"sr-Latn-RS": "Serbian (Latin)",
"si": "Sinhala (Sri Lanka)",
"sk": "Slovak",
"sl": "Slovenian",
"es": "Spanish",
"sv": "Swedish",
"tr": "Turkish",
"uk": "Ukrainian",
"ur": "Urdu",
"vi": "Vietnamese",
"aa-AA": "Test Language"
}
},
"plugins": {
"pluginsData": []

View File

@ -1,6 +1,7 @@
{
"server": {
"siteUrl": "/",
"maxFileSize": 104857600,
"storageFolder": "/var/lib/onlyoffice/documentserver-example/files",
"enableForgotten": false
}

View File

@ -1,6 +1,7 @@
{
"server": {
"siteUrl": "/",
"maxFileSize": 104857600,
"enableForgotten": false
}
}

View File

@ -18,6 +18,8 @@
const path = require('path');
const fileSystem = require('fs');
const urllib = require('urllib');
const { emitWarning } = require('process');
const configServer = require('config').get('server');
const fileUtility = require('./fileUtility');
const documentService = require('./documentService');
@ -203,11 +205,12 @@ DocManager.prototype.getProtocol = function getProtocol() {
};
// get callback url
DocManager.prototype.getCallback = function getCallback(fileName) {
DocManager.prototype.getCallback = function getCallback(fileName, fromBuffer = false) {
const server = this.getServerUrl(true);
const hostAddress = this.curUserHostAddress();
const route = fromBuffer ? '/forcesave' : '/track';
// get callback handler
const handler = `/track?filename=${encodeURIComponent(fileName)}&useraddress=${encodeURIComponent(hostAddress)}`;
const handler = `${route}?filename=${encodeURIComponent(fileName)}&useraddress=${encodeURIComponent(hostAddress)}`;
return server + handler;
};
@ -269,6 +272,129 @@ DocManager.prototype.forcesavePath = function forcesavePath(fileName, userAddres
return directory;
};
DocManager.prototype.forcesaveFile = async function forcesaveFile(
data,
fileName,
downloadExt,
userAddress,
body = null,
newFileName = false,
isSubmitForm = false,
) {
let correctName = fileName;
let forcesavePath = '';
if (isSubmitForm) {
// new file
if (newFileName) {
correctName = this.getCorrectName(
`${fileUtility.getFileName(fileName, true)}-form${downloadExt}`,
userAddress,
);
} else {
const ext = fileUtility.getFileExtension(fileName);
correctName = this.getCorrectName(
`${fileUtility.getFileName(fileName, true)}-form${ext}`,
userAddress,
);
}
forcesavePath = this.storagePath(correctName, userAddress);
} else {
if (newFileName) {
correctName = this.getCorrectName(fileUtility.getFileName(
fileName,
true,
) + downloadExt, userAddress);
}
// create forcesave path if it doesn't exist
forcesavePath = this.forcesavePath(correctName, userAddress, false);
if (forcesavePath === '') {
forcesavePath = this.forcesavePath(correctName, userAddress, true);
}
}
fileSystem.writeFileSync(forcesavePath, data);
if (isSubmitForm) {
const uid = body.actions[0].userid;
this.saveFileData(correctName, uid, 'Filling Form', userAddress);
const { formsdataurl } = body;
if (formsdataurl) {
const formsdataName = this.getCorrectName(
`${fileUtility.getFileName(correctName, true)}.txt`,
userAddress,
);
// get the path to the file with forms data
const formsdataPath = this.storagePath(formsdataName, userAddress);
const formsdata = await urllib.request(formsdataurl, { method: 'GET' });
const statusFormsdata = formsdata.status;
const dataFormsdata = formsdata.data;
if (statusFormsdata === 200) {
fileSystem.writeFileSync(formsdataPath, dataFormsdata); // write the forms data
} else {
emitWarning(`Document editing service returned status: ${statusFormsdata}`);
}
} else {
emitWarning('Document editing service do not returned formsdataurl');
}
}
};
DocManager.prototype.saveFile = async function saveFile(fileName, newFileName, userAddress, body, data = null) {
const storagePath = this.storagePath(newFileName, userAddress);
let historyPath = this.historyPath(newFileName, userAddress); // get the path to the history data
if (historyPath === '') { // if the history path doesn't exist
historyPath = this.historyPath(newFileName, userAddress, true); // create it
this.createDirectory(historyPath); // and create a directory for the history data
}
const countVersion = this.countVersion(historyPath); // get the next file version number
const version = countVersion + 1;
// get the path to the specified file version
const versionPath = this.versionPath(newFileName, userAddress, version);
this.createDirectory(versionPath); // create a directory to the specified file version
const downloadZip = body.changesurl;
if (downloadZip) {
// get the path to the file with document versions differences
const pathChanges = this.diffPath(newFileName, userAddress, version);
const zip = await urllib.request(downloadZip, { method: 'GET' });
const statusZip = zip.status;
const dataZip = zip.data;
if (statusZip === 200) {
fileSystem.writeFileSync(pathChanges, dataZip); // write the document version differences to the archive
} else {
emitWarning(`Document editing service returned status: ${statusZip}`);
}
}
const changeshistory = body.changeshistory || JSON.stringify(body.history);
if (changeshistory) {
// get the path to the file with document changes
const pathChangesJson = this.changesPath(newFileName, userAddress, version);
fileSystem.writeFileSync(pathChangesJson, changeshistory); // and write this data to the path in json format
}
const pathKey = this.keyPath(newFileName, userAddress, version); // get the path to the key.txt file
fileSystem.writeFileSync(pathKey, body.key); // write the key value to the key.txt file
// get the path to the previous file version
const pathPrev = path.join(versionPath, `prev${fileUtility.getFileExtension(fileName)}`);
// and write it to the current path
fileSystem.renameSync(this.storagePath(fileName, userAddress), pathPrev);
// get the path to the forcesaved file
const forcesavePath = this.forcesavePath(newFileName, userAddress, false);
if (forcesavePath !== '') { // if this path is not empty
if (data === null) fileSystem.writeFileSync(storagePath, fileSystem.readFileSync(forcesavePath));
fileSystem.unlinkSync(forcesavePath); // remove it
}
if (data !== null) fileSystem.writeFileSync(storagePath, data);
};
// create the path to the file history
DocManager.prototype.historyPath = function historyPath(fileName, userAddress, create) {
let directory = this.storageRootPath(userAddress);
@ -315,7 +441,7 @@ DocManager.prototype.changesUser = function changesUser(fileName, userAddress, v
};
// get all the stored files
DocManager.prototype.getStoredFiles = async function getStoredFiles() {
DocManager.prototype.getStoredFiles = function getStoredFiles() {
const userAddress = this.curUserHostAddress();
const directory = this.storageRootPath(userAddress);
this.createDirectory(directory);
@ -335,10 +461,8 @@ DocManager.prototype.getStoredFiles = async function getStoredFiles() {
const item = { // create an object with element data
time,
name: storedFiles[i],
// eslint-disable-next-line no-await-in-loop
documentType: await fileUtility.getFileType(storedFiles[i]),
// eslint-disable-next-line no-await-in-loop
actions: await fileUtility.getFormatActions(fileUtility.getFileExtension(storedFiles[i], true)),
documentType: fileUtility.getFileType(storedFiles[i]),
actions: fileUtility.getFormatActions(fileUtility.getFileExtension(storedFiles[i], true)),
version: version + 1,
};
@ -587,10 +711,10 @@ DocManager.prototype.cleanFolderRecursive = function cleanFolderRecursive(folder
};
// get files information
DocManager.prototype.getFilesInfo = async function getFilesInfo(fileId) {
DocManager.prototype.getFilesInfo = function getFilesInfo(fileId) {
const userAddress = this.curUserHostAddress();
const directory = this.storageRootPath(userAddress);
const filesInDirectory = await this.getStoredFiles(); // get all the stored files from the folder
const filesInDirectory = this.getStoredFiles(); // get all the stored files from the folder
const responseArray = [];
let responseObject;
// run through all the files from the directory

View File

@ -20,7 +20,6 @@
const urlModule = require('url');
const urllib = require('urllib');
const jwt = require('jsonwebtoken');
const { getLangNameFromCode } = require('language-name-map');
const configServer = require('config').get('server');
const fileUtility = require('./fileUtility');
const guidManager = require('./guidManager');
@ -28,78 +27,16 @@ const guidManager = require('./guidManager');
const siteUrl = configServer.get('siteUrl'); // the path to the editors installation
const cfgSignatureEnable = configServer.get('token.enable');
const cfgSignatureUseForRequest = configServer.get('token.useforrequest');
const cfgSignatureAuthorizationHeader = configServer.get('token.authorizationHeader');
const cfgSignatureAuthorizationHeaderPrefix = configServer.get('token.authorizationHeaderPrefix');
const cfgSignatureSecretExpiresIn = configServer.get('token.expiresIn');
const cfgSignatureSecret = configServer.get('token.secret');
const cfgSignatureSecretAlgorithmRequest = configServer.get('token.algorithmRequest');
let configCache;
let formatsCache;
const pendingPromise = {
config: null,
formats: null,
};
const documentService = {};
documentService.userIp = null;
async function fetchMeta(path) {
if (pendingPromise[path]) return pendingPromise[path];
pendingPromise[path] = fetch(`${siteUrl}meta/${path}`)
.then((r) => {
let data;
try {
if (r.status !== 200) throw new Error(`Failed to fetch ${path}. Response status: ${r.status}`);
data = r.json();
} catch (e) {
console.log(e);
}
return data;
})
.then((data) => {
pendingPromise[path] = null;
return data;
});
return pendingPromise[path];
}
documentService.config = async function config() {
if (!configCache) {
configCache = await fetchMeta('config');
configCache.langObject = Object.fromEntries(['en', ...configCache.langs.filter((v) => v !== 'en')].map((k) => {
switch (k) {
case 'pt-pt': return [k, 'Portuguese (Portugal)'];
case 'sr-cyrl': return [k, 'Serbian (Cyrillic)'];
case 'zh-tw': return [k, 'Chinese (Traditional)'];
default:
try {
return [k, getLangNameFromCode(k).name];
} catch {
return [k, k];
}
}
}));
setTimeout(() => {
configCache = null;
}, 1000 * 60 * 60);
}
return configCache;
};
documentService.formats = async function formats() {
if (!formatsCache) {
formatsCache = await fetchMeta('formats');
setTimeout(() => {
formatsCache = null;
}, 1000 * 60 * 60);
}
return formatsCache;
};
// get the url of the converted file (synchronous)
documentService.getConvertedUriSync = function getConvertedUriSync(
documentUri,
@ -114,7 +51,7 @@ documentService.getConvertedUriSync = function getConvertedUriSync(
};
// get the url of the converted file
documentService.getConvertedUri = async function getConvertedUri(
documentService.getConvertedUri = function getConvertedUri(
documentUri,
fromExtension,
toExtension,
@ -145,8 +82,7 @@ documentService.getConvertedUri = async function getConvertedUri(
region: lang,
};
// get the absolute converter url
const uri = siteUrl + (await documentService.config()).urls.converter.replace('/', '');
const uri = siteUrl + configServer.get('converterUrl'); // get the absolute converter url
const headers = {
'Content-Type': 'application/json',
Accept: 'application/json',
@ -154,8 +90,7 @@ documentService.getConvertedUri = async function getConvertedUri(
if (cfgSignatureEnable && cfgSignatureUseForRequest) { // if the signature is enabled and it can be used for request
// write signature authorization header
const { authorization } = (await documentService.config());
headers[authorization.header] = authorization.prefix + this.fillJwtByUrl(uri, params);
headers[cfgSignatureAuthorizationHeader] = cfgSignatureAuthorizationHeaderPrefix + this.fillJwtByUrl(uri, params);
params.token = documentService.getToken(params); // get token and save it to the parameters
}
@ -264,7 +199,7 @@ documentService.getResponseUri = function getResponseUri(json) {
};
// create a command request
documentService.commandRequest = async function commandRequest(method, documentRevisionId, callback, meta = null) {
documentService.commandRequest = function commandRequest(method, documentRevisionId, callback, meta = null) {
const revisionId = documentService.generateRevisionId(documentRevisionId); // generate the document key value
const params = { // create a parameter object with command method and the document key value in it
c: method,
@ -275,13 +210,12 @@ documentService.commandRequest = async function commandRequest(method, documentR
params.meta = meta;
}
const uri = siteUrl + (await documentService.config()).urls.command.replace('/', ''); // get the absolute command url
const uri = siteUrl + configServer.get('commandUrl'); // get the absolute command url
const headers = { // create a headers object
'Content-Type': 'application/json',
};
if (cfgSignatureEnable && cfgSignatureUseForRequest) {
const { authorization } = (await documentService.config());
headers[authorization.header] = authorization.prefix + this.fillJwtByUrl(uri, params);
headers[cfgSignatureAuthorizationHeader] = cfgSignatureAuthorizationHeaderPrefix + this.fillJwtByUrl(uri, params);
params.token = documentService.getToken(params);
}
@ -299,15 +233,13 @@ documentService.commandRequest = async function commandRequest(method, documentR
};
// check jwt token headers
documentService.checkJwtHeader = async function checkJwtHeader(req) {
documentService.checkJwtHeader = function checkJwtHeader(req) {
let decoded = null;
// get signature authorization header from the request
const authorization = req.get((await documentService.config()).authorization.header);
const authorizationPrefix = (await documentService.config()).authorization.prefix;
const authorization = req.get(cfgSignatureAuthorizationHeader); // get signature authorization header from the request
// if authorization header exists and it starts with the authorization header prefix
if (authorization && authorization.startsWith(authorizationPrefix)) {
if (authorization && authorization.startsWith(cfgSignatureAuthorizationHeaderPrefix)) {
// the resulting token starts after the authorization header prefix
const token = authorization.substring(authorizationPrefix.length);
const token = authorization.substring(cfgSignatureAuthorizationHeaderPrefix.length);
try {
decoded = jwt.verify(token, cfgSignatureSecret); // verify signature on jwt token using signature secret
} catch (err) {

View File

@ -17,10 +17,14 @@
*/
const pathModule = require('path');
const documentService = require('./documentService');
const supportedFormats = require('../public/assets/document-formats/onlyoffice-docs-formats.json'); // eslint-disable-line
const fileUtility = {};
fileUtility.getFormats = function getFormats() {
return supportedFormats;
};
// get file name from the given url
fileUtility.getFileNameFromUrl = function getFileNameFromUrl(url, withoutExtension) {
if (!url) return '';
@ -59,9 +63,8 @@ fileUtility.getFileExtension = function getFileExtension(path, withoutDot, isUrl
};
// get file type from the given path
fileUtility.getFileType = async function getFileType(path) {
fileUtility.getFileType = function getFileType(path) {
const ext = fileUtility.getFileExtension(path, true); // get the file extension from the given path
const supportedFormats = await documentService.formats();
for (let i = 0; i < supportedFormats.length; i++) {
if (supportedFormats[i].name === ext && supportedFormats[i].type !== '') return supportedFormats[i].type;
@ -78,34 +81,34 @@ fileUtility.fileType = {
diagram: 'diagram',
};
fileUtility.getFormatActions = async function getExtensionActions(ext) {
return (await documentService.formats()).filter((format) => format.name === ext)[0]?.actions || [];
fileUtility.getFormatActions = function getExtensionActions(ext) {
return supportedFormats.filter((format) => format.name === ext)[0]?.actions || [];
};
fileUtility.getSuppotredExtensions = async function getSuppotredExtensions() {
return (await documentService.formats()).reduce((extensions, format) => [...extensions, format.name], []);
fileUtility.getSuppotredExtensions = function getSuppotredExtensions() {
return supportedFormats.reduce((extensions, format) => [...extensions, format.name], []);
};
fileUtility.getViewExtensions = async function getViewExtensions() {
return (await documentService.formats()).filter(
fileUtility.getViewExtensions = function getViewExtensions() {
return supportedFormats.filter(
(format) => format.actions.includes('view'),
).reduce((extensions, format) => [...extensions, format.name], []);
};
fileUtility.getEditExtensions = async function getEditExtensions() {
return (await documentService.formats()).filter(
fileUtility.getEditExtensions = function getEditExtensions() {
return supportedFormats.filter(
(format) => format.actions.includes('edit') || format.actions.includes('lossy-edit'),
).reduce((extensions, format) => [...extensions, format.name], []);
};
fileUtility.getFillExtensions = async function getFillExtensions() {
return (await documentService.formats()).filter(
fileUtility.getFillExtensions = function getFillExtensions() {
return supportedFormats.filter(
(format) => format.actions.includes('fill'),
).reduce((extensions, format) => [...extensions, format.name], []);
};
fileUtility.getConvertExtensions = async function getConvertExtensions() {
return (await documentService.formats()).filter(
fileUtility.getConvertExtensions = function getConvertExtensions() {
return supportedFormats.filter(
(format) => format.actions.includes('auto-convert'),
).reduce((extensions, format) => [...extensions, format.name], []);
};

View File

@ -21,7 +21,6 @@ const tokenValidator = require('./tokenValidator');
const filesController = require('./filesController');
const utils = require('./utils');
const DocManager = require('../docManager');
const documentService = require('../documentService');
const fileUtility = require('../fileUtility');
const users = require('../users');
@ -59,7 +58,7 @@ exports.registerRoutes = function registerRoutes(app) {
try {
// get all the stored files
const files = await req.DocManager.getStoredFiles();
const files = req.DocManager.getStoredFiles();
// run through all the files and write the corresponding information to each file
// eslint-disable-next-line no-restricted-syntax
@ -89,7 +88,7 @@ exports.registerRoutes = function registerRoutes(app) {
params: req.DocManager.getCustomParams(),
users,
preloaderUrl: siteUrl + configServer.get('preloaderUrl'),
languages: (await documentService.config()).langObject,
languages: configServer.get('languages'),
enableForgotten: configServer.get('enableForgotten'),
editNewExts,
});

View File

@ -20,7 +20,6 @@
"he": "^1.2.0",
"jsonwebtoken": "^9.0.2",
"jwa": "^2.0.0",
"language-name-map": "^0.3.0",
"log4js": "^6.9.1",
"mime": "^2.4.6",
"serve-favicon": "^2.5.0",
@ -2427,12 +2426,6 @@
"safe-buffer": "^5.0.1"
}
},
"node_modules/language-name-map": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/language-name-map/-/language-name-map-0.3.0.tgz",
"integrity": "sha512-uoBHtfY6h4S2RoIpyqvQGhynX2hshQu/9S4ySbppGxG5VwEsiWZJ83xSjzx25Mb+Bmc+WxpQC0H54eNZVMWLuA==",
"license": "BSD-2-Clause"
},
"node_modules/levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@ -5698,11 +5691,6 @@
}
}
},
"language-name-map": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/language-name-map/-/language-name-map-0.3.0.tgz",
"integrity": "sha512-uoBHtfY6h4S2RoIpyqvQGhynX2hshQu/9S4ySbppGxG5VwEsiWZJ83xSjzx25Mb+Bmc+WxpQC0H54eNZVMWLuA=="
},
"levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",

View File

@ -25,7 +25,6 @@
"he": "^1.2.0",
"jsonwebtoken": "^9.0.2",
"jwa": "^2.0.0",
"language-name-map": "^0.3.0",
"log4js": "^6.9.1",
"mime": "^2.4.6",
"serve-favicon": "^2.5.0",

View File

@ -77,8 +77,30 @@
var onAppReady = function () { // the application is loaded into the browser
innerAlert("Document editor ready");
if (config.document.url === "_data_") {
let xhr = new XMLHttpRequest();
xhr.open("GET", "download?fileName=" + encodeURIComponent(config.document.title));
xhr.responseType = "arraybuffer";
xhr.send();
xhr.onload = () => {
if (xhr.status === 200) {
docEditor.openDocument(new Uint8Array(xhr.response));
} else {
innerAlert("Failed to download document");
}
};
};
};
var onSaveDocument = function (event) {
if (config.document.url !== "_data_") return;
let xhr = new XMLHttpRequest();
xhr.open("POST", "<%- bufferCallback %>");
xhr.setRequestHeader('Content-Type', 'application/octet-stream');
xhr.send(event.data);
}
var onDocumentStateChange = function (event) { // the document is modified
var title = document.title.replace(/^\*/g, "");
document.title = (event.data ? "*" : "") + title;
@ -454,6 +476,7 @@
"onRequestSelectDocument": onRequestSelectDocument,
"onRequestSelectSpreadsheet": onRequestSelectSpreadsheet,
"onRequestOpen": onRequestOpen,
"onSaveDocument": onSaveDocument,
};
if (<%- JSON.stringify(editor.userid) %> != null) {

View File

@ -111,11 +111,19 @@
<tr>
<td valign="middle">
<label class="side-option">
<input id="directUrl" type="checkbox" class="checkbox collectable" name="directUrl" />Try opening on client
<input id="directUrl" type="checkbox" class="checkbox collectable" name="directUrl" />Opening direct link
<img id="directUrlInfo" class="info info-tooltip" data-id="directUrlInfo" data-tooltip="Some files can be opened in the user's browser without connecting to the document server. Open each file in only one way." src="images/info.svg" />
</label>
</td>
</tr>
<tr>
<td valign="middle">
<label class="side-option">
<input id="fromBuffer" type="checkbox" class="checkbox collectable" name="fromBuffer" />Opening on client
<img id="fromBufferInfo" class="info info-tooltip" data-id="fromBufferInfo" data-tooltip="Download the file and transfer it to the editor on the client without requests from the document server." src="images/info.svg" />
</label>
</td>
</tr>
</tbody>
</table>
</div>