mirror of
https://github.com/ONLYOFFICE/document-server-integration.git
synced 2026-04-07 14:06:11 +08:00
316 lines
14 KiB
JavaScript
316 lines
14 KiB
JavaScript
/**
|
|
*
|
|
* (c) Copyright Ascensio System SIA 2021
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
*/
|
|
|
|
const reqConsts = require('./request');
|
|
const fileUtility = require("../fileUtility");
|
|
const lockManager = require("./lockManager");
|
|
const fileSystem = require("fs");
|
|
const mime = require("mime");
|
|
const path = require("path");
|
|
const users = require("../users");
|
|
const docManager = require("../docManager");
|
|
|
|
const actionMapping = {};
|
|
actionMapping[reqConsts.requestType.GetFile] = getFile;
|
|
actionMapping[reqConsts.requestType.PutFile] = putFile;
|
|
actionMapping[reqConsts.requestType.CheckFileInfo] = checkFileInfo;
|
|
actionMapping[reqConsts.requestType.UnlockAndRelock] = unlockAndRelock;
|
|
actionMapping[reqConsts.requestType.Lock] = lock;
|
|
actionMapping[reqConsts.requestType.GetLock] = getLock;
|
|
actionMapping[reqConsts.requestType.RefreshLock] = refreshLock;
|
|
actionMapping[reqConsts.requestType.Unlock] = unlock;
|
|
|
|
// parse wopi request
|
|
function parseWopiRequest(req) {
|
|
let wopiData = {
|
|
requestType: reqConsts.requestType.None,
|
|
accessToken: req.query["access_token"],
|
|
id: req.params['id']
|
|
}
|
|
|
|
// get the request path
|
|
let reqPath = req.path.substring("/wopi/".length)
|
|
|
|
if (reqPath.startsWith("files")) { // if it starts with "files"
|
|
if (reqPath.endsWith("/contents")) { // ends with "/contents"
|
|
if (req.method == "GET") { // and the request method is GET
|
|
wopiData.requestType = reqConsts.requestType.GetFile; // then the request type is GetFile
|
|
} else if (req.method == "POST") { // if the request method is POST
|
|
wopiData.requestType = reqConsts.requestType.PutFile; // then the request type is PutFile
|
|
}
|
|
} else {
|
|
if (req.method == "GET") { // otherwise, if the request method is GET
|
|
wopiData.requestType = reqConsts.requestType.CheckFileInfo; // the request type is CheckFileInfo
|
|
} else if (req.method == "POST") { // if the request method is POST
|
|
let wopiOverride = req.headers[reqConsts.requestHeaders.RequestType.toLowerCase()]; // get the X-WOPI-Override header which determines the request type
|
|
switch (wopiOverride) {
|
|
case "LOCK": // if it is equal to LOCK
|
|
if (req.headers[reqConsts.requestHeaders.OldLock.toLowerCase()]) { // check if the request sends the X-WOPI-OldLock header
|
|
wopiData.requestType = reqConsts.requestType.UnlockAndRelock; // if yes, then the request type is UnlockAndRelock
|
|
} else {
|
|
wopiData.requestType = reqConsts.requestType.Lock; // otherwise, it is Lock
|
|
}
|
|
break;
|
|
|
|
case "GET_LOCK": // if it is equal to GET_LOCK
|
|
wopiData.requestType = reqConsts.requestType.GetLock; // the request type is GetLock
|
|
break;
|
|
|
|
case "REFRESH_LOCK": // if it is equal to REFRESH_LOCK
|
|
wopiData.requestType = reqConsts.requestType.RefreshLock; // the request type is RefreshLock
|
|
break;
|
|
|
|
case "UNLOCK": // if it is equal to UNLOCK
|
|
wopiData.requestType = reqConsts.requestType.Unlock; // the request type is Unlock
|
|
break;
|
|
|
|
case "PUT_RELATIVE": // if it is equal to PUT_RELATIVE
|
|
wopiData.requestType = reqConsts.requestType.PutRelativeFile; // the request type is PutRelativeFile (creates a new file on the host based on the current file)
|
|
break;
|
|
|
|
case "RENAME_FILE": // if it is equal to RENAME_FILE
|
|
wopiData.requestType = reqConsts.requestType.RenameFile; // the request type is RenameFile (renames a file)
|
|
break;
|
|
|
|
case "PUT_USER_INFO": // if it is equal to PUT_USER_INFO
|
|
wopiData.requestType = reqConsts.requestType.PutUserInfo; // the request type is PutUserInfo (stores some basic user information on the host)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else if (reqPath.startsWith("folders")) {
|
|
|
|
}
|
|
|
|
return wopiData;
|
|
}
|
|
|
|
// lock file editing
|
|
function lock(wopi, req, res, userHost) {
|
|
let requestLock = req.headers[reqConsts.requestHeaders.Lock.toLowerCase()];
|
|
|
|
let userAddress = req.docManager.curUserHostAddress(userHost); // get current user host address
|
|
let filePath = req.docManager.storagePath(wopi.id, userAddress); // get the storage path of the given file
|
|
|
|
if (!lockManager.hasLock(filePath)) {
|
|
// file isn't locked => lock
|
|
lockManager.lock(filePath, requestLock);
|
|
res.sendStatus(200);
|
|
} else if (lockManager.getLock(filePath) == requestLock) {
|
|
// lock matches current lock => extend duration
|
|
lockManager.lock(filePath, requestLock);
|
|
res.sendStatus(200);
|
|
} else {
|
|
// file locked by someone else => return lock mismatch
|
|
let lock = lockManager.getLock(filePath);
|
|
returnLockMismatch(res, lock, "File already locked by " + lock)
|
|
}
|
|
}
|
|
|
|
// retrieve a lock on a file
|
|
function getLock(wopi, req, res, userHost) {
|
|
let userAddress = req.docManager.curUserHostAddress(userHost);
|
|
let filePath = req.docManager.storagePath(wopi.id, userAddress);
|
|
|
|
// get the lock of the specified file and set it as the X-WOPI-Lock header
|
|
res.setHeader(reqConsts.requestHeaders.lock, lockManager.getLock(filePath));
|
|
res.sendStatus(200);
|
|
}
|
|
|
|
// refresh the lock on a file by resetting its automatic expiration timer to 30 minutes
|
|
function refreshLock(wopi, req, res, userHost) {
|
|
let requestLock = req.headers[reqConsts.requestHeaders.Lock.toLowerCase()];
|
|
|
|
let userAddress = req.docManager.curUserHostAddress(userHost);
|
|
let filePath = req.docManager.storagePath(wopi.id, userAddress);
|
|
|
|
if (!lockManager.hasLock(filePath)) {
|
|
// file isn't locked => mismatch
|
|
returnLockMismatch(res, "", "File isn't locked");
|
|
} else if (lockManager.getLock(filePath) == requestLock) {
|
|
// lock matches current lock => extend duration
|
|
lockManager.lock(filePath, requestLock);
|
|
res.sendStatus(200);
|
|
} else {
|
|
// lock mismatch
|
|
returnLockMismatch(res, lockManager.getLock(filePath), "Lock mismatch");
|
|
}
|
|
}
|
|
|
|
// allow for file editing
|
|
function unlock(wopi, req, res, userHost) {
|
|
let requestLock = req.headers[reqConsts.requestHeaders.Lock.toLowerCase()];
|
|
|
|
let userAddress = req.docManager.curUserHostAddress(userHost);
|
|
let filePath = req.docManager.storagePath(wopi.id, userAddress);
|
|
|
|
if (!lockManager.hasLock(filePath)) {
|
|
// file isn't locked => mismatch
|
|
returnLockMismatch(res, "", "File isn't locked");
|
|
} else if (lockManager.getLock(filePath) == requestLock) {
|
|
// lock matches current lock => unlock
|
|
lockManager.unlock(filePath);
|
|
res.sendStatus(200);
|
|
} else {
|
|
// lock mismatch
|
|
returnLockMismatch(res, lockManager.getLock(filePath), "Lock mismatch");
|
|
}
|
|
}
|
|
|
|
// allow for file editing, and then immediately take a new lock on the file
|
|
function unlockAndRelock(wopi, req, res, userHost) {
|
|
let requestLock = req.headers[reqConsts.requestHeaders.Lock.toLowerCase()];
|
|
let oldLock = req.headers[reqConsts.requestHeaders.oldLock.toLowerCase()]; // get the X-WOPI-OldLock header
|
|
|
|
let userAddress = req.docManager.curUserHostAddress(userHost);
|
|
let filePath = req.docManager.storagePath(wopi.id, userAddress);
|
|
|
|
if (!lockManager.hasLock(filePath)) {
|
|
// file isn't locked => mismatch
|
|
returnLockMismatch(res, "", "File isn't locked");
|
|
} else if (lockManager.getLock(filePath) == oldLock) {
|
|
// lock matches current lock => lock with new key
|
|
lockManager.lock(filePath, requestLock);
|
|
res.sendStatus(200);
|
|
} else {
|
|
// lock mismatch
|
|
returnLockMismatch(res, lockManager.getLock(filePath), "Lock mismatch");
|
|
}
|
|
}
|
|
|
|
// request a message to retrieve a file
|
|
function getFile(wopi, req, res, userHost) {
|
|
let userAddress = req.docManager.curUserHostAddress(userHost);
|
|
|
|
let path = req.docManager.storagePath(wopi.id, userAddress);
|
|
|
|
res.setHeader("Content-Length", fileSystem.statSync(path).size);
|
|
res.setHeader("Content-Type", mime.getType(path));
|
|
|
|
res.setHeader("Content-Disposition", "attachment; filename*=UTF-8\'\'" + encodeURIComponent(wopi.id));
|
|
|
|
let filestream = fileSystem.createReadStream(path); // open a file as a readable stream
|
|
filestream.pipe(res); // retrieve data from file stream and output it to the response object
|
|
}
|
|
|
|
// request a message to update a file
|
|
function putFile(wopi, req, res, userHost) {
|
|
let requestLock = req.headers[reqConsts.requestHeaders.Lock.toLowerCase()];
|
|
|
|
let userAddress = req.docManager.curUserHostAddress(userHost);
|
|
let storagePath = req.docManager.storagePath(wopi.id, userAddress);
|
|
|
|
if (!lockManager.hasLock(storagePath)) {
|
|
// ToDo: if body length is 0 bytes => handle document creation
|
|
|
|
// file isn't locked => mismatch
|
|
returnLockMismatch(res, "", "File isn't locked");
|
|
} else if (lockManager.getLock(storagePath) == requestLock) {
|
|
// lock matches current lock => put file
|
|
if (req.body) {
|
|
var historyPath = req.docManager.historyPath(wopi.id, userAddress); // get the path to the file history
|
|
if (historyPath == "") { // if it is empty
|
|
historyPath = req.docManager.historyPath(wopi.id, userAddress, true); // create it
|
|
req.docManager.createDirectory(historyPath); // and create a new directory for the history
|
|
}
|
|
|
|
var count_version = req.docManager.countVersion(historyPath); // get the last file version
|
|
version = count_version + 1; // get a number of a new file version
|
|
res.setHeader(reqConsts.requestHeaders.ItemVersion, version + 1); // set the X-WOPI-ItemVersion header
|
|
var versionPath = req.docManager.versionPath(wopi.id, userAddress, version); // get the path to the specified file version
|
|
req.docManager.createDirectory(versionPath); // and create a new directory for the specified version
|
|
|
|
var path_prev = path.join(versionPath, "prev" + fileUtility.getFileExtension(wopi.id)); // get the path to the previous file version
|
|
fileSystem.renameSync(req.docManager.storagePath(wopi.id, userAddress), path_prev); // synchronously rename the given file as the previous file version
|
|
|
|
let filestream = fileSystem.createWriteStream(storagePath);
|
|
req.pipe(filestream);
|
|
req.on('end', () => {
|
|
filestream.close();
|
|
res.sendStatus(200);
|
|
})
|
|
} else {
|
|
res.sendStatus(404);
|
|
}
|
|
} else {
|
|
// lock mismatch
|
|
returnLockMismatch(res, lockManager.getLock(storagePath), "Lock mismatch");
|
|
}
|
|
}
|
|
|
|
// return information about the file properties, access rights and editor settings
|
|
function checkFileInfo(wopi, req, res, userHost) {
|
|
let userAddress = req.docManager.curUserHostAddress(userHost);
|
|
let version = req.docManager.getKey(wopi.id, userAddress);
|
|
|
|
let path = req.docManager.storagePath(wopi.id, userAddress);
|
|
// add wopi query
|
|
var query = new URLSearchParams(wopi.accessToken);
|
|
let user = users.getUser(query.get("userid"));
|
|
|
|
// create the file information object
|
|
let fileInfo = {
|
|
"BaseFileName": wopi.id,
|
|
"OwnerId": req.docManager.getFileData(wopi.id, userAddress)[1],
|
|
"Size": fileSystem.statSync(path).size,
|
|
"UserId": user.id,
|
|
"UserFriendlyName": user.name,
|
|
"Version": version,
|
|
"UserCanWrite": true,
|
|
"SupportsGetLock": true,
|
|
"SupportsLocks": true,
|
|
"SupportsUpdate": true,
|
|
};
|
|
res.status(200).send(fileInfo);
|
|
}
|
|
|
|
// return lock mismatch
|
|
function returnLockMismatch(res, lock, reason) {
|
|
res.setHeader(reqConsts.requestHeaders.Lock, lock || ""); // set the X-WOPI-Lock header
|
|
if (reason) { // if there is a reason for lock mismatch
|
|
res.setHeader(reqConsts.requestHeaders.LockFailureReason, reason); // set it as the X-WOPI-LockFailureReason header
|
|
}
|
|
res.sendStatus(409); // conflict
|
|
}
|
|
|
|
exports.fileRequestHandler = (req, res) => {
|
|
let userAddress = null;
|
|
req.docManager = new docManager(req, res);
|
|
if (req.params['id'].includes("@")) { // if there is the "@" sign in the id parameter
|
|
let split = req.params['id'].split("@"); // split this parameter by "@"
|
|
req.params['id'] = split[0]; // rewrite id with the first part of the split parameter
|
|
userAddress = split[1]; // save the second part as the user address
|
|
}
|
|
|
|
let wopiData = parseWopiRequest(req); // get the wopi data
|
|
|
|
// an error of the unknown request type
|
|
if (wopiData.requestType == reqConsts.requestType.None) {
|
|
res.status(500).send({ 'title': 'fileHandler', 'method': req.method, 'id': req.params['id'], 'error': "unknown" });
|
|
return;
|
|
}
|
|
|
|
// an error of the unsupported request type
|
|
let action = actionMapping[wopiData.requestType];
|
|
if (!action) {
|
|
res.status(501).send({ 'title': 'fileHandler', 'method': req.method, 'id': req.params['id'], 'error': "unsupported" });
|
|
return;
|
|
}
|
|
|
|
action(wopiData, req, res, userAddress);
|
|
} |