Add /z/* route for export, make routes optional, add tests for /z /j/ /p routes

This commit is contained in:
David Humphrey (:humph) david.humphrey@senecacollege.ca 2014-08-05 15:57:10 -04:00
Родитель 611a6869e5
Коммит 08beb186b0
9 изменённых файлов: 339 добавлений и 31 удалений

Просмотреть файл

@ -6,4 +6,4 @@ before_install:
before_script:
- grunt init
env:
- "PORT=9090 ALLOWED_CORS_DOMAINS=\"http://localhost:7777\" NODE_ENV=\"development\" ENABLE_GELF_LOGS=false SESSION_SECRET=\"secret value\" FORCE_SSL=false LOGIN_SERVER_URL_WITH_AUTH=\"http://localhost:3000\""
- "PORT=9090 ALLOWED_CORS_DOMAINS=\"http://localhost:7777\" NODE_ENV=\"development\" ENABLE_GELF_LOGS=false SESSION_SECRET=\"secret value\" FORCE_SSL=false LOGIN_SERVER_URL_WITH_AUTH=\"http://localhost:3000\" ENABLE_PATH_ROUTE=true ENABLE_JSON_ROUTE=true ENABLE_ZIP_ROUTE=true"

Просмотреть файл

@ -11,6 +11,14 @@ export PORT=9090
# Add domains that we allow (e.g., for /api/sync route)
export ALLOWED_CORS_DOMAINS='["http://localhost:7777", "http://localhost:5001"]'
# Various optional HTTP routes for getting data out of MakeDrive. Set to false to turn off.
# /p/path/into/filesystem -> serves path like Apache would
export ENABLE_PATH_ROUTE=true
# /j/path/into/filesystem -> serves path as JSON for API consumption
export ENABLE_JSON_ROUTE=true
# /z/path/into/filesystem -> serves path as .zip file for export
export ENABLE_ZIP_ROUTE=true
# AWS-S3 information
export S3_BUCKET="org.webmadecontent.staging.makedrive"
export S3_KEY=

Просмотреть файл

@ -22,6 +22,7 @@
"url": "https://github.com/mozilla/makedrive/issues"
},
"dependencies": {
"archiver": "^0.10.1",
"MD5": "^1.2.1",
"async": "^0.9.0",
"webmaker-auth": "0.0.14",

Просмотреть файл

@ -17,16 +17,7 @@ function write(content, contentType, res, status) {
* Send an Apache-style 404
*/
function handle404(url, res) {
var html = '<!DOCTYPE html>' +
'<html><head>' +
'<title>404 Not Found</title>' +
'</head><body>' +
'<h1>Not Found</h1>' +
'<p>The requested URL ' + url + ' was not found on this server.</p>' +
'<hr>' +
'<address>MakeDrive/' + version + ' (Web) Server</address>' +
'</body></html>';
write(html, 'text/html', res, 404);
util.standard404(url, res);
}
/**

Просмотреть файл

@ -5,6 +5,7 @@
var filesystem = require('../filesystem.js');
var DefaultHandler = require('./default-handler.js');
var JSONHandler = require('./json-handler.js');
var ZIPHandler = require('./zip-handler.js');
function FilerWebServer(username, res, options) {
options = options || {};
@ -17,6 +18,8 @@ function FilerWebServer(username, res, options) {
// Pick the appropriate handler type to create
if(options.json) {
this.handler = new JSONHandler(fs, res);
} else if(options.zip) {
this.handler = new ZIPHandler(fs, res);
} else {
this.handler = new DefaultHandler(fs, res);
}

Просмотреть файл

@ -1,3 +1,5 @@
var version = require('../../../package.json').version;
function formatDate(d) {
// 20-Apr-2004 17:14
return d.getDay() + '-' +
@ -41,9 +43,24 @@ function isImage(ext) {
ext === '.ico';
}
function standard404(url, res) {
var html = '<!DOCTYPE html>' +
'<html><head>' +
'<title>404 Not Found</title>' +
'</head><body>' +
'<h1>Not Found</h1>' +
'<p>The requested URL ' + url + ' was not found on this server.</p>' +
'<hr>' +
'<address>MakeDrive/' + version + ' (Web) Server</address>' +
'</body></html>';
res.header({'Content-Type': 'text/html'});
res.send(404, html);
}
module.exports = {
formatDate: formatDate,
formatSize: formatSize,
isMedia: isMedia,
isImage: isImage
isImage: isImage,
standard404: standard404
};

Просмотреть файл

@ -0,0 +1,94 @@
/**
* A ZIP Handler, for exporting filesystem contents.
* The generated zip archive roots all files/dirs in
* an export/ folder, and returns export.zip.
*/
var archiver = require('archiver');
var Path = require('../../../lib/filer.js').Path;
var async = require('async');
var util = require('./util.js');
function archivePath(fs, path, res) {
function fixPath(path) {
// Make path relative within the zip archive
return path.replace(/^\//, 'export/');
}
function addFile(path, callback) {
fs.readFile(path, function(err, data) {
if(err) return callback(err);
archive.append(data, {name: fixPath(path)});
callback();
});
}
function addDir(path, callback) {
fs.readdir(path, function(err, list) {
if(err) return callback(err);
// Add the directory itself
archive.append(null, {name: fixPath(path) + '/'});
// Add all children of this dir, too
async.eachSeries(list, function(entry, callback) {
add(Path.join(path, entry), callback);
}, callback);
});
}
function add(path, callback) {
fs.stat(path, function(err, stats) {
if(err) return callback(err);
if(stats.isDirectory()) {
addDir(path, callback);
} else {
addFile(path, callback);
}
});
}
function error() {
// Signal to the client that things are broken by hanging up.
// There may be a better way to handle the error case here.
if(res.socket) {
res.socket.destroy();
}
}
res.header('Content-Type', 'application/zip');
res.header('Content-Disposition', 'attachment; filename=export.zip');
var archive = archiver('zip');
archive.on('error', error);
archive.pipe(res);
add(path, function(err) {
if(err) {
error();
} else {
archive.finalize();
}
});
}
function ZIPHandler(fs, res) {
this.fs = fs;
this.res = res;
}
ZIPHandler.prototype.handle404 = function(path) {
util.standard404(path, this.res);
};
ZIPHandler.prototype.handleDir = function(path) {
archivePath(this.fs, path, this.res);
};
ZIPHandler.prototype.handleFile = function(path) {
archivePath(this.fs, path, this.res);
};
module.exports = ZIPHandler;

Просмотреть файл

@ -30,15 +30,26 @@ module.exports = function createRoutes( app, webmakerAuth ) {
});
}
/**
* Serve a path as JSON (for APIs) from a user's Filer filesystem
*/
setupWWWRoutes('/j/*', {json: true});
/**
* Serve a path from a user's Filer filesystem
*/
setupWWWRoutes('/p/*', null);
if(env.get('ENABLE_PATH_ROUTE')) {
setupWWWRoutes('/p/*');
}
/**
* Serve a path as JSON (for APIs) from a user's Filer filesystem
*/
if(env.get('ENABLE_JSON_ROUTE')) {
setupWWWRoutes('/j/*', {json: true});
}
/**
* Serve a path as a .zip (for export) from a user's Filer filesystem
*/
if(env.get('ENABLE_ZIP_ROUTE')) {
setupWWWRoutes('/z/*', {zip: true});
}
app.get( "/api/sync", middleware.crossOriginHandler, middleware.authenticationHandler, function( req, res ) {
var username = req.params.username;

Просмотреть файл

@ -1,32 +1,215 @@
var expect = require('chai').expect;
var request = require('request');
var util = require('../lib/util');
// Ensure the client timeout restricts tests to a reasonable length
var Filer = require('../../lib/filer.js');
var FileSystem = Filer.FileSystem;
var Path = Filer.Path;
var env = require('../../server/lib/environment');
env.set('ALLOWED_CORS_DOMAINS', util.serverURL);
var ALLOW_DOMAINS = process.env.ALLOWED_CORS_DOMAINS;
describe('[HTTP route tests]', function() {
it('should allow CORS access to /api/sync route', function(done) {
request.get(util.serverURL + '/api/sync', { headers: {origin: ALLOW_DOMAINS }}, function(req, res, body) {
expect(ALLOW_DOMAINS).to.contain(res.headers['access-control-allow-origin']);
done();
});
});
it('/p/ should return a 404 error page if the path is not recognized', function(done) {
util.authenticate(function(err, result) {
expect(err).not.to.exist;
expect(result.jar).to.exist;
request.get({
url: util.serverURL + '/p/no/file/here.html',
jar: result.jar
}, function(err, res, body) {
describe('/p/ route tests', function() {
it('should return a 404 error page if the path is not recognized', function(done) {
util.authenticate(function(err, result) {
expect(err).not.to.exist;
expect(res.statusCode).to.equal(404);
expect(body).to.match(/<title>404 Not Found<\/title>/);
expect(body).to.match(/The requested URL \/no\/file\/here.html was not found on this server./);
done();
expect(result.jar).to.exist;
request.get({
url: util.serverURL + '/p/no/file/here.html',
jar: result.jar
}, function(err, res, body) {
expect(err).not.to.exist;
expect(res.statusCode).to.equal(404);
expect(body).to.match(/<title>404 Not Found<\/title>/);
expect(body).to.match(/The requested URL \/no\/file\/here.html was not found on this server./);
done();
});
});
});
it('should return the contents of a file if the path is valid', function(done) {
var username = util.username();
var content = "This is the content of the file.";
util.upload(username, '/index.html', content, function(err) {
if(err) throw err;
util.authenticate({username: username}, function(err, result) {
if(err) throw err;
// /p/index.html should come back as uploaded
request.get({
url: util.serverURL + '/p/index.html',
jar: result.jar
}, function(err, res, body) {
expect(err).not.to.exist;
expect(res.statusCode).to.equal(200);
expect(body).to.equal(content);
// /p/ should come back with dir listing
request.get({
url: util.serverURL + '/p/',
jar: result.jar
}, function(err, res, body) {
expect(err).not.to.exist;
expect(res.statusCode).to.equal(200);
// Look for artifacts we'd expect in the directory listing
expect(body).to.match(/<head><title>Index of \/<\/title>/);
expect(body).to.match(/<a href="\/p\/index.html">index.html<\/a>/);
done();
});
});
});
});
});
});
describe('/j/ route tests', function() {
it('should return a 404 error page if the path is not recognized', function(done) {
util.authenticate(function(err, result) {
expect(err).not.to.exist;
expect(result.jar).to.exist;
request.get({
url: util.serverURL + '/j/no/file/here.html',
jar: result.jar,
json: true
}, function(err, res, body) {
expect(err).not.to.exist;
expect(res.statusCode).to.equal(404);
expect(body.error.code).to.equal(404);
expect(body.error.message).to.match(/The requested URL \/no\/file\/here.html was not found on this server./);
done();
});
});
});
it('should return the contents of a file if the path is valid', function(done) {
var username = util.username();
var content = "This is the content of the file.";
util.upload(username, '/index.html', content, function(err) {
if(err) throw err;
util.authenticate({username: username}, function(err, result) {
if(err) throw err;
// /j/index.html should come back as JSON
request.get({
url: util.serverURL + '/j/index.html',
jar: result.jar,
json: true
}, function(err, res, body) {
expect(err).not.to.exist;
expect(res.statusCode).to.equal(200);
expect(body).to.equal(content);
// /j/ should come back with dir listing
request.get({
url: util.serverURL + '/j/',
jar: result.jar,
json: true
}, function(err, res, body) {
expect(err).not.to.exist;
expect(res.statusCode).to.equal(200);
/**
* We expect JSON something like this:
* [{path: 'index.html',
* links: 1,
* size: 32,
* modified: 1407336648736,
* type: 'FILE'}]
*/
expect(body.length).to.equal(1);
expect(body[0].path).to.equal('index.html');
expect(body[0].links).to.equal(1);
expect(body[0].size).to.be.a.number;
expect(body[0].modified).to.be.a.number;
expect(body[0].type).to.equal('FILE');
done();
});
});
});
});
});
});
describe('/z/ route tests', function() {
it('should return a 404 error page if the path is not recognized', function(done) {
util.authenticate(function(err, result) {
expect(err).not.to.exist;
expect(result.jar).to.exist;
request.get({
url: util.serverURL + '/z/no/file/here.html',
jar: result.jar
}, function(err, res, body) {
expect(err).not.to.exist;
expect(res.statusCode).to.equal(404);
expect(body).to.match(/<title>404 Not Found<\/title>/);
expect(body).to.match(/The requested URL \/no\/file\/here.html was not found on this server./);
done();
});
});
});
it('should return export.zip for a valid path', function(done) {
var username = util.username();
var content = "This is the content of the file.";
util.upload(username, '/index.html', content, function(err) {
if(err) throw err;
util.authenticate({username: username}, function(err, result) {
if(err) throw err;
// /z/ should come back as export.zip with one dir and file
// in the archive.
request.get({
url: util.serverURL + '/z/',
jar: result.jar,
encoding: null
}, function(err, res, body) {
expect(err).not.to.exist;
expect(res.statusCode).to.equal(200);
expect(res.headers['content-type']).to.equal('application/zip');
expect(res.headers['content-disposition']).to.equal('attachment; filename=export.zip');
// Write the zip file to filer, unzip, and compare file to original
var fs = new FileSystem({provider: new FileSystem.providers.Memory(username)});
var sh = fs.Shell();
sh.tempDir(function(err, tmp) {
if(err) throw err;
fs.writeFile('/exports.zip', body, function(err) {
if(err) throw err;
sh.unzip('/exports.zip', { destination: tmp }, function(err) {
if(err) throw err;
fs.readFile(Path.join(tmp, 'export/index.html'), 'utf8', function(err, data) {
if(err) throw err;
expect(data).to.equal(content);
done();
});
});
});
});
});
});
});
});
});