Avoid rewriting about:srcdoc into chrome://srcdoc

Rewriting about:srcdoc into chrome://srcdoc is undesirable because

1. about:srcdoc has a special meaning and just like about:blank has been
   reserved by specs like
   https://html.spec.whatwg.org/multipage/urls-and-fetching.html

2. chrome:-scheme URLs are special and might have extra privileges.
   Therefore chrome: URLs should not be reachable by an unprivileged webpage
   (OTOH, the rewriting fixed here only applies to the URL *shown* to
   the user, not the URL that gets committed - compare WebContents's
   GetVisibleURL vs GetLastCommittedURL).

Bug: 973628
Change-Id: I021e623caf0d7e5c02a2546291bb4913412b3125
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1654909
Auto-Submit: Łukasz Anforowicz <[email protected]>
Commit-Queue: Łukasz Anforowicz <[email protected]>
Commit-Queue: Avi Drissman <[email protected]>
Reviewed-by: Avi Drissman <[email protected]>
Reviewed-by: Charlie Harrison <[email protected]>
Reviewed-by: Peter Kasting <[email protected]>
Cr-Commit-Position: refs/heads/master@{#669328}
diff --git a/chrome/browser/browser_about_handler.cc b/chrome/browser/browser_about_handler.cc
index 55949cc..2b94c590 100644
--- a/chrome/browser/browser_about_handler.cc
+++ b/chrome/browser/browser_about_handler.cc
@@ -39,7 +39,8 @@
   FixupBrowserAboutURL(url, browser_context);
 
   // Check that about: URLs are fixed up to chrome: by url_formatter::FixupURL.
-  DCHECK((url->IsAboutBlank()) || !url->SchemeIs(url::kAboutScheme));
+  DCHECK(url->IsAboutBlank() || url->IsAboutSrcdoc() ||
+         !url->SchemeIs(url::kAboutScheme));
 
   // Only handle chrome://foo/, url_formatter::FixupURL translates about:foo.
   if (!url->SchemeIs(content::kChromeUIScheme))
diff --git a/components/url_formatter/url_fixer.cc b/components/url_formatter/url_fixer.cc
index 3afc7008..502f598 100644
--- a/components/url_formatter/url_fixer.cc
+++ b/components/url_formatter/url_fixer.cc
@@ -563,11 +563,11 @@
     return GURL();
   }
 
-  // 'about:blank' is special-cased in various places in the code so it
-  // shouldn't be transformed into 'chrome://blank' as the code below will do.
+  // 'about:blank' and 'about:srcdoc' are special-cased in various places in the
+  // code and shouldn't use the chrome: scheme.
   if (base::LowerCaseEqualsASCII(scheme, url::kAboutScheme)) {
     GURL about_url(base::ToLowerASCII(trimmed));
-    if (about_url.IsAboutBlank())
+    if (about_url.IsAboutBlank() || about_url.IsAboutSrcdoc())
       return about_url;
   }
 
diff --git a/components/url_formatter/url_fixer_unittest.cc b/components/url_formatter/url_fixer_unittest.cc
index 05a2b54..91c2115 100644
--- a/components/url_formatter/url_fixer_unittest.cc
+++ b/components/url_formatter/url_fixer_unittest.cc
@@ -311,9 +311,9 @@
   if (url.length() <= 8)
     return false;
   if (std::string("file:///") != url.substr(0, 8))
-    return false; // no file:/// prefix
+    return false;  // no file:/// prefix
   if (url.find('\\') != std::string::npos)
-    return false; // contains backslashes
+    return false;  // contains backslashes
 
   base::FilePath derived_path;
   net::FileURLToFilePath(GURL(url), &derived_path);
@@ -339,6 +339,11 @@
     {"about:version", "chrome://version/"},
     {"about:blank", "about:blank"},
     {"About:blaNk", "about:blank"},
+    {"about:blank#blah", "about:blank#blah"},
+    {"about:blank/#blah", "about:blank/#blah"},
+    {"about:srcdoc", "about:srcdoc"},
+    {"about:srcdoc#blah", "about:srcdoc#blah"},
+    {"about:srcdoc/#blah", "about:srcdoc/#blah"},
     {"about:usr:pwd@hst:20/pth?qry#ref", "chrome://hst/pth?qry#ref"},
     {"about://usr:pwd@hst/pth?qry#ref", "chrome://hst/pth?qry#ref"},
     {"chrome:usr:pwd@hst/pth?qry#ref", "chrome://hst/pth?qry#ref"},
@@ -398,7 +403,6 @@
     {"chrome-devtools://bundled/devtools/inspector.html?ws=ws://localhost:9222/"
      "guid",
      "devtools://bundled/devtools/inspector.html?ws=ws://localhost:9222/guid"},
-
 };
 
 TEST(URLFixerTest, FixupURL) {
diff --git a/url/gurl.cc b/url/gurl.cc
index 84e7bf1..91b0f24 100644
--- a/url/gurl.cc
+++ b/url/gurl.cc
@@ -8,6 +8,7 @@
 
 #include <algorithm>
 #include <ostream>
+#include <utility>
 
 #include "base/lazy_instance.h"
 #include "base/logging.h"
@@ -339,16 +340,11 @@
 }
 
 bool GURL::IsAboutBlank() const {
-  if (!SchemeIs(url::kAboutScheme))
-    return false;
+  return IsAboutUrl(url::kAboutBlankPath);
+}
 
-  if (has_host() || has_username() || has_password() || has_port())
-    return false;
-
-  if (path() != url::kAboutBlankPath && path() != url::kAboutBlankWithHashPath)
-    return false;
-
-  return true;
+bool GURL::IsAboutSrcdoc() const {
+  return IsAboutUrl(url::kAboutSrcdocPath);
 }
 
 bool GURL::SchemeIs(base::StringPiece lower_ascii_scheme) const {
@@ -487,6 +483,30 @@
          (parsed_.inner_parsed() ? sizeof(url::Parsed) : 0);
 }
 
+bool GURL::IsAboutUrl(base::StringPiece allowed_path) const {
+  if (!SchemeIs(url::kAboutScheme))
+    return false;
+
+  if (has_host() || has_username() || has_password() || has_port())
+    return false;
+
+  if (!path_piece().starts_with(allowed_path))
+    return false;
+
+  if (path_piece().size() == allowed_path.size()) {
+    DCHECK_EQ(path_piece(), allowed_path);
+    return true;
+  }
+
+  if ((path_piece().size() == allowed_path.size() + 1) &&
+      path_piece().back() == '/') {
+    DCHECK_EQ(path_piece(), allowed_path.as_string() + '/');
+    return true;
+  }
+
+  return false;
+}
+
 std::ostream& operator<<(std::ostream& out, const GURL& url) {
   return out << url.possibly_invalid_spec();
 }
diff --git a/url/gurl.h b/url/gurl.h
index 1b0669e..9c680e0 100644
--- a/url/gurl.h
+++ b/url/gurl.h
@@ -217,6 +217,10 @@
   // about:blank/#foo.
   bool IsAboutBlank() const;
 
+  // Returns true when the url is of the form about:srcdoc, about:srcdoc?foo or
+  // about:srcdoc/#foo.
+  bool IsAboutSrcdoc() const;
+
   // Returns true if the given parameter (should be lower-case ASCII to match
   // the canonicalized scheme) is the scheme for this URL. Do not include a
   // colon.
@@ -445,6 +449,9 @@
 
   void InitializeFromCanonicalSpec();
 
+  // Helper used by IsAboutBlank and IsAboutSrcdoc.
+  bool IsAboutUrl(base::StringPiece allowed_path) const;
+
   // Returns the substring of the input identified by the given component.
   std::string ComponentString(const url::Component& comp) const {
     if (comp.len <= 0)
diff --git a/url/gurl_unittest.cc b/url/gurl_unittest.cc
index 379c04f..0d7b65b 100644
--- a/url/gurl_unittest.cc
+++ b/url/gurl_unittest.cc
@@ -863,11 +863,34 @@
   const std::string kNotAboutBlankUrls[] = {
       "http:blank",      "about:blan",          "about://blank",
       "about:blank/foo", "about://:8000/blank", "about://foo:foo@/blank",
-      "foo@about:blank", "foo:bar@about:blank", "about:blank:8000"};
+      "foo@about:blank", "foo:bar@about:blank", "about:blank:8000",
+      "about:blANk"};
   for (const auto& url : kNotAboutBlankUrls)
     EXPECT_FALSE(GURL(url).IsAboutBlank()) << url;
 }
 
+TEST(GURLTest, IsAboutSrcdoc) {
+  const std::string kAboutSrcdocUrls[] = {
+      "about:srcdoc", "about:srcdoc/", "about:srcdoc?foo", "about:srcdoc/#foo",
+      "about:srcdoc?foo#foo"};
+  for (const auto& url : kAboutSrcdocUrls)
+    EXPECT_TRUE(GURL(url).IsAboutSrcdoc()) << url;
+
+  const std::string kNotAboutSrcdocUrls[] = {"http:srcdoc",
+                                             "about:srcdo",
+                                             "about://srcdoc",
+                                             "about://srcdoc\\",
+                                             "about:srcdoc/foo",
+                                             "about://:8000/srcdoc",
+                                             "about://foo:foo@/srcdoc",
+                                             "foo@about:srcdoc",
+                                             "foo:bar@about:srcdoc",
+                                             "about:srcdoc:8000",
+                                             "about:srCDOc"};
+  for (const auto& url : kNotAboutSrcdocUrls)
+    EXPECT_FALSE(GURL(url).IsAboutSrcdoc()) << url;
+}
+
 TEST(GURLTest, EqualsIgnoringRef) {
   const struct {
     const char* url_a;
diff --git a/url/url_constants.cc b/url/url_constants.cc
index 110c6a7b..38f86bbb 100644
--- a/url/url_constants.cc
+++ b/url/url_constants.cc
@@ -9,7 +9,7 @@
 const char kAboutBlankURL[] = "about:blank";
 
 const char kAboutBlankPath[] = "blank";
-const char kAboutBlankWithHashPath[] = "blank/";
+const char kAboutSrcdocPath[] = "srcdoc";
 
 const char kAboutScheme[] = "about";
 const char kBlobScheme[] = "blob";
diff --git a/url/url_constants.h b/url/url_constants.h
index 38a0e38..7f322f89 100644
--- a/url/url_constants.h
+++ b/url/url_constants.h
@@ -14,7 +14,7 @@
 COMPONENT_EXPORT(URL) extern const char kAboutBlankURL[];
 
 COMPONENT_EXPORT(URL) extern const char kAboutBlankPath[];
-COMPONENT_EXPORT(URL) extern const char kAboutBlankWithHashPath[];
+COMPONENT_EXPORT(URL) extern const char kAboutSrcdocPath[];
 
 COMPONENT_EXPORT(URL) extern const char kAboutScheme[];
 COMPONENT_EXPORT(URL) extern const char kBlobScheme[];