var path = require('path'); var fs = require('fs-ext'); var fsExtra = require('fs-extra'); var utils = require('./utils'); var filetype = require('../content/src/filetype'); var filemeta = require('./filemeta'); var DirCache = require('./dircache').DirCache; var globalRootDirCache = {}; // Do not serve cached content older than 5 minutes. var maxDirCacheAge = 5 * 60 * 1000; // Rebuild cache in background after serving data older than 1 minute. var autoRebuildCacheAge = 1 * 60 * 1000; // Serve at most 600 usernames at a time from root directory. var maxRootDirEntries = 600; function getRootDirCache(dir) { var dircache = globalRootDirCache[dir] if (!dircache) { dircache = new DirCache(dir); globalRootDirCache[dir] = dircache; } return dircache; } exports.handleLoad = function(req, res, app, format) { var filename = utils.filenameFromUri(req); var callback = utils.param(req, 'callback'); var user = res.locals.owner; var origfilename = filename; if (filename == null) { filename = ''; } try { // The root listing, which lists all users, // is handled differently from other requests. var isrootlisting = !user && filename == '' && format == 'json'; if (isrootlisting) { var prefix = utils.param(req, 'prefix') || ''; var count = Math.max(maxRootDirEntries, utils.param(req, 'count') || maxRootDirEntries); // Grab the dir cache object for this root directory path. var dircache = getRootDirCache(app.locals.config.dirs.datadir); if (dircache.age() > maxDirCacheAge) { // A very-old or never-built cache must be rebuilt before serving. dircache.rebuild(sendCachedResult); } else if (prefix) { // When a specific prefix is requested, probe for an exact match. dircache.update(prefix, sendCachedResult); } else { // Fresh cache without prefix: just send the cached result. sendCachedResult(true); } function sendCachedResult(ok) { var data; if (!ok) { data = {error: "Could not read file /"}; } else { data = { directory: "/", list: dircache.readPrefix(prefix, count), auth: false }; // If the cache was sort-of-old, kick off an early rebuild. if (dircache.age() > autoRebuildCacheAge) { // No callback needed. dircache.rebuild(null); } } res.set('Cache-Control', 'must-revalidate'); res.set('Content-Type', 'text/javascript'); res.jsonp(data); } return; } // Validate username if (user) { utils.validateUserName(user); if (filename.length > 0) { filename = path.join(user, filename); } else { filename = user + '/'; } } // Validate filename if (filename.length > 0) { if (!utils.isFileNameValid(filename, false)) { utils.errorExit('Bad filename: ' + filename); } } var absfile = utils.makeAbsolute(filename, app); if (format == 'json') { var haskey = userhaskey(user, app); // Handle the case of a file that's present if (utils.isPresent(absfile, 'file')) { var m = filemeta.parseMetaString( fs.readFileSync(absfile)), data = m.data, meta = m.meta; var statObj = fs.statSync(absfile); var mimetype = filetype.mimeForFilename(filename); res.set('Cache-Control', 'must-revalidate'); resp = { file: '/' + filename, data: data, auth: haskey, mtime: statObj.mtime.getTime(), mime: mimetype }; if (meta != null) { resp.meta = meta; } res.jsonp(resp); return; } // Handle the case of a directory that's present if (utils.isPresent(absfile, 'dir')) { if (filename.length > 0 && filename[filename.length - 1] != '/') { filename += '/'; } var list = buildDirList(absfile, fs.readdirSync(absfile).sort()); var jsonRet = {'directory': '/' + filename, 'list': list, 'auth': haskey}; res.set('Cache-Control', 'must-revalidate'); res.jsonp(jsonRet); return; } // Handle the case of a new file create if (filename.length > 0 && filename[filename.length - 1] != '/' && isValidNewFile(absfile, app)) { res.set('Cache-Control', 'must-revalidate'); res.jsonp({'error': 'could not read file ' + filename, 'newfile': true, 'auth': haskey, 'info': absfile}); return; } utils.errorExit('Could not read file ' + filename); } else if (format == 'code') { // For loading the code only var mt = filetype.mimeForFilename(filename), m = filemeta.parseMetaString( fs.readFileSync(absfile)), data = m.data, meta = m.meta; res.set('Cache-Control', 'must-revalidate'); res.set('Content-Type', (meta && meta.type) || mt.replace(/\bx-pencilcode\b/, 'cofeescript')); res.send(data); return; } else if (format == 'print') { // For printing the code var mt = filetype.mimeForFilename(filename), m = filemeta.parseMetaString( fs.readFileSync(absfile)), data = m.data, meta = m.meta, needline = false, out = []; if (mt.indexOf('text') !== 0) { res.set('Content-Type', mt); res.send(data); return; } res.set('Cache-Control', 'must-revalidate'); res.set('Content-Type', 'text/html;charset=utf-8'); out.push('', '', '
'); // Add a title in the form username: file. out.push('');
if (/\S/.test(data)) {
out.push(addHTMLLineNumbers(data));
needline = true;
}
if (meta && meta.css && /\S/.test(meta.css)) {
if (needline)
out.push('
');
out.push(addHTMLLineNumbers(meta.css));
needline = true;
}
if (meta && meta.html && /\S/.test(meta.html)) {
if (needline)
out.push('
');
out.push(addHTMLLineNumbers(meta.html));
needline = true;
}
out.push('');
out.push('', '');
res.send(out.join('\n'));
return;
}
else if (format == 'run') { // File loading outside the editor
if (utils.isPresent(absfile, 'file')) {
var mt = filetype.mimeForFilename(filename),
m = filemeta.parseMetaString(
fs.readFileSync(absfile));
// For turtle bits, assume it's coffeescript
if (mt.indexOf('text/x-pencilcode') == 0) {
data = filetype.wrapTurtle(m, res.locals.site);
mt = mt.replace('x-pencilcode', 'html');
} else {
data = m.data;
}
res.set('Cache-Control', 'must-revalidate');
res.set('Content-Type', mt);
res.send(data);
return;
}
else if (utils.isPresent(absfile, 'dir') ||
filename.indexOf('/') == filename.length) {
if (filename.length > 0 && filename[filename.length - 1] != '/') {
res.redirect(301, '/home' + req.path + '/');
return;
}
res.set('Cache-Control', 'must-revalidate');
res.set('Content-Type', 'text/html;charset=utf-8');
var text = '\n';
var lastIndex = req.path.lastIndexOf('/');
if (lastIndex > 0) {
var prevIndex = req.path.substring(0, lastIndex).lastIndexOf('/');
if (prevIndex >= 0) {
var parentDir = req.path.substring(0, prevIndex + 1);
text += 'Up to ' + parentDir;
text += '\n';
}
}
var contents = (utils.isPresent(absfile, 'dir')) ?
fs.readdirSync(absfile).sort() : new Array();
for (var i = 0; i < contents.length; i++) {
if (contents[i][0] == '.') {
// Skip past any dirs starting with a '.'
continue;
}
var name = contents[i];
var af = path.join(absfile, name);
if (utils.isPresent(af, 'dir') &&
name.charAt(name.length - 1) != '/') {
name += '/';
}
var link = '' + name + '';
if (utils.isPresent(af, 'file')) {
link += ' edit';
}
text += link + '\n';
}
text += '\n';
if (contents.length == 0) {
text += '(directory is empty)\n';
text += 'No file ' + origfilename + ' found.\n\n';
var strip = (req.path.charAt(req.path.length - 1) == '/') ?
req.path.substring(0, req.path.length - 1) : req.path;
var parentDir = path.dirname(strip) + '/';
text += 'Up to ' + parentDir + '\n';
var extIdx = filename.lastIndexOf('.');
if (extIdx > 0) {
var ext = filename.substring(extIdx + 1);
if (ext == 'htm' || ext == 'html' || ext == 'js' || ext == 'css') {
text += 'Create /home/' + origfilename + '\n';
}
}
text += '\n';
res.send(text);
return;
}
}
}
catch (e) {
if (e instanceof utils.ImmediateReturnError) {
if (format == 'json') {
res.jsonp((e.jsonObj) ? e.jsonObj : {'error': e.toString()});
}
else {
res.set('Content-Type', 'text/html');
if (/ENOENT/.test(e.message)) {
res.status(404);
res.send('