diff --git a/src/middleware/index.js b/src/middleware/index.js index 7b2117e03..98f8071ea 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -3,7 +3,6 @@ exports = module.exports = { cookieParser: require('cookie-parser'), cors: require('./cors.js'), - proxy: require('./proxy-middleware.js'), lastMile: require('connect-lastmile'), multipart: require('./multipart.js'), timeout: require('connect-timeout') diff --git a/src/middleware/proxy-middleware.js b/src/middleware/proxy-middleware.js deleted file mode 100644 index e38672005..000000000 --- a/src/middleware/proxy-middleware.js +++ /dev/null @@ -1,58 +0,0 @@ -'use strict'; - -// https://github.com/cloudron-io/node-proxy-middleware -// MIT license -// contains https://github.com/gonzalocasas/node-proxy-middleware/pull/59 - -const { request } = require('http'); - -function slashJoin(p1, p2) { - var trailing_slash = false; - - if (p1.length && p1[p1.length - 1] === '/') { trailing_slash = true; } - if (trailing_slash && p2.length && p2[0] === '/') {p2 = p2.substring(1); } - - return p1 + p2; -} - -module.exports = function proxyMiddleware(targetUrl) { - const options = require('url').parse(targetUrl); - - return function (req, resp, next) { - const { url } = req; - const opts = Object.assign({}, options); - if (url && url.charAt(0) === '?') { // prevent /api/resource/?offset=0 - if (options.pathname.length > 1 && options.pathname.charAt(options.pathname.length - 1) === '/') { - opts.path = options.pathname.substring(0, options.pathname.length - 1) + url; - } else { - opts.path = options.pathname + url; - } - } else if (url) { - opts.path = slashJoin(options.pathname, url); - } else { - opts.path = options.pathname; - } - opts.method = req.method; - opts.headers = Object.assign({}, req.headers); - delete opts.headers.host; - - var myReq = request(opts, function (myRes) { - resp.writeHead(myRes.statusCode, myRes.headers); - myRes.on('error', function (err) { - next(err); - }); - myRes.on('end', function (err) { - next(); - }); - myRes.pipe(resp); - }); - myReq.on('error', function (err) { - next(err); - }); - if (!req.readable) { - myReq.end(); - } else { - req.pipe(myReq); - } - }; -}; diff --git a/src/routes/filemanager.js b/src/routes/filemanager.js index 621759887..c6b26912a 100644 --- a/src/routes/filemanager.js +++ b/src/routes/filemanager.js @@ -6,11 +6,10 @@ exports = module.exports = { const assert = require('assert'), BoxError = require('../boxerror.js'), - middleware = require('../middleware/index.js'), + http = require('http'), HttpError = require('connect-lastmile').HttpError, safe = require('safetydance'), - services = require('../services.js'), - url = require('url'); + services = require('../services.js'); function proxy(kind) { assert(kind === 'mail' || kind === 'volume' || kind === 'app'); @@ -25,23 +24,32 @@ function proxy(kind) { case 'mail': id = 'mail'; break; } - const [error, result] = await safe(services.getContainerDetails('sftp', 'CLOUDRON_SFTP_TOKEN')); + const [error, addonDetails] = await safe(services.getContainerDetails('sftp', 'CLOUDRON_SFTP_TOKEN')); if (error) return next(BoxError.toHttpError(error)); - const parsedUrl = url.parse(req.url, true /* parseQueryString */); - parsedUrl.query['access_token'] = result.token; + const searchParams = new URLSearchParams(req.url.slice(req.url.indexOf('?')+1)); + searchParams.delete('access_token'); + searchParams.append('access_token', addonDetails.token); - req.url = url.format({ pathname: `/files/${id}/${encodeURIComponent(req.params[0])}`, query: parsedUrl.query }); // params[0] already contains leading '/' + const opts = { + hostname: addonDetails.ip, + port: 3000, + path: `/files/${id}/${encodeURIComponent(req.params[0])}?${searchParams.toString()}`, // params[0] already contains leading '/' + method: req.method, + headers: req.headers + }; - const fileManagerProxy = middleware.proxy(`http://${result.ip}:3000`); - - fileManagerProxy(req, res, function (error) { - if (!error) return; // note: response was already sent by proxy by this point - - if (error.code === 'ECONNREFUSED') return next(new HttpError(424, 'Unable to connect to filemanager server')); - if (error.code === 'ECONNRESET') return next(new HttpError(424, 'Unable to query filemanager server')); - - next(new HttpError(500, error)); - }); - }; + const sftpReq = http.request(opts, function (sftpRes) { + res.writeHead(sftpRes.statusCode, sftpRes.headers); + sftpRes.on('error', (error) => next(new HttpError(500, `filemanager error: ${error.message} ${error.code}`))); + sftpRes.on('end', () => next()); + sftpRes.pipe(res); + }); + sftpReq.on('error', (error) => next(new HttpError(424, `Unable to connect to filemanager: ${error.message} ${error.code}`))); + if (!req.readable) { + sftpReq.end(); + } else { + req.pipe(sftpReq); + } + }; } diff --git a/src/routes/mailserver.js b/src/routes/mailserver.js index c5483f630..6602067d3 100644 --- a/src/routes/mailserver.js +++ b/src/routes/mailserver.js @@ -13,39 +13,43 @@ const assert = require('assert'), AuditSource = require('../auditsource.js'), BoxError = require('../boxerror.js'), debug = require('debug')('box:routes/mailserver'), + http = require('http'), HttpError = require('connect-lastmile').HttpError, HttpSuccess = require('connect-lastmile').HttpSuccess, mailServer = require('../mailserver.js'), - middleware = require('../middleware/index.js'), safe = require('safetydance'), - services = require('../services.js'), - url = require('url'); + services = require('../services.js'); async function proxyToMailContainer(port, pathname, req, res, next) { - const parsedUrl = url.parse(req.url, true /* parseQueryString */); - - // do not proxy protected values - delete parsedUrl.query['access_token']; - delete req.headers['authorization']; - delete req.headers['cookies']; + req.clearTimeout(); const [error, addonDetails] = await safe(services.getContainerDetails('mail', 'CLOUDRON_MAIL_TOKEN')); if (error) return next(BoxError.toHttpError(error)); - parsedUrl.query['access_token'] = addonDetails.token; - req.url = url.format({ pathname, query: parsedUrl.query }); + const searchParams = new URLSearchParams(req.url.slice(req.url.indexOf('?')+1)); + searchParams.delete('access_token'); + searchParams.append('access_token', addonDetails.token); - const mailserverProxy = middleware.proxy(`http://${addonDetails.ip}:${port}`); + const opts = { + hostname: addonDetails.ip, + port: 3000, + path: `/${pathname}?${searchParams.toString()}`, + method: req.method, + headers: req.headers + }; - req.clearTimeout(); // TODO: add timeout to mail server proxy logic instead of this - mailserverProxy(req, res, function (error) { - if (!error) return next(); // note: response was already sent by proxy by this point - - if (error.code === 'ECONNREFUSED') return next(new HttpError(424, 'Unable to connect to mail server')); - if (error.code === 'ECONNRESET') return next(new HttpError(424, 'Unable to query mail server')); - - next(new HttpError(500, error)); + const sftpReq = http.request(opts, function (sftpRes) { + res.writeHead(sftpRes.statusCode, sftpRes.headers); + sftpRes.on('error', (error) => next(new HttpError(500, `mailserver error: ${error.message} ${error.code}`))); + sftpRes.on('end', () => next()); + sftpRes.pipe(res); }); + sftpReq.on('error', (error) => next(new HttpError(424, `Unable to connect to mailserver: ${error.message} ${error.code}`))); + if (!req.readable) { + sftpReq.end(); + } else { + req.pipe(sftpReq); + } } async function proxy(req, res, next) {