Skip to content

Commit

Permalink
1.0 (#4)
Browse files Browse the repository at this point in the history
* chore: upgrade deps

* feat: remove serve-static dependency and factory

* feat: specify file postion in query parameter

* chore: update CI scripts
  • Loading branch information
generalov committed Dec 12, 2018
1 parent 0db0248 commit 2ee996d
Show file tree
Hide file tree
Showing 16 changed files with 1,881 additions and 416 deletions.
2 changes: 1 addition & 1 deletion .eslintrc
@@ -1,3 +1,3 @@
{
"extends": "semistandard"
"extends": "standard"
}
7 changes: 3 additions & 4 deletions .travis.yml
@@ -1,9 +1,8 @@
language: node_js
node_js:
- "4.8"
- "5.12"
- "6.10"
- "7.9"
- "6.14"
- "8.10"
- "9.10"
sudo: false
cache:
directories:
Expand Down
30 changes: 5 additions & 25 deletions README.md
Expand Up @@ -50,7 +50,7 @@ Append line number to the URL in the location bar of a browser and a file will b

Open the `server.js` file in default editor and put a cursor to line 123.

`curl -X POST -i "http://localhost:3000/server.js:123?edit=sublime"`
`curl -X POST -i "http://localhost:3000/server.js?edit=sublime&at=123"`

Open the `server.js` file in Sublime Editor and put a cursor to line 123.

Expand All @@ -71,7 +71,6 @@ Open the `server.js` file in Sublime Editor and put a cursor to line 123.
var openInEditor = require('open-in-editor-connect');

- `openInEditor(root, options)`
- `openInEditor(options)`

Create a new middleware function to handle files from within a given `root` directory.

Expand Down Expand Up @@ -139,34 +138,15 @@ Use these setting if the editor currently is not supported or if the editor's pa

var connect = require('connect');
var serveStatic = require('serve-static');
var openInEditor = require('open-in-editor-connect', {
editor: { name: 'code' }
});
var openInEditor = require('open-in-editor-connect');

var app = connect();
app.use(openInEditor('.'));
app.use(openInEditor('.', {
editor: { name: 'code' }
}));
app.use(serveStatic('.'));
app.listen(3000);

### Connect: Wrap the serve-static to handle the same path

var connect = require('connect');
//var serveStatic = require('serve-static');
var serveStatic = require('open-in-editor-connect', {
editor: { name: 'code' },
serveStatic: true
});

var app = connect();
app.use(serveStatic('.', { index: ['index.html'] }));
app.listen(3000);

### Customize options per directory

var openInEditor = require('open-in-editor-connect);

app.use(openInEditor('/', { editor: { name: 'code' } }));

## License

[MIT](LICENSE)
Expand Down
7 changes: 3 additions & 4 deletions appveyor.yml
@@ -1,9 +1,8 @@
environment:
matrix:
- nodejs_version: "4.8"
- nodejs_version: "5.12"
- nodejs_version: "6.10"
- nodejs_version: "7.9"
- nodejs_version: "6.14"
- nodejs_version: "8.10"
- nodejs_version: "9.10"
cache:
- node_modules
install:
Expand Down
4 changes: 2 additions & 2 deletions index.js
@@ -1,3 +1,3 @@
var middleware = require('./lib/middleware');
var middleware = require('./lib/middleware')

module.exports = middleware;
module.exports = middleware
164 changes: 88 additions & 76 deletions lib/middleware.js
@@ -1,131 +1,143 @@
// @ts-check

var fs = require('fs');
var url = require('url');
var resolveFile = require('./resolve');
var util = require('./util');
const fs = require('fs')
const url = require('url')
const resolveFile = require('./resolve')
const util = require('./util')

var defaultOptions = {
const defaultOptions = {
dotfiles: 'ignore',
editorParam: 'edit',
serveStatic: false
};
editorAtParam: 'at'
}

function getEditorOptions (config) {
var editorOptions = {};
var opts = (config || {});
const editorOptions = {}
const opts = (config || {})

if (opts.name != null) {
editorOptions.editor = String(opts.name);
editorOptions.editor = String(opts.name)
}
if (opts.binary != null) {
editorOptions.cmd = String(opts.binary);
editorOptions.cmd = String(opts.binary)
}
if (opts.args != null) {
editorOptions.pattern = String(opts.args);
editorOptions.pattern = String(opts.args)
}
if (opts.terminal != null) {
editorOptions.terminal = Boolean(opts.terminal);
editorOptions.terminal = Boolean(opts.terminal)
}

return editorOptions;
return editorOptions
}

function getQueryOptions (urlParts, options) {
var queryOptions = null;
let editor

if (urlParts.searchParams.has(options.editorParam)) {
editor = urlParts.searchParams.get(options.editorParam)

if (urlParts.query && urlParts.query[options.editorParam]) {
queryOptions = {
editor: urlParts.query[options.editorParam]
};
if (!editor && options.editor && options.editor.name) {
editor = options.editor.name
}
}

return queryOptions;
return editor ? { editor } : null
}

function getFileName (path) {
var ms = path.match(/^(.*?)(:[0-9]+)?(:[0-9]+)?$/);
const ms = path.match(/^(.*?)(:[0-9]+)?(:[0-9]+)?$/)

return ms && ms[1]
}

function getRequestUrl (req) {
const proto = req.connection.encrypted ? 'https' : 'http'

return ms && ms[1];
return proto + '://' + req.headers['host'] + req.url
}

function openInEditorMiddlewareFactory (rootPath, options) {
if (!rootPath) {
throw new Error('root path required');
throw new Error('root path required')
}
if (typeof rootPath !== 'string') {
throw new Error('root path should be a string');
throw new Error('root path should be a string')
}
options = Object.assign({}, defaultOptions, options)

var editorOptions = getEditorOptions(options.editor);
const editorOptions = getEditorOptions(options.editor)

var self = function openInEditorMiddleware (req, res, next) {
var urlParts = url.parse(req.url, true);
return function openInEditorMiddleware (req, res, next) {
const fullUrl = getRequestUrl(req)
const urlParts = new url.URL(fullUrl)

if (!urlParts.pathname.match(/:\d+$/) && !(urlParts.query && options.editorParam in urlParts.query)) {
next();
return;
if (!urlParts.searchParams.has(options.editorParam) && !(
urlParts.pathname.match(/:\d+$/) || urlParts.searchParams.has(options.editorAtParam))
) {
next()
return
}

var filePath = resolveFile(urlParts.pathname, { root: rootPath, dotfiles: options.dotfiles });
var fileName = getFileName(filePath);
const filePath = resolveFile(urlParts.pathname, {
root: rootPath,
dotfiles: options.dotfiles
})

const fileName = getFileName(filePath)

fs.access(fileName, function (err) {
if (err) {
next();
return;
next()
return
}

if (req.method === 'POST') {
var params = getQueryOptions(urlParts, options) || editorOptions;
util.open(filePath, params)
.then(function () {
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify({
status: 'ok'
}));
})
.catch(function (err) {
res.writeHead(500, {'Content-Type': 'application/json'});
res.end(JSON.stringify({
status: 'error',
message: String(err)
}));
});
let pathToOpen = filePath
const params = getQueryOptions(urlParts, options) || editorOptions

if (urlParts.searchParams.has(options.editorAtParam)) {
const at = String(urlParts.searchParams.get(options.editorAtParam))

if (at.match(/^\d+(:\d+)?$/)) {
pathToOpen += ':' + at
}
}

util.open(pathToOpen, params)
.then(function () {
res.writeHead(200, {
'Content-Type': 'application/json'
})
res.end(JSON.stringify({
status: 'ok'
}))
})
.catch(function (err) {
res.writeHead(500, {
'Content-Type': 'application/json'
})
res.end(JSON.stringify({
status: 'error',
message: String(err)
}))
})
} else {
var template = `<!DOCTYPE html>
const template = `<!DOCTYPE html>
<html>
<body onload="document.getElementById('form').submit()">
<form id="form" method="post" action="">
<button type="submit">Open</button>
</form>
</body>
</html>`;
res.writeHead(200, {'Content-Type': 'text/html'});
res.end(template);
</html>`
res.writeHead(200, {
'Content-Type': 'text/html'
})
res.end(template)
}
});
};

return self;
}

function openInEditorMiddlewareFactoryFactory (rootPath, settings) {
if (typeof rootPath === 'object') {
settings = rootPath;
rootPath = undefined;
}

var opts = Object.assign({}, defaultOptions, settings);

if (opts.serveStatic) {
return require('./serve-static-adapter')(openInEditorMiddlewareFactory, opts);
} else if (!rootPath && typeof settings === 'object') {
return function (rootPath, options) {
return openInEditorMiddlewareFactoryFactory(rootPath, Object.assign({}, opts, options));
};
} else {
return openInEditorMiddlewareFactory(rootPath, opts);
})
}
}

module.exports = openInEditorMiddlewareFactoryFactory;
module.exports = openInEditorMiddlewareFactory
42 changes: 21 additions & 21 deletions lib/resolve.js
@@ -1,48 +1,48 @@
// @ts-check

var path = require('path');
var path = require('path')

function containsDotFile (pathParts) {
return !!pathParts.filter(function (value) {
return value[0] === '.';
}).length;
return value[0] === '.'
}).length
}

module.exports = function resolve (filePath, options) {
var opts = options || {};
var UP_PATH_REGEXP = /(?:^|[\\/])\.\.(?:[\\/]|$)/;
var absRoot = opts.root ? path.resolve(opts.root) : null;
var filename;
var pathParts;
var opts = options || {}
var UP_PATH_REGEXP = /(?:^|[\\/])\.\.(?:[\\/]|$)/
var absRoot = opts.root ? path.resolve(opts.root) : null
var filename
var pathParts

try {
filename = decodeURIComponent(filePath);
filename = decodeURIComponent(filePath)
} catch (err) {
throw new Error('decode the path');
throw new Error('decode the path')
}

if (~filename.indexOf('\0')) {
throw new Error('null byte(s)');
throw new Error('null byte(s)')
}

if (absRoot !== null) {
if (UP_PATH_REGEXP.test(path.normalize('.' + path.sep + filename))) {
throw new Error('malicious path');
throw new Error('malicious path')
}
filename = path.normalize(path.join(absRoot, filename));
absRoot = path.normalize(absRoot + path.sep);
pathParts = filename.substr(absRoot.length).split(path.sep);
filename = path.normalize(path.join(absRoot, filename))
absRoot = path.normalize(absRoot + path.sep)
pathParts = filename.substr(absRoot.length).split(path.sep)
} else {
if (UP_PATH_REGEXP.test(filename)) {
throw new Error('".." is malicious without "root"');
throw new Error('".." is malicious without "root"')
}
pathParts = path.normalize(filename).split(path.sep);
filename = path.resolve(filename);
pathParts = path.normalize(filename).split(path.sep)
filename = path.resolve(filename)
}

if (containsDotFile(pathParts) && opts.dotfiles !== 'allow') {
throw new Error('dotfile handling');
throw new Error('dotfile handling')
}

return filename;
};
return filename
}

0 comments on commit 2ee996d

Please sign in to comment.