/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ const { Observers } = ChromeUtils.importESModule( "resource://services-common/observers.sys.mjs" ); const { Resource } = ChromeUtils.importESModule( "resource://services-sync/resource.sys.mjs" ); const { SyncAuthManager } = ChromeUtils.importESModule( "resource://services-sync/sync_auth.sys.mjs" ); var fetched = false; function server_open(metadata, response) { let body; if (metadata.method == "GET") { fetched = true; body = "This path exists"; response.setStatusLine(metadata.httpVersion, 200, "OK"); } else { body = "Wrong request method"; response.setStatusLine(metadata.httpVersion, 405, "Method Not Allowed"); } response.bodyOutputStream.write(body, body.length); } function server_protected(metadata, response) { let body; if (has_hawk_header(metadata)) { body = "This path exists and is protected"; response.setStatusLine(metadata.httpVersion, 200, "OK, authorized"); response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false); } else { body = "This path exists and is protected - failed"; response.setStatusLine(metadata.httpVersion, 401, "Unauthorized"); response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false); } response.bodyOutputStream.write(body, body.length); } function server_404(metadata, response) { let body = "File not found"; response.setStatusLine(metadata.httpVersion, 404, "Not Found"); response.bodyOutputStream.write(body, body.length); } var pacFetched = false; function server_pac(metadata, response) { _("Invoked PAC handler."); pacFetched = true; let body = 'function FindProxyForURL(url, host) { return "DIRECT"; }'; response.setStatusLine(metadata.httpVersion, 200, "OK"); response.setHeader( "Content-Type", "application/x-ns-proxy-autoconfig", false ); response.bodyOutputStream.write(body, body.length); } var sample_data = { some: "sample_data", injson: "format", number: 42, }; function server_upload(metadata, response) { let body; let input = readBytesFromInputStream(metadata.bodyInputStream); if (input == JSON.stringify(sample_data)) { body = "Valid data upload via " + metadata.method; response.setStatusLine(metadata.httpVersion, 200, "OK"); } else { body = "Invalid data upload via " + metadata.method + ": " + input; response.setStatusLine(metadata.httpVersion, 500, "Internal Server Error"); } response.bodyOutputStream.write(body, body.length); } function server_delete(metadata, response) { let body; if (metadata.method == "DELETE") { body = "This resource has been deleted"; response.setStatusLine(metadata.httpVersion, 200, "OK"); } else { body = "Wrong request method"; response.setStatusLine(metadata.httpVersion, 405, "Method Not Allowed"); } response.bodyOutputStream.write(body, body.length); } function server_json(metadata, response) { let body = JSON.stringify(sample_data); response.setStatusLine(metadata.httpVersion, 200, "OK"); response.bodyOutputStream.write(body, body.length); } const TIMESTAMP = 1274380461; function server_timestamp(metadata, response) { let body = "Thank you for your request"; response.setHeader("X-Weave-Timestamp", "" + TIMESTAMP, false); response.setStatusLine(metadata.httpVersion, 200, "OK"); response.bodyOutputStream.write(body, body.length); } function server_backoff(metadata, response) { let body = "Hey, back off!"; response.setHeader("X-Weave-Backoff", "600", false); response.setStatusLine(metadata.httpVersion, 200, "OK"); response.bodyOutputStream.write(body, body.length); } function server_quota_notice(request, response) { let body = "You're approaching quota."; response.setHeader("X-Weave-Quota-Remaining", "1048576", false); response.setStatusLine(request.httpVersion, 200, "OK"); response.bodyOutputStream.write(body, body.length); } function server_quota_error(request, response) { let body = "14"; response.setHeader("X-Weave-Quota-Remaining", "-1024", false); response.setStatusLine(request.httpVersion, 400, "OK"); response.bodyOutputStream.write(body, body.length); } function server_headers(metadata, response) { let ignore_headers = [ "host", "user-agent", "accept-language", "accept-encoding", "accept-charset", "keep-alive", "connection", "pragma", "origin", "cache-control", "content-length", ]; let headers = metadata.headers; let header_names = []; while (headers.hasMoreElements()) { let header = headers.getNext().toString(); if (!ignore_headers.includes(header)) { header_names.push(header); } } header_names = header_names.sort(); headers = {}; for (let header of header_names) { headers[header] = metadata.getHeader(header); } let body = JSON.stringify(headers); response.setStatusLine(metadata.httpVersion, 200, "OK"); response.bodyOutputStream.write(body, body.length); } var quotaValue; Observers.add("weave:service:quota:remaining", function (subject) { quotaValue = subject; }); function run_test() { Log.repository.rootLogger.addAppender(new Log.DumpAppender()); Svc.Prefs.set("network.numRetries", 1); // speed up test run_next_test(); } // This apparently has to come first in order for our PAC URL to be hit. // Don't put any other HTTP requests earlier in the file! add_task(async function test_proxy_auth_redirect() { _( "Ensure that a proxy auth redirect (which switches out our channel) " + "doesn't break Resource." ); let server = httpd_setup({ "/open": server_open, "/pac2": server_pac, }); PACSystemSettings.PACURI = server.baseURI + "/pac2"; installFakePAC(); let res = new Resource(server.baseURI + "/open"); let result = await res.get(); Assert.ok(pacFetched); Assert.ok(fetched); Assert.equal("This path exists", result.data); pacFetched = fetched = false; uninstallFakePAC(); await promiseStopServer(server); }); add_task(async function test_new_channel() { _("Ensure a redirect to a new channel is handled properly."); let resourceRequested = false; function resourceHandler(metadata, response) { resourceRequested = true; let body = "Test"; response.setHeader("Content-Type", "text/plain"); response.bodyOutputStream.write(body, body.length); } let locationURL; function redirectHandler(metadata, response) { let body = "Redirecting"; response.setStatusLine(metadata.httpVersion, 307, "TEMPORARY REDIRECT"); response.setHeader("Location", locationURL); response.bodyOutputStream.write(body, body.length); } let server = httpd_setup({ "/resource": resourceHandler, "/redirect": redirectHandler, }); locationURL = server.baseURI + "/resource"; let request = new Resource(server.baseURI + "/redirect"); let content = await request.get(); Assert.ok(resourceRequested); Assert.equal(200, content.status); Assert.ok("content-type" in content.headers); Assert.equal("text/plain", content.headers["content-type"]); await promiseStopServer(server); }); var server; add_test(function setup() { server = httpd_setup({ "/open": server_open, "/protected": server_protected, "/404": server_404, "/upload": server_upload, "/delete": server_delete, "/json": server_json, "/timestamp": server_timestamp, "/headers": server_headers, "/backoff": server_backoff, "/pac2": server_pac, "/quota-notice": server_quota_notice, "/quota-error": server_quota_error, }); run_next_test(); }); add_test(function test_members() { _("Resource object members"); let uri = server.baseURI + "/open"; let res = new Resource(uri); Assert.ok(res.uri instanceof Ci.nsIURI); Assert.equal(res.uri.spec, uri); Assert.equal(res.spec, uri); Assert.equal(typeof res.headers, "object"); Assert.equal(typeof res.authenticator, "object"); run_next_test(); }); add_task(async function test_get() { _("GET a non-password-protected resource"); let res = new Resource(server.baseURI + "/open"); let content = await res.get(); Assert.equal(content.data, "This path exists"); Assert.equal(content.status, 200); Assert.ok(content.success); // Observe logging messages. let resLogger = res._log; let dbg = resLogger.debug; let debugMessages = []; resLogger.debug = function (msg, extra) { debugMessages.push(`${msg}: ${JSON.stringify(extra)}`); dbg.call(this, msg); }; // Since we didn't receive proper JSON data, accessing content.obj // will result in a SyntaxError from JSON.parse let didThrow = false; try { content.obj; } catch (ex) { didThrow = true; } Assert.ok(didThrow); Assert.equal(debugMessages.length, 1); Assert.equal( debugMessages[0], 'Parse fail: Response body starts: "This path exists"' ); resLogger.debug = dbg; }); add_test(function test_basicauth() { _("Test that the BasicAuthenticator doesn't screw up header case."); let res1 = new Resource(server.baseURI + "/foo"); res1.setHeader("Authorization", "Basic foobar"); Assert.equal(res1._headers.authorization, "Basic foobar"); Assert.equal(res1.headers.authorization, "Basic foobar"); run_next_test(); }); add_task(async function test_get_protected_fail() { _( "GET a password protected resource (test that it'll fail w/o pass, no throw)" ); let res2 = new Resource(server.baseURI + "/protected"); let content = await res2.get(); Assert.equal(content.data, "This path exists and is protected - failed"); Assert.equal(content.status, 401); Assert.ok(!content.success); }); add_task(async function test_get_protected_success() { _("GET a password protected resource"); let identityConfig = makeIdentityConfig(); let syncAuthManager = new SyncAuthManager(); configureFxAccountIdentity(syncAuthManager, identityConfig); let auth = syncAuthManager.getResourceAuthenticator(); let res3 = new Resource(server.baseURI + "/protected"); res3.authenticator = auth; Assert.equal(res3.authenticator, auth); let content = await res3.get(); Assert.equal(content.data, "This path exists and is protected"); Assert.equal(content.status, 200); Assert.ok(content.success); }); add_task(async function test_get_404() { _("GET a non-existent resource (test that it'll fail, but not throw)"); let res4 = new Resource(server.baseURI + "/404"); let content = await res4.get(); Assert.equal(content.data, "File not found"); Assert.equal(content.status, 404); Assert.ok(!content.success); // Check some headers of the 404 response Assert.equal(content.headers.connection, "close"); Assert.equal(content.headers.server, "httpd.js"); Assert.equal(content.headers["content-length"], 14); }); add_task(async function test_put_string() { _("PUT to a resource (string)"); let res_upload = new Resource(server.baseURI + "/upload"); let content = await res_upload.put(JSON.stringify(sample_data)); Assert.equal(content.data, "Valid data upload via PUT"); Assert.equal(content.status, 200); }); add_task(async function test_put_object() { _("PUT to a resource (object)"); let res_upload = new Resource(server.baseURI + "/upload"); let content = await res_upload.put(sample_data); Assert.equal(content.data, "Valid data upload via PUT"); Assert.equal(content.status, 200); }); add_task(async function test_post_string() { _("POST to a resource (string)"); let res_upload = new Resource(server.baseURI + "/upload"); let content = await res_upload.post(JSON.stringify(sample_data)); Assert.equal(content.data, "Valid data upload via POST"); Assert.equal(content.status, 200); }); add_task(async function test_post_object() { _("POST to a resource (object)"); let res_upload = new Resource(server.baseURI + "/upload"); let content = await res_upload.post(sample_data); Assert.equal(content.data, "Valid data upload via POST"); Assert.equal(content.status, 200); }); add_task(async function test_delete() { _("DELETE a resource"); let res6 = new Resource(server.baseURI + "/delete"); let content = await res6.delete(); Assert.equal(content.data, "This resource has been deleted"); Assert.equal(content.status, 200); }); add_task(async function test_json_body() { _("JSON conversion of response body"); let res7 = new Resource(server.baseURI + "/json"); let content = await res7.get(); Assert.equal(content.data, JSON.stringify(sample_data)); Assert.equal(content.status, 200); Assert.equal(JSON.stringify(content.obj), JSON.stringify(sample_data)); }); add_task(async function test_weave_timestamp() { _("X-Weave-Timestamp header updates Resource.serverTime"); // Before having received any response containing the // X-Weave-Timestamp header, Resource.serverTime is null. Assert.equal(Resource.serverTime, null); let res8 = new Resource(server.baseURI + "/timestamp"); await res8.get(); Assert.equal(Resource.serverTime, TIMESTAMP); }); add_task(async function test_get_default_headers() { _("GET: Accept defaults to application/json"); let res_headers = new Resource(server.baseURI + "/headers"); let content = JSON.parse((await res_headers.get()).data); Assert.equal(content.accept, "application/json;q=0.9,*/*;q=0.2"); }); add_task(async function test_put_default_headers() { _( "PUT: Accept defaults to application/json, Content-Type defaults to text/plain" ); let res_headers = new Resource(server.baseURI + "/headers"); let content = JSON.parse((await res_headers.put("data")).data); Assert.equal(content.accept, "application/json;q=0.9,*/*;q=0.2"); Assert.equal(content["content-type"], "text/plain"); }); add_task(async function test_post_default_headers() { _( "POST: Accept defaults to application/json, Content-Type defaults to text/plain" ); let res_headers = new Resource(server.baseURI + "/headers"); let content = JSON.parse((await res_headers.post("data")).data); Assert.equal(content.accept, "application/json;q=0.9,*/*;q=0.2"); Assert.equal(content["content-type"], "text/plain"); }); add_task(async function test_setHeader() { _("setHeader(): setting simple header"); let res_headers = new Resource(server.baseURI + "/headers"); res_headers.setHeader("X-What-Is-Weave", "awesome"); Assert.equal(res_headers.headers["x-what-is-weave"], "awesome"); let content = JSON.parse((await res_headers.get()).data); Assert.equal(content["x-what-is-weave"], "awesome"); }); add_task(async function test_setHeader_overwrite() { _("setHeader(): setting multiple headers, overwriting existing header"); let res_headers = new Resource(server.baseURI + "/headers"); res_headers.setHeader("X-WHAT-is-Weave", "more awesomer"); res_headers.setHeader("X-Another-Header", "hello world"); Assert.equal(res_headers.headers["x-what-is-weave"], "more awesomer"); Assert.equal(res_headers.headers["x-another-header"], "hello world"); let content = JSON.parse((await res_headers.get()).data); Assert.equal(content["x-what-is-weave"], "more awesomer"); Assert.equal(content["x-another-header"], "hello world"); }); add_task(async function test_put_override_content_type() { _("PUT: override default Content-Type"); let res_headers = new Resource(server.baseURI + "/headers"); res_headers.setHeader("Content-Type", "application/foobar"); Assert.equal(res_headers.headers["content-type"], "application/foobar"); let content = JSON.parse((await res_headers.put("data")).data); Assert.equal(content["content-type"], "application/foobar"); }); add_task(async function test_post_override_content_type() { _("POST: override default Content-Type"); let res_headers = new Resource(server.baseURI + "/headers"); res_headers.setHeader("Content-Type", "application/foobar"); let content = JSON.parse((await res_headers.post("data")).data); Assert.equal(content["content-type"], "application/foobar"); }); add_task(async function test_weave_backoff() { _("X-Weave-Backoff header notifies observer"); let backoffInterval; function onBackoff(subject, data) { backoffInterval = subject; } Observers.add("weave:service:backoff:interval", onBackoff); let res10 = new Resource(server.baseURI + "/backoff"); await res10.get(); Assert.equal(backoffInterval, 600); }); add_task(async function test_quota_error() { _("X-Weave-Quota-Remaining header notifies observer on successful requests."); let res10 = new Resource(server.baseURI + "/quota-error"); let content = await res10.get(); Assert.equal(content.status, 400); Assert.equal(quotaValue, undefined); // HTTP 400, so no observer notification. }); add_task(async function test_quota_notice() { let res10 = new Resource(server.baseURI + "/quota-notice"); let content = await res10.get(); Assert.equal(content.status, 200); Assert.equal(quotaValue, 1048576); }); add_task(async function test_preserve_exceptions() { _("Error handling preserves exception information"); let res11 = new Resource("http://localhost:12345/does/not/exist"); await Assert.rejects(res11.get(), error => { Assert.notEqual(error, null); Assert.equal(error.result, Cr.NS_ERROR_CONNECTION_REFUSED); Assert.equal(error.name, "NS_ERROR_CONNECTION_REFUSED"); return true; }); }); add_task(async function test_timeout() { _("Ensure channel timeouts are thrown appropriately."); let res19 = new Resource(server.baseURI + "/json"); res19.ABORT_TIMEOUT = 0; await Assert.rejects(res19.get(), error => { Assert.equal(error.result, Cr.NS_ERROR_NET_TIMEOUT); return true; }); }); add_test(function test_uri_construction() { _("Testing URI construction."); let args = []; args.push("newer=" + 1234); args.push("limit=" + 1234); args.push("sort=" + 1234); let query = "?" + args.join("&"); let uri1 = CommonUtils.makeURI("http://foo/" + query).QueryInterface( Ci.nsIURL ); let uri2 = CommonUtils.makeURI("http://foo/").QueryInterface(Ci.nsIURL); uri2 = uri2.mutate().setQuery(query).finalize().QueryInterface(Ci.nsIURL); Assert.equal(uri1.query, uri2.query); run_next_test(); }); /** * End of tests that rely on a single HTTP server. * All tests after this point must begin and end their own. */ add_test(function eliminate_server() { server.stop(run_next_test); });