cloudcms-server
Version:
Cloud CMS Application Server Module
1,395 lines (1,191 loc) • 78 kB
JavaScript
var path = require('path');
var http = require('http');
var util = require("../../util/util");
var async = require("async");
var Gitana = require("gitana");
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var mime = require("mime");
var cloudcmsUtil = require("../../util/cloudcms");
var SENTINEL_NOT_FOUND_VALUE = "null";
var logEnabled = true;
/**
* Cloud CMS middleware.
*
* @type {*}
*/
/**
* Looks up the user by username or email.
*
* @param req
* @param username
* @param callback
*/
var findUser = function(req, username, callback)
{
var query = {
"$or": [{
"name": username
}, {
"email": username
}]
};
var domain = req.gitana.datastore("principals");
Chain(domain).queryPrincipals(query).then(function() {
if (this.size() === 0)
{
// If user not found in principals domain, try the primary domain
req.gitana.getPlatform().readPrimaryDomain().then(function() {
this.queryPrincipals(query).then(function() {
if (this.size() === 0) {
return callback({
"message": "Unable to find user for username or email: " + username
});
}
else
{
this.keepOne().then(function() {
callback(null, this);
});
}
});
});
}
else
{
this.keepOne().then(function() {
callback(null, this);
});
}
});
};
passport.use(new LocalStrategy({
passReqToCallback: true
}, function(req, username, password, done) {
var clientKey = req.gitanaConfig.clientKey;
var clientSecret = req.gitanaConfig.clientSecret;
var applicationId = req.gitanaConfig.application;
var baseURL = req.gitanaConfig.baseURL;
// pick the domain that we'll authenticate against
var domainId = req.gitana.datastore("principals").getId();
findUser(req, username, function(err, user) {
if (err) {
return done(null, false, { "message": err.message });
}
if (!user) {
return done(null, false, { "message": "Unable to find user: " + username });
}
// update username to the username of the actual user
username = user.name;
// update domain id in case the user is not in the principals domain
if (user.domainId)
{
domainId = user.domainId;
}
// authenticate to cloud cms
// automatically caches based on ticket
Gitana.connect({
"clientKey": clientKey,
"clientSecret": clientSecret,
"application": applicationId,
"username": domainId + "/" + username,
"password": password,
"baseURL": baseURL,
"invalidatePlatformCache": true
}, function(err) {
if (err) {
return done(null, false, err);
}
// authentication was successful!
// auth info
var authInfo = this.platform().getDriver().getAuthInfo();
// ticket
var ticket = authInfo.getTicket();
// user object
var user = {
"id": authInfo.getPrincipalId(),
"domainId": authInfo.getPrincipalDomainId(),
"name": authInfo.getPrincipalName(),
"firstName": authInfo["user"]["firstName"],
"middleName": authInfo["user"]["middleName"],
"lastName": authInfo["user"]["lastName"]
};
// construct full name
var fullName = null;
if (user.firstName)
{
fullName = user.firstName;
if (user.lastName) {
fullName += " " + user.lastName;
}
}
if (!fullName) {
fullName = user.name;
}
user.fullName = fullName;
done(null, user, {
"ticket": ticket,
"user": user,
"test": 1
});
});
});
}
));
////////////////////////////////////////////////////////////////////////////
//
// INTERFACE METHODS
//
////////////////////////////////////////////////////////////////////////////
exports = module.exports = function()
{
var handleErrorMessage = function(req, res, errorMessage)
{
if (req.flash)
{
// stores error message into "flash" session memory
req.flash("info", errorMessage);
}
var failureUrl = req.query["failureUrl"];
if (failureUrl)
{
// redirect
//res.redirect(failureUrl);
req.session.save(function(){
res.redirect(failureUrl);
});
}
else
{
// otherwise, send JSON response
util.status(res, 503);
var body = {
"ok": false
};
if (errorMessage)
{
body.message = errorMessage;
}
res.send(body);
}
};
var handleLogin = function(req, res, next)
{
var successUrl = req.query["successUrl"];
var failureUrl = req.query["failureUrl"];
var options = {
//session: false
};
passport.authenticate("local", options, function(err, user, info) {
var errorMessage = null;
if (err)
{
process.log("ERROR DURING AUTHENTICATION: " + err + ", text: " + JSON.stringify(err));
errorMessage = "There was a problem logging in, please contact your system administrator";
}
if (!user)
{
if (info && info.message)
{
errorMessage = info.message;
}
else
{
errorMessage = "There was a problem logging in, please contact your system administrator. User not found.";
}
}
// if there was an error, respond thusly
if (errorMessage)
{
return handleErrorMessage(req, res, errorMessage);
}
// otherwise, we're ok
// info contains the "GITANA_COOKIE" that we handle back as a SSO token
// it should be sent over in the GITANA_COOKIE or a "GITANA_TICKET" header on every follow-on request
var ticket = info.ticket;
var user = info.user;
// convert to a regular old JS object to be compatible with session serialization
user = util.clone(user, true);
// try to log in to cloud cms with this user
req.logIn(user, function(err) {
if (err)
{
// failed to log in...
process.log("ERROR DURING LOGIN: " + err + ", text: " + JSON.stringify(err));
errorMessage = "There was a problem logging in, please contact your system administrator";
return handleErrorMessage(req, res, errorMessage);
}
var config = process.configuration || {};
config.auth = config.auth || {};
if (successUrl)
{
if(config.auth.passTicket)
{
res.redirect(successUrl + "?ticket=" + ticket);
}
else
{
res.reoirect(successUrl);
}
}
else
{
// otherwise, send JSON response
util.status(res, 200);
var result = {
"ok": true,
"user": user
};
if (config.auth.passTicket) {
result.ticket = ticket;
}
res.send(result);
}
});
})(req, res, next);
};
var handleLogout = function(req, res, next)
{
var logoutStrategiesFn = function(req, res, finished)
{
// if we have authentication strategies configured, walk each one
// if a strategy has an authenticator, then invoke it's logout function
var authenticationService = require("../authentication/authentication");
authenticationService.buildStrategies(req, function (err, strategyResults) {
if (err || !strategyResults) {
return finished();
}
var fns = [];
for (var strategyId in strategyResults)
{
var authenticator = strategyResults[strategyId].authenticator;
if (authenticator)
{
var fn = function(req, res, strategyId, authenticator) {
return function(done) {
process.log("Logging out for strategy: " + strategyId);
authenticator.logout(req, res, function () {
done();
});
}
}(req, res, strategyId, authenticator);
fns.push(fn);
}
}
async.series(fns, function() {
finished();
});
});
};
logoutStrategiesFn(req, res, function(err) {
var redirectUri = req.query["redirectUri"];
if (!redirectUri)
{
redirectUri = req.query["redirectURI"];
}
if (!redirectUri)
{
redirectUri = req.query["redirect"];
}
// throw this in here for safe measure
if (req.logout)
{
req.logout();
}
var ticket = req.query["ticket"];
if (ticket)
{
Gitana.disconnect(ticket);
}
// redirect?
if (redirectUri)
{
return res.redirect(redirectUri);
}
util.status(res, 200);
res.send({
"ok": true
});
});
};
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// RESULTING OBJECT
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
var r = {};
/**
* Determines which gitana repository to use in future operations.
*
* @return {Function}
*/
r.repositoryInterceptor = function()
{
return util.createInterceptor("repository", function(req, res, next, stores, cache, configuration) {
if (req.gitana && req.gitana.datastore)
{
var repository = req.gitana.datastore("content");
if (repository)
{
req.repositoryId = repository.getId();
// helper function
req.repository = function(repository) {
return function(callback) {
callback(null, repository);
};
}(repository);
}
}
if (!req.repository) {
req.repository = function(callback){
callback();
};
}
next();
});
};
// store a cache of branches since we don't want to reload these every time
var CACHED_BRANCHES = {};
var _branch_key = function(repository, branchId) {
return repository._doc + "_" + branchId;
};
var _load_branch = function(repository, branchId, callback)
{
var cacheKey = _branch_key(repository, branchId);
var branch = CACHED_BRANCHES[cacheKey];
if (branch) {
return callback(null, Chain(branch));
}
var loadFn = function(finished) {
Chain(repository).trap(function(e) {
// unable to load branch!
process.log("Unable to load branch: " + repository._doc + "/" + branchId + ", err: " + e);
process.log(e);
finished({
"message": "Unable to load branch: " + repository._doc + "/" + branchId
});
return false;
}).readBranch(branchId).then(function() {
finished(null, this);
});
};
loadFn(function(err, branch) {
if (err || !branch)
{
// try again...
return loadFn(function(err, branch) {
if (err) {
return callback(err);
}
// success!
// store in cache
CACHED_BRANCHES[cacheKey] = branch;
return callback(null, branch);
});
}
// store in cache
CACHED_BRANCHES[cacheKey] = branch;
// do the callback
return callback(null, branch);
});
};
var _invalidate_branch = function(repository, branchId)
{
var cacheKey = _branch_key(repository, branchId);
delete CACHED_BRANCHES[cacheKey];
};
/**
* Allows for branch switching via request parameter.
*
* @return {Function}
*/
r.branchInterceptor = function()
{
return util.createInterceptor("branch", function(req, res, next, stores, cache, configuration) {
if (req.gitana)
{
req.application(function(err, application) {
var branchCookieName = null;
var branchCookieId = null;
if (!err && application)
{
// if cookies are parsed ahead of interceptor, check them...
if (req.cookies)
{
branchCookieName = "cloudcms-server-application-" + application.getId() + "-branch-id";
branchCookieId = req.cookies[branchCookieName];
}
}
// pick which branch
var branchId = req.query["branch"];
if (!branchId)
{
branchId = req.query["branchId"];
}
if (!branchId)
{
branchId = req.header("CLOUDCMS_BRANCH");
}
if (!branchId)
{
branchId = req.header("BRANCH");
}
if (!branchId)
{
branchId = req.header("branchId");
}
if (!branchId)
{
// does the runtime tell us which branch to use?
if (req.runtime && req.runtime.branchId)
{
branchId = req.runtime.branchId;
}
}
if (!branchId)
{
// allow for the branch to specified via an environment parameter
if (process.env.CLOUDCMS_BRANCH_ID)
{
branchId = process.env.CLOUDCMS_BRANCH_ID;
}
}
if (!branchId)
{
// allow for the branch to specified via an environment parameter
if (process.env.CLOUDCMS_RUNTIME_BRANCH_ID)
{
branchId = process.env.CLOUDCMS_RUNTIME_BRANCH_ID;
}
}
if (!branchId)
{
branchId = branchCookieId;
}
// fallback to master if no other choice
if (!branchId)
{
branchId = "master";
}
// allow value to be forced?
if (process.env.CLOUDCMS_RUNTIME_BRANCH_ID)
{
branchId = process.env.CLOUDCMS_RUNTIME_BRANCH_ID;
}
req.branchId = branchId;
// write a cookie down to store branch ID if it changed
if (branchId !== branchCookieId)
{
if (branchCookieName)
{
util.setCookie(req, res, branchCookieName, req.branchId);
// legacy cleanup
util.clearCookie(res, "cloudcms-server-branch-id");
}
}
// declare the helper function
req.branch = function()
{
var _branch = null;
return function(callback)
{
// if we already have a cached branch on this request, just hand that back
if (_branch)
{
return callback(null, Chain(_branch));
}
// load the branch, first get the repository
req.repository(function(err, repository) {
if (err) {
process.log("Attempting to load branch, could not load repository, err: " + err);
process.log(err);
return callback({
"message": "Attempting to load branch, failed to load repository for branch: " + req.branchId
});
}
// now load the branch with a sync lock
_load_branch(repository, req.branchId, function(err, branch) {
if (err)
{
// make sure to remove cookie
var cookieName = "cloudcms-server-application-" + req.applicationId + "-branch-id";
util.clearCookie(res, cookieName);
return callback(err);
}
_branch = branch;
callback(null, _branch);
});
});
}
}();
});
}
if (!req.branch) {
req.branch = function(callback){
callback();
};
}
next();
});
};
/**
* Determines which gitana domain to use in future operations.
*
* @return {Function}
*/
r.domainInterceptor = function()
{
return util.createInterceptor("domain", function(req, res, next, stores, cache, configuration) {
if (req.gitana && req.gitana.datastore)
{
var domain = req.gitana.datastore("principals");
if (domain)
{
req.domainId = domain.getId();
}
// helper function
req.principalsDomain = function(callback)
{
callback(null, Chain(domain));
};
}
if (!req.principalsDomain) {
req.principalsDomain = function(callback){
callback();
};
}
next();
});
};
/**
* Determines which gitana domain to use in future operations.
*
* @return {Function}
*/
r.applicationInterceptor = function()
{
return util.createInterceptor("application", function(req, res, next, stores, cache, configuration) {
if (req.gitana && req.gitana.datastore)
{
var application = req.gitana.application();
if (application)
{
req.applicationId = application.getId();
}
// helper function
req.application = function(application) {
return function (callback) {
callback(null, application);
};
}(application);
}
if (!req.application) {
req.application = function(callback){
callback();
};
}
next();
});
};
var CACHED_APP_SETTINGS_TTL = 5 * 60 * 1000; // five minutes
var CACHED_APP_SETTINGS = {};
var CACHED_APP_SETTINGS_TIMESTAMPS = {};
r.applicationSettingsInterceptor = function()
{
return util.createInterceptor("applicationSettings", function(req, res, next, stores, cache, configuration) {
if (req.gitana && req.gitana.application)
{
var application = req.gitana.application();
if (application)
{
// helper function
req.applicationSettings = function (req, application) {
return function (callback) {
var cacheKey = cacheSettingsKey(application.ref(), "application", "application");
var nowMs = Date.now();
var timestamp = CACHED_APP_SETTINGS_TIMESTAMPS[cacheKey];
if (!timestamp || (nowMs - timestamp > CACHED_APP_SETTINGS_TTL))
{
// expire or didn't exist
delete CACHED_APP_SETTINGS[cacheKey];
delete CACHED_APP_SETTINGS_TIMESTAMPS[cacheKey];
}
var x = CACHED_APP_SETTINGS[cacheKey];
if (x)
{
if (x === SENTINEL_NOT_FOUND_VALUE) {
return callback({
"message": "Failed to find application settings"
});
}
return callback(null, Chain(x));
}
Chain(application).trap(function(e){
// store null sentinel
CACHED_APP_SETTINGS[cacheKey] = SENTINEL_NOT_FOUND_VALUE;
CACHED_APP_SETTINGS_TIMESTAMPS[cacheKey] = nowMs;
callback({
"message": "Failed to find application settings"
});
return false;
}).querySettings({
"scope": "application",
"key": "application"
}).keepOne().then(function() {
// store onto cache
CACHED_APP_SETTINGS[cacheKey] = this;
CACHED_APP_SETTINGS_TIMESTAMPS[cacheKey] = nowMs;
// respond
callback(null, this);
});
};
}(req, application);
}
}
// default
if (!req.applicationSettings) {
req.applicationSettings = function(callback) {
callback();
};
}
next();
});
};
/**
* Allows for an in-context menu when connected to Cloud CMS for editing content.
*
* @return {Function}
*/
r.iceInterceptor = function()
{
return function(req, res, next)
{
if (req.gitana)
{
req.ice = true;
}
next();
}
};
/**
* Binds the req.cmsLog(message, level, data) method for use in logging to Cloud CMS.
*
* @return {Function}
*/
r.cmsLogInterceptor = function() {
return util.createInterceptor("cmslog", function(req, res, next, stores, cache, configuration) {
// define function
req.cmsLog = function (message, level, data, callback) {
if (!this.gitana)
{
process.log("Cannot find req.gitana instance, skipping logging");
return;
}
if (typeof(data) === "function")
{
callback = data;
data = {};
}
if (!data)
{
data = {};
}
var obj = {
"data": data
};
this.gitana.platform().createLogEntry(message, level, obj).then(function () {
if (callback)
{
callback();
}
});
};
next();
});
};
/**
* Provides virtualized content retrieval from Cloud CMS.
*
* This handler checks to see if the requested resource is already cached to disk. If not, it makes an attempt
* to retrieve the content from Cloud CMS (and cache to disk).
*
* If nothing found, this handler passes through, allowing other handlers downstream to serve back the content.
*
* URIs may include the following structures:
*
* (preferred)
*
* /static/{filename}?repository={repositoryId}&branch={branchId}&node={nodeId}
* /static/{filename}?repository={repositoryId}&branch={branchId}&path={path}
* /static/{filename}?ref={ref}
*
* /preview/{filename}?repository={repositoryId}&branch={branchId}&node={nodeId}
* /preview/{filename}?repository={repositoryId}&branch={branchId}&path={path}
* /preview/{filename}?ref={ref}
*
* (legacy)
*
* /static/path/{path...}
* /static/node/{nodeId}
* /static/node/{nodeId}/{attachmentId}
* /static/node/{nodeId}/{attachmentId}/{filename}
* /static/repository/{repositoryId}/branch/{branchId}/node/{nodeId}/{attachmentId}
* /static/repository/{repositoryId}/branch/{branchId}/node/{nodeId}/{attachmentId}/{filename}
* /static/repository/{repositoryId}/branch/{branchId}/path/A/B/C/D...
* /static/repository/{repositoryId}/branch/{branchId}?path=/A/B/C/D
*
* /preview/path/{path...}
* /preview/node/{nodeId}
* /preview/node/{nodeId}/{previewId}
* /preview/repository/{repositoryId}/branch/{branchId}/node/{nodeId}/{previewId}
* /preview/repository/{repositoryId}/branch/{branchId}/node/{nodeId}?name={previewId}
* /preview/repository/{repositoryId}/branch/{branchId}/path/A/B/C/D/{previewId}
* /preview/repository/{repositoryId}/branch/{branchId}/{previewId}?path={path}
* /s/{applicationsPath}
*
* And the following flags are supported:
*
* metadata - set to true to retrieve JSON metadata for object
* full - set to true to retrieve JSON recordset data
* attachment - the ID of the attachment ("default")
* force - whether to overwrite saved state
* a - set to true to set Content Disposition response header
*
* For preview, the following are also supported:
*
* name - sets the name of the preview attachment id to be written / cached
* mimetype - sets the desired mimetype of response
* size - for images, sets the width in px of response image
*
* @param directory
* @return {Function}
*/
r.virtualNodeHandler = function()
{
// bind listeners for broadcast events
bindSubscriptions.call(this);
return util.createHandler("virtualNode", null, function(req, res, next, stores, cache, configuration) {
var contentStore = stores.content;
var repositoryId = req.repositoryId;
var branchId = req.branchId;
var locale = req.locale;
var previewId = null;
var gitana = req.gitana;
if (gitana)
{
var offsetPath = req.path;
var virtualizedPath = null;
var virtualizedNode = null;
var virtualizedNodeExtra = null;
var virtualizedUriExtra = null;
var previewPath = null;
var previewNode = null;
var previewUriExtra = null;
if (offsetPath.indexOf("/static/path/") === 0)
{
virtualizedPath = offsetPath.substring(13);
}
else if (offsetPath.indexOf("/static/node/") === 0)
{
virtualizedNode = offsetPath.substring(13);
// trim off anything extra...
var x = virtualizedNode.indexOf("/");
if (x > 0)
{
virtualizedNodeExtra = virtualizedNode.substring(x+1);
virtualizedNode = virtualizedNode.substring(0,x);
}
}
else if (offsetPath.indexOf("/static/repository/") === 0)
{
// examples
// /static/repository/ABC/branch/DEF/node/XYZ
// /static/repository/ABC/branch/DEF/node/XYZ/filename.ext
// /static/repository/ABC/branch/DEF/path/A/B/C/D/E.jpg
var z = offsetPath.substring(19); // ABC/branch/DEF/node/XYZ
// pluck off the repository id
var x1 = z.indexOf("/");
repositoryId = z.substring(0, x1);
// advance to branch
x1 = z.indexOf("/", x1+1);
z = z.substring(x1+1); // DEF/node/XYZ
// pluck off the branch id
x1 = z.indexOf("/");
if (x1 > -1)
{
branchId = z.substring(0, x1);
// advance to "thing" (either node or path)
z = z.substring(x1+1); // node/XYZ or path/1/2/3/4
}
else
{
branchId = z;
z = "";
}
// pluck off the thing
// "node" or "path" or "{filename}
x1 = z.indexOf("/");
var thing = null;
if (x1 > -1) {
thing = z.substring(0, x1);
} else {
thing = z;
}
if (thing === "node")
{
virtualizedNode = z.substring(x1+1);
// trim off anything extra...
var x = virtualizedNode.indexOf("/");
if (x > 0)
{
virtualizedNodeExtra = virtualizedNode.substring(x+1);
virtualizedNode = virtualizedNode.substring(0,x);
}
}
else if (thing === "path")
{
virtualizedPath = z.substring(x1+1);
}
else
{
virtualizedPath = req.query["path"];
}
}
else if (offsetPath.indexOf("/preview/path/") === 0)
{
previewPath = offsetPath.substring(14);
}
else if (offsetPath.indexOf("/preview/node/") === 0)
{
previewNode = offsetPath.substring(14);
// trim off anything extra...
var x = previewNode.indexOf("/");
if (x > 0)
{
previewNode = previewNode.substring(0,x);
// if preview node has "/" in it, then it is "<nodeId>/<filename>"
x1 = previewNode.indexOf("/");
if (x1 > -1) {
previewId = previewNode.substring(x1 + 1);
previewNode = previewNode.substring(0, x1);
}
}
}
else if (offsetPath.indexOf("/preview/repository/") === 0)
{
// examples
// /preview/repository/ABC/branch/DEF/node/XYZ
// /preview/repository/ABC/branch/DEF/path/1/2/3/4
// /preview/repository/ABC/branch/DEF/{previewId}?path={path}
var z = offsetPath.substring(20); // ABC/branch/DEF/node/XYZ
// pluck off the repository id
var x1 = z.indexOf("/");
repositoryId = z.substring(0, x1);
// advance to branch
x1 = z.indexOf("/", x1+1);
z = z.substring(x1+1); // DEF/node/XYZ
// pluck off the branch id
x1 = z.indexOf("/");
branchId = z.substring(0, x1);
// advance to "thing" (either node or path or preview ID)
z = z.substring(x1+1); // node/XYZ or path/1/2/3/4 or {previewId}
// pluck off the thing
// "node" or "path" or "{previewId}
x1 = z.indexOf("/");
var thing = null;
if (x1 > -1) {
thing = z.substring(0, x1);
} else {
thing = z;
}
if (thing === "node")
{
previewNode = z.substring(x1+1);
// if preview node has "/" in it, then it is "<nodeId>/<filename>"
x1 = previewNode.indexOf("/");
if (x1 > -1) {
previewId = previewNode.substring(x1 + 1);
previewNode = previewNode.substring(0, x1);
}
}
else if (thing == "path")
{
previewPath = z.substring(x1+1);
}
else
{
previewId = thing;
previewPath = req.query["path"];
}
}
else if ((offsetPath.indexOf("/static") === 0) || (offsetPath.indexOf("/preview") === 0))
{
var isStatic = (offsetPath.indexOf("/static") === 0);
if (isStatic) {
virtualizedUriExtra = offsetPath.substring(7);
if (virtualizedUriExtra.indexOf("/") === 0) {
virtualizedUriExtra = virtualizedUriExtra.substring(1);
}
}
var isPreview = (offsetPath.indexOf("/preview") === 0);
if (isPreview) {
previewUriExtra = offsetPath.substring(8);
if (previewUriExtra.indexOf("/") === 0) {
previewUriExtra = previewUriExtra.substring(1);
}
}
var _repositoryId = req.query["repository"];
if (_repositoryId)
{
repositoryId = _repositoryId;
}
var _branchId = req.query["branch"];
if (_branchId)
{
branchId = _branchId;
}
var _node = req.query["node"];
if (_node)
{
if (isStatic) {
virtualizedNode = _node;
} else if (isPreview) {
previewNode = _node;
}
}
var _path = req.query["path"];
if (_path)
{
if (isStatic) {
virtualizedPath = _path;
} else if (isPreview) {
previewPath = _path;
}
}
}
// TODO: handle certain mimetypes
// TODO: images, css, html, js?
// virtualized content retrieval
// these urls can have request parameters
//
// "metadata"
// "full"
// "attachment"
// "force"
// "a" (to force content disposition header)
//
// Virtual Path is:
// /static/path/{...path}?options...
//
// Virtual Node is:
// /static/node/{nodeId}?options...
// /static/node/GUID/tommy.jpg?options...
//
if (virtualizedPath || virtualizedNode)
{
// node and path to offset against
var nodePath = null;
var nodeId = null;
if (virtualizedNode) {
nodeId = virtualizedNode;
nodePath = null;
} else if (virtualizedPath) {
nodeId = "root";
nodePath = virtualizedPath;
}
var requestedFilename = null;
var attachmentId = "default";
if (virtualizedNode && virtualizedNodeExtra)
{
attachmentId = virtualizedNodeExtra;
if (attachmentId)
{
// if the attachment id is "a/b" or something with a slash in it
// we keep everything ahead of the slash
var p = attachmentId.indexOf("/");
if (p > -1)
{
requestedFilename = attachmentId.substring(p+1);
attachmentId = attachmentId.substring(0, p);
}
else
{
requestedFilename = attachmentId;
}
}
if (attachmentId)
{
var a = attachmentId.indexOf(".");
if (a > -1)
{
attachmentId = attachmentId.substring(0, a);
}
}
}
// pass in the ?metadata=true parameter to get back the JSON for any Gitana object
// otherwise, the "default" attachment is gotten
if (req.query["metadata"])
{
attachmentId = null;
}
// or override the attachmentId
if (req.query["attachment"])
{
attachmentId = req.query["attachment"];
}
// check whether there is a file matching this uri
if (nodePath && "/" === nodePath) {
nodePath = "index.html";
}
// the cache can be invalidated with either the "force" or "invalidate" request parameters
var forceCommand = req.query["force"] ? req.query["force"] : false;
var invalidateCommand = req.query["invalidate"] ? req.query["invalidate"] : false;
var forceReload = forceCommand || invalidateCommand;
// whether to set content disposition on response
var useContentDispositionResponse = false;
var a = req.query["a"];
if (a === "true") {
useContentDispositionResponse = true;
}
var filename = req.query["filename"];
if (filename) {
useContentDispositionResponse = true;
}
cloudcmsUtil.download(contentStore, gitana, repositoryId, branchId, nodeId, attachmentId, nodePath, locale, forceReload, function(err, filePath, cacheInfo, releaseLock) {
// if the file was found on disk or was downloaded, then stream it back
if (!err && filePath && cacheInfo)
{
if (useContentDispositionResponse)
{
var filename = resolveFilename(req, filePath, cacheInfo, requestedFilename);
contentStore.downloadFile(res, filePath, filename, function(err) {
// something went wrong while streaming the content back...
if (err)
{
util.status(res, 503);
res.send(err);
res.end();
}
releaseLock();
});
}
else
{
util.applyDefaultContentTypeCaching(res, cacheInfo);
//util.applyResponseContentType(res, cacheInfo, filePath);
contentStore.sendFile(res, filePath, cacheInfo, function(err) {
if (err)
{
util.handleSendFileError(req, res, filePath, cacheInfo, req.log, err);
}
releaseLock();
});
}
}
else
{
if (err && err.invalidateGitanaDriver)
{
process.log("Found err.invalidateGitanaDriver true");
if (req.gitanaConfig)
{
// at this point, our gitana driver's auth token was pronounced dead and we need to invalidate
// to get a new one, so blow things away here
// in terms of the current request, it is allowed to do the fallback
// however the next request will go to the gitana.json and attempt to login
// if that fails and virtual driver mode, then a new gitana.json will be pulled down
if (req.gitanaConfig.key)
{
process.log("Disconnecting driver: " + req.gitanaConfig.key);
try
{
Gitana.disconnect(req.gitanaConfig.key);
}
catch (e)
{
}
}
// remove from cache
if (req.virtualHost)
{
process.log("Remove driver cache for virtual host: " + req.virtualHost);
try
{
process.driverConfigCache.invalidate(req.virtualHost, function () {
// all done
});
}
catch (e)
{
}
}
}
}
if (req.query["fallback"])
{
// redirect to the fallback
res.redirect(req.query["fallback"]);
}
else
{
// otherwise, allow other handlers to process this request
next();
}
releaseLock();
}
});
}
else if (previewPath || previewNode)
{
/*
Params are:
"name"
"mimetype"
"size"
"force"
Preview path is:
/preview/path/{...path}?name={name}...rest of options
Preview node is:
/preview/node/{nodeId}?name={name}... rest of options
/preview/node/GUID/tommy.jpg?name={name}... rest of options
*/
// node and path to offset against
var nodePath = null;
var nodeId = null;
if (previewNode) {
nodeId = previewNode;
nodePath = null;
} else if (previewPath) {
nodeId = "root";
nodePath = previewPath;
}
// mimetype (allow null or undefined)
var mimetype = req.query["mimetype"];
// determine attachment id
var attachmentId = "default";
if (req.query["attachment"])
{
attachmentId = req.query["attachment"];
}
var requestedFilename = null;
if (previewId)
{
requestedFilename = previewId;
var p = previewId.indexOf(".");
if (p > -1)
{
var extension = previewId.substring(p + 1);
if (extension)
{
// see if we can determine the requested mimetype from the file extension of the previewId
mimetype = util.lookupMimeType(extension);
//mimetype = mime.lookup(extension);
}
previewId = previewId.substring(0, p);
}
}
if (req.query["name"])
{
previewId = req.query["name"];
}
// note: mimetype can be null or undefined at this