Bug 1210302 - Add a NS_ParseRequestContentType API; ba=al, r=mcmanus, r=sicking, a=al
authorEhsan Akhgari <ehsan@mozilla.com>
Tue, 20 Oct 2015 11:40:12 +0200
changeset 260799 e86f9e9310838095413e11ebeee54ca0dedc46e4
parent 260798 44d784fdf614048a0ad46fe109921f00fce0a611
child 260800 7909587936ebe419477aa7a564616d51003884e8
push id190
push usercbook@mozilla.com
push dateTue, 20 Oct 2015 09:41:19 +0000
treeherdermozilla-esr38@e86f9e931083 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmcmanus, sicking, al
bugs1210302
milestone38.3.0
Bug 1210302 - Add a NS_ParseRequestContentType API; ba=al, r=mcmanus, r=sicking, a=al
docshell/base/nsDocShell.cpp
dom/base/Navigator.cpp
dom/base/nsContentUtils.cpp
dom/html/nsHTMLDocument.cpp
dom/manifest/ManifestProcessor.jsm
netwerk/base/moz.build
netwerk/base/nsINetUtil_ESR_38.idl
netwerk/base/nsIOService.cpp
netwerk/base/nsIOService.h
netwerk/base/nsNetUtil.h
netwerk/base/nsURLHelper.cpp
netwerk/base/nsURLHelper.h
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -13514,17 +13514,17 @@ nsDocShell::OnLinkClickSync(nsIContent* 
 
   // If this is an anchor element, grab its type property to use as a hint
   nsAutoString typeHint;
   nsCOMPtr<nsIDOMHTMLAnchorElement> anchor(do_QueryInterface(aContent));
   if (anchor) {
     anchor->GetType(typeHint);
     NS_ConvertUTF16toUTF8 utf8Hint(typeHint);
     nsAutoCString type, dummy;
-    NS_ParseContentType(utf8Hint, type, dummy);
+    NS_ParseRequestContentType(utf8Hint, type, dummy);
     CopyUTF8toUTF16(type, typeHint);
   }
 
   // Clone the URI now, in case a content policy or something messes
   // with it under InternalLoad; we do _not_ want to change the URI
   // our caller passed in.
   nsCOMPtr<nsIURI> clonedURI;
   aURI->Clone(getter_AddRefs(clonedURI));
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -1216,19 +1216,19 @@ Navigator::SendBeacon(const nsAString& a
 
   rv = cors->Init(channel, true);
   NS_ENSURE_SUCCESS(rv, false);
 
   // Start a preflight if cross-origin and content type is not whitelisted
   rv = secMan->CheckSameOriginURI(documentURI, uri, false);
   bool crossOrigin = NS_FAILED(rv);
   nsAutoCString contentType, parsedCharset;
-  rv = NS_ParseContentType(mimeType, contentType, parsedCharset);
+  rv = NS_ParseRequestContentType(mimeType, contentType, parsedCharset);
   if (crossOrigin &&
-      contentType.Length() > 0 &&
+      mimeType.Length() > 0 &&
       !contentType.Equals(APPLICATION_WWW_FORM_URLENCODED) &&
       !contentType.Equals(MULTIPART_FORM_DATA) &&
       !contentType.Equals(TEXT_PLAIN)) {
 
     // we need to set the sameOriginChecker as a notificationCallback
     // so we can tell the channel not to follow redirects
     nsCOMPtr<nsIInterfaceRequestor> soc = nsContentUtils::SameOriginChecker();
     channel->SetNotificationCallbacks(soc);
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -6996,17 +6996,17 @@ nsContentUtils::IsForbiddenResponseHeade
 
 // static
 bool
 nsContentUtils::IsAllowedNonCorsContentType(const nsACString& aHeaderValue)
 {
   nsAutoCString contentType;
   nsAutoCString unused;
 
-  nsresult rv = NS_ParseContentType(aHeaderValue, contentType, unused);
+  nsresult rv = NS_ParseRequestContentType(aHeaderValue, contentType, unused);
   if (NS_FAILED(rv)) {
     return false;
   }
 
   return contentType.LowerCaseEqualsLiteral("text/plain") ||
          contentType.LowerCaseEqualsLiteral("application/x-www-form-urlencoded") ||
          contentType.LowerCaseEqualsLiteral("multipart/form-data");
 }
--- a/dom/html/nsHTMLDocument.cpp
+++ b/dom/html/nsHTMLDocument.cpp
@@ -1417,17 +1417,17 @@ nsHTMLDocument::Open(JSContext* cx,
   }
 
   nsAutoCString contentType;
   contentType.AssignLiteral("text/html");
 
   nsAutoString type;
   nsContentUtils::ASCIIToLower(aType, type);
   nsAutoCString actualType, dummy;
-  NS_ParseContentType(NS_ConvertUTF16toUTF8(type), actualType, dummy);
+  NS_ParseRequestContentType(NS_ConvertUTF16toUTF8(type), actualType, dummy);
   if (!actualType.EqualsLiteral("text/html") &&
       !type.EqualsLiteral("replace")) {
     contentType.AssignLiteral("text/plain");
   }
 
   // If we already have a parser we ignore the document.open call.
   if (mParser || mParserAborted) {
     // The WHATWG spec says: "If the document has an active parser that isn't
--- a/dom/manifest/ManifestProcessor.jsm
+++ b/dom/manifest/ManifestProcessor.jsm
@@ -26,17 +26,17 @@ const {
   utils: Cu,
   classes: Cc,
   interfaces: Ci
 } = Components;
 const imports = {};
 Cu.import('resource://gre/modules/Services.jsm', imports);
 Cu.importGlobalProperties(['URL']);
 const securityManager = imports.Services.scriptSecurityManager;
-const netutil = Cc['@mozilla.org/network/util;1'].getService(Ci.nsINetUtil);
+const netutil = Cc['@mozilla.org/network/util;1'].getService(Ci.nsINetUtil_ESR_38);
 const defaultDisplayMode = 'browser';
 const displayModes = new Set([
   'fullscreen',
   'standalone',
   'minimal-ui',
   'browser'
 ]);
 const orientationTypes = new Set([
@@ -253,17 +253,17 @@ this.ManifestProcessor.prototype.process
         obj = {
           objectName: 'icon',
           object: icon,
           property: 'type',
           expectedType: 'string'
         };
       let value = extractValue(obj),
         isParsable = (typeof value === 'string' && value.length > 0);
-      value = (isParsable) ? netutil.parseContentType(value.trim(), charset, hadCharset) : undefined;
+      value = (isParsable) ? netutil.parseRequestContentType(value.trim(), charset, hadCharset) : undefined;
       return (value === '') ? undefined : value;
     }
 
     function processDensityMember(icon) {
       const hasDensity = Object.prototype.hasOwnProperty.call(icon, 'density'),
         rawValue = (hasDensity) ? icon.density : undefined,
         value = parseFloat(rawValue),
         result = (Number.isNaN(value) || value === +Infinity || value <= 0) ? 1.0 : value;
@@ -349,9 +349,9 @@ this.ManifestProcessor.prototype.process
     display: processDisplayMember(manifest),
     orientation: processOrientationMember(manifest),
     name: processNameMember(manifest),
     icons: processIconsMember(manifest, manifestURL),
     short_name: processShortNameMember(manifest)
   };
   processedManifest.scope = processScopeMember(manifest, manifestURL, docURL, processedManifest.start_url);
   return processedManifest;
-};
\ No newline at end of file
+};
--- a/netwerk/base/moz.build
+++ b/netwerk/base/moz.build
@@ -54,16 +54,17 @@ XPIDL_SOURCES += [
     'nsILoadGroup.idl',
     'nsILoadGroupChild.idl',
     'nsILoadInfo.idl',
     'nsIMIMEInputStream.idl',
     'nsIMultiPartChannel.idl',
     'nsINestedURI.idl',
     'nsINetAddr.idl',
     'nsINetUtil.idl',
+    'nsINetUtil_ESR_38.idl',
     'nsINetworkInterceptController.idl',
     'nsINetworkLinkService.idl',
     'nsINetworkPredictor.idl',
     'nsINetworkPredictorVerifier.idl',
     'nsINetworkProperties.idl',
     'nsINSSErrorsService.idl',
     'nsINullChannel.idl',
     'nsIParentChannel.idl',
new file mode 100644
--- /dev/null
+++ b/netwerk/base/nsINetUtil_ESR_38.idl
@@ -0,0 +1,14 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(e82f2b9d-8bac-48bb-ade7-26a7cd4fb894)]
+interface nsINetUtil_ESR_38 : nsISupports
+{
+  AUTF8String parseRequestContentType(in AUTF8String aTypeHeader,
+                                      out AUTF8String aCharset,
+                                      out boolean aHadCharset);
+};
--- a/netwerk/base/nsIOService.cpp
+++ b/netwerk/base/nsIOService.cpp
@@ -316,16 +316,17 @@ nsIOService::GetInstance() {
     NS_ADDREF(gIOService);
     return gIOService;
 }
 
 NS_IMPL_ISUPPORTS(nsIOService,
                   nsIIOService,
                   nsIIOService2,
                   nsINetUtil,
+                  nsINetUtil_ESR_38,
                   nsISpeculativeConnect,
                   nsIObserver,
                   nsISupportsWeakReference)
 
 ////////////////////////////////////////////////////////////////////////////////
 
 nsresult
 nsIOService::AsyncOnChannelRedirect(nsIChannel* oldChan, nsIChannel* newChan,
@@ -1275,16 +1276,27 @@ nsIOService::Observe(nsISupports *subjec
 #endif
     }
 
     return NS_OK;
 }
 
 // nsINetUtil interface
 NS_IMETHODIMP
+nsIOService::ParseRequestContentType(const nsACString &aTypeHeader,
+                                     nsACString &aCharset,
+                                     bool *aHadCharset,
+                                     nsACString &aContentType)
+{
+    net_ParseRequestContentType(aTypeHeader, aContentType, aCharset, aHadCharset);
+    return NS_OK;
+}
+
+// nsINetUtil interface
+NS_IMETHODIMP
 nsIOService::ParseContentType(const nsACString &aTypeHeader,
                               nsACString &aCharset,
                               bool *aHadCharset,
                               nsACString &aContentType)
 {
     net_ParseContentType(aTypeHeader, aContentType, aCharset, aHadCharset);
     return NS_OK;
 }
--- a/netwerk/base/nsIOService.h
+++ b/netwerk/base/nsIOService.h
@@ -9,16 +9,17 @@
 #include "nsStringFwd.h"
 #include "nsIIOService2.h"
 #include "nsTArray.h"
 #include "nsCOMPtr.h"
 #include "nsWeakPtr.h"
 #include "nsIObserver.h"
 #include "nsWeakReference.h"
 #include "nsINetUtil.h"
+#include "nsINetUtil_ESR_38.h"
 #include "nsIChannelEventSink.h"
 #include "nsCategoryCache.h"
 #include "nsISpeculativeConnect.h"
 #include "nsDataHashtable.h"
 #include "mozilla/Attributes.h"
 
 #define NS_N(x) (sizeof(x)/sizeof(*x))
 
@@ -42,25 +43,27 @@ namespace mozilla {
 namespace net {
     class NeckoChild;
 } // namespace net
 } // namespace mozilla
 
 class nsIOService final : public nsIIOService2
                             , public nsIObserver
                             , public nsINetUtil
+                            , public nsINetUtil_ESR_38
                             , public nsISpeculativeConnect
                             , public nsSupportsWeakReference
 {
 public:
     NS_DECL_THREADSAFE_ISUPPORTS
     NS_DECL_NSIIOSERVICE
     NS_DECL_NSIIOSERVICE2
     NS_DECL_NSIOBSERVER
     NS_DECL_NSINETUTIL
+    NS_DECL_NSINETUTIL_ESR_38
     NS_DECL_NSISPECULATIVECONNECT
 
     // Gets the singleton instance of the IO Service, creating it as needed
     // Returns nullptr on out of memory or failure to initialize.
     // Returns an addrefed pointer.
     static nsIOService* GetInstance();
 
     nsresult Init();
--- a/netwerk/base/nsNetUtil.h
+++ b/netwerk/base/nsNetUtil.h
@@ -51,16 +51,17 @@
 #include "nsIFileStreams.h"
 #include "nsIBufferedStreams.h"
 #include "nsIInputStreamPump.h"
 #include "nsIAsyncStreamCopier.h"
 #include "nsIPersistentProperties2.h"
 #include "nsISyncStreamListener.h"
 #include "nsInterfaceRequestorAgg.h"
 #include "nsINetUtil.h"
+#include "nsINetUtil_ESR_38.h"
 #include "nsIURIWithPrincipal.h"
 #include "nsIAuthPrompt.h"
 #include "nsIAuthPrompt2.h"
 #include "nsIAuthPromptAdapterFactory.h"
 #include "nsComponentManagerUtils.h"
 #include "nsServiceManagerUtils.h"
 #include "nsINestedURI.h"
 #include "nsIMutable.h"
@@ -1223,16 +1224,36 @@ NS_GetReferrerFromChannel(nsIChannel *ch
         if (NS_FAILED(rv))
           *referrer = nullptr;
       }
     }
     return rv;
 }
 
 inline nsresult
+NS_ParseRequestContentType(const nsACString &rawContentType,
+                           nsCString        &contentType,
+                           nsCString        &contentCharset)
+{
+    // contentCharset is left untouched if not present in rawContentType
+    nsresult rv;
+    nsCOMPtr<nsINetUtil> util = do_GetNetUtil(&rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+    nsCOMPtr<nsINetUtil_ESR_38> utilESR38 = do_QueryInterface(util, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+    nsCString charset;
+    bool hadCharset;
+    rv = utilESR38->ParseRequestContentType(rawContentType, charset, &hadCharset,
+                                            contentType);
+    if (NS_SUCCEEDED(rv) && hadCharset)
+        contentCharset = charset;
+    return rv;
+}
+
+inline nsresult
 NS_ParseContentType(const nsACString &rawContentType,
                     nsCString        &contentType,
                     nsCString        &contentCharset)
 {
     // contentCharset is left untouched if not present in rawContentType
     nsresult rv;
     nsCOMPtr<nsINetUtil> util = do_GetNetUtil(&rv);
     NS_ENSURE_SUCCESS(rv, rv);
--- a/netwerk/base/nsURLHelper.cpp
+++ b/netwerk/base/nsURLHelper.cpp
@@ -798,33 +798,36 @@ net_FindMediaDelimiter(const nsCString& 
 // function sets them.
 static void
 net_ParseMediaType(const nsACString &aMediaTypeStr,
                    nsACString       &aContentType,
                    nsACString       &aContentCharset,
                    int32_t          aOffset,
                    bool             *aHadCharset,
                    int32_t          *aCharsetStart,
-                   int32_t          *aCharsetEnd)
+                   int32_t          *aCharsetEnd,
+                   bool             aStrict)
 {
     const nsCString& flatStr = PromiseFlatCString(aMediaTypeStr);
     const char* start = flatStr.get();
     const char* end = start + flatStr.Length();
 
     // Trim LWS leading and trailing whitespace from type.  We include '(' in
     // the trailing trim set to catch media-type comments, which are not at all
     // standard, but may occur in rare cases.
     const char* type = net_FindCharNotInSet(start, end, HTTP_LWS);
     const char* typeEnd = net_FindCharInSet(type, end, HTTP_LWS ";(");
 
     const char* charset = "";
     const char* charsetEnd = charset;
     int32_t charsetParamStart = 0;
     int32_t charsetParamEnd = 0;
 
+    uint32_t consumed = typeEnd - type;
+
     // Iterate over parameters
     bool typeHasCharset = false;
     uint32_t paramStart = flatStr.FindChar(';', typeEnd - start);
     if (paramStart != uint32_t(kNotFound)) {
         // We have parameters.  Iterate over them.
         uint32_t curParamStart = paramStart + 1;
         do {
             uint32_t curParamEnd =
@@ -838,16 +841,17 @@ net_ParseMediaType(const nsACString &aMe
                                sizeof(charsetStr) - 1) == 0) {
                 charset = paramName + sizeof(charsetStr) - 1;
                 charsetEnd = start + curParamEnd;
                 typeHasCharset = true;
                 charsetParamStart = curParamStart - 1;
                 charsetParamEnd = curParamEnd;
             }
 
+            consumed = curParamEnd;
             curParamStart = curParamEnd + 1;
         } while (curParamStart < flatStr.Length());
     }
 
     bool charsetNeedsQuotedStringUnescaping = false;
     if (typeHasCharset) {
         // Trim LWS leading and trailing whitespace from charset.  We include
         // '(' in the trailing trim set to catch media-type comments, which are
@@ -867,18 +871,20 @@ net_ParseMediaType(const nsACString &aMe
     // if the server sent "*/*", it is meaningless, so do not store it.
     // also, if type is the same as aContentType, then just update the
     // charset.  however, if charset is empty and aContentType hasn't
     // changed, then don't wipe-out an existing aContentCharset.  We
     // also want to reject a mime-type if it does not include a slash.
     // some servers give junk after the charset parameter, which may
     // include a comma, so this check makes us a bit more tolerant.
 
-    if (type != typeEnd && strncmp(type, "*/*", typeEnd - type) != 0 &&
-        memchr(type, '/', typeEnd - type) != nullptr) {
+    if (type != typeEnd &&
+        memchr(type, '/', typeEnd - type) != nullptr &&
+        (aStrict ? (net_FindCharNotInSet(start + consumed, end, HTTP_LWS) == end) :
+                   (strncmp(type, "*/*", typeEnd - type) != 0))) {
         // Common case here is that aContentType is empty
         bool eq = !aContentType.IsEmpty() &&
             aContentType.Equals(Substring(type, typeEnd),
                                 nsCaseInsensitiveCStringComparator());
         if (!eq) {
             aContentType.Assign(type, typeEnd - type);
             ToLowerCase(aContentType);
         }
@@ -975,23 +981,69 @@ net_ParseContentType(const nsACString &a
         uint32_t curTypeEnd =
             net_FindMediaDelimiter(flatStr, curTypeStart, ',');
         
         // At this point curTypeEnd points to the spot where the media-type
         // starting at curTypeEnd ends.  Time to parse that!
         net_ParseMediaType(Substring(flatStr, curTypeStart,
                                      curTypeEnd - curTypeStart),
                            aContentType, aContentCharset, curTypeStart,
-                           aHadCharset, aCharsetStart, aCharsetEnd);
+                           aHadCharset, aCharsetStart, aCharsetEnd, false);
 
         // And let's move on to the next media-type
         curTypeStart = curTypeEnd + 1;
     } while (curTypeStart < flatStr.Length());
 }
 
+void
+net_ParseRequestContentType(const nsACString &aHeaderStr,
+                            nsACString       &aContentType,
+                            nsACString       &aContentCharset,
+                            bool             *aHadCharset)
+{
+    //
+    // Augmented BNF (from RFC 7231 section 3.1.1.1):
+    //
+    //   media-type   = type "/" subtype *( OWS ";" OWS parameter )
+    //   type         = token
+    //   subtype      = token
+    //   parameter    = token "=" ( token / quoted-string )
+    //
+    // Examples:
+    //
+    //   text/html
+    //   text/html; charset=ISO-8859-1
+    //   text/html; charset="ISO-8859-1"
+    //   application/octet-stream
+    //
+
+    aContentType.Truncate();
+    aContentCharset.Truncate();
+    *aHadCharset = false;
+    const nsCString& flatStr = PromiseFlatCString(aHeaderStr);
+
+    // At this point curTypeEnd points to the spot where the media-type
+    // starting at curTypeEnd ends.  Time to parse that!
+    nsAutoCString contentType, contentCharset;
+    bool hadCharset = false;
+    int32_t dummy1, dummy2;
+    uint32_t typeEnd = net_FindMediaDelimiter(flatStr, 0, ',');
+    if (typeEnd != flatStr.Length()) {
+        // We have some stuff left at the end, so this is not a valid
+        // request Content-Type header.
+        return;
+    }
+    net_ParseMediaType(flatStr, contentType, contentCharset, 0,
+                       &hadCharset, &dummy1, &dummy2, true);
+
+    aContentType = contentType;
+    aContentCharset = contentCharset;
+    *aHadCharset = hadCharset;
+}
+
 bool
 net_IsValidHostName(const nsCSubstring &host)
 {
     const char *end = host.EndReading();
     // Use explicit whitelists to select which characters we are
     // willing to send to lower-level DNS logic. This is more
     // self-documenting, and can also be slightly faster than the
     // blacklist approach, since DNS names are the common case, and
--- a/netwerk/base/nsURLHelper.h
+++ b/netwerk/base/nsURLHelper.h
@@ -167,36 +167,52 @@ char * net_RFindCharNotInSet(const char 
 
 /**
  * Parses a content-type header and returns the content type and
  * charset (if any).  aCharset is not modified if no charset is
  * specified in anywhere in aHeaderStr.  In that case (no charset
  * specified), aHadCharset is set to false.  Otherwise, it's set to
  * true.  Note that aContentCharset can be empty even if aHadCharset
  * is true.
+ *
+ * This parsing is suitable for HTTP request.  Use net_ParseContentType
+ * for parsing this header in HTTP responses.
+ */
+void net_ParseRequestContentType(const nsACString &aHeaderStr,
+                                 nsACString       &aContentType,
+                                 nsACString       &aContentCharset,
+                                 bool*          aHadCharset);
+
+/**
+ * Parses a content-type header and returns the content type and
+ * charset (if any).  aCharset is not modified if no charset is
+ * specified in anywhere in aHeaderStr.  In that case (no charset
+ * specified), aHadCharset is set to false.  Otherwise, it's set to
+ * true.  Note that aContentCharset can be empty even if aHadCharset
+ * is true.
  */
 void net_ParseContentType(const nsACString &aHeaderStr,
-                                      nsACString       &aContentType,
-                                      nsACString       &aContentCharset,
-                                      bool*          aHadCharset);
+                          nsACString       &aContentType,
+                          nsACString       &aContentCharset,
+                          bool*          aHadCharset);
 /**
  * As above, but also returns the start and end indexes for the charset
  * parameter in aHeaderStr.  These are indices for the entire parameter, NOT
  * just the value.  If there is "effectively" no charset parameter (e.g. if an
  * earlier type with one is overridden by a later type without one),
  * *aHadCharset will be true but *aCharsetStart will be set to -1.  Note that
  * it's possible to have aContentCharset empty and *aHadCharset true when
  * *aCharsetStart is nonnegative; this corresponds to charset="".
  */
 void net_ParseContentType(const nsACString &aHeaderStr,
-                                      nsACString       &aContentType,
-                                      nsACString       &aContentCharset,
-                                      bool             *aHadCharset,
-                                      int32_t          *aCharsetStart,
-                                      int32_t          *aCharsetEnd);
+                          nsACString       &aContentType,
+                          nsACString       &aContentCharset,
+                          bool             *aHadCharset,
+                          int32_t          *aCharsetStart,
+                          int32_t          *aCharsetEnd);
 
 /* inline versions */
 
 /* remember the 64-bit platforms ;-) */
 #define NET_MAX_ADDRESS (((char*)0)-1)
 
 inline char *net_FindCharInSet(const char *str, const char *set)
 {