" + sApp + ' v' + sVer + ' ' + usr.formatDate("Y-m-d H:i:s") + ' ' + sRemoteIP + ' ' + sDataFile + "
\n"; sDataFile = path.join(sServerRoot, sDataFile); fs.writeFile(sDataFile, sData); var sTypeFile = path.join(sServerRoot, "/logs/" + sType + "s.html"); fs.appendFile(sTypeFile, sReport); res.status(200).send(ReportAPI.RES.OK); return true; } return false; }; /** * processUserAPI(req, res) * * @param {Object} req is an Express request object (http://expressjs.com/api.html#req.params) * @param {Object} res is an Express response object (http://expressjs.com/api.html#res.status) * @return {boolean} true if the request was handled as an API request, false if not */ HTTPAPI.processUserAPI = function(req, res) { var reqParms = req.method == "GET"? req.query : req.body; var sReq = reqParms[UserAPI.QUERY.REQ]; var sUser = reqParms[UserAPI.QUERY.USER]; var sState = reqParms[UserAPI.QUERY.STATE]; var sData = reqParms[UserAPI.QUERY.DATA]; HTMLOut.logDebug('HTTPAPI.processUserAPI("' + sUser + '"): req=' + sReq + (sState? (', state=' + sState) : '')); if (sUser) { switch(sReq) { case UserAPI.REQ.CREATE: if (HTTPAPI.createUserID(sUser, res)) return true; break; case UserAPI.REQ.VERIFY: if (HTTPAPI.verifyUserID(sUser, res)) return true; break; case UserAPI.REQ.STORE: if (HTTPAPI.storeUserData(sUser, sState, sData, res)) return true; break; case UserAPI.REQ.LOAD: if (HTTPAPI.loadUserData(sUser, sState, res)) return true; break; default: break; } } return false; }; /** * createUserID(sUser, res) * * sUser must consist of the authorizing key, a colon, and the key to be authorized. * * @param {string} sUser * @param {Object} res is an Express response object (http://expressjs.com/api.html#res.status) * @return {boolean} true if request was valid (does not imply success), false if invalid */ HTTPAPI.createUserID = function(sUser, res) { HTMLOut.logDebug('HTTPAPI.createUserID("' + sUser + '")'); var asUsers = sUser.split(':'); if (asUsers[0] && asUsers[1]) { HTTPAPI.verifyUserID(asUsers[0], res, function doneVerifyAuthorizedID(iVerified, result, res) { HTMLOut.logDebug('HTTPAPI.doneVerifyAuthorizedID("' + asUsers[0] + '"): ' + iVerified); /* * The authorizing key has been verified, but it may authorize another key only * if it is the first key in the list (or there are no keys at all yet and the authorizing * key matches GORT_COMMAND). */ if (iVerified == 1 || iVerified <= 0 && asUsers[0] == net.GORT_COMMAND) { HTTPAPI.verifyUserID(asUsers[1], res, function doneVerifyUserID(iVerified, resultSecond, res) { HTMLOut.logDebug('HTTPAPI.doneVerifyUserID("' + asUsers[1] + '"): ' + iVerified); if (iVerified <= 0) { var sUserFile = path.join(sServerRoot, "/logs/users.log"); fs.appendFile(sUserFile, asUsers[1] + "\n"); result[UserAPI.RES.CODE] = UserAPI.CODE.OK; result[UserAPI.RES.DATA] = asUsers[1]; } else { result[UserAPI.RES.CODE] = UserAPI.CODE.FAIL; result[UserAPI.RES.DATA] = UserAPI.FAIL.DUPLICATE; } if (res) res.status(200).send(JSON.stringify(result) + "\n"); }); return; } result[UserAPI.RES.CODE] = UserAPI.CODE.FAIL; result[UserAPI.RES.DATA] = UserAPI.FAIL.VERIFY; if (res) res.status(200).send(JSON.stringify(result) + "\n"); }); return true; } return false; }; /** * verifyUserID(sUser, res, done) * * If a done() handler is specified, the first parameter it receives is iVerified, which * will be -1 if the "users.log" file hasn't been initialized yet, 0 if the key doesn't exist, * or a positive number representing the line number at which the key appears. * * Moreover, when a done() handler is provided, it simply passes the response object (res) to * done(), which must actually send the response, based on the provided result; this function sends * a response only if done() is NOT provided. * * @param {string} sUser * @param {Object} res is an Express response object (http://expressjs.com/api.html#res.status) * @param {function(number, Object, Object)} [done] * @return {boolean} true if request was valid (does not imply success), false if invalid */ HTTPAPI.verifyUserID = function(sUser, res, done) { HTMLOut.logDebug('HTTPAPI.verifyUserID("' + sUser + '")'); /* * If a colon separator is present, this is an implicit REQ.CREATE call * (in fact, at present, PCjs does not issue any explicit REQ.CREATE calls). */ if (sUser.indexOf(':') > 0 && HTTPAPI.createUserID(sUser, res)) { return true; } var iVerified = -1; var sUserFile = path.join(sServerRoot, "/logs/users.log"); fs.readFile(sUserFile, {encoding: "utf8"}, function doneReadUserIDs(err, sData) { var sResCode = UserAPI.CODE.FAIL; var sResData = UserAPI.FAIL.VERIFY; if (err) { HTMLOut.logError(err); } else { var asUsers = sData.split("\n"); iVerified = asUsers.indexOf(sUser); if (iVerified >= 0) { if (HTTPAPI.createUserDir(sUser)) { sResCode = UserAPI.CODE.OK; sResData = sUser; } } iVerified++; } var result = {}; result[UserAPI.RES.CODE] = sResCode; result[UserAPI.RES.DATA] = sResData; if (done) { done(iVerified, result, res); return; } if (res) res.status(200).send(JSON.stringify(result) + "\n"); }); return true; }; /** * getUserDir(sUser) * * @param {string} sUser * @return {string} */ HTTPAPI.getUserDir = function(sUser) { return path.join(sServerRoot, "/logs/users/" + /* sUser.substr(0, 2) + "/" + */ sUser); }; /** * createUserDir(sUser) * * TODO: Creation is relatively rare, so I'm lazy and use synchronous calls, but fix this someday. * * @param {string} sUser * @return {boolean} true if successful, false if not */ HTTPAPI.createUserDir = function(sUser) { var sDir = HTTPAPI.getUserDir(sUser); return (fs.existsSync(sDir) || !!mkdirp.sync(sDir)); }; /** * verifyUserDir(sUser, done) * * @param {string} sUser * @param {function(string|null)} done */ HTTPAPI.verifyUserDir = function(sUser, done) { HTMLOut.logDebug('HTTPAPI.verifyUserDir("' + sUser + '")'); if (sUser) { var sDir = HTTPAPI.getUserDir(sUser); fs.exists(sDir, function(fExists) { if (!fExists) { HTTPAPI.verifyUserID(sUser, null, function(iVerified, result, res) { done(iVerified > 0? sDir : null); }); return; } done(sDir); }); return; } done(null); }; /** * loadUserData(sUser, sState, res) * * @param {string} sUser * @param {string} sState is a state ID * @param {Object} res is an Express response object (http://expressjs.com/api.html#res.status) * @return {boolean} true if request was valid (does not imply success), false if invalid */ HTTPAPI.loadUserData = function(sUser, sState, res) { HTMLOut.logDebug('HTTPAPI.loadUserData("' + sUser + '","' + sState + '")'); if (sState) { HTTPAPI.verifyUserDir(sUser, function(sDir) { var result = {}; result[UserAPI.RES.CODE] = UserAPI.CODE.FAIL; result[UserAPI.RES.DATA] = UserAPI.FAIL.VERIFY; if (sDir) { if (sState.indexOf("..") >= 0) { result[UserAPI.RES.DATA] = UserAPI.FAIL.BADSTATE; } else { var sFile = path.join(sDir, sState); fs.readFile(sFile, {encoding: "utf8"}, function doneReadUserData(err, sData) { if (err) { HTMLOut.logError(err); /* * We'll assume that any error here means the file doesn't exist (ie, NOSTATE instead * of BADLOAD). */ result[UserAPI.RES.DATA] = UserAPI.FAIL.NOSTATE; } else { /* * Suppress the normal RES_CODE+RES_DATA output format and simply return the "raw" state; * this makes life simpler for the client app. */ result = sData; /* * Without the addition of "no-store", Chrome (and perhaps other browsers) will assume that * a previous response to a previously seen URL can be re-used without hitting the server * again, which would be bad if the requested state has been modified in the meantime. * * Here's the scenario: the user loads a web page with a machine that uses server-side state, * the user changes the state of that machine and switches away from the machine (eg, clicks * a link to a different page), which causes the state to be updated on the server; then they * click the Back button, and the browser, seeing the same server-side state request, simply * uses the state data it originally retrieved, instead of requesting the updated state from the * server. */ res.set("Cache-Control", "no-cache, no-store"); } res.status(200).send(typeof result == "string"? result : JSON.stringify(result) + "\n"); }); return; } } res.status(200).send(JSON.stringify(result) + "\n"); }); return true; } return false; }; /** * storeUserData(sUser, sState, sData, res) * * @param {string} sUser * @param {string} sState is a state ID * @param {string} sData * @param {Object} res is an Express response object (http://expressjs.com/api.html#res.status) * @return {boolean} true if request was valid (does not imply success), false if invalid */ HTTPAPI.storeUserData = function(sUser, sState, sData, res) { HTMLOut.logDebug('HTTPAPI.storeUserData("' + sUser + '","' + sState + '")'); if (sState && sData) { HTTPAPI.verifyUserDir(sUser, function(sDir) { var result = {}; result[UserAPI.RES.CODE] = UserAPI.CODE.FAIL; result[UserAPI.RES.DATA] = UserAPI.FAIL.VERIFY; if (sDir) { if (sState.indexOf("..") >= 0) { result[UserAPI.RES.DATA] = UserAPI.FAIL.BADSTATE; } else { var sFile = path.join(sDir, sState); fs.writeFile(sFile, sData); result[UserAPI.RES.CODE] = UserAPI.CODE.OK; result[UserAPI.RES.DATA] = sData.length + " bytes stored"; } } res.status(200).send(JSON.stringify(result) + "\n"); }); return true; } return false; }; module.exports = HTTPAPI;