/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";

const { UrlClassifierTestUtils } = ChromeUtils.importESModule(
  "resource://testing-common/UrlClassifierTestUtils.sys.mjs"
);

// Value for network.cookie.cookieBehavior to reject all third-party cookies.
const { BEHAVIOR_REJECT_FOREIGN } = Ci.nsICookieService;

const server = createHttpServer({ hosts: ["example.net", "itisatracker.org"] });
server.registerPathHandler("/setcookies", (request, response) => {
  response.setHeader("Content-Type", "text/html; charset=utf-8", false);
  response.setHeader("Set-Cookie", "c_none=1; sameSite=none", true);
  response.setHeader("Set-Cookie", "c_lax=1; sameSite=lax", true);
  response.setHeader("Set-Cookie", "c_strict=1; sameSite=strict", true);
});

server.registerPathHandler("/download", (request, response) => {
  response.setStatusLine(request.httpVersion, 200, "OK");

  let cookies = request.hasHeader("Cookie") ? request.getHeader("Cookie") : "";
  // Assign the result through the MIME-type, to make it easier to read the
  // result via the downloads API.
  response.setHeader("Content-Type", `dummy/${encodeURIComponent(cookies)}`);
  // Response of length 7.
  response.write("1234567");
});

server.registerPathHandler("/redirect", (request, response) => {
  response.setStatusLine(request.httpVersion, 302, "Found");
  response.setHeader("Location", "/download");
});

function createDownloadTestExtension(extraPermissions = []) {
  return ExtensionTestUtils.loadExtension({
    manifest: {
      permissions: ["downloads", ...extraPermissions],
    },
    incognitoOverride: "spanning",
    background() {
      async function getCookiesForDownload(url) {
        let donePromise = new Promise(resolve => {
          browser.downloads.onChanged.addListener(async delta => {
            if (delta.state?.current === "complete") {
              resolve(delta.id);
            }
          });
        });
        // TODO bug 1653636: Remove this when the correct browsing mode is used.
        const incognito = browser.extension.inIncognitoContext;
        let downloadId = await browser.downloads.download({ url, incognito });
        browser.test.assertEq(await donePromise, downloadId, "got download");
        let [download] = await browser.downloads.search({ id: downloadId });
        browser.test.log(`Download results: ${JSON.stringify(download)}`);

        // Delete the file since we aren't interested in it.
        // TODO bug 1654819: On Windows the file may be recreated.
        await browser.downloads.removeFile(download.id);
        // Sanity check to verify that we got the result from /download.
        browser.test.assertEq(7, download.fileSize, "download succeeded");

        // The "/download" endpoint mirrors received cookies via Content-Type.
        let cookies = decodeURIComponent(download.mime.replace("dummy/", ""));
        return cookies;
      }

      browser.test.onMessage.addListener(async url => {
        browser.test.sendMessage("result", await getCookiesForDownload(url));
      });
    },
  });
}

async function downloadAndGetCookies(extension, url) {
  extension.sendMessage(url);
  return extension.awaitMessage("result");
}

add_task(async function setup() {
  const nsIFile = Ci.nsIFile;
  const downloadDir = FileUtils.getDir("TmpD", ["downloads"]);
  downloadDir.createUnique(nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
  Services.prefs.setIntPref("browser.download.folderList", 2);
  Services.prefs.setComplexValue("browser.download.dir", nsIFile, downloadDir);

  // Support sameSite=none despite the server using http instead of https.
  Services.prefs.setBoolPref(
    "network.cookie.sameSite.noneRequiresSecure",
    false
  );
  async function loadAndClose(url) {
    let contentPage = await ExtensionTestUtils.loadContentPage(url);
    await contentPage.close();
  }
  // Generate cookies for use in this test.
  await loadAndClose("http://example.net/setcookies");
  await loadAndClose("http://itisatracker.org/setcookies");

  await UrlClassifierTestUtils.addTestTrackers();
  registerCleanupFunction(() => {
    UrlClassifierTestUtils.cleanupTestTrackers();
    Services.cookies.removeAll();

    Services.prefs.clearUserPref("browser.download.folderList");
    Services.prefs.clearUserPref("browser.download.dir");

    downloadDir.remove(false);
  });
});

// Checks that (sameSite) cookies are included in download requests.
add_task(async function download_cookies_basic() {
  let extension = createDownloadTestExtension(["*://example.net/*"]);
  await extension.startup();

  equal(
    await downloadAndGetCookies(extension, "http://example.net/download"),
    "c_none=1; c_lax=1; c_strict=1",
    "Cookies for downloads.download with sameSite cookies"
  );

  equal(
    await downloadAndGetCookies(extension, "http://example.net/redirect"),
    "c_none=1; c_lax=1; c_strict=1",
    "Cookies for downloads.download with redirect"
  );

  await runWithPrefs(
    [["network.cookie.cookieBehavior", BEHAVIOR_REJECT_FOREIGN]],
    async () => {
      equal(
        await downloadAndGetCookies(extension, "http://example.net/download"),
        "c_none=1; c_lax=1; c_strict=1",
        "Cookies for downloads.download with all third-party cookies disabled"
      );
    }
  );

  await extension.unload();
});

// Checks that (sameSite) cookies are included even when tracking protection
// would block cookies from third-party requests.
add_task(async function download_cookies_from_tracker_url() {
  let extension = createDownloadTestExtension(["*://itisatracker.org/*"]);
  await extension.startup();

  equal(
    await downloadAndGetCookies(extension, "http://itisatracker.org/download"),
    "c_none=1; c_lax=1; c_strict=1",
    "Cookies for downloads.download of itisatracker.org"
  );

  await extension.unload();
});

// Checks that (sameSite) cookies are included even without host permissions.
add_task(async function download_cookies_without_host_permissions() {
  let extension = createDownloadTestExtension();
  await extension.startup();

  equal(
    await downloadAndGetCookies(extension, "http://example.net/download"),
    "c_none=1; c_lax=1; c_strict=1",
    "Cookies for downloads.download without host permissions"
  );

  equal(
    await downloadAndGetCookies(extension, "http://itisatracker.org/download"),
    "c_none=1; c_lax=1; c_strict=1",
    "Cookies for downloads.download of itisatracker.org"
  );

  await runWithPrefs(
    [["network.cookie.cookieBehavior", BEHAVIOR_REJECT_FOREIGN]],
    async () => {
      equal(
        await downloadAndGetCookies(extension, "http://example.net/download"),
        "c_none=1; c_lax=1; c_strict=1",
        "Cookies for downloads.download with all third-party cookies disabled"
      );
    }
  );

  await extension.unload();
});

// Checks that (sameSite) cookies from private browsing are included.
add_task(async function download_cookies_in_perma_private_browsing() {
  Services.prefs.setBoolPref("browser.privatebrowsing.autostart", true);
  Services.prefs.setBoolPref("dom.security.https_first_pbm", false);

  let extension = createDownloadTestExtension(["*://example.net/*"]);
  await extension.startup();

  equal(
    await downloadAndGetCookies(extension, "http://example.net/download"),
    "",
    "Initially no cookies in permanent private browsing mode"
  );

  let contentPage = await ExtensionTestUtils.loadContentPage(
    "http://example.net/setcookies",
    { privateBrowsing: true }
  );

  equal(
    await downloadAndGetCookies(extension, "http://example.net/download"),
    "c_none=1; c_lax=1; c_strict=1",
    "Cookies for downloads.download in perma-private-browsing mode"
  );

  await extension.unload();
  await contentPage.close();
  Services.prefs.clearUserPref("browser.privatebrowsing.autostart");
  Services.prefs.clearUserPref("dom.security.https_first_pbm");
});
