html-pages
Version:
Simple development HTTP Server for file serving and directory listing made by a Designer. Use it for hacking your HTML/JavaScript/CSS files but not for deploying your final site.
209 lines (174 loc) • 5.32 kB
JavaScript
// Native
const path = require('path');
/* eslint node/no-deprecated-api: "off" */
const { parse, format } = require('url');
// Packages
const auth = require('basic-auth');
const red = require('chalk').red;
const fs = require('fs-promise');
const pathType = require('path-type');
const mime = require('mime-types');
const coroutine = require('bluebird').coroutine;
const _ = require('lodash');
// Ours
const pkg = require('../package');
const renderDirectory = require('./render');
const httpRequestObj = require('./http-request');
module.exports = coroutine(function * (req, res, flags, current, fu) {
// Initial request time
global.requestTime = global.utils.getMsTime();
const httpRequest = httpRequestObj(fu);
const headers = {};
let corsOpts = {
origin: false, // reflecting request origin
credentials: true, // allowing requests with credentials
methods: ''
};
if (flags.cors) {
corsOpts = {
origin: true, // reflecting request origin
credentials: true, // allowing requests with credentials
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
optionsSuccessStatus: 204
};
// console.log('flags.cors in');
// headers['access-control-allow-origin'] = '*';
// headers['access-control-allow-methods'] = 'GET,HEAD,PUT,POST';
// headers['access-control-allow-headers'] = 'Origin, X-Requested-With, Content-Type, Accept, Range';
}
require('cors')(corsOpts)(req, res, function () {});
// console.log('req.headers',req.headers);
_.merge(headers, req.headers);
// console.log('headers',headers);
for (const header in headers) {
if (!{}.hasOwnProperty.call(headers, header)) {
continue;
}
// console.log('each Header', header, headers[header]);
// res.setHeader(header, headers[header]);
}
// If the http-auth option is available
if (flags.auth) {
const credentials = auth(req);
if (!process.env.PAGES_USER || !process.env.PAGES_PASSWORD) {
console.error(
red(
'You are running `' +
pkg.name +
'` with basic auth but did not set the ' +
'`PAGES_USER` and `PAGES_PASSWORD` environment variables.'
)
);
}
if (!credentials ||
credentials.name !== process.env.PAGES_USER ||
credentials.pass !== process.env.PAGES_PASSWORD
) {
res.statusCode = 401;
res.setHeader('WWW-Authenticate', 'Basic realm="User Visible Realm"');
return httpRequest.micro({
req,
res,
code: res.statusCode,
html: 'Access Denied'
});
}
}
const {
pathname
} = parse(req.url);
const assetDir = process.env.ASSET_DIR;
let related = path.parse(path.join(current, pathname));
if (related.dir.indexOf(assetDir) > -1) {
const relative = path.relative(assetDir, pathname);
related = path.parse(path.join(__dirname, '/../public', relative));
}
related = path.format(related);
related = decodeURIComponent(related);
related = path.normalize(related).replace(/^(\.\.[/\\])+/, '');
const relatedExists = fs.existsSync(related);
if (!related.startsWith(process.env.CURRENT_PATH) && !related.startsWith(process.env.SERVER_PATH)) {
return httpRequest.micro({
req,
res,
code: 403
});
} else if (!relatedExists) { // If not exists return an error 404
return httpRequest.micro({
req,
res,
code: 404
});
}
const streamOptions = {};
if (flags.cache) {
streamOptions.maxAge = flags.cache;
}
// Check if directory
if (pathType.isDirectorySync(related)) {
const url = parse(req.url);
// If the url doesn't end with a slash, add it and redirect the page
if (url.pathname.substr(-1) !== '/') {
url.pathname += '/';
const newPath = format(url);
httpRequest.logRequests({
req,
res,
code: 301
}, 301);
res.writeHead(301, {
Location: newPath
});
res.end('Redirecting to ' + newPath);
return;
}
const directoryIndexPath = path.join(related, '/' + flags.directoryIndex);
// If exists open the directory index file
if (flags.directoryIndex !== '' && fs.existsSync(directoryIndexPath)) {
res.setHeader(
'Content-Type',
mime.contentType(path.extname(directoryIndexPath))
);
return httpRequest.stream({
req,
path: directoryIndexPath,
streamOptions,
res
});
}
// If no listing is available return an error 403
if (flags.noListing === true) {
return httpRequest.micro({
req,
res,
code: 403
});
}
// Try to render the current directory's content
const port = flags.port || req.socket.localPort;
const renderedDir = yield renderDirectory(port, current, related, fu);
// If it works, send the directory listing to the user
if (renderedDir) {
return httpRequest.micro({
req,
res,
code: 200,
html: renderedDir
});
}
return httpRequest.micro({
req,
res,
code: 500
});
}
return httpRequest.stream({
req,
path: related,
streamOptions: Object.assign({
dotfiles: 'allow',
cacheControl: flags.noCache === true
}, streamOptions),
res
});
});