Bug 1433179 - Add ReferrerUrl and HostUrl to the Zone.Information stream. r=paolo
authorMasatoshi Kimura <VYV03354@nifty.ne.jp>
Wed, 06 Mar 2019 10:39:46 +0000
changeset 520460 4d823462e5bd632f0e04cc8bf63ef495e52d4602
parent 520459 c6b7a7d1835236a71bdc762a42f7bad27bcff4d0
child 520461 764939fcdaf3bc4796882229e372f659e6ead276
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspaolo
bugs1433179
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1433179 - Add ReferrerUrl and HostUrl to the Zone.Information stream. r=paolo Differential Revision: https://phabricator.services.mozilla.com/D21829
toolkit/components/downloads/DownloadIntegration.jsm
toolkit/components/downloads/test/unit/common_test_Download.js
--- a/toolkit/components/downloads/DownloadIntegration.jsm
+++ b/toolkit/components/downloads/DownloadIntegration.jsm
@@ -454,16 +454,51 @@ var DownloadIntegration = {
       }
     } catch (ex) {
       // If the key is not present, files should be marked by default.
       return true;
     }
   },
 
   /**
+   * Builds a key and URL value pair for the "Zone.Identifier" Alternate Data
+   * Stream.
+   *
+   * @param aKey
+   *        String to write before the "=" sign. This is not validated.
+   * @param aUrl
+   *        URL string to write after the "=" sign. Only the "http(s)" and
+   *        "ftp" schemes are allowed, and usernames and passwords are
+   *        stripped.
+   * @param [optional] aFallback
+   *        Value to place after the "=" sign in case the URL scheme is not
+   *        allowed. If unspecified, an empty string is returned when the
+   *        scheme is not allowed.
+   *
+   * @return Line to add to the stream, including the final CRLF, or an empty
+   *         string if the validation failed.
+   */
+  _zoneIdKey(aKey, aUrl, aFallback) {
+    try {
+      let url;
+      const uri = NetUtil.newURI(aUrl);
+      if (["http", "https", "ftp"].includes(uri.scheme)) {
+        url = uri.mutate().setUserPass("").finalize().spec;
+      } else if (aFallback) {
+        url = aFallback;
+      } else {
+        return "";
+      }
+      return aKey + "=" + url + "\r\n";
+    } catch (e) {
+      return "";
+    }
+  },
+
+  /**
    * Performs platform-specific operations when a download is done.
    *
    * aParam aDownload
    *        The Download object.
    *
    * @return {Promise}
    * @resolves When all the operations completed successfully.
    * @rejects JavaScript exception if any of the operations failed.
@@ -492,17 +527,23 @@ var DownloadIntegration = {
         if (zone >= Ci.mozIDownloadPlatform.ZONE_INTERNET) {
           let streamPath = aDownload.target.path + ":Zone.Identifier";
           let stream = await OS.File.open(
             streamPath,
             { create: true },
             { winAllowLengthBeyondMaxPathWithCaveats: true }
           );
           try {
-            await stream.write(new TextEncoder().encode("[ZoneTransfer]\r\nZoneId=" + zone + "\r\n"));
+            let zoneId = "[ZoneTransfer]\r\nZoneId=" + zone + "\r\n";
+            if (!aDownload.source.isPrivate) {
+              zoneId +=
+                this._zoneIdKey("ReferrerUrl", aDownload.source.referrer) +
+                this._zoneIdKey("HostUrl", aDownload.source.url, "about:internet");
+            }
+            await stream.write(new TextEncoder().encode(zoneId));
           } finally {
             await stream.close();
           }
         }
       } catch (ex) {
         // If writing to the stream fails, we ignore the error and continue.
         // The Windows API error 123 (ERROR_INVALID_NAME) is expected to
         // occur when working on a file system that does not support
--- a/toolkit/components/downloads/test/unit/common_test_Download.js
+++ b/toolkit/components/downloads/test/unit/common_test_Download.js
@@ -349,36 +349,72 @@ add_task(async function test_windows_zon
                                            ["xpcshell-download-test.txt"]);
 
   // The template file name lenght is more than MAX_PATH characters. The final
   // full path will be shortened to MAX_PATH length by the createUnique call.
   let longTargetFile = FileUtils.getFile("LocalAppData",
                                          ["T".repeat(256) + ".txt"]);
   longTargetFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
 
-  for (let targetFile of [normalTargetFile, longTargetFile]) {
+  const httpSourceUrl = httpUrl("source.txt");
+  const dataSourceUrl = "data:text/html," + TEST_DATA_SHORT;
+  const tests = [
+    { expectedZoneId: "[ZoneTransfer]\r\nZoneId=3\r\n" +
+                      "HostUrl=" + httpSourceUrl + "\r\n" },
+    { targetFile: longTargetFile,
+      expectedZoneId: "[ZoneTransfer]\r\nZoneId=3\r\n" +
+                      "HostUrl=" + httpSourceUrl + "\r\n" },
+    { sourceUrl: dataSourceUrl,
+      expectedZoneId: "[ZoneTransfer]\r\nZoneId=3\r\n" +
+                      "HostUrl=about:internet\r\n" },
+    { options: { referrer: TEST_REFERRER_URL },
+      expectedZoneId: "[ZoneTransfer]\r\nZoneId=3\r\n" +
+                      "ReferrerUrl=" + TEST_REFERRER_URL + "\r\n" +
+                      "HostUrl=" + httpSourceUrl + "\r\n" },
+    { options: { referrer: dataSourceUrl },
+      expectedZoneId: "[ZoneTransfer]\r\nZoneId=3\r\n" +
+                      "HostUrl=" + httpSourceUrl + "\r\n" },
+    { options: { referrer: "http://example.com/a\rb\nc" },
+      expectedZoneId: "[ZoneTransfer]\r\nZoneId=3\r\n" +
+                      "ReferrerUrl=http://example.com/abc\r\n" +
+                      "HostUrl=" + httpSourceUrl + "\r\n" },
+    { options: { referrer: "ftp://user:pass@example.com/" },
+      expectedZoneId: "[ZoneTransfer]\r\nZoneId=3\r\n" +
+                      "ReferrerUrl=ftp://example.com/\r\n" +
+                      "HostUrl=" + httpSourceUrl + "\r\n" },
+    { options: { isPrivate: true },
+      expectedZoneId: "[ZoneTransfer]\r\nZoneId=3\r\n" },
+    { options: { referrer: TEST_REFERRER_URL, isPrivate: true },
+      expectedZoneId: "[ZoneTransfer]\r\nZoneId=3\r\n" },
+  ];
+  for (const test of tests) {
+    const sourceUrl = test.sourceUrl || httpSourceUrl;
+    const targetFile = test.targetFile || normalTargetFile;
+    info(targetFile.path);
     try {
       if (!gUseLegacySaver) {
         let download = await Downloads.createDownload({
-          source: httpUrl("source.txt"),
+          source: test.options ? Object.assign({ url: sourceUrl }, test.options)
+                               : sourceUrl,
           target: targetFile.path,
         });
         await download.start();
       } else {
-        let download = await promiseStartLegacyDownload(null, { targetFile });
+        let download = await promiseStartLegacyDownload(sourceUrl,
+          Object.assign({ targetFile }, test.options || {}));
         await promiseDownloadStopped(download);
       }
       await promiseVerifyContents(targetFile.path, TEST_DATA_SHORT);
 
       // Verify that the Alternate Data Stream has been written.
       let file = await OS.File.open(targetFile.path + ":Zone.Identifier", {},
                  { winAllowLengthBeyondMaxPathWithCaveats: true });
       try {
         Assert.equal(new TextDecoder().decode(await file.read()),
-                     "[ZoneTransfer]\r\nZoneId=3\r\n");
+                     test.expectedZoneId);
       } finally {
         file.close();
       }
     } finally {
       await OS.File.remove(targetFile.path);
     }
   }
 });