diff --git a/README.md b/README.md index 518803fbc..9aded714f 100644 --- a/README.md +++ b/README.md @@ -337,6 +337,18 @@ proxyServer.listen(8015); * **hostRewrite**: rewrites the location hostname on (201/301/302/307/308) redirects. * **autoRewrite**: rewrites the location host/port on (201/301/302/307/308) redirects based on requested host/port. Default: false. * **protocolRewrite**: rewrites the location protocol on (201/301/302/307/308) redirects to 'http' or 'https'. Default: null. +* **cookieDomainRewrite**: rewrites domain of `set-cookie` headers. Possible values: + * `false` (default): disable cookie rewriting + * String: new domain, for example `cookieDomainRewrite: "new.domain"`. To remove the domain, use `cookieDomainRewrite: ""`. + * Object: mapping of domains to new domains, use `"*"` to match all domains. + For example keep one domain unchanged, rewrite one domain and remove other domains: + ``` + cookieDomainRewrite: { + "unchanged.domain": "unchanged.domain", + "old.domain": "new.domain", + "*": "" + } + ``` * **headers**: object with extra headers to be added to target requests. **NOTE:** diff --git a/lib/http-proxy/common.js b/lib/http-proxy/common.js index bf467a2cd..aa9700234 100644 --- a/lib/http-proxy/common.js +++ b/lib/http-proxy/common.js @@ -4,7 +4,8 @@ var common = exports, required = require('requires-port'); var upgradeHeader = /(^|,)\s*upgrade\s*($|,)/i, - isSSL = /^https|wss/; + isSSL = /^https|wss/, + cookieDomainRegex = /(;\s*domain=)([^;]+)/i; /** * Simple Regex for testing if protocol is https @@ -201,6 +202,41 @@ common.urlJoin = function() { return retSegs.join('?') }; +/** + * Rewrites or removes the domain of a cookie header + * + * @param {String|Array} Header + * @param {Object} Config, mapping of domain to rewritten domain. + * '*' key to match any domain, null value to remove the domain. + * + * @api private + */ +common.rewriteCookieDomain = function rewriteCookieDomain(header, config) { + if (Array.isArray(header)) { + return header.map(function (headerElement) { + return rewriteCookieDomain(headerElement, config); + }); + } + return header.replace(cookieDomainRegex, function(match, prefix, previousDomain) { + var newDomain; + if (previousDomain in config) { + newDomain = config[previousDomain]; + } else if ('*' in config) { + newDomain = config['*']; + } else { + //no match, return previous domain + return match; + } + if (newDomain) { + //replace domain + return prefix + newDomain; + } else { + //remove domain + return ''; + } + }); +}; + /** * Check the host and see if it potentially has a port in it (keep it simple) * diff --git a/lib/http-proxy/passes/web-outgoing.js b/lib/http-proxy/passes/web-outgoing.js index 7047c098f..d8c17a282 100644 --- a/lib/http-proxy/passes/web-outgoing.js +++ b/lib/http-proxy/passes/web-outgoing.js @@ -1,4 +1,5 @@ var url = require('url'), + common = require('../common'), passes = exports; var redirectRegex = /^201|30(1|2|7|8)$/; @@ -77,13 +78,22 @@ var redirectRegex = /^201|30(1|2|7|8)$/; * @param {ClientRequest} Req Request object * @param {IncomingMessage} Res Response object * @param {proxyResponse} Res Response object from the proxy request + * @param {Object} Options options.cookieDomainRewrite: Config to rewrite cookie domain * * @api private */ - function writeHeaders(req, res, proxyRes) { + function writeHeaders(req, res, proxyRes, options) { + var rewriteCookieDomainConfig = options.cookieDomainRewrite; + if (typeof rewriteCookieDomainConfig === 'string') { //also test for '' + rewriteCookieDomainConfig = { '*': rewriteCookieDomainConfig }; + } Object.keys(proxyRes.headers).forEach(function(key) { - if(proxyRes.headers[key] != undefined){ - res.setHeader(String(key).trim(), proxyRes.headers[key]); + var header = proxyRes.headers[key]; + if (header != undefined) { + if (rewriteCookieDomainConfig && key.toLowerCase() === 'set-cookie') { + header = common.rewriteCookieDomain(header, rewriteCookieDomainConfig); + } + res.setHeader(String(key).trim(), header); } }); }, diff --git a/test/lib-http-proxy-passes-web-outgoing-test.js b/test/lib-http-proxy-passes-web-outgoing-test.js index c8b5ec12a..f9d947e4b 100644 --- a/test/lib-http-proxy-passes-web-outgoing-test.js +++ b/test/lib-http-proxy-passes-web-outgoing-test.js @@ -6,23 +6,23 @@ describe('lib/http-proxy/passes/web-outgoing.js', function () { beforeEach(function() { this.req = { headers: { - host: "ext-auto.com" + host: 'ext-auto.com' } }; this.proxyRes = { statusCode: 301, headers: { - location: "http://backend.com/" + location: 'http://backend.com/' } }; this.options = { - target: "http://backend.com" + target: 'http://backend.com' }; }); context('rewrites location host with hostRewrite', function() { beforeEach(function() { - this.options.hostRewrite = "ext-manual.com"; + this.options.hostRewrite = 'ext-manual.com'; }); [201, 301, 302, 307, 308].forEach(function(code) { it('on ' + code, function() { @@ -52,14 +52,14 @@ describe('lib/http-proxy/passes/web-outgoing.js', function () { it('not when the redirected location does not match target host', function() { this.proxyRes.statusCode = 302; - this.proxyRes.headers.location = "http://some-other/"; + this.proxyRes.headers.location = 'http://some-other/'; httpProxy.setRedirectHostRewrite(this.req, {}, this.proxyRes, this.options); expect(this.proxyRes.headers.location).to.eql('http://some-other/'); }); it('not when the redirected location does not match target port', function() { this.proxyRes.statusCode = 302; - this.proxyRes.headers.location = "http://backend.com:8080/"; + this.proxyRes.headers.location = 'http://backend.com:8080/'; httpProxy.setRedirectHostRewrite(this.req, {}, this.proxyRes, this.options); expect(this.proxyRes.headers.location).to.eql('http://backend.com:8080/'); }); @@ -91,14 +91,14 @@ describe('lib/http-proxy/passes/web-outgoing.js', function () { it('not when the redirected location does not match target host', function() { this.proxyRes.statusCode = 302; - this.proxyRes.headers.location = "http://some-other/"; + this.proxyRes.headers.location = 'http://some-other/'; httpProxy.setRedirectHostRewrite(this.req, {}, this.proxyRes, this.options); expect(this.proxyRes.headers.location).to.eql('http://some-other/'); }); it('not when the redirected location does not match target port', function() { this.proxyRes.statusCode = 302; - this.proxyRes.headers.location = "http://backend.com:8080/"; + this.proxyRes.headers.location = 'http://backend.com:8080/'; httpProxy.setRedirectHostRewrite(this.req, {}, this.proxyRes, this.options); expect(this.proxyRes.headers.location).to.eql('http://backend.com:8080/'); }); @@ -129,13 +129,13 @@ describe('lib/http-proxy/passes/web-outgoing.js', function () { }); it('works together with hostRewrite', function() { - this.options.hostRewrite = 'ext-manual.com' + this.options.hostRewrite = 'ext-manual.com'; httpProxy.setRedirectHostRewrite(this.req, {}, this.proxyRes, this.options); expect(this.proxyRes.headers.location).to.eql('https://ext-manual.com/'); }); it('works together with autoRewrite', function() { - this.options.autoRewrite = true + this.options.autoRewrite = true; httpProxy.setRedirectHostRewrite(this.req, {}, this.proxyRes, this.options); expect(this.proxyRes.headers.location).to.eql('https://ext-auto.com/'); }); @@ -199,31 +199,89 @@ describe('lib/http-proxy/passes/web-outgoing.js', function () { writeHead: function(n) { expect(n).to.eql(200); } - } + }; httpProxy.writeStatusCode({}, res, { statusCode: 200 }); }); }); describe('#writeHeaders', function() { - var proxyRes = { - headers: { - hey: 'hello', - how: 'are you?' - } - }; + beforeEach(function() { + this.proxyRes = { + headers: { + hey: 'hello', + how: 'are you?', + 'set-cookie': 'hello; domain=my.domain; path=/' + } + }; + this.res = { + setHeader: function(k, v) { + this.headers[k] = v; + }, + headers: {} + }; + }); - var res = { - setHeader: function(k, v) { - this.headers[k] = v; - }, - headers: {} - }; + it('writes headers', function() { + var options = {}; - httpProxy.writeHeaders({}, res, proxyRes); + httpProxy.writeHeaders({}, this.res, this.proxyRes, options); + + expect(this.res.headers.hey).to.eql('hello'); + expect(this.res.headers.how).to.eql('are you?'); + }); - expect(res.headers.hey).to.eql('hello'); - expect(res.headers.how).to.eql('are you?'); + it('does not rewrite domain', function() { + var options = {}; + + httpProxy.writeHeaders({}, this.res, this.proxyRes, options); + + expect(this.res.headers['set-cookie']).to.eql('hello; domain=my.domain; path=/'); + }); + + it('rewrites domain', function() { + var options = { + cookieDomainRewrite: 'my.new.domain' + }; + + httpProxy.writeHeaders({}, this.res, this.proxyRes, options); + + expect(this.res.headers['set-cookie']).to.eql('hello; domain=my.new.domain; path=/'); + }); + + it('removes domain', function() { + var options = { + cookieDomainRewrite: '' + }; + + httpProxy.writeHeaders({}, this.res, this.proxyRes, options); + + expect(this.res.headers['set-cookie']).to.eql('hello; path=/'); + }); + + it('rewrites headers with advanced configuration', function() { + var options = { + cookieDomainRewrite: { + '*': '', + 'my.old.domain': 'my.new.domain', + 'my.special.domain': 'my.special.domain' + } + }; + this.proxyRes.headers['set-cookie'] = [ + 'hello-on-my.domain; domain=my.domain; path=/', + 'hello-on-my.old.domain; domain=my.old.domain; path=/', + 'hello-on-my.special.domain; domain=my.special.domain; path=/' + ]; + + httpProxy.writeHeaders({}, this.res, this.proxyRes, options); + + expect(this.res.headers['set-cookie']) + .to.contain('hello-on-my.domain; path=/'); + expect(this.res.headers['set-cookie']) + .to.contain('hello-on-my.old.domain; domain=my.new.domain; path=/'); + expect(this.res.headers['set-cookie']) + .to.contain('hello-on-my.special.domain; domain=my.special.domain; path=/'); + }); });