etherpad-lite/src/node/handler/APIHandler.js
muxator 11453d544c prepare to async: stricter checks
This change is in preparation of the future async refactoring by Ray. It tries
to extract as many changes in boolean conditions as possible, in order to make
more evident identifying eventual logic bugs in the future work.

This proved already useful in at least one case.

BEWARE: this commit exposes an incoherency in the DB API, in which, depending
on the driver used, some functions can return null or undefined. This condition
will be externally fixed by the final commit in this series ("db/DB.js: prevent
DB layer from returning undefined"). Until that commit, the code base may have
some bugs.
2019-03-01 09:43:41 +01:00

245 lines
7.4 KiB
JavaScript

/**
* The API Handler handles all API http requests
*/
/*
* 2011 Peter 'Pita' Martischka (Primary Technology Ltd)
*
* 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.
*/
var absolutePaths = require('../utils/AbsolutePaths');
var ERR = require("async-stacktrace");
var fs = require("fs");
var api = require("../db/API");
var log4js = require('log4js');
var padManager = require("../db/PadManager");
var randomString = require("../utils/randomstring");
var argv = require('../utils/Cli').argv;
var apiHandlerLogger = log4js.getLogger('APIHandler');
//ensure we have an apikey
var apikey = null;
var apikeyFilename = absolutePaths.makeAbsolute(argv.apikey || "./APIKEY.txt");
try {
apikey = fs.readFileSync(apikeyFilename,"utf8");
apiHandlerLogger.info(`Api key file read from: "${apikeyFilename}"`);
} catch(e) {
apiHandlerLogger.info(`Api key file "${apikeyFilename}" not found. Creating with random contents.`);
apikey = randomString(32);
fs.writeFileSync(apikeyFilename,apikey,"utf8");
}
// a list of all functions
var version = {};
version["1"] = Object.assign({},
{ "createGroup" : []
, "createGroupIfNotExistsFor" : ["groupMapper"]
, "deleteGroup" : ["groupID"]
, "listPads" : ["groupID"]
, "createPad" : ["padID", "text"]
, "createGroupPad" : ["groupID", "padName", "text"]
, "createAuthor" : ["name"]
, "createAuthorIfNotExistsFor": ["authorMapper" , "name"]
, "listPadsOfAuthor" : ["authorID"]
, "createSession" : ["groupID", "authorID", "validUntil"]
, "deleteSession" : ["sessionID"]
, "getSessionInfo" : ["sessionID"]
, "listSessionsOfGroup" : ["groupID"]
, "listSessionsOfAuthor" : ["authorID"]
, "getText" : ["padID", "rev"]
, "setText" : ["padID", "text"]
, "getHTML" : ["padID", "rev"]
, "setHTML" : ["padID", "html"]
, "getRevisionsCount" : ["padID"]
, "getLastEdited" : ["padID"]
, "deletePad" : ["padID"]
, "getReadOnlyID" : ["padID"]
, "setPublicStatus" : ["padID", "publicStatus"]
, "getPublicStatus" : ["padID"]
, "setPassword" : ["padID", "password"]
, "isPasswordProtected" : ["padID"]
, "listAuthorsOfPad" : ["padID"]
, "padUsersCount" : ["padID"]
}
);
version["1.1"] = Object.assign({}, version["1"],
{ "getAuthorName" : ["authorID"]
, "padUsers" : ["padID"]
, "sendClientsMessage" : ["padID", "msg"]
, "listAllGroups" : []
}
);
version["1.2"] = Object.assign({}, version["1.1"],
{ "checkToken" : []
}
);
version["1.2.1"] = Object.assign({}, version["1.2"],
{ "listAllPads" : []
}
);
version["1.2.7"] = Object.assign({}, version["1.2.1"],
{ "createDiffHTML" : ["padID", "startRev", "endRev"]
, "getChatHistory" : ["padID", "start", "end"]
, "getChatHead" : ["padID"]
}
);
version["1.2.8"] = Object.assign({}, version["1.2.7"],
{ "getAttributePool" : ["padID"]
, "getRevisionChangeset" : ["padID", "rev"]
}
);
version["1.2.9"] = Object.assign({}, version["1.2.8"],
{ "copyPad" : ["sourceID", "destinationID", "force"]
, "movePad" : ["sourceID", "destinationID", "force"]
}
);
version["1.2.10"] = Object.assign({}, version["1.2.9"],
{ "getPadID" : ["roID"]
}
);
version["1.2.11"] = Object.assign({}, version["1.2.10"],
{ "getSavedRevisionsCount" : ["padID"]
, "listSavedRevisions" : ["padID"]
, "saveRevision" : ["padID", "rev"]
, "restoreRevision" : ["padID", "rev"]
}
);
version["1.2.12"] = Object.assign({}, version["1.2.11"],
{ "appendChatMessage" : ["padID", "text", "authorID", "time"]
}
);
version["1.2.13"] = Object.assign({}, version["1.2.12"],
{ "appendText" : ["padID", "text"]
}
);
// set the latest available API version here
exports.latestApiVersion = '1.2.13';
// exports the versions so it can be used by the new Swagger endpoint
exports.version = version;
/**
* Handles a HTTP API call
* @param functionName the name of the called function
* @param fields the params of the called function
* @req express request object
* @res express response object
*/
exports.handle = function(apiVersion, functionName, fields, req, res)
{
//check if this is a valid apiversion
var isKnownApiVersion = false;
for (var knownApiVersion in version) {
if (knownApiVersion == apiVersion) {
isKnownApiVersion = true;
break;
}
}
// say goodbye if this is an unknown API version
if (!isKnownApiVersion) {
res.statusCode = 404;
res.send({code: 3, message: "no such api version", data: null});
return;
}
// check if this is a valid function name
var isKnownFunctionname = false;
for (var knownFunctionname in version[apiVersion]) {
if (knownFunctionname == functionName) {
isKnownFunctionname = true;
break;
}
}
// say goodbye if this is an unknown function
if (!isKnownFunctionname) {
res.send({code: 3, message: "no such function", data: null});
return;
}
// check the api key!
fields["apikey"] = fields["apikey"] || fields["api_key"];
if (fields["apikey"] !== apikey.trim()) {
res.statusCode = 401;
res.send({code: 4, message: "no or wrong API Key", data: null});
return;
}
// sanitize any padIDs before continuing
if (fields["padID"]) {
padManager.sanitizePadId(fields["padID"], function(padId) {
fields["padID"] = padId;
callAPI(apiVersion, functionName, fields, req, res);
});
} else if (fields["padName"]) {
padManager.sanitizePadId(fields["padName"], function(padId) {
fields["padName"] = padId;
callAPI(apiVersion, functionName, fields, req, res);
});
} else {
callAPI(apiVersion, functionName, fields, req, res);
}
}
// calls the api function
function callAPI(apiVersion, functionName, fields, req, res)
{
// put the function parameters in an array
var functionParams = version[apiVersion][functionName].map(function (field) {
return fields[field]
});
// add a callback function to handle the response
functionParams.push(function(err, data) {
if (err == null) {
// no error happened, everything is fine
if (!data) {
data = null;
}
res.send({code: 0, message: "ok", data: data});
} else if (err.name == "apierror") {
// parameters were wrong and the api stopped execution, pass the error
res.send({code: 1, message: err.message, data: null});
} else {
// an unknown error happened
res.send({code: 2, message: "internal error", data: null});
ERR(err);
}
});
// call the api function
api[functionName].apply(this, functionParams);
}