Skip to content
This repository has been archived by the owner on Nov 9, 2017. It is now read-only.

Commit

Permalink
Opt-in to the realpathSync/statSync change via --no-resolve-symlinks …
Browse files Browse the repository at this point in the history
…flag.
  • Loading branch information
Daniel Stockman committed Oct 23, 2013
1 parent 51e1b24 commit 5ec3ed3
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 105 deletions.
5 changes: 5 additions & 0 deletions lib/args.js
Expand Up @@ -14,6 +14,7 @@ var knownOpts = {
"pids": path,
"port": Number,
"restart": Boolean,
"resolveSymlinks": Boolean,
"root": [String, Array],
"rootsFile": path,
"server": path,
Expand All @@ -26,6 +27,7 @@ var knownOpts = {
};

var shortHands = {
"--no-resolve-symlinks": ["--no-resolveSymlinks"],
"h": ["--help"],
"v": ["--version"],
"a": ["--server"],
Expand Down Expand Up @@ -66,6 +68,9 @@ exports = module.exports = {
msg.push(" Set this to `0` to expire immediately, `null` to omit these");
msg.push(" headers entirely.");
msg.push("");
msg.push(" --no-resolve-symlinks");
msg.push(" If passed, rootPaths that are symlinks will not be resolved (the default).");
msg.push("");
msg.push("Cluster Options:");
msg.push(" --cluster Enable clustering of server across multiple processes.");
msg.push(" -d, --pids Directory where pidfiles are stored. [$PREFIX/var/run]");
Expand Down
2 changes: 1 addition & 1 deletion lib/combohandler.js
Expand Up @@ -44,7 +44,7 @@ exports.combine = function (config) {
// Intentionally using the sync method because this only runs when the
// middleware is initialized, and we want it to throw if there's an
// error.
rootPathResolved = resolvePathSync(config.rootPath);
rootPathResolved = resolvePathSync(config.rootPath, config.resolveSymlinks);
}

function combineMiddleware(req, res, next) {
Expand Down
9 changes: 5 additions & 4 deletions lib/middleware/dynamicPath.js
Expand Up @@ -43,7 +43,7 @@ exports = module.exports = function (options) {
}

// cache config object used in middleware
dynamicPathMiddleware.CONFIG = parseConfig(options.rootPath);
dynamicPathMiddleware.CONFIG = parseConfig(options.rootPath, options.resolveSymlinks);

return dynamicPathMiddleware;
};
Expand Down Expand Up @@ -102,15 +102,16 @@ function getDynamicKeys(rootPath) {
Create a config object for use in dynamicPathMiddleware.
@method parseConfig
@param {String} rootPatha
@param {String} rootPath
@param {Boolean} resolveSymlinks
@return {Object} config
@property {String} config.rootPath
@property {String} config.dynamicParam
@property {String} config.rootSuffixes
@property {String} config.statCache
@private
**/
function parseConfig(rootPath) {
function parseConfig(rootPath, resolveSymlinks) {
rootPath = path.normalize(rootPath);

var dynamicKeys = getDynamicKeys(rootPath);
Expand Down Expand Up @@ -144,7 +145,7 @@ function parseConfig(rootPath) {

// Intentionally using the sync method because this only runs when the
// middleware is initialized, and we want it to throw if there's an error.
rootPath = resolvePathSync(rootPath);
rootPath = resolvePathSync(rootPath, resolveSymlinks);

return {
"rootPath" : rootPath,
Expand Down
12 changes: 9 additions & 3 deletions lib/utils.js
Expand Up @@ -43,12 +43,18 @@ function memoize(source) {
};
}

function resolvePathSync(rootPath) {
// Turns out fs.realpathSync was always defaulting empty strings to cwd().
function resolvePathSync(rootPath, resolveSymlinks) {
// Turns out fs.realpathSync always defaults empty strings to cwd().
rootPath = rootPath || process.cwd();

// Intentionally using the sync method because this only runs when the
// middleware is initialized, and we want it to throw if there's an error.
fs.statSync(rootPath);
if (resolveSymlinks !== false) {
rootPath = fs.realpathSync(rootPath);
} else {
fs.statSync(rootPath);
}

// The resulting rootPath should always have a trailing slash.
return path.normalize(rootPath + path.sep);
}
237 changes: 140 additions & 97 deletions test/server.js
Expand Up @@ -674,47 +674,64 @@ describe('combohandler', function () {
].join('\n');
var SIMPLE_RAW = SIMPLE_IMPORTS_RAW + SIMPLE_URLS_RAW;

describe("route with fully-qualified dynamic path", function () {
before(function () {
var combined = combo.combine({
webRoot : COMPLEX_ROOT,
rootPath: COMPLEX_ROOT + '/versioned/:version/base/'
});
function dynamicFiletree(opts) {
var expectedRelativePath = opts.relativePath || "js/a.js";
var expectedResolvedPath = path.join(COMPLEX_ROOT, opts.realPath, expectedRelativePath);
var expectedRootPath = path.join(COMPLEX_ROOT, opts.rootPath);

app.get("/c/:version/fs-fq", combined, function (req, res, next) {
var rootPath = res.locals.rootPath;
rootPath.should.equal(path.join(COMPLEX_ROOT, '/versioned/deeper/base/'));
next();
}, combo.respond);
return function (req, res, next) {
var rootPath = res.locals.rootPath;
rootPath.should.equal(expectedRootPath);

app.get("/c/:version/ln-fq", combined, function (req, res, next) {
var rootPath = res.locals.rootPath;
rootPath.should.equal(path.join(COMPLEX_ROOT, '/versioned/shallower/base/'));
var relativePath = res.locals.relativePaths[0];
relativePath.should.equal(expectedRelativePath);

var relativePath = res.locals.relativePaths[0];
relativePath.should.equal('js/a.js');
fs.realpath(path.join(rootPath, relativePath), function (err, resolved) {
assert.ifError(err);
resolved.should.equal(expectedResolvedPath);
next();
});
};
}

fs.realpath(path.join(rootPath, relativePath), function (err, resolved) {
assert.ifError(err);
resolved.should.equal(path.join(COMPLEX_ROOT, '/base/', relativePath));
next();
});
}, combo.respond);
function dynamicSymlinks(opts) {
var expectedTemplateFile = opts.template || TEMPLATE_SIMPLE;
var expectedRelativePath = opts.relativePath || "css/urls/simple.css";
var expectedResolvedBody = expectedTemplateFile.replace(/__ROOT__/g, opts.rootPath);

app.get("/c/:version/fq-noimports", combined, function (req, res, next) {
var rootPath = res.locals.rootPath;
rootPath.should.equal(path.join(COMPLEX_ROOT, '/versioned/shallower/base/'));
return function (req, res, next) {
var relativePath = res.locals.relativePaths[0];
relativePath.should.equal(expectedRelativePath);

var relativePath = res.locals.relativePaths[0];
relativePath.should.equal('css/urls/simple.css');
// console.error(res.body);
res.body.should.equal(expectedResolvedBody);

// console.error(res.body);
var expected = (SIMPLE_IMPORTS_RAW + TEMPLATE_URLS_SIMPLE)
.replace(/__ROOT__/g, '/versioned/shallower/base/');
res.body.should.equal(expected);
next();
};
}

next();
}, combo.respond);
describe("route with fully-qualified dynamic path", function () {
before(function () {
var combined = combo.combine({
webRoot : COMPLEX_ROOT,
rootPath: COMPLEX_ROOT + '/versioned/:version/base/'
});

app.get("/c/:version/fs-fq", combined, dynamicFiletree({
realPath: "/versioned/deeper/base/",
rootPath: "/versioned/deeper/base/"
}), combo.respond);

app.get("/c/:version/ln-fq", combined, dynamicFiletree({
realPath: "/base/",
rootPath: "/versioned/shallower/base/"
}), combo.respond);

app.get("/c/:version/fq-noimports", combined, dynamicSymlinks({
template: SIMPLE_IMPORTS_RAW + TEMPLATE_URLS_SIMPLE,
realPath: "/versioned/shallower/base/",
rootPath: "/versioned/shallower/base/"
}), combo.respond);
});

it("should read rootPath from filesystem directly", assertResponds({
Expand All @@ -732,91 +749,117 @@ describe('combohandler', function () {

describe("route with one-sided dynamic path", function () {
describe("and rootPath symlinked shallower", function () {
before(function () {
var combined = combo.combine({
rewriteImports: true,
webRoot : COMPLEX_ROOT,
rootPath: COMPLEX_ROOT + '/versioned/shallower/base/'
describe("when resolveSymlinks is true", function () {
before(function () {
var resolved = combo.combine({
rewriteImports: true,
webRoot : COMPLEX_ROOT,
rootPath: COMPLEX_ROOT + '/versioned/shallower/base/'
});

app.get("/r/:version/fs-shallow", resolved, dynamicFiletree({
realPath: "/base/",
rootPath: "/base/"
}), combo.respond);

app.get("/r/:version/ln-shallow", resolved, dynamicSymlinks({
rootPath: "/base/"
}), combo.respond);
});

app.get("/c/:version/fs-shallow", combined, function (req, res, next) {
var rootPath = res.locals.rootPath;
rootPath.should.equal(path.join(COMPLEX_ROOT, '/versioned/shallower/base/'));
it("should resolve files from realpath in filesystem", assertResponds({
path: "/r/cafebabe/fs-shallow?js/a.js&js/b.js"
}));

var relativePath = res.locals.relativePaths[0];
relativePath.should.equal('js/a.js');
it("should rewrite url() through symlink", assertResponds({
path: "/r/cafebabe/ln-shallow?css/urls/simple.css"
}));
});

fs.realpath(path.join(rootPath, relativePath), function (err, resolved) {
assert.ifError(err);
resolved.should.equal(path.join(COMPLEX_ROOT, '/base/', relativePath));
next();
describe("when resolveSymlinks is false", function () {
before(function () {
var symlinkd = combo.combine({
rewriteImports: true,
resolveSymlinks: false,
webRoot : COMPLEX_ROOT,
rootPath: COMPLEX_ROOT + '/versioned/shallower/base/'
});
}, combo.respond);

app.get("/c/:version/ln-shallow", combined, function (req, res, next) {
var relativePath = res.locals.relativePaths[0];
relativePath.should.equal('css/urls/simple.css');
app.get("/s/:version/fs-shallow", symlinkd, dynamicFiletree({
realPath: "/base/",
rootPath: "/versioned/shallower/base/"
}), combo.respond);

// console.error(res.body);
var expected = TEMPLATE_SIMPLE
.replace(/__ROOT__/g, '/versioned/shallower/base/');
res.body.should.equal(expected);

next();
}, combo.respond);
});
app.get("/s/:version/ln-shallow", symlinkd, dynamicSymlinks({
rootPath: "/versioned/shallower/base/"
}), combo.respond);
});

it("should resolve files from symlink in filesystem", assertResponds({
path: "/c/cafebabe/fs-shallow?js/a.js&js/b.js"
}));
it("should resolve files from symlink in filesystem", assertResponds({
path: "/s/cafebabe/fs-shallow?js/a.js&js/b.js"
}));

it("should rewrite url() through symlink", assertResponds({
path: "/c/cafebabe/ln-shallow?css/urls/simple.css"
}));
it("should rewrite url() using symlink", assertResponds({
path: "/s/cafebabe/ln-shallow?css/urls/simple.css"
}));
});
});

describe("and rootPath symlinked deeper", function () {
before(function () {
var combined = combo.combine({
rewriteImports: true,
webRoot : COMPLEX_ROOT,
rootPath: COMPLEX_ROOT + '/deep-link/'
describe("when resolveSymlinks is true", function () {
before(function () {
var resolved = combo.combine({
rewriteImports: true,
webRoot : COMPLEX_ROOT,
rootPath: COMPLEX_ROOT + '/deep-link/'
});

app.get("/r/:version/fs-deeper", resolved, dynamicFiletree({
realPath: "/versioned/deeper/base/",
rootPath: "/versioned/deeper/base/"
}), combo.respond);

app.get("/r/:version/ln-deeper", resolved, dynamicSymlinks({
rootPath: "/versioned/deeper/base/"
}), combo.respond);
});

app.get("/c/:version/fs-deeper", combined, function (req, res, next) {
var rootPath = res.locals.rootPath;
rootPath.should.equal(path.join(COMPLEX_ROOT, '/deep-link/'));
it("should read rootPath from filesystem directly", assertResponds({
path: "/r/cafebabe/fs-deeper?js/a.js&js/b.js"
}));

var relativePath = res.locals.relativePaths[0];
relativePath.should.equal('js/a.js');
it("should *still* rewrite url() through symlink", assertResponds({
path: "/r/cafebabe/ln-deeper?css/urls/simple.css"
}));
});

fs.realpath(path.join(rootPath, relativePath), function (err, resolved) {
assert.ifError(err);
resolved.should.equal(path.join(COMPLEX_ROOT, '/versioned/deeper/base/', relativePath));
next();
describe("when resolveSymlinks is false", function () {
before(function () {
var symlinkd = combo.combine({
rewriteImports: true,
resolveSymlinks: false,
webRoot : COMPLEX_ROOT,
rootPath: COMPLEX_ROOT + '/deep-link/'
});
}, combo.respond);

app.get("/c/:version/ln-deeper", combined, function (req, res, next) {
var relativePath = res.locals.relativePaths[0];
relativePath.should.equal('css/urls/simple.css');
app.get("/s/:version/fs-deeper", symlinkd, dynamicFiletree({
realPath: "/versioned/deeper/base/",
rootPath: "/deep-link/"
}), combo.respond);

// console.error(res.body);
var expected = TEMPLATE_SIMPLE
.replace(/__ROOT__/g, '/deep-link/');
res.body.should.equal(expected);

next();
}, combo.respond);
});
app.get("/s/:version/ln-deeper", symlinkd, dynamicSymlinks({
rootPath: "/deep-link/"
}), combo.respond);
});

it("should read rootPath from filesystem directly", assertResponds({
path: "/c/cafebabe/fs-deeper?js/a.js&js/b.js"
}));
it("should read rootPath from symlink in filesystem", assertResponds({
path: "/s/cafebabe/fs-deeper?js/a.js&js/b.js"
}));

it("should *still* rewrite url() through symlink", assertResponds({
path: "/c/cafebabe/ln-deeper?css/urls/simple.css"
}));
it("should *still* rewrite url() using symlink", assertResponds({
path: "/s/cafebabe/ln-deeper?css/urls/simple.css"
}));
});
});
});
});
Expand Down

0 comments on commit 5ec3ed3

Please sign in to comment.