test_http1-proxy.js (7270B)
1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ 2 /* vim:set ts=2 sw=2 sts=2 et: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 /** 8 * This test checks following expectations when using HTTP/1 proxy: 9 * 10 * - check we are seeing expected nsresult error codes on channels 11 * (nsIChannel.status) corresponding to different proxy status code 12 * responses (502, 504, 407, ...) 13 * - check we don't try to ask for credentials or otherwise authenticate to 14 * the proxy when 407 is returned and there is no Proxy-Authenticate 15 * response header sent 16 */ 17 18 "use strict"; 19 20 const { HttpServer } = ChromeUtils.importESModule( 21 "resource://testing-common/httpd.sys.mjs" 22 ); 23 24 const pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].getService(); 25 26 let server_port; 27 let http_server; 28 29 class ProxyFilter { 30 constructor(type, host, port, flags) { 31 this._type = type; 32 this._host = host; 33 this._port = port; 34 this._flags = flags; 35 this.QueryInterface = ChromeUtils.generateQI(["nsIProtocolProxyFilter"]); 36 } 37 applyFilter(uri, pi, cb) { 38 if (uri.spec.match(/(\/proxy-session-counter)/)) { 39 cb.onProxyFilterResult(pi); 40 return; 41 } 42 cb.onProxyFilterResult( 43 pps.newProxyInfo( 44 this._type, 45 this._host, 46 this._port, 47 "", 48 "", 49 this._flags, 50 1000, 51 null 52 ) 53 ); 54 } 55 } 56 57 class UnxpectedAuthPrompt2 { 58 constructor(signal) { 59 this.signal = signal; 60 this.QueryInterface = ChromeUtils.generateQI(["nsIAuthPrompt2"]); 61 } 62 asyncPromptAuth() { 63 this.signal.triggered = true; 64 throw Components.Exception("", Cr.ERROR_UNEXPECTED); 65 } 66 } 67 68 class AuthRequestor { 69 constructor(prompt) { 70 this.prompt = prompt; 71 this.QueryInterface = ChromeUtils.generateQI(["nsIInterfaceRequestor"]); 72 } 73 getInterface(iid) { 74 if (iid.equals(Ci.nsIAuthPrompt2)) { 75 return this.prompt(); 76 } 77 throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE); 78 } 79 } 80 81 function make_channel(url) { 82 return NetUtil.newChannel({ 83 uri: url, 84 loadUsingSystemPrincipal: true, 85 // Using TYPE_DOCUMENT for the authentication dialog test, it'd be blocked for other types 86 contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT, 87 }); 88 } 89 90 function get_response(channel, flags = CL_ALLOW_UNKNOWN_CL) { 91 return new Promise(resolve => { 92 channel.asyncOpen( 93 new ChannelListener( 94 (request, data) => { 95 request.QueryInterface(Ci.nsIHttpChannel); 96 const status = request.status; 97 const http_code = status ? undefined : request.responseStatus; 98 request.QueryInterface(Ci.nsIProxiedChannel); 99 const proxy_connect_response_code = 100 request.httpProxyConnectResponseCode; 101 resolve({ status, http_code, data, proxy_connect_response_code }); 102 }, 103 null, 104 flags 105 ) 106 ); 107 }); 108 } 109 110 function connect_handler(request, response) { 111 Assert.equal(request.method, "CONNECT"); 112 113 switch (request.host) { 114 case "404.example.com": 115 response.setStatusLine(request.httpVersion, 404, "Not found"); 116 break; 117 case "407.example.com": 118 response.setStatusLine(request.httpVersion, 407, "Authenticate"); 119 // And deliberately no Proxy-Authenticate header 120 break; 121 case "429.example.com": 122 response.setStatusLine(request.httpVersion, 429, "Too Many Requests"); 123 break; 124 case "502.example.com": 125 response.setStatusLine(request.httpVersion, 502, "Bad Gateway"); 126 break; 127 case "504.example.com": 128 response.setStatusLine(request.httpVersion, 504, "Gateway timeout"); 129 break; 130 default: 131 response.setStatusLine(request.httpVersion, 500, "I am dumb"); 132 } 133 } 134 135 add_task(async function setup() { 136 http_server = new HttpServer(); 137 http_server.identity.add("https", "404.example.com", 443); 138 http_server.identity.add("https", "407.example.com", 443); 139 http_server.identity.add("https", "429.example.com", 443); 140 http_server.identity.add("https", "502.example.com", 443); 141 http_server.identity.add("https", "504.example.com", 443); 142 http_server.registerPathHandler("CONNECT", connect_handler); 143 http_server.start(-1); 144 server_port = http_server.identity.primaryPort; 145 146 // make all native resolve calls "secretly" resolve localhost instead 147 Services.prefs.setBoolPref("network.dns.native-is-localhost", true); 148 149 pps.registerFilter(new ProxyFilter("http", "localhost", server_port, 0), 10); 150 }); 151 152 registerCleanupFunction(() => { 153 Services.prefs.clearUserPref("network.dns.native-is-localhost"); 154 }); 155 156 /** 157 * Test series beginning. 158 */ 159 160 // The proxy responses with 407 instead of 200 Connected, make sure we get a proper error 161 // code from the channel and not try to ask for any credentials. 162 add_task(async function proxy_auth_failure() { 163 const chan = make_channel(`https://407.example.com/`); 164 const auth_prompt = { triggered: false }; 165 chan.notificationCallbacks = new AuthRequestor( 166 () => new UnxpectedAuthPrompt2(auth_prompt) 167 ); 168 const { status, http_code, proxy_connect_response_code } = await get_response( 169 chan, 170 CL_EXPECT_FAILURE 171 ); 172 173 Assert.equal(status, Cr.NS_ERROR_PROXY_AUTHENTICATION_FAILED); 174 Assert.equal(proxy_connect_response_code, 407); 175 Assert.equal(http_code, undefined); 176 Assert.equal(auth_prompt.triggered, false, "Auth prompt didn't trigger"); 177 }); 178 179 // 502 Bad gateway code returned by the proxy. 180 add_task(async function proxy_bad_gateway_failure() { 181 const { status, http_code, proxy_connect_response_code } = await get_response( 182 make_channel(`https://502.example.com/`), 183 CL_EXPECT_FAILURE 184 ); 185 186 Assert.equal(status, Cr.NS_ERROR_PROXY_BAD_GATEWAY); 187 Assert.equal(proxy_connect_response_code, 502); 188 Assert.equal(http_code, undefined); 189 }); 190 191 // 504 Gateway timeout code returned by the proxy. 192 add_task(async function proxy_gateway_timeout_failure() { 193 const { status, http_code, proxy_connect_response_code } = await get_response( 194 make_channel(`https://504.example.com/`), 195 CL_EXPECT_FAILURE 196 ); 197 198 Assert.equal(status, Cr.NS_ERROR_PROXY_GATEWAY_TIMEOUT); 199 Assert.equal(proxy_connect_response_code, 504); 200 Assert.equal(http_code, undefined); 201 }); 202 203 // 404 Not Found means the proxy could not resolve the host. 204 add_task(async function proxy_host_not_found_failure() { 205 const { status, http_code, proxy_connect_response_code } = await get_response( 206 make_channel(`https://404.example.com/`), 207 CL_EXPECT_FAILURE 208 ); 209 210 Assert.equal(status, Cr.NS_ERROR_UNKNOWN_HOST); 211 Assert.equal(proxy_connect_response_code, 404); 212 Assert.equal(http_code, undefined); 213 }); 214 215 // 429 Too Many Requests means we sent too many requests. 216 add_task(async function proxy_too_many_requests_failure() { 217 const { status, http_code, proxy_connect_response_code } = await get_response( 218 make_channel(`https://429.example.com/`), 219 CL_EXPECT_FAILURE 220 ); 221 222 Assert.equal(status, Cr.NS_ERROR_PROXY_TOO_MANY_REQUESTS); 223 Assert.equal(proxy_connect_response_code, 429); 224 Assert.equal(http_code, undefined); 225 }); 226 227 add_task(async function shutdown() { 228 await new Promise(resolve => { 229 http_server.stop(resolve); 230 }); 231 });