Bug 1229639 - Part 1: Match CSP host source with percent-decoded URI. r=ckerschb draft
authorHenry Chang <hchang@mozilla.com>
Tue, 06 Sep 2016 18:29:26 +0800
changeset 410192 f64cb0b9cab61ec09faa29139f72d28272fbbedb
parent 410066 8c9c4e816e86f903c1d820f3f29715dc070a5a4a
child 410193 f98f5c0336ffa5d63bc2e54e4162693e550f1482
child 410955 dc4ac339817a052f687179988e28ec02764bd3e7
push id28666
push userhchang@mozilla.com
push dateTue, 06 Sep 2016 10:31:12 +0000
reviewersckerschb
bugs1229639
milestone51.0a1
Bug 1229639 - Part 1: Match CSP host source with percent-decoded URI. r=ckerschb MozReview-Commit-ID: CSGeoSR2qw8
dom/security/nsCSPParser.cpp
dom/security/nsCSPParser.h
dom/security/nsCSPUtils.cpp
dom/security/nsCSPUtils.h
--- a/dom/security/nsCSPParser.cpp
+++ b/dom/security/nsCSPParser.cpp
@@ -186,73 +186,16 @@ nsCSPParser::resetCurChar(const nsAStrin
 // number sign ("#") character, or by the end of the URI.
 // http://tools.ietf.org/html/rfc3986#section-3.3
 bool
 nsCSPParser::atEndOfPath()
 {
   return (atEnd() || peek(QUESTIONMARK) || peek(NUMBER_SIGN));
 }
 
-void
-nsCSPParser::percentDecodeStr(const nsAString& aEncStr, nsAString& outDecStr)
-{
-  outDecStr.Truncate();
-
-  // helper function that should not be visible outside this methods scope
-  struct local {
-    static inline char16_t convertHexDig(char16_t aHexDig) {
-      if (isNumberToken(aHexDig)) {
-        return aHexDig - '0';
-      }
-      if (aHexDig >= 'A' && aHexDig <= 'F') {
-        return aHexDig - 'A' + 10;
-      }
-      // must be a lower case character
-      // (aHexDig >= 'a' && aHexDig <= 'f')
-      return aHexDig - 'a' + 10;
-    }
-  };
-
-  const char16_t *cur, *end, *hexDig1, *hexDig2;
-  cur = aEncStr.BeginReading();
-  end = aEncStr.EndReading();
-
-  while (cur != end) {
-    // if it's not a percent sign then there is
-    // nothing to do for that character
-    if (*cur != PERCENT_SIGN) {
-      outDecStr.Append(*cur);
-      cur++;
-      continue;
-    }
-
-    // get the two hexDigs following the '%'-sign
-    hexDig1 = cur + 1;
-    hexDig2 = cur + 2;
-
-    // if there are no hexdigs after the '%' then
-    // there is nothing to do for us.
-    if (hexDig1 == end || hexDig2 == end ||
-        !isValidHexDig(*hexDig1) ||
-        !isValidHexDig(*hexDig2)) {
-      outDecStr.Append(PERCENT_SIGN);
-      cur++;
-      continue;
-    }
-
-    // decode "% hexDig1 hexDig2" into a character.
-    char16_t decChar = (local::convertHexDig(*hexDig1) << 4) +
-                       local::convertHexDig(*hexDig2);
-    outDecStr.Append(decChar);
-
-    // increment 'cur' to after the second hexDig
-    cur = ++hexDig2;
-  }
-}
-
 // unreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~"
 bool
 nsCSPParser::atValidUnreservedChar()
 {
   return (peek(isCharacterToken) || peek(isNumberToken) ||
           peek(DASH) || peek(DOT) ||
           peek(UNDERLINE) || peek(TILDE));
 }
@@ -393,17 +336,17 @@ nsCSPParser::subPath(nsCSPHostSrc* aCspH
   uint32_t charCounter = 0;
   nsString pctDecodedSubPath;
 
   while (!atEndOfPath()) {
     if (peek(SLASH)) {
       // before appendig any additional portion of a subpath we have to pct-decode
       // that portion of the subpath. atValidPathChar() already verified a correct
       // pct-encoding, now we can safely decode and append the decoded-sub path.
-      percentDecodeStr(mCurValue, pctDecodedSubPath);
+      CSP_PercentDecodeStr(mCurValue, pctDecodedSubPath);
       aCspHost->appendPath(pctDecodedSubPath);
       // Resetting current value since we are appending parts of the path
       // to aCspHost, e.g; "http://www.example.com/path1/path2" then the
       // first part is "/path1", second part "/path2"
       resetCurValue();
     }
     else if (!atValidPathChar()) {
       const char16_t* params[] = { mCurToken.get() };
@@ -422,17 +365,17 @@ nsCSPParser::subPath(nsCSPHostSrc* aCspH
     advance();
     if (++charCounter > kSubHostPathCharacterCutoff) {
       return false;
     }
   }
   // before appendig any additional portion of a subpath we have to pct-decode
   // that portion of the subpath. atValidPathChar() already verified a correct
   // pct-encoding, now we can safely decode and append the decoded-sub path.
-  percentDecodeStr(mCurValue, pctDecodedSubPath);
+  CSP_PercentDecodeStr(mCurValue, pctDecodedSubPath);
   aCspHost->appendPath(pctDecodedSubPath);
   resetCurValue();
   return true;
 }
 
 bool
 nsCSPParser::path(nsCSPHostSrc* aCspHost)
 {
--- a/dom/security/nsCSPParser.h
+++ b/dom/security/nsCSPParser.h
@@ -139,18 +139,16 @@ class nsCSPParser {
     bool                port();
     bool                path(nsCSPHostSrc* aCspHost);
 
     bool subHost();                                         // helper function to parse subDomains
     bool atValidUnreservedChar();                           // helper function to parse unreserved
     bool atValidSubDelimChar();                             // helper function to parse sub-delims
     bool atValidPctEncodedChar();                           // helper function to parse pct-encoded
     bool subPath(nsCSPHostSrc* aCspHost);                   // helper function to parse paths
-    void percentDecodeStr(const nsAString& aEncStr,         // helper function to percent-decode
-                          nsAString& outDecStr);
 
     inline bool atEnd()
     {
       return mCurChar >= mEndChar;
     }
 
     inline bool accept(char16_t aSymbol)
     {
--- a/dom/security/nsCSPUtils.cpp
+++ b/dom/security/nsCSPUtils.cpp
@@ -26,16 +26,73 @@ GetCspUtilsLog()
   static mozilla::LazyLogModule gCspUtilsPRLog("CSPUtils");
   return gCspUtilsPRLog;
 }
 
 #define CSPUTILSLOG(args) MOZ_LOG(GetCspUtilsLog(), mozilla::LogLevel::Debug, args)
 #define CSPUTILSLOGENABLED() MOZ_LOG_TEST(GetCspUtilsLog(), mozilla::LogLevel::Debug)
 
 void
+CSP_PercentDecodeStr(const nsAString& aEncStr, nsAString& outDecStr)
+{
+  outDecStr.Truncate();
+
+  // helper function that should not be visible outside this methods scope
+  struct local {
+    static inline char16_t convertHexDig(char16_t aHexDig) {
+      if (isNumberToken(aHexDig)) {
+        return aHexDig - '0';
+      }
+      if (aHexDig >= 'A' && aHexDig <= 'F') {
+        return aHexDig - 'A' + 10;
+      }
+      // must be a lower case character
+      // (aHexDig >= 'a' && aHexDig <= 'f')
+      return aHexDig - 'a' + 10;
+    }
+  };
+
+  const char16_t *cur, *end, *hexDig1, *hexDig2;
+  cur = aEncStr.BeginReading();
+  end = aEncStr.EndReading();
+
+  while (cur != end) {
+    // if it's not a percent sign then there is
+    // nothing to do for that character
+    if (*cur != PERCENT_SIGN) {
+      outDecStr.Append(*cur);
+      cur++;
+      continue;
+    }
+
+    // get the two hexDigs following the '%'-sign
+    hexDig1 = cur + 1;
+    hexDig2 = cur + 2;
+
+    // if there are no hexdigs after the '%' then
+    // there is nothing to do for us.
+    if (hexDig1 == end || hexDig2 == end ||
+        !isValidHexDig(*hexDig1) ||
+        !isValidHexDig(*hexDig2)) {
+      outDecStr.Append(PERCENT_SIGN);
+      cur++;
+      continue;
+    }
+
+    // decode "% hexDig1 hexDig2" into a character.
+    char16_t decChar = (local::convertHexDig(*hexDig1) << 4) +
+                       local::convertHexDig(*hexDig2);
+    outDecStr.Append(decChar);
+
+    // increment 'cur' to after the second hexDig
+    cur = ++hexDig2;
+  }
+}
+
+void
 CSP_GetLocalizedStr(const char16_t* aName,
                     const char16_t** aParams,
                     uint32_t aLength,
                     char16_t** outResult)
 {
   nsCOMPtr<nsIStringBundle> keyStringBundle;
   nsCOMPtr<nsIStringBundleService> stringBundleService =
     mozilla::services::GetStringBundleService();
@@ -562,30 +619,33 @@ nsCSPHostSrc::permits(nsIURI* aUri, cons
   }
 
   // Before we can check if the host matches, we have to
   // extract the host part from aUri.
   nsAutoCString uriHost;
   nsresult rv = aUri->GetHost(uriHost);
   NS_ENSURE_SUCCESS(rv, false);
 
+  nsString decodedUriHost;
+  CSP_PercentDecodeStr(NS_ConvertUTF8toUTF16(uriHost), decodedUriHost);
+
   // 4.5) host matching: Check if the allowed host starts with a wilcard.
   if (mHost.First() == '*') {
     NS_ASSERTION(mHost[1] == '.', "Second character needs to be '.' whenever host starts with '*'");
 
     // Eliminate leading "*", but keeping the FULL STOP (.) thereafter before checking
     // if the remaining characters match
     nsString wildCardHost = mHost;
     wildCardHost = Substring(wildCardHost, 1, wildCardHost.Length() - 1);
-    if (!StringEndsWith(NS_ConvertUTF8toUTF16(uriHost), wildCardHost)) {
+    if (!StringEndsWith(decodedUriHost, wildCardHost)) {
       return false;
     }
   }
   // 4.6) host matching: Check if hosts match.
-  else if (!mHost.Equals(NS_ConvertUTF8toUTF16(uriHost))) {
+  else if (!mHost.Equals(decodedUriHost)) {
     return false;
   }
 
   // Port matching: Check if the ports match.
   if (!permitsPort(mScheme, mPort, aUri)) {
     return false;
   }
 
@@ -599,28 +659,32 @@ nsCSPHostSrc::permits(nsIURI* aUri, cons
     nsCOMPtr<nsIURL> url = do_QueryInterface(aUri);
     if (!url) {
       NS_ASSERTION(false, "can't QI into nsIURI");
       return false;
     }
     nsAutoCString uriPath;
     rv = url->GetFilePath(uriPath);
     NS_ENSURE_SUCCESS(rv, false);
+
+    nsString decodedUriPath;
+    CSP_PercentDecodeStr(NS_ConvertUTF8toUTF16(uriPath), decodedUriPath);
+
     // check if the last character of mPath is '/'; if so
     // we just have to check loading resource is within
     // the allowed path.
     if (mPath.Last() == '/') {
-      if (!StringBeginsWith(NS_ConvertUTF8toUTF16(uriPath), mPath)) {
+      if (!StringBeginsWith(decodedUriPath, mPath)) {
         return false;
       }
     }
     // otherwise mPath whitelists a specific file, and we have to
     // check if the loading resource matches that whitelisted file.
     else {
-      if (!mPath.Equals(NS_ConvertUTF8toUTF16(uriPath))) {
+      if (!mPath.Equals(decodedUriPath)) {
         return false;
       }
     }
   }
 
   // At the end: scheme, host, port and path match -> allow the load.
   return true;
 }
--- a/dom/security/nsCSPUtils.h
+++ b/dom/security/nsCSPUtils.h
@@ -188,16 +188,18 @@ nsCSPHostSrc* CSP_CreateHostSrcFromURI(n
 bool CSP_IsValidDirective(const nsAString& aDir);
 bool CSP_IsDirective(const nsAString& aValue, CSPDirective aDir);
 bool CSP_IsKeyword(const nsAString& aValue, enum CSPKeyword aKey);
 bool CSP_IsQuotelessKeyword(const nsAString& aKey);
 CSPDirective CSP_ContentTypeToDirective(nsContentPolicyType aType);
 
 class nsCSPSrcVisitor;
 
+void CSP_PercentDecodeStr(const nsAString& aEncStr, nsAString& outDecStr);
+
 /* =============== nsCSPSrc ================== */
 
 class nsCSPBaseSrc {
   public:
     nsCSPBaseSrc();
     virtual ~nsCSPBaseSrc();
 
     virtual bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected,