mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-30 00:01:50 +00:00
Bug 1545421 - New nsresult error codes for 407, 502 and 504 http response codes returned by proxies + test, r=dragana
Differential Revision: https://phabricator.services.mozilla.com/D32817 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
ce7abe5c5c
commit
c35df87597
@ -4104,7 +4104,8 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI,
|
|||||||
formatStrCount = 1;
|
formatStrCount = 1;
|
||||||
errorDescriptionID = "dnsNotFound2";
|
errorDescriptionID = "dnsNotFound2";
|
||||||
error = "dnsNotFound";
|
error = "dnsNotFound";
|
||||||
} else if (NS_ERROR_CONNECTION_REFUSED == aError) {
|
} else if (NS_ERROR_CONNECTION_REFUSED == aError ||
|
||||||
|
NS_ERROR_PROXY_BAD_GATEWAY == aError) {
|
||||||
NS_ENSURE_ARG_POINTER(aURI);
|
NS_ENSURE_ARG_POINTER(aURI);
|
||||||
addHostPort = true;
|
addHostPort = true;
|
||||||
error = "connectionFailure";
|
error = "connectionFailure";
|
||||||
@ -4112,7 +4113,8 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI,
|
|||||||
NS_ENSURE_ARG_POINTER(aURI);
|
NS_ENSURE_ARG_POINTER(aURI);
|
||||||
addHostPort = true;
|
addHostPort = true;
|
||||||
error = "netInterrupt";
|
error = "netInterrupt";
|
||||||
} else if (NS_ERROR_NET_TIMEOUT == aError) {
|
} else if (NS_ERROR_NET_TIMEOUT == aError ||
|
||||||
|
NS_ERROR_PROXY_GATEWAY_TIMEOUT == aError) {
|
||||||
NS_ENSURE_ARG_POINTER(aURI);
|
NS_ENSURE_ARG_POINTER(aURI);
|
||||||
// Get the host
|
// Get the host
|
||||||
nsAutoCString host;
|
nsAutoCString host;
|
||||||
@ -4341,6 +4343,7 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI,
|
|||||||
error = "proxyResolveFailure";
|
error = "proxyResolveFailure";
|
||||||
break;
|
break;
|
||||||
case NS_ERROR_PROXY_CONNECTION_REFUSED:
|
case NS_ERROR_PROXY_CONNECTION_REFUSED:
|
||||||
|
case NS_ERROR_PROXY_AUTHENTICATION_FAILED:
|
||||||
// Proxy connection was refused.
|
// Proxy connection was refused.
|
||||||
error = "proxyConnectFailure";
|
error = "proxyConnectFailure";
|
||||||
break;
|
break;
|
||||||
@ -6935,15 +6938,18 @@ nsresult nsDocShell::EndPageLoad(nsIWebProgress* aProgress,
|
|||||||
aStatus == NS_ERROR_CONNECTION_REFUSED ||
|
aStatus == NS_ERROR_CONNECTION_REFUSED ||
|
||||||
aStatus == NS_ERROR_UNKNOWN_PROXY_HOST ||
|
aStatus == NS_ERROR_UNKNOWN_PROXY_HOST ||
|
||||||
aStatus == NS_ERROR_PROXY_CONNECTION_REFUSED ||
|
aStatus == NS_ERROR_PROXY_CONNECTION_REFUSED ||
|
||||||
|
aStatus == NS_ERROR_PROXY_AUTHENTICATION_FAILED ||
|
||||||
aStatus == NS_ERROR_BLOCKED_BY_POLICY) &&
|
aStatus == NS_ERROR_BLOCKED_BY_POLICY) &&
|
||||||
(isTopFrame || UseErrorPages())) {
|
(isTopFrame || UseErrorPages())) {
|
||||||
DisplayLoadError(aStatus, url, nullptr, aChannel);
|
DisplayLoadError(aStatus, url, nullptr, aChannel);
|
||||||
} else if (aStatus == NS_ERROR_NET_TIMEOUT ||
|
} else if (aStatus == NS_ERROR_NET_TIMEOUT ||
|
||||||
|
aStatus == NS_ERROR_PROXY_GATEWAY_TIMEOUT ||
|
||||||
aStatus == NS_ERROR_REDIRECT_LOOP ||
|
aStatus == NS_ERROR_REDIRECT_LOOP ||
|
||||||
aStatus == NS_ERROR_UNKNOWN_SOCKET_TYPE ||
|
aStatus == NS_ERROR_UNKNOWN_SOCKET_TYPE ||
|
||||||
aStatus == NS_ERROR_NET_INTERRUPT ||
|
aStatus == NS_ERROR_NET_INTERRUPT ||
|
||||||
aStatus == NS_ERROR_NET_RESET || aStatus == NS_ERROR_OFFLINE ||
|
aStatus == NS_ERROR_NET_RESET ||
|
||||||
aStatus == NS_ERROR_MALWARE_URI ||
|
aStatus == NS_ERROR_PROXY_BAD_GATEWAY ||
|
||||||
|
aStatus == NS_ERROR_OFFLINE || aStatus == NS_ERROR_MALWARE_URI ||
|
||||||
aStatus == NS_ERROR_PHISHING_URI ||
|
aStatus == NS_ERROR_PHISHING_URI ||
|
||||||
aStatus == NS_ERROR_UNWANTED_URI ||
|
aStatus == NS_ERROR_UNWANTED_URI ||
|
||||||
aStatus == NS_ERROR_HARMFUL_URI ||
|
aStatus == NS_ERROR_HARMFUL_URI ||
|
||||||
|
@ -12,6 +12,17 @@ function getTestServerPort() {
|
|||||||
return port;
|
return port;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getTestProxyPort() {
|
||||||
|
let portEnv = Cc["@mozilla.org/process/environment;1"]
|
||||||
|
.getService(Ci.nsIEnvironment).get("MOZHTTP2_PROXY_PORT");
|
||||||
|
let port = parseInt(portEnv, 10);
|
||||||
|
if (!Number.isFinite(port) || port < 1 || port > 65535) {
|
||||||
|
throw new Error(`Invalid port in MOZHTTP2_PROXY_PORT env var: ${portEnv}`);
|
||||||
|
}
|
||||||
|
info(`Using HTTP/2 proxy on port ${port}`);
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
function readFile(file) {
|
function readFile(file) {
|
||||||
let fstream = Cc["@mozilla.org/network/file-input-stream;1"]
|
let fstream = Cc["@mozilla.org/network/file-input-stream;1"]
|
||||||
.createInstance(Ci.nsIFileInputStream);
|
.createInstance(Ci.nsIFileInputStream);
|
||||||
|
@ -145,6 +145,9 @@ XPC_MSG_DEF(NS_ERROR_ALREADY_CONNECTED , "The connection is already
|
|||||||
XPC_MSG_DEF(NS_ERROR_NOT_CONNECTED , "The connection does not exist")
|
XPC_MSG_DEF(NS_ERROR_NOT_CONNECTED , "The connection does not exist")
|
||||||
XPC_MSG_DEF(NS_ERROR_CONNECTION_REFUSED , "The connection was refused")
|
XPC_MSG_DEF(NS_ERROR_CONNECTION_REFUSED , "The connection was refused")
|
||||||
XPC_MSG_DEF(NS_ERROR_PROXY_CONNECTION_REFUSED , "The connection to the proxy server was refused")
|
XPC_MSG_DEF(NS_ERROR_PROXY_CONNECTION_REFUSED , "The connection to the proxy server was refused")
|
||||||
|
XPC_MSG_DEF(NS_ERROR_PROXY_AUTHENTICATION_FAILED , "The proxy requires authentication")
|
||||||
|
XPC_MSG_DEF(NS_ERROR_PROXY_BAD_GATEWAY , "The request failed on the proxy")
|
||||||
|
XPC_MSG_DEF(NS_ERROR_PROXY_GATEWAY_TIMEOUT , "The request timed out on the proxy")
|
||||||
XPC_MSG_DEF(NS_ERROR_NET_TIMEOUT , "The connection has timed out")
|
XPC_MSG_DEF(NS_ERROR_NET_TIMEOUT , "The connection has timed out")
|
||||||
XPC_MSG_DEF(NS_ERROR_OFFLINE , "The requested action could not be completed in the offline state")
|
XPC_MSG_DEF(NS_ERROR_OFFLINE , "The requested action could not be completed in the offline state")
|
||||||
XPC_MSG_DEF(NS_ERROR_PORT_ACCESS_NOT_ALLOWED , "Establishing a connection to an unsafe or otherwise banned port was prohibited")
|
XPC_MSG_DEF(NS_ERROR_PORT_ACCESS_NOT_ALLOWED , "Establishing a connection to an unsafe or otherwise banned port was prohibited")
|
||||||
|
@ -1036,7 +1036,7 @@ nsresult Http2Stream::ConvertResponseHeaders(Http2Decompressor* decompressor,
|
|||||||
if ((httpResponseCode / 100) != 2) {
|
if ((httpResponseCode / 100) != 2) {
|
||||||
MapStreamToPlainText();
|
MapStreamToPlainText();
|
||||||
}
|
}
|
||||||
MapStreamToHttpConnection();
|
MapStreamToHttpConnection(httpResponseCode);
|
||||||
ClearTransactionsBlockedOnTunnel();
|
ClearTransactionsBlockedOnTunnel();
|
||||||
} else if (mIsWebsocket) {
|
} else if (mIsWebsocket) {
|
||||||
LOG3(("Http2Stream %p websocket response code %d", this, httpResponseCode));
|
LOG3(("Http2Stream %p websocket response code %d", this, httpResponseCode));
|
||||||
@ -1599,12 +1599,14 @@ void Http2Stream::MapStreamToPlainText() {
|
|||||||
qiTrans->ForcePlainText();
|
qiTrans->ForcePlainText();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Http2Stream::MapStreamToHttpConnection() {
|
void Http2Stream::MapStreamToHttpConnection(int32_t httpResponseCode) {
|
||||||
RefPtr<SpdyConnectTransaction> qiTrans(
|
RefPtr<SpdyConnectTransaction> qiTrans(
|
||||||
mTransaction->QuerySpdyConnectTransaction());
|
mTransaction->QuerySpdyConnectTransaction());
|
||||||
MOZ_ASSERT(qiTrans);
|
MOZ_ASSERT(qiTrans);
|
||||||
|
|
||||||
qiTrans->MapStreamToHttpConnection(mSocketTransport,
|
qiTrans->MapStreamToHttpConnection(mSocketTransport,
|
||||||
mTransaction->ConnectionInfo());
|
mTransaction->ConnectionInfo(),
|
||||||
|
mIsTunnel ? httpResponseCode : -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
@ -369,7 +369,7 @@ class Http2Stream : public nsAHttpSegmentReader,
|
|||||||
private:
|
private:
|
||||||
void ClearTransactionsBlockedOnTunnel();
|
void ClearTransactionsBlockedOnTunnel();
|
||||||
void MapStreamToPlainText();
|
void MapStreamToPlainText();
|
||||||
void MapStreamToHttpConnection();
|
void MapStreamToHttpConnection(int32_t httpResponseCode = -1);
|
||||||
|
|
||||||
bool mIsTunnel;
|
bool mIsTunnel;
|
||||||
bool mPlainTextTunnel;
|
bool mPlainTextTunnel;
|
||||||
|
@ -1038,7 +1038,8 @@ void SpdyConnectTransaction::ForcePlainText() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SpdyConnectTransaction::MapStreamToHttpConnection(
|
void SpdyConnectTransaction::MapStreamToHttpConnection(
|
||||||
nsISocketTransport* aTransport, nsHttpConnectionInfo* aConnInfo) {
|
nsISocketTransport* aTransport, nsHttpConnectionInfo* aConnInfo,
|
||||||
|
int32_t httpResponseCode) {
|
||||||
mConnInfo = aConnInfo;
|
mConnInfo = aConnInfo;
|
||||||
|
|
||||||
mTunnelTransport = new SocketTransportShim(aTransport, mIsWebsocket);
|
mTunnelTransport = new SocketTransportShim(aTransport, mIsWebsocket);
|
||||||
@ -1046,10 +1047,27 @@ void SpdyConnectTransaction::MapStreamToHttpConnection(
|
|||||||
mTunnelStreamOut = new OutputStreamShim(this, mIsWebsocket);
|
mTunnelStreamOut = new OutputStreamShim(this, mIsWebsocket);
|
||||||
mTunneledConn = new nsHttpConnection();
|
mTunneledConn = new nsHttpConnection();
|
||||||
|
|
||||||
|
switch (httpResponseCode) {
|
||||||
|
case 404:
|
||||||
|
CreateShimError(NS_ERROR_UNKNOWN_HOST);
|
||||||
|
break;
|
||||||
|
case 407:
|
||||||
|
CreateShimError(NS_ERROR_PROXY_AUTHENTICATION_FAILED);
|
||||||
|
break;
|
||||||
|
case 502:
|
||||||
|
CreateShimError(NS_ERROR_PROXY_BAD_GATEWAY);
|
||||||
|
break;
|
||||||
|
case 504:
|
||||||
|
CreateShimError(NS_ERROR_PROXY_GATEWAY_TIMEOUT);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// this new http connection has a specific hashkey (i.e. to a particular
|
// this new http connection has a specific hashkey (i.e. to a particular
|
||||||
// host via the tunnel) and is associated with the tunnel streams
|
// host via the tunnel) and is associated with the tunnel streams
|
||||||
LOG(("SpdyConnectTransaction new httpconnection %p %s\n", mTunneledConn.get(),
|
LOG(("SpdyConnectTransaction %p new httpconnection %p %s\n", this,
|
||||||
aConnInfo->HashKey().get()));
|
mTunneledConn.get(), aConnInfo->HashKey().get()));
|
||||||
|
|
||||||
nsCOMPtr<nsIInterfaceRequestor> callbacks;
|
nsCOMPtr<nsIInterfaceRequestor> callbacks;
|
||||||
GetSecurityCallbacks(getter_AddRefs(callbacks));
|
GetSecurityCallbacks(getter_AddRefs(callbacks));
|
||||||
@ -1202,6 +1220,9 @@ nsresult SpdyConnectTransaction::ReadSegments(nsAHttpSegmentReader* reader,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SpdyConnectTransaction::CreateShimError(nsresult code) {
|
void SpdyConnectTransaction::CreateShimError(nsresult code) {
|
||||||
|
LOG(("SpdyConnectTransaction::CreateShimError %p 0x%08" PRIx32, this,
|
||||||
|
static_cast<uint32_t>(code)));
|
||||||
|
|
||||||
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
MOZ_ASSERT(OnSocketThread(), "not on socket thread");
|
||||||
MOZ_ASSERT(NS_FAILED(code));
|
MOZ_ASSERT(NS_FAILED(code));
|
||||||
|
|
||||||
|
@ -207,7 +207,8 @@ class SpdyConnectTransaction final : public NullHttpTransaction {
|
|||||||
// error.
|
// error.
|
||||||
void ForcePlainText();
|
void ForcePlainText();
|
||||||
void MapStreamToHttpConnection(nsISocketTransport* aTransport,
|
void MapStreamToHttpConnection(nsISocketTransport* aTransport,
|
||||||
nsHttpConnectionInfo* aConnInfo);
|
nsHttpConnectionInfo* aConnInfo,
|
||||||
|
int32_t httpResponseCode);
|
||||||
|
|
||||||
MOZ_MUST_USE nsresult ReadSegments(nsAHttpSegmentReader* reader,
|
MOZ_MUST_USE nsresult ReadSegments(nsAHttpSegmentReader* reader,
|
||||||
uint32_t count, uint32_t* countRead) final;
|
uint32_t count, uint32_t* countRead) final;
|
||||||
|
@ -1954,16 +1954,18 @@ nsresult nsHttpChannel::ProcessFailedProxyConnect(uint32_t httpStatus) {
|
|||||||
rv = NS_ERROR_CONNECTION_REFUSED;
|
rv = NS_ERROR_CONNECTION_REFUSED;
|
||||||
break;
|
break;
|
||||||
case 403: // HTTP/1.1: "Forbidden"
|
case 403: // HTTP/1.1: "Forbidden"
|
||||||
case 407: // ProcessAuthentication() failed
|
|
||||||
case 501: // HTTP/1.1: "Not Implemented"
|
case 501: // HTTP/1.1: "Not Implemented"
|
||||||
// user sees boilerplate Mozilla "Proxy Refused Connection" page.
|
// user sees boilerplate Mozilla "Proxy Refused Connection" page.
|
||||||
rv = NS_ERROR_PROXY_CONNECTION_REFUSED;
|
rv = NS_ERROR_PROXY_CONNECTION_REFUSED;
|
||||||
break;
|
break;
|
||||||
// Squid sends 404 if DNS fails (regular 404 from target is tunneled)
|
case 407: // ProcessAuthentication() failed (e.g. no header)
|
||||||
|
rv = NS_ERROR_PROXY_AUTHENTICATION_FAILED;
|
||||||
|
break;
|
||||||
|
// Squid sends 404 if DNS fails (regular 404 from target is tunneled)
|
||||||
case 404: // HTTP/1.1: "Not Found"
|
case 404: // HTTP/1.1: "Not Found"
|
||||||
// RFC 2616: "some deployed proxies are known to return 400 or 500 when
|
// RFC 2616: "some deployed proxies are known to return 400 or
|
||||||
// DNS lookups time out." (Squid uses 500 if it runs out of sockets: so
|
// 500 when DNS lookups time out." (Squid uses 500 if it runs
|
||||||
// we have a conflict here).
|
// out of sockets: so we have a conflict here).
|
||||||
case 400: // HTTP/1.1 "Bad Request"
|
case 400: // HTTP/1.1 "Bad Request"
|
||||||
case 500: // HTTP/1.1: "Internal Server Error"
|
case 500: // HTTP/1.1: "Internal Server Error"
|
||||||
/* User sees: "Address Not Found: Firefox can't find the server at
|
/* User sees: "Address Not Found: Firefox can't find the server at
|
||||||
@ -1972,8 +1974,10 @@ nsresult nsHttpChannel::ProcessFailedProxyConnect(uint32_t httpStatus) {
|
|||||||
rv = NS_ERROR_UNKNOWN_HOST;
|
rv = NS_ERROR_UNKNOWN_HOST;
|
||||||
break;
|
break;
|
||||||
case 502: // HTTP/1.1: "Bad Gateway" (invalid resp from target server)
|
case 502: // HTTP/1.1: "Bad Gateway" (invalid resp from target server)
|
||||||
// Squid returns 503 if target request fails for anything but DNS.
|
rv = NS_ERROR_PROXY_BAD_GATEWAY;
|
||||||
|
break;
|
||||||
case 503: // HTTP/1.1: "Service Unavailable"
|
case 503: // HTTP/1.1: "Service Unavailable"
|
||||||
|
// Squid returns 503 if target request fails for anything but DNS.
|
||||||
/* User sees: "Failed to Connect:
|
/* User sees: "Failed to Connect:
|
||||||
* Firefox can't establish a connection to the server at
|
* Firefox can't establish a connection to the server at
|
||||||
* www.foo.com. Though the site seems valid, the browser
|
* www.foo.com. Though the site seems valid, the browser
|
||||||
@ -1986,7 +1990,7 @@ nsresult nsHttpChannel::ProcessFailedProxyConnect(uint32_t httpStatus) {
|
|||||||
case 504: // HTTP/1.1: "Gateway Timeout"
|
case 504: // HTTP/1.1: "Gateway Timeout"
|
||||||
// user sees: "Network Timeout: The server at www.foo.com
|
// user sees: "Network Timeout: The server at www.foo.com
|
||||||
// is taking too long to respond."
|
// is taking too long to respond."
|
||||||
rv = NS_ERROR_NET_TIMEOUT;
|
rv = NS_ERROR_PROXY_GATEWAY_TIMEOUT;
|
||||||
break;
|
break;
|
||||||
// Confused proxy server or malicious response
|
// Confused proxy server or malicious response
|
||||||
default:
|
default:
|
||||||
|
176
netwerk/test/unit/test_http1-proxy.js
Normal file
176
netwerk/test/unit/test_http1-proxy.js
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||||
|
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This test checks following expectations when using HTTP/1 proxy:
|
||||||
|
*
|
||||||
|
* - check we are seeing expected nsresult error codes on channels
|
||||||
|
* (nsIChannel.status) corresponding to different proxy status code
|
||||||
|
* responses (502, 504, 407, ...)
|
||||||
|
* - check we don't try to ask for credentials or otherwise authenticate to
|
||||||
|
* the proxy when 407 is returned and there is no Proxy-Authenticate
|
||||||
|
* response header sent
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
|
||||||
|
|
||||||
|
const pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();
|
||||||
|
|
||||||
|
let server_port;
|
||||||
|
let http_server;
|
||||||
|
|
||||||
|
class ProxyFilter {
|
||||||
|
constructor(type, host, port, flags) {
|
||||||
|
this._type = type;
|
||||||
|
this._host = host;
|
||||||
|
this._port = port;
|
||||||
|
this._flags = flags;
|
||||||
|
this.QueryInterface = ChromeUtils.generateQI([Ci.nsIProtocolProxyFilter]);
|
||||||
|
}
|
||||||
|
applyFilter(pps, uri, pi, cb) {
|
||||||
|
if (uri.spec.match(/(\/proxy-session-counter)/)) {
|
||||||
|
cb.onProxyFilterResult(pi);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cb.onProxyFilterResult(pps.newProxyInfo(
|
||||||
|
this._type, this._host, this._port,
|
||||||
|
"", "", this._flags, 1000, null));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class UnxpectedAuthPrompt2 {
|
||||||
|
constructor(signal) {
|
||||||
|
this.signal = signal;
|
||||||
|
this.QueryInterface = ChromeUtils.generateQI([Ci.nsIAuthPrompt2]);
|
||||||
|
}
|
||||||
|
asyncPromptAuth() {
|
||||||
|
this.signal.triggered = true;
|
||||||
|
throw Cr.ERROR_UNEXPECTED;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class AuthRequestor {
|
||||||
|
constructor(prompt) {
|
||||||
|
this.prompt = prompt;
|
||||||
|
this.QueryInterface = ChromeUtils.generateQI([Ci.nsIInterfaceRequestor]);
|
||||||
|
}
|
||||||
|
getInterface(iid) {
|
||||||
|
if (iid.equals(Ci.nsIAuthPrompt2)) {
|
||||||
|
return this.prompt();
|
||||||
|
}
|
||||||
|
throw Cr.NS_ERROR_NO_INTERFACE;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function make_channel(url) {
|
||||||
|
return NetUtil.newChannel({
|
||||||
|
uri: url,
|
||||||
|
loadUsingSystemPrincipal: true,
|
||||||
|
// Using TYPE_DOCUMENT for the authentication dialog test, it'd be blocked for other types
|
||||||
|
contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_response(channel, flags = CL_ALLOW_UNKNOWN_CL) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
channel.asyncOpen(new ChannelListener((request, data) => {
|
||||||
|
request.QueryInterface(Ci.nsIHttpChannel);
|
||||||
|
const status = request.status;
|
||||||
|
const http_code = status ? undefined : request.responseStatus;
|
||||||
|
|
||||||
|
resolve({ status, http_code, data });
|
||||||
|
}, null, flags));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function connect_handler(request, response) {
|
||||||
|
Assert.equal(request.method, "CONNECT");
|
||||||
|
|
||||||
|
switch (request.host) {
|
||||||
|
case "404.example.com":
|
||||||
|
response.setStatusLine(request.httpVersion, 404, "Not found");
|
||||||
|
break;
|
||||||
|
case "407.example.com":
|
||||||
|
response.setStatusLine(request.httpVersion, 407, "Authenticate");
|
||||||
|
// And deliberately no Proxy-Authenticate header
|
||||||
|
break;
|
||||||
|
case "502.example.com":
|
||||||
|
response.setStatusLine(request.httpVersion, 502, "Bad Gateway");
|
||||||
|
break;
|
||||||
|
case "504.example.com":
|
||||||
|
response.setStatusLine(request.httpVersion, 504, "Gateway timeout");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
response.setStatusLine(request.httpVersion, 500, "I am dumb");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
add_task(async function setup() {
|
||||||
|
http_server = new HttpServer();
|
||||||
|
http_server.identity.add("https", "404.example.com", 443);
|
||||||
|
http_server.identity.add("https", "407.example.com", 443);
|
||||||
|
http_server.identity.add("https", "502.example.com", 443);
|
||||||
|
http_server.identity.add("https", "504.example.com", 443);
|
||||||
|
http_server.registerPathHandler("CONNECT", connect_handler);
|
||||||
|
http_server.start(-1);
|
||||||
|
server_port = http_server.identity.primaryPort;
|
||||||
|
|
||||||
|
// make all native resolve calls "secretly" resolve localhost instead
|
||||||
|
Services.prefs.setBoolPref("network.dns.native-is-localhost", true);
|
||||||
|
|
||||||
|
pps.registerFilter(new ProxyFilter("http", "localhost", server_port, 0), 10);
|
||||||
|
});
|
||||||
|
|
||||||
|
registerCleanupFunction(() => {
|
||||||
|
Services.prefs.clearUserPref("network.dns.native-is-localhost");
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test series beginning.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// The proxy responses with 407 instead of 200 Connected, make sure we get a proper error
|
||||||
|
// code from the channel and not try to ask for any credentials.
|
||||||
|
add_task(async function proxy_auth_failure() {
|
||||||
|
const chan = make_channel(`https://407.example.com/`);
|
||||||
|
const auth_prompt = { triggered: false };
|
||||||
|
chan.notificationCallbacks = new AuthRequestor(() => new UnxpectedAuthPrompt2(auth_prompt));
|
||||||
|
const { status, http_code } = await get_response(chan, CL_EXPECT_FAILURE);
|
||||||
|
|
||||||
|
Assert.equal(status, Cr.NS_ERROR_PROXY_AUTHENTICATION_FAILED);
|
||||||
|
Assert.equal(http_code, undefined);
|
||||||
|
Assert.equal(auth_prompt.triggered, false, "Auth prompt didn't trigger");
|
||||||
|
});
|
||||||
|
|
||||||
|
// 502 Bad gateway code returned by the proxy.
|
||||||
|
add_task(async function proxy_bad_gateway_failure() {
|
||||||
|
const { status, http_code } = await get_response(make_channel(`https://502.example.com/`), CL_EXPECT_FAILURE);
|
||||||
|
|
||||||
|
Assert.equal(status, Cr.NS_ERROR_PROXY_BAD_GATEWAY);
|
||||||
|
Assert.equal(http_code, undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 504 Gateway timeout code returned by the proxy.
|
||||||
|
add_task(async function proxy_gateway_timeout_failure() {
|
||||||
|
const { status, http_code } = await get_response(make_channel(`https://504.example.com/`), CL_EXPECT_FAILURE);
|
||||||
|
|
||||||
|
Assert.equal(status, Cr.NS_ERROR_PROXY_GATEWAY_TIMEOUT);
|
||||||
|
Assert.equal(http_code, undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 404 Not Found means the proxy could not resolve the host.
|
||||||
|
add_task(async function proxy_host_not_found_failure() {
|
||||||
|
const { status, http_code } = await get_response(make_channel(`https://404.example.com/`), CL_EXPECT_FAILURE);
|
||||||
|
|
||||||
|
Assert.equal(status, Cr.NS_ERROR_UNKNOWN_HOST);
|
||||||
|
Assert.equal(http_code, undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function shutdown() {
|
||||||
|
await new Promise(resolve => {
|
||||||
|
http_server.stop(resolve);
|
||||||
|
});
|
||||||
|
});
|
266
netwerk/test/unit/test_http2-proxy.js
Normal file
266
netwerk/test/unit/test_http2-proxy.js
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||||
|
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This test checks following expectations when using HTTP/2 proxy:
|
||||||
|
*
|
||||||
|
* - when we request https access, we don't create different sessions for
|
||||||
|
* different origins, only new tunnels inside a single session
|
||||||
|
* - when the isolation key (`proxy_isolation`) is changed, new single session
|
||||||
|
* is created for new requests to same origins as before
|
||||||
|
* - error code returned from the tunnel (a proxy error - not end-server
|
||||||
|
* error!) doesn't kill the existing session
|
||||||
|
* - check we are seeing expected nsresult error codes on channels
|
||||||
|
* (nsIChannel.status) corresponding to different proxy status code
|
||||||
|
* responses (502, 504, 407, ...)
|
||||||
|
* - check we don't try to ask for credentials or otherwise authenticate to
|
||||||
|
* the proxy when 407 is returned and there is no Proxy-Authenticate
|
||||||
|
* response header sent
|
||||||
|
*/
|
||||||
|
|
||||||
|
const pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();
|
||||||
|
|
||||||
|
let proxy_port;
|
||||||
|
let server_port;
|
||||||
|
|
||||||
|
// See moz-http2
|
||||||
|
const proxy_auth = 'authorization-token';
|
||||||
|
let proxy_isolation;
|
||||||
|
|
||||||
|
class ProxyFilter {
|
||||||
|
constructor(type, host, port, flags) {
|
||||||
|
this._type = type;
|
||||||
|
this._host = host;
|
||||||
|
this._port = port;
|
||||||
|
this._flags = flags;
|
||||||
|
this.QueryInterface = ChromeUtils.generateQI([Ci.nsIProtocolProxyFilter]);
|
||||||
|
}
|
||||||
|
applyFilter(pps, uri, pi, cb) {
|
||||||
|
if (uri.spec.match(/(\/proxy-session-counter)/)) {
|
||||||
|
cb.onProxyFilterResult(pi);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cb.onProxyFilterResult(pps.newProxyInfo(
|
||||||
|
this._type, this._host, this._port,
|
||||||
|
proxy_auth, proxy_isolation, this._flags, 1000, null));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class UnxpectedAuthPrompt2 {
|
||||||
|
constructor(signal) {
|
||||||
|
this.signal = signal;
|
||||||
|
this.QueryInterface = ChromeUtils.generateQI([Ci.nsIAuthPrompt2]);
|
||||||
|
}
|
||||||
|
asyncPromptAuth() {
|
||||||
|
this.signal.triggered = true;
|
||||||
|
throw Cr.ERROR_UNEXPECTED;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class AuthRequestor {
|
||||||
|
constructor(prompt) {
|
||||||
|
this.prompt = prompt;
|
||||||
|
this.QueryInterface = ChromeUtils.generateQI([Ci.nsIInterfaceRequestor]);
|
||||||
|
}
|
||||||
|
getInterface(iid) {
|
||||||
|
if (iid.equals(Ci.nsIAuthPrompt2)) {
|
||||||
|
return this.prompt();
|
||||||
|
}
|
||||||
|
throw Cr.NS_ERROR_NO_INTERFACE;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function make_channel(url) {
|
||||||
|
return NetUtil.newChannel({
|
||||||
|
uri: url,
|
||||||
|
loadUsingSystemPrincipal: true,
|
||||||
|
// Using TYPE_DOCUMENT for the authentication dialog test, it'd be blocked for other types
|
||||||
|
contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_response(channel, flags = CL_ALLOW_UNKNOWN_CL) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
channel.asyncOpen(new ChannelListener((request, data) => {
|
||||||
|
request.QueryInterface(Ci.nsIHttpChannel);
|
||||||
|
const status = request.status;
|
||||||
|
const http_code = status ? undefined : request.responseStatus;
|
||||||
|
|
||||||
|
resolve({ status, http_code, data });
|
||||||
|
}, null, flags));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let initial_session_count = 0;
|
||||||
|
|
||||||
|
function proxy_session_counter() {
|
||||||
|
return new Promise(async resolve => {
|
||||||
|
const channel = make_channel(`https://localhost:${server_port}/proxy-session-counter`);
|
||||||
|
const { data } = await get_response(channel);
|
||||||
|
resolve(parseInt(data) - initial_session_count);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
add_task(async function setup() {
|
||||||
|
const env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
|
||||||
|
server_port = env.get("MOZHTTP2_PORT");
|
||||||
|
Assert.notEqual(server_port, null);
|
||||||
|
proxy_port = env.get("MOZHTTP2_PROXY_PORT");
|
||||||
|
Assert.notEqual(proxy_port, null);
|
||||||
|
|
||||||
|
// Set to allow the cert presented by our H2 server
|
||||||
|
do_get_profile();
|
||||||
|
|
||||||
|
Services.prefs.setBoolPref("network.http.spdy.enabled", true);
|
||||||
|
Services.prefs.setBoolPref("network.http.spdy.enabled.http2", true);
|
||||||
|
|
||||||
|
// make all native resolve calls "secretly" resolve localhost instead
|
||||||
|
Services.prefs.setBoolPref("network.dns.native-is-localhost", true);
|
||||||
|
|
||||||
|
// The moz-http2 cert is for foo.example.com and is signed by http2-ca.pem
|
||||||
|
// so add that cert to the trust list as a signing cert.
|
||||||
|
let certdb = Cc["@mozilla.org/security/x509certdb;1"]
|
||||||
|
.getService(Ci.nsIX509CertDB);
|
||||||
|
addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
|
||||||
|
|
||||||
|
pps.registerFilter(new ProxyFilter("https", "localhost", proxy_port, 0), 10);
|
||||||
|
|
||||||
|
initial_session_count = await proxy_session_counter();
|
||||||
|
info(`Initial proxy session count = ${initial_session_count}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
registerCleanupFunction(() => {
|
||||||
|
Services.prefs.clearUserPref("network.http.spdy.enabled");
|
||||||
|
Services.prefs.clearUserPref("network.http.spdy.enabled.http2");
|
||||||
|
Services.prefs.clearUserPref("network.dns.native-is-localhost");
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test series beginning.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Check we reach the h2 end server and keep only one session with the proxy for two different origin.
|
||||||
|
// Here we use the first isolation token.
|
||||||
|
add_task(async function proxy_success_one_session() {
|
||||||
|
proxy_isolation = "TOKEN1";
|
||||||
|
|
||||||
|
const foo = await get_response(make_channel(`https://foo.example.com/random-request-1`));
|
||||||
|
const alt1 = await get_response(make_channel(`https://alt1.example.com/random-request-2`));
|
||||||
|
|
||||||
|
Assert.equal(foo.status, Cr.NS_OK);
|
||||||
|
Assert.equal(foo.http_code, 200);
|
||||||
|
Assert.ok(foo.data.match("random-request-1"));
|
||||||
|
Assert.ok(foo.data.match("You Win!"));
|
||||||
|
Assert.equal(alt1.status, Cr.NS_OK);
|
||||||
|
Assert.equal(alt1.http_code, 200);
|
||||||
|
Assert.ok(alt1.data.match("random-request-2"));
|
||||||
|
Assert.ok(alt1.data.match("You Win!"));
|
||||||
|
Assert.equal(await proxy_session_counter(), 1, "Created just one session with the proxy");
|
||||||
|
});
|
||||||
|
|
||||||
|
// The proxy responses with 407 instead of 200 Connected, make sure we get a proper error
|
||||||
|
// code from the channel and not try to ask for any credentials.
|
||||||
|
add_task(async function proxy_auth_failure() {
|
||||||
|
const chan = make_channel(`https://407.example.com/`);
|
||||||
|
const auth_prompt = { triggered: false };
|
||||||
|
chan.notificationCallbacks = new AuthRequestor(() => new UnxpectedAuthPrompt2(auth_prompt));
|
||||||
|
const { status, http_code } = await get_response(chan, CL_EXPECT_FAILURE);
|
||||||
|
|
||||||
|
Assert.equal(status, Cr.NS_ERROR_PROXY_AUTHENTICATION_FAILED);
|
||||||
|
Assert.equal(http_code, undefined);
|
||||||
|
Assert.equal(auth_prompt.triggered, false, "Auth prompt didn't trigger");
|
||||||
|
Assert.equal(await proxy_session_counter(), 1, "No new session created by 407");
|
||||||
|
});
|
||||||
|
|
||||||
|
// 502 Bad gateway code returned by the proxy, still one session only, proper different code
|
||||||
|
// from the channel.
|
||||||
|
add_task(async function proxy_bad_gateway_failure() {
|
||||||
|
const { status, http_code } = await get_response(make_channel(`https://502.example.com/`), CL_EXPECT_FAILURE);
|
||||||
|
|
||||||
|
Assert.equal(status, Cr.NS_ERROR_PROXY_BAD_GATEWAY);
|
||||||
|
Assert.equal(http_code, undefined);
|
||||||
|
Assert.equal(await proxy_session_counter(), 1, "No new session created by 502 after 407");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Second 502 Bad gateway code returned by the proxy, still one session only with the proxy.
|
||||||
|
add_task(async function proxy_bad_gateway_failure_two() {
|
||||||
|
const { status, http_code } = await get_response(make_channel(`https://502.example.com/`), CL_EXPECT_FAILURE);
|
||||||
|
|
||||||
|
Assert.equal(status, Cr.NS_ERROR_PROXY_BAD_GATEWAY);
|
||||||
|
Assert.equal(http_code, undefined);
|
||||||
|
Assert.equal(await proxy_session_counter(), 1, "No new session created by second 502");
|
||||||
|
});
|
||||||
|
|
||||||
|
// 504 Gateway timeout code returned by the proxy, still one session only, proper different code
|
||||||
|
// from the channel.
|
||||||
|
add_task(async function proxy_gateway_timeout_failure() {
|
||||||
|
const { status, http_code } = await get_response(make_channel(`https://504.example.com/`), CL_EXPECT_FAILURE);
|
||||||
|
|
||||||
|
Assert.equal(status, Cr.NS_ERROR_PROXY_GATEWAY_TIMEOUT);
|
||||||
|
Assert.equal(http_code, undefined);
|
||||||
|
Assert.equal(await proxy_session_counter(), 1, "No new session created by 504 after 502");
|
||||||
|
});
|
||||||
|
|
||||||
|
// 404 Not Found means the proxy could not resolve the host. As for other error responses
|
||||||
|
// we still expect this not to close the existing session.
|
||||||
|
add_task(async function proxy_host_not_found_failure() {
|
||||||
|
const { status, http_code } = await get_response(make_channel(`https://404.example.com/`), CL_EXPECT_FAILURE);
|
||||||
|
|
||||||
|
Assert.equal(status, Cr.NS_ERROR_UNKNOWN_HOST);
|
||||||
|
Assert.equal(http_code, undefined);
|
||||||
|
Assert.equal(await proxy_session_counter(), 1, "No new session created by 404 after 504");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Make sure that the above error codes don't kill the session and we still reach the end server
|
||||||
|
add_task(async function proxy_success_still_one_session() {
|
||||||
|
const foo = await get_response(make_channel(`https://foo.example.com/random-request-1`));
|
||||||
|
const alt1 = await get_response(make_channel(`https://alt1.example.com/random-request-2`));
|
||||||
|
|
||||||
|
Assert.equal(foo.status, Cr.NS_OK);
|
||||||
|
Assert.equal(foo.http_code, 200);
|
||||||
|
Assert.ok(foo.data.match("random-request-1"));
|
||||||
|
Assert.equal(alt1.status, Cr.NS_OK);
|
||||||
|
Assert.equal(alt1.http_code, 200);
|
||||||
|
Assert.ok(alt1.data.match("random-request-2"));
|
||||||
|
Assert.equal(await proxy_session_counter(), 1, "No new session created after proxy error codes");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Have a new isolation key, this means we are expected to create a new, and again one only,
|
||||||
|
// session with the proxy to reach the end server.
|
||||||
|
add_task(async function proxy_success_isolated_session() {
|
||||||
|
Assert.notEqual(proxy_isolation, "TOKEN2");
|
||||||
|
proxy_isolation = "TOKEN2";
|
||||||
|
|
||||||
|
const foo = await get_response(make_channel(`https://foo.example.com/random-request-1`));
|
||||||
|
const alt1 = await get_response(make_channel(`https://alt1.example.com/random-request-2`));
|
||||||
|
const lh = await get_response(make_channel(`https://localhost/random-request-3`));
|
||||||
|
|
||||||
|
Assert.equal(foo.status, Cr.NS_OK);
|
||||||
|
Assert.equal(foo.http_code, 200);
|
||||||
|
Assert.ok(foo.data.match("random-request-1"));
|
||||||
|
Assert.ok(foo.data.match("You Win!"));
|
||||||
|
Assert.equal(alt1.status, Cr.NS_OK);
|
||||||
|
Assert.equal(alt1.http_code, 200);
|
||||||
|
Assert.ok(alt1.data.match("random-request-2"));
|
||||||
|
Assert.ok(alt1.data.match("You Win!"));
|
||||||
|
Assert.equal(lh.status, Cr.NS_OK);
|
||||||
|
Assert.equal(lh.http_code, 200);
|
||||||
|
Assert.ok(lh.data.match("random-request-3"));
|
||||||
|
Assert.ok(lh.data.match("You Win!"));
|
||||||
|
Assert.equal(await proxy_session_counter(), 2, "Just one new session seen after changing the isolation key");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check that error codes are still handled the same way with new isolation, just in case.
|
||||||
|
add_task(async function proxy_bad_gateway_failure_isolated() {
|
||||||
|
const failure1 = await get_response(make_channel(`https://502.example.com/`), CL_EXPECT_FAILURE);
|
||||||
|
const failure2 = await get_response(make_channel(`https://502.example.com/`), CL_EXPECT_FAILURE);
|
||||||
|
|
||||||
|
Assert.equal(failure1.status, Cr.NS_ERROR_PROXY_BAD_GATEWAY);
|
||||||
|
Assert.equal(failure1.http_code, undefined);
|
||||||
|
Assert.equal(failure2.status, Cr.NS_ERROR_PROXY_BAD_GATEWAY);
|
||||||
|
Assert.equal(failure2.http_code, undefined);
|
||||||
|
Assert.equal(await proxy_session_counter(), 2, "No new session created by 502");
|
||||||
|
});
|
@ -1,72 +0,0 @@
|
|||||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
|
||||||
/* vim:set ts=2 sw=2 sts=2 et: */
|
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
// This test checks that the proxy-auth header is propagated to the CONNECT request when
|
|
||||||
// set on a proxy-info object via a proxy filter
|
|
||||||
|
|
||||||
const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
|
|
||||||
const pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();
|
|
||||||
|
|
||||||
class TestFilter {
|
|
||||||
constructor(type, host, port, flags, auth) {
|
|
||||||
this._type = type;
|
|
||||||
this._host = host;
|
|
||||||
this._port = port;
|
|
||||||
this._flags = flags;
|
|
||||||
this._auth = auth;
|
|
||||||
this.QueryInterface = ChromeUtils.generateQI([Ci.nsIProtocolProxyFilter]);
|
|
||||||
}
|
|
||||||
|
|
||||||
applyFilter(pps, uri, pi, cb) {
|
|
||||||
cb.onProxyFilterResult(pps.newProxyInfo(
|
|
||||||
this._type, this._host, this._port,
|
|
||||||
this._auth, "", this._flags, 1000, null));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let httpServer = null;
|
|
||||||
let port;
|
|
||||||
let connectProcessesed = false;
|
|
||||||
const proxyAuthHeader = 'proxy-auth-header-value';
|
|
||||||
|
|
||||||
function make_channel(url) {
|
|
||||||
return NetUtil.newChannel({
|
|
||||||
uri: url,
|
|
||||||
loadUsingSystemPrincipal: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function connect_handler(request, response)
|
|
||||||
{
|
|
||||||
Assert.equal(request.method, "CONNECT");
|
|
||||||
Assert.ok(request.hasHeader("Proxy-Authorization"));
|
|
||||||
Assert.equal(request.getHeader("Proxy-Authorization"), proxyAuthHeader);
|
|
||||||
|
|
||||||
// Just refuse to connect, we have what we need now.
|
|
||||||
response.setStatusLine(request.httpVersion, 500, "STOP");
|
|
||||||
connectProcessesed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function finish_test()
|
|
||||||
{
|
|
||||||
Assert.ok(connectProcessesed);
|
|
||||||
httpServer.stop(do_test_finished);
|
|
||||||
}
|
|
||||||
|
|
||||||
function run_test()
|
|
||||||
{
|
|
||||||
httpServer = new HttpServer();
|
|
||||||
httpServer.identity.add("https", "mozilla.org", 443);
|
|
||||||
httpServer.registerPathHandler("CONNECT", connect_handler);
|
|
||||||
httpServer.start(-1);
|
|
||||||
port = httpServer.identity.primaryPort;
|
|
||||||
|
|
||||||
pps.registerFilter(new TestFilter("http", "localhost", port, 0, proxyAuthHeader), 10);
|
|
||||||
|
|
||||||
let chan = make_channel("https://mozilla.org/");
|
|
||||||
chan.asyncOpen(new ChannelListener(finish_test, null, CL_EXPECT_FAILURE));
|
|
||||||
do_test_pending();
|
|
||||||
}
|
|
@ -450,7 +450,9 @@ skip-if = os == "android" # DNSv6 issues on android
|
|||||||
[test_stale-while-revalidate_negative.js]
|
[test_stale-while-revalidate_negative.js]
|
||||||
[test_stale-while-revalidate_positive.js]
|
[test_stale-while-revalidate_positive.js]
|
||||||
[test_stale-while-revalidate_max-age-0.js]
|
[test_stale-while-revalidate_max-age-0.js]
|
||||||
[test_proxy-authorization-via-proxyinfo.js]
|
[test_http1-proxy.js]
|
||||||
# Bug 1549368
|
[test_http2-proxy.js]
|
||||||
|
run-sequentially = one http2 node proxy is used for all tests, this test is using global session counter
|
||||||
|
# so far has to be disabled until we update nodejs to at least 8.4+, then keep off on android (os == "android")
|
||||||
skip-if = true
|
skip-if = true
|
||||||
[test_head_request_no_response_body.js]
|
[test_head_request_no_response_body.js]
|
||||||
|
@ -11,11 +11,19 @@ if (process.env.NODE_HTTP2_ROOT) {
|
|||||||
}
|
}
|
||||||
var http2 = require(node_http2_root);
|
var http2 = require(node_http2_root);
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
|
var net = require('net');
|
||||||
var url = require('url');
|
var url = require('url');
|
||||||
var crypto = require('crypto');
|
var crypto = require('crypto');
|
||||||
const dnsPacket = require(`${node_http2_root}/../dns-packet`);
|
const dnsPacket = require(`${node_http2_root}/../dns-packet`);
|
||||||
const ip = require(`${node_http2_root}/../node-ip`);
|
const ip = require(`${node_http2_root}/../node-ip`);
|
||||||
|
|
||||||
|
let http2_internal = null;
|
||||||
|
try {
|
||||||
|
http2_internal = require('http2');
|
||||||
|
} catch (_) {
|
||||||
|
// silently ignored
|
||||||
|
}
|
||||||
|
|
||||||
// Hook into the decompression code to log the decompressed name-value pairs
|
// Hook into the decompression code to log the decompressed name-value pairs
|
||||||
var compression_module = node_http2_root + "/lib/protocol/compressor";
|
var compression_module = node_http2_root + "/lib/protocol/compressor";
|
||||||
var http2_compression = require(compression_module);
|
var http2_compression = require(compression_module);
|
||||||
@ -76,6 +84,7 @@ function getHttpContent(path) {
|
|||||||
var content = '<!doctype html>' +
|
var content = '<!doctype html>' +
|
||||||
'<html>' +
|
'<html>' +
|
||||||
'<head><title>HOORAY!</title></head>' +
|
'<head><title>HOORAY!</title></head>' +
|
||||||
|
// 'You Win!' used in tests to check we reached this server
|
||||||
'<body>You Win! (by requesting' + path + ')</body>' +
|
'<body>You Win! (by requesting' + path + ')</body>' +
|
||||||
'</html>';
|
'</html>';
|
||||||
return content;
|
return content;
|
||||||
@ -1111,6 +1120,12 @@ function handleRequest(req, res) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else if (u.pathname === "/proxy-session-counter") {
|
||||||
|
// Incremented with every newly created session on the proxy
|
||||||
|
res.end(proxy_session_count.toString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
res.setHeader('Content-Type', 'text/html');
|
res.setHeader('Content-Type', 'text/html');
|
||||||
if (req.httpVersionMajor != 2) {
|
if (req.httpVersionMajor != 2) {
|
||||||
res.setHeader('Connection', 'close');
|
res.setHeader('Connection', 'close');
|
||||||
@ -1141,18 +1156,91 @@ server.on('connection', function(socket) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
var proxy = http2_internal ? http2_internal.createSecureServer(options) : null;
|
||||||
|
var proxy_session_count = 0;
|
||||||
|
|
||||||
|
if (http2_internal) {
|
||||||
|
proxy.on('session', () => {
|
||||||
|
// Can be queried directly on the h2 server under "/proxy-session-counter"
|
||||||
|
++proxy_session_count;
|
||||||
|
});
|
||||||
|
|
||||||
|
proxy.on('stream', (stream, headers) => {
|
||||||
|
if (headers[':method'] !== 'CONNECT') {
|
||||||
|
// Only accept CONNECT requests
|
||||||
|
stream.close(http2.constants.NGHTTP2_REFUSED_STREAM);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const target = headers[':authority'];
|
||||||
|
|
||||||
|
const authorization_token = headers['proxy-authorization'];
|
||||||
|
if ('authorization-token' != authorization_token || target == '407.example.com:443') {
|
||||||
|
stream.respond({ ':status': 407 });
|
||||||
|
// Deliberately send no Proxy-Authenticate header
|
||||||
|
stream.end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (target == '404.example.com:443') {
|
||||||
|
// 404 Not Found, a response code that a proxy should return when the host can't be found
|
||||||
|
stream.respond({ ':status': 404 });
|
||||||
|
stream.end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (target == '502.example.com:443') {
|
||||||
|
// 502 Bad Gateway, a response code mostly resembling immediate connection error
|
||||||
|
stream.respond({ ':status': 502 });
|
||||||
|
stream.end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (target == '504.example.com:443') {
|
||||||
|
// 504 Gateway Timeout, did not receive a timely response from an upstream server
|
||||||
|
stream.respond({ ':status': 504 });
|
||||||
|
stream.end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const socket = net.connect(serverPort, '127.0.0.1', () => {
|
||||||
|
try {
|
||||||
|
stream.respond({ ':status': 200 });
|
||||||
|
socket.pipe(stream);
|
||||||
|
stream.pipe(socket);
|
||||||
|
} catch (exception) {
|
||||||
|
stream.close(http2.constants.NGHTTP2_STREAM_CLOSED);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
socket.on('error', (error) => {
|
||||||
|
throw `Unxpected error when conneting the HTTP/2 server from the HTTP/2 proxy during CONNECT handling: '${error}'`;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
var serverPort;
|
var serverPort;
|
||||||
function listenok() {
|
|
||||||
serverPort = server._server.address().port;
|
const listen = (server, envport) => {
|
||||||
console.log('HTTP2 server listening on port ' + serverPort);
|
if (!server) {
|
||||||
}
|
return Promise.resolve(0);
|
||||||
var portSelection = 0;
|
|
||||||
var envport = process.env.MOZHTTP2_PORT;
|
|
||||||
if (envport !== undefined) {
|
|
||||||
try {
|
|
||||||
portSelection = parseInt(envport, 10);
|
|
||||||
} catch (e) {
|
|
||||||
portSelection = -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let portSelection = 0;
|
||||||
|
if (envport !== undefined) {
|
||||||
|
try {
|
||||||
|
portSelection = parseInt(envport, 10);
|
||||||
|
} catch (e) {
|
||||||
|
portSelection = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Promise(resolve => {
|
||||||
|
server.listen(portSelection, "0.0.0.0", 200, () => {
|
||||||
|
resolve(server.address().port);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
server.listen(portSelection, "0.0.0.0", 200, listenok);
|
|
||||||
|
Promise.all([
|
||||||
|
listen(server, process.env.MOZHTTP2_PORT).then(port => serverPort = port),
|
||||||
|
listen(proxy, process.env.MOZHTTP2_PROXY_PORT)
|
||||||
|
]).then(([serverPort, proxyPort]) => {
|
||||||
|
console.log(`HTTP2 server listening on ports ${serverPort},${proxyPort}`);
|
||||||
|
});
|
||||||
|
@ -1131,9 +1131,11 @@ class XPCShellTests(object):
|
|||||||
# tell us it's started
|
# tell us it's started
|
||||||
msg = process.stdout.readline()
|
msg = process.stdout.readline()
|
||||||
if 'server listening' in msg:
|
if 'server listening' in msg:
|
||||||
searchObj = re.search(r'HTTP2 server listening on port (.*)', msg, 0)
|
searchObj = re.search(r'HTTP2 server listening on ports ([0-9]+),([0-9]+)',
|
||||||
|
msg, 0)
|
||||||
if searchObj:
|
if searchObj:
|
||||||
self.env["MOZHTTP2_PORT"] = searchObj.group(1)
|
self.env["MOZHTTP2_PORT"] = searchObj.group(1)
|
||||||
|
self.env["MOZHTTP2_PROXY_PORT"] = searchObj.group(2)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
# This occurs if the subprocess couldn't be started
|
# This occurs if the subprocess couldn't be started
|
||||||
self.log.error('Could not run %s server: %s' % (name, str(e)))
|
self.log.error('Could not run %s server: %s' % (name, str(e)))
|
||||||
|
@ -329,6 +329,12 @@ with modules["NETWORK"]:
|
|||||||
errors["NS_ERROR_NET_INTERRUPT"] = FAILURE(71)
|
errors["NS_ERROR_NET_INTERRUPT"] = FAILURE(71)
|
||||||
# The connection attempt to a proxy failed.
|
# The connection attempt to a proxy failed.
|
||||||
errors["NS_ERROR_PROXY_CONNECTION_REFUSED"] = FAILURE(72)
|
errors["NS_ERROR_PROXY_CONNECTION_REFUSED"] = FAILURE(72)
|
||||||
|
# The proxy requires authentication; used when we can't easily propagate 407s.
|
||||||
|
errors["NS_ERROR_PROXY_AUTHENTICATION_FAILED"] = FAILURE(407)
|
||||||
|
# The proxy failed to connect the remote server.
|
||||||
|
errors["NS_ERROR_PROXY_BAD_GATEWAY"] = FAILURE(502)
|
||||||
|
# The proxy did get any response from the remote server in time.
|
||||||
|
errors["NS_ERROR_PROXY_GATEWAY_TIMEOUT"] = FAILURE(504)
|
||||||
# A transfer was only partially done when it completed.
|
# A transfer was only partially done when it completed.
|
||||||
errors["NS_ERROR_NET_PARTIAL_TRANSFER"] = FAILURE(76)
|
errors["NS_ERROR_NET_PARTIAL_TRANSFER"] = FAILURE(76)
|
||||||
# HTTP/2 detected invalid TLS configuration
|
# HTTP/2 detected invalid TLS configuration
|
||||||
|
Loading…
Reference in New Issue
Block a user