diff --git a/Gulpfile.js b/Gulpfile.js index 67810903c..1aeb64211 100644 --- a/Gulpfile.js +++ b/Gulpfile.js @@ -15,6 +15,8 @@ const util = require('gulp-util'); const ll = require('gulp-ll'); const path = require('path'); +const selfSignedCertificate = require('openssl-self-signed-certificate'); + ll .tasks('lint') .onlyInDebug([ @@ -192,12 +194,21 @@ gulp.task('test-client-travis', ['build'], () => { .pipe(qunitHarness(CLIENT_TESTS_SETTINGS, SAUCELABS_SETTINGS)); }); -gulp.task('playground', ['set-dev-mode', 'build'], () => { +gulp.task('http-playground', ['set-dev-mode', 'build'], () => { require('./test/playground/server.js').start(); return hang(); }); +gulp.task('https-playground', ['set-dev-mode', 'build'], () => { + require('./test/playground/server.js').start({ + key: selfSignedCertificate.key, + cert: selfSignedCertificate.cert + }); + + return hang(); +}); + gulp.task('travis', [process.env.GULP_TASK || '']); gulp.task('set-dev-mode', function () { diff --git a/src/client/utils/url.js b/src/client/utils/url.js index c9e2ea01e..0cbb44827 100644 --- a/src/client/utils/url.js +++ b/src/client/utils/url.js @@ -23,14 +23,18 @@ export function getProxyUrl (url, opts) { return url; /*eslint-disable no-restricted-properties*/ - const proxyHostname = opts && opts.proxyHostname || location.hostname; - const proxyPort = opts && opts.proxyPort || location.port.toString(); + const proxyHostname = opts && opts.proxyHostname || location.hostname; + const proxyPort = opts && opts.proxyPort || location.port.toString(); + const proxyServerProtocol = opts && opts.proxyProtocol || location.protocol; /*eslint-enable no-restricted-properties*/ - const proxyProtocol = parsedResourceType.isWebSocket ? 'ws:' : void 0; - const sessionId = opts && opts.sessionId || settings.get().sessionId; - let charset = opts && opts.charset; - let reqOrigin = opts && opts.reqOrigin; + const proxyProtocol = parsedResourceType.isWebSocket + ? proxyServerProtocol.replace('http', 'ws') + : proxyServerProtocol; + + const sessionId = opts && opts.sessionId || settings.get().sessionId; + let charset = opts && opts.charset; + let reqOrigin = opts && opts.reqOrigin; const crossDomainPort = getCrossDomainProxyPort(proxyPort); @@ -69,7 +73,8 @@ export function getProxyUrl (url, opts) { // NOTE: It seems that the relative URL had the leading slash or dots, so that the proxy info path part was // removed by the resolver and we have an origin URL with the incorrect host and protocol. /*eslint-disable no-restricted-properties*/ - if (parsedUrl.protocol === 'http:' && parsedUrl.hostname === proxyHostname && parsedUrl.port === proxyPort) { + if (parsedUrl.protocol === proxyServerProtocol && parsedUrl.hostname === proxyHostname && + parsedUrl.port === proxyPort) { const parsedDestLocation = destLocation.getParsed(); parsedUrl.protocol = parsedDestLocation.protocol; diff --git a/src/proxy/index.js b/src/proxy/index.js index 5e4bf8096..64dde9922 100644 --- a/src/proxy/index.js +++ b/src/proxy/index.js @@ -1,5 +1,6 @@ import Router from './router'; import http from 'http'; +import https from 'https'; import * as urlUtils from '../utils/url'; import { readSync as read } from 'read-file-relative'; import { respond204, respond500, respondWithJSON, fetchBody, preventCaching } from '../utils/http'; @@ -22,26 +23,36 @@ function parseAsJson (msg) { } } -function createServerInfo (hostname, port, crossDomainPort) { +function createServerInfo (hostname, port, crossDomainPort, protocol) { return { hostname: hostname, port: port, crossDomainPort: crossDomainPort, - domain: `http://${hostname}:${port}` + protocol: protocol, + domain: `${protocol}//${hostname}:${port}` }; } // Proxy export default class Proxy extends Router { - constructor (hostname, port1, port2) { + constructor (hostname, port1, port2, sslOptions) { super(); this.openSessions = {}; - this.server1Info = createServerInfo(hostname, port1, port2); - this.server2Info = createServerInfo(hostname, port2, port1); - this.server1 = http.createServer((req, res) => this._onRequest(req, res, this.server1Info)); - this.server2 = http.createServer((req, res) => this._onRequest(req, res, this.server2Info)); + const protocol = sslOptions ? 'https:' : 'http:'; + + this.server1Info = createServerInfo(hostname, port1, port2, protocol); + this.server2Info = createServerInfo(hostname, port2, port1, protocol); + + if (sslOptions) { + this.server1 = https.createServer(sslOptions, (req, res) => this._onRequest(req, res, this.server1Info)); + this.server2 = https.createServer(sslOptions, (req, res) => this._onRequest(req, res, this.server2Info)); + } + else { + this.server1 = http.createServer((req, res) => this._onRequest(req, res, this.server1Info)); + this.server2 = http.createServer((req, res) => this._onRequest(req, res, this.server2Info)); + } this.server1.on('upgrade', (req, socket, head) => this._onUpgradeRequest(req, socket, head, this.server1Info)); this.server2.on('upgrade', (req, socket, head) => this._onUpgradeRequest(req, socket, head, this.server2Info)); @@ -171,6 +182,7 @@ export default class Proxy extends Router { return urlUtils.getProxyUrl(url, { proxyHostname: this.server1Info.hostname, proxyPort: this.server1Info.port, + proxyProtocol: this.server1Info.protocol, sessionId: session.id }); } diff --git a/src/request-pipeline/context.js b/src/request-pipeline/context.js index b096b126a..43f2ded38 100644 --- a/src/request-pipeline/context.js +++ b/src/request-pipeline/context.js @@ -249,11 +249,13 @@ export default class RequestPipelineContext { toProxyUrl (url, isCrossDomain, resourceType, charset) { const proxyHostname = this.serverInfo.hostname; + const proxyProtocol = this.serverInfo.protocol; const proxyPort = isCrossDomain ? this.serverInfo.crossDomainPort : this.serverInfo.port; const sessionId = this.session.id; return urlUtils.getProxyUrl(url, { proxyHostname, + proxyProtocol, proxyPort, sessionId, resourceType, diff --git a/test/client/fixtures/utils/url-test.js b/test/client/fixtures/utils/url-test.js index 23ef23949..97582be8b 100644 --- a/test/client/fixtures/utils/url-test.js +++ b/test/client/fixtures/utils/url-test.js @@ -12,12 +12,13 @@ var PROXY_PORT = 1337; var PROXY_HOSTNAME = '127.0.0.1'; var PROXY_HOST = PROXY_HOSTNAME + ':' + PROXY_PORT; -function getProxyUrl (url, resourceType) { +function getProxyUrl (url, resourceType, protocol) { return urlUtils.getProxyUrl(url, { proxyHostname: PROXY_HOSTNAME, proxyPort: PROXY_PORT, sessionId: 'sessionId', - resourceType: resourceType + resourceType: resourceType, + proxyProtocol: protocol || 'http:' }); } @@ -170,7 +171,6 @@ test('already proxied', function () { var newUrl = getProxyUrl(proxyUrl, 'i'); strictEqual(urlUtils.parseProxyUrl(newUrl).resourceType, 'i'); - }); test('destination with query, path, hash and host', function () { @@ -195,13 +195,11 @@ test('destination with https protocol', function () { }); test('relative path', function () { - var destUrl = '/Image1.jpg'; - var proxyUrl = urlUtils.getProxyUrl(destUrl); + var proxyUrl = urlUtils.getProxyUrl('/Image1.jpg'); strictEqual(proxyUrl, 'http://' + location.host + '/sessionId/https://example.com/Image1.jpg'); - var relativeUrl = 'share?id=1kjQMWh7IcHdTBbTv6otRvCGYr-p02q206M7aR7dmog0'; - var parsedUrl = urlUtils.parseUrl(relativeUrl); + var parsedUrl = urlUtils.parseUrl('share?id=1kjQMWh7IcHdTBbTv6otRvCGYr-p02q206M7aR7dmog0'); ok(!parsedUrl.hostname); ok(!parsedUrl.host); @@ -293,6 +291,42 @@ test('convert a charset to lower case (GH-752)', function () { strictEqual(sharedUrlUtils.getProxyUrl(url, opts), 'http://localhost:5555/sessionId!utf-8/' + url); }); +module('https proxy protocol'); + +test('destination with host only', function () { + var destUrl = 'http://test.example.com/'; + var proxyUrl = getProxyUrl(destUrl, '', 'https:'); + + strictEqual(proxyUrl, 'https://' + PROXY_HOST + '/sessionId/' + destUrl); +}); + +test('relative path', function () { + var proxyUrl = getProxyUrl('/Image1.jpg', '', 'https:'); + + strictEqual(proxyUrl, 'https://' + PROXY_HOST + '/sessionId/https://example.com/Image1.jpg'); +}); + +test('special pages', function () { + sharedUrlUtils.SPECIAL_PAGES.forEach(function (url) { + var proxyUrl = getProxyUrl(url, '', 'https:'); + + strictEqual(proxyUrl, 'https://' + PROXY_HOST + '/sessionId/' + url); + }); +}); + +test('parse proxy url', function () { + var proxyUrl = 'https://' + PROXY_HOST + '/sessionId/http://test.example.com:53/PA/TH/?#testHash'; + var parsingResult = urlUtils.parseProxyUrl(proxyUrl); + + strictEqual(parsingResult.destUrl, 'http://test.example.com:53/PA/TH/?#testHash'); + strictEqual(parsingResult.destResourceInfo.protocol, 'http:'); + strictEqual(parsingResult.destResourceInfo.host, 'test.example.com:53'); + strictEqual(parsingResult.destResourceInfo.hostname, 'test.example.com'); + strictEqual(parsingResult.destResourceInfo.port, '53'); + strictEqual(parsingResult.destResourceInfo.partAfterHost, '/PA/TH/?#testHash'); + strictEqual(parsingResult.sessionId, 'sessionId'); +}); + module('parse proxy url'); test('http', function () { diff --git a/test/playground/server.js b/test/playground/server.js index 04687f5a1..af9a19464 100644 --- a/test/playground/server.js +++ b/test/playground/server.js @@ -27,10 +27,10 @@ function createSession () { return session; } -exports.start = () => { +exports.start = sslOptions => { const app = express(); - const proxy = new Proxy('localhost', PROXY_PORT_1, PROXY_PORT_2); const appServer = http.createServer(app); + const proxy = new Proxy('localhost', PROXY_PORT_1, PROXY_PORT_2, sslOptions); app.use(express.bodyParser()); diff --git a/test/server/data/page/expected-https.html b/test/server/data/page/expected-https.html new file mode 100644 index 000000000..906120628 --- /dev/null +++ b/test/server/data/page/expected-https.html @@ -0,0 +1,113 @@ +
+ + + + + + + + + +