bug 477578 - http methods should be case sensitive r=hurley
authorPatrick McManus <mcmanus@ducksong.com>
Tue, 18 Mar 2014 12:36:18 -0400
changeset 174368 20d8f9639b9cb4b4503bf3a9e204b8890873a638
parent 174367 b9f1c40b476d29dab66a1ed05c5777a10a712254
child 174369 e577832d88939c8d51e38f5c81b4b60994117179
push id26451
push usercbook@mozilla.com
push dateThu, 20 Mar 2014 12:56:37 +0000
treeherdermozilla-central@358d369c8388 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershurley
bugs477578
milestone31.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 477578 - http methods should be case sensitive r=hurley
content/base/src/nsXMLHttpRequest.cpp
netwerk/ipc/NeckoChannelParams.ipdlh
netwerk/protocol/http/Http2Stream.cpp
netwerk/protocol/http/HttpBaseChannel.cpp
netwerk/protocol/http/HttpBaseChannel.h
netwerk/protocol/http/HttpChannelChild.cpp
netwerk/protocol/http/HttpChannelParent.cpp
netwerk/protocol/http/HttpChannelParent.h
netwerk/protocol/http/SpdyStream3.cpp
netwerk/protocol/http/SpdyStream31.cpp
netwerk/protocol/http/nsHttp.cpp
netwerk/protocol/http/nsHttp.h
netwerk/protocol/http/nsHttpAtomList.h
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpChannel.h
netwerk/protocol/http/nsHttpConnection.cpp
netwerk/protocol/http/nsHttpRequestHead.cpp
netwerk/protocol/http/nsHttpRequestHead.h
netwerk/protocol/http/nsHttpTransaction.cpp
netwerk/test/mochitests/method.sjs
netwerk/test/mochitests/mochitest.ini
netwerk/test/mochitests/test_xhr_method_case.html
netwerk/test/unit/test_bug412945.js
netwerk/test/unit/test_bug477578.js
netwerk/test/unit/test_bug618835.js
netwerk/test/unit/xpcshell.ini
--- a/content/base/src/nsXMLHttpRequest.cpp
+++ b/content/base/src/nsXMLHttpRequest.cpp
@@ -1532,37 +1532,57 @@ nsXMLHttpRequest::Open(const nsACString&
   Optional<nsAString> realPassword;
   if (optional_argc > 2) {
     realPassword = &password;
   }
   return Open(method, url, async, realUser, realPassword);
 }
 
 nsresult
-nsXMLHttpRequest::Open(const nsACString& method, const nsACString& url,
+nsXMLHttpRequest::Open(const nsACString& inMethod, const nsACString& url,
                        bool async, const Optional<nsAString>& user,
                        const Optional<nsAString>& password)
 {
-  NS_ENSURE_ARG(!method.IsEmpty());
+  NS_ENSURE_ARG(!inMethod.IsEmpty());
 
   if (!async && !DontWarnAboutSyncXHR() && GetOwner() &&
       GetOwner()->GetExtantDoc()) {
     GetOwner()->GetExtantDoc()->WarnOnceAbout(nsIDocument::eSyncXMLHttpRequest);
   }
 
   Telemetry::Accumulate(Telemetry::XMLHTTPREQUEST_ASYNC_OR_SYNC,
                         async ? 0 : 1);
 
   NS_ENSURE_TRUE(mPrincipal, NS_ERROR_NOT_INITIALIZED);
 
   // Disallow HTTP/1.1 TRACE method (see bug 302489)
   // and MS IIS equivalent TRACK (see bug 381264)
-  if (method.LowerCaseEqualsLiteral("trace") ||
-      method.LowerCaseEqualsLiteral("track")) {
-    return NS_ERROR_INVALID_ARG;
+  // and CONNECT
+  if (inMethod.LowerCaseEqualsLiteral("trace") ||
+      inMethod.LowerCaseEqualsLiteral("connect") ||
+      inMethod.LowerCaseEqualsLiteral("track")) {
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
+
+  nsAutoCString method;
+  // GET, POST, DELETE, HEAD, OPTIONS, PUT methods normalized to upper case
+  if (inMethod.LowerCaseEqualsLiteral("get")) {
+    method.Assign(NS_LITERAL_CSTRING("GET"));
+  } else if (inMethod.LowerCaseEqualsLiteral("post")) {
+    method.Assign(NS_LITERAL_CSTRING("POST"));
+  } else if (inMethod.LowerCaseEqualsLiteral("delete")) {
+    method.Assign(NS_LITERAL_CSTRING("DELETE"));
+  } else if (inMethod.LowerCaseEqualsLiteral("head")) {
+    method.Assign(NS_LITERAL_CSTRING("HEAD"));
+  } else if (inMethod.LowerCaseEqualsLiteral("options")) {
+    method.Assign(NS_LITERAL_CSTRING("OPTIONS"));
+  } else if (inMethod.LowerCaseEqualsLiteral("put")) {
+    method.Assign(NS_LITERAL_CSTRING("PUT"));
+  } else {
+    method = inMethod; // other methods are not normalized
   }
 
   // sync request is not allowed using withCredential or responseType
   // in window context
   if (!async && HasOrHasHadOwner() &&
       (mState & XML_HTTP_REQUEST_AC_WITH_CREDENTIALS ||
        mTimeoutMilliseconds ||
        mResponseType != XML_HTTP_RESPONSE_TYPE_DEFAULT)) {
--- a/netwerk/ipc/NeckoChannelParams.ipdlh
+++ b/netwerk/ipc/NeckoChannelParams.ipdlh
@@ -28,17 +28,17 @@ struct HttpChannelOpenArgs
   // set originalURI != uri (about:credits?); also not clear if
   // chrome channel would ever need to know.  Get rid of next arg?
   OptionalURIParams           original;
   OptionalURIParams           doc;
   OptionalURIParams           referrer;
   OptionalURIParams           apiRedirectTo;
   uint32_t                    loadFlags;
   RequestHeaderTuples         requestHeaders;
-  nsHttpAtom                  requestMethod;
+  nsCString                   requestMethod;
   OptionalInputStreamParams   uploadStream;
   bool                        uploadStreamHasHeaders;
   uint16_t                    priority;
   uint8_t                     redirectionLimit;
   bool                        allowPipelining;
   bool                        forceAllowThirdPartyCookie;
   bool                        resumeAt;
   uint64_t                    startPos;
--- a/netwerk/protocol/http/Http2Stream.cpp
+++ b/netwerk/protocol/http/Http2Stream.cpp
@@ -295,17 +295,17 @@ Http2Stream::ParseHttpRequestHeaders(con
   mTransaction->RequestHead()->GetHeader(nsHttp::Host, hostHeader);
 
   CreatePushHashKey(NS_LITERAL_CSTRING("https"),
                     hostHeader, mSession->Serial(),
                     mTransaction->RequestHead()->RequestURI(),
                     mOrigin, hashkey);
 
   // check the push cache for GET
-  if (mTransaction->RequestHead()->Method() == nsHttp::Get) {
+  if (mTransaction->RequestHead()->IsGet()) {
     // from :scheme, :authority, :path
     nsILoadGroupConnectionInfo *loadGroupCI = mTransaction->LoadGroupConnectionInfo();
     SpdyPushCache *cache = nullptr;
     if (loadGroupCI)
       loadGroupCI->GetSpdyPushCache(&cache);
 
     Http2PushedStream *pushedStream = nullptr;
     // we remove the pushedstream from the push cache so that
@@ -351,37 +351,37 @@ Http2Stream::ParseHttpRequestHeaders(con
     return NS_ERROR_UNEXPECTED;
   }
 
   // Now we need to convert the flat http headers into a set
   // of HTTP/2 headers by writing to mTxInlineFrame{sz}
 
   nsCString compressedData;
   mSession->Compressor()->EncodeHeaderBlock(mFlatHttpRequestHeaders,
-                                            nsCString(mTransaction->RequestHead()->Method().get()),
+                                            mTransaction->RequestHead()->Method(),
                                             mTransaction->RequestHead()->RequestURI(),
                                             hostHeader,
                                             NS_LITERAL_CSTRING("https"),
                                             compressedData);
 
   // Determine whether to put the fin bit on the header frame or whether
   // to wait for a data packet to put it on.
   uint8_t firstFrameFlags =  Http2Session::kFlag_PRIORITY;
 
-  if (mTransaction->RequestHead()->Method() == nsHttp::Get ||
-      mTransaction->RequestHead()->Method() == nsHttp::Connect ||
-      mTransaction->RequestHead()->Method() == nsHttp::Head) {
+  if (mTransaction->RequestHead()->IsGet() ||
+      mTransaction->RequestHead()->IsConnect() ||
+      mTransaction->RequestHead()->IsHead()) {
     // for GET, CONNECT, and HEAD place the fin bit right on the
     // header packet
 
     SetSentFin(true);
     firstFrameFlags |= Http2Session::kFlag_END_STREAM;
-  } else if (mTransaction->RequestHead()->Method() == nsHttp::Post ||
-             mTransaction->RequestHead()->Method() == nsHttp::Put ||
-             mTransaction->RequestHead()->Method() == nsHttp::Options) {
+  } else if (mTransaction->RequestHead()->IsPost() ||
+             mTransaction->RequestHead()->IsPut() ||
+             mTransaction->RequestHead()->IsOptions()) {
     // place fin in a data frame even for 0 length messages for iterop
   } else if (!mRequestBodyLenRemaining) {
     // for other HTTP extension methods, rely on the content-length
     // to determine whether or not to put fin on headers
     SetSentFin(true);
     firstFrameFlags |= Http2Session::kFlag_END_STREAM;
   }
 
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -116,18 +116,18 @@ HttpBaseChannel::Init(nsIURI *aURI,
   if (NS_FAILED(rv)) return rv;
 
   LOG(("host=%s port=%d\n", host.get(), port));
 
   rv = mURI->GetAsciiSpec(mSpec);
   if (NS_FAILED(rv)) return rv;
   LOG(("uri=%s\n", mSpec.get()));
 
-  // Set default request method
-  mRequestHead.SetMethod(nsHttp::Get);
+  // Assert default request method
+  MOZ_ASSERT(mRequestHead.EqualsMethod(nsHttpRequestHead::kMethod_Get));
 
   // Set request headers
   nsAutoCString hostLine;
   rv = nsHttpHandler::GenerateHostPort(host, port, hostLine);
   if (NS_FAILED(rv)) return rv;
 
   rv = mRequestHead.SetHeader(nsHttp::Host, hostLine);
   if (NS_FAILED(rv)) return rv;
@@ -485,30 +485,30 @@ HttpBaseChannel::SetUploadStream(nsIInpu
   // and so we select POST as the request method if contentType and
   // contentLength are unspecified.
 
   if (stream) {
     nsAutoCString method;
     bool hasHeaders;
 
     if (contentType.IsEmpty()) {
-      method = nsHttp::Post;
+      method = NS_LITERAL_CSTRING("POST");
       hasHeaders = true;
     } else {
-      method = nsHttp::Put;
+      method = NS_LITERAL_CSTRING("PUT");
       hasHeaders = false;
     }
     return ExplicitSetUploadStream(stream, contentType, contentLength,
                                    method, hasHeaders);
   }
 
   // if stream is null, ExplicitSetUploadStream returns error.
   // So we need special case for GET method.
   mUploadStreamHasHeaders = false;
-  mRequestHead.SetMethod(nsHttp::Get); // revert to GET request
+  mRequestHead.SetMethod(NS_LITERAL_CSTRING("GET")); // revert to GET request
   mUploadStream = stream;
   return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 // HttpBaseChannel::nsIUploadChannel2
 //-----------------------------------------------------------------------------
 
@@ -821,21 +821,17 @@ HttpBaseChannel::SetRequestMethod(const 
   ENSURE_CALLED_BEFORE_CONNECT();
 
   const nsCString& flatMethod = PromiseFlatCString(aMethod);
 
   // Method names are restricted to valid HTTP tokens.
   if (!nsHttp::IsValidToken(flatMethod))
     return NS_ERROR_INVALID_ARG;
 
-  nsHttpAtom atom = nsHttp::ResolveAtom(flatMethod.get());
-  if (!atom)
-    return NS_ERROR_FAILURE;
-
-  mRequestHead.SetMethod(atom);
+  mRequestHead.SetMethod(flatMethod);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 HttpBaseChannel::GetReferrer(nsIURI **referrer)
 {
   NS_ENSURE_ARG_POINTER(referrer);
   *referrer = mReferrer;
@@ -1605,17 +1601,17 @@ HttpBaseChannel::AdjustPriority(int32_t 
 // HttpBaseChannel::nsIResumableChannel
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 HttpBaseChannel::GetEntityID(nsACString& aEntityID)
 {
   // Don't return an entity ID for Non-GET requests which require
   // additional data
-  if (mRequestHead.Method() != nsHttp::Get) {
+  if (!mRequestHead.IsGet()) {
     return NS_ERROR_NOT_RESUMABLE;
   }
 
   uint64_t size = UINT64_MAX;
   nsAutoCString etag, lastmod;
   if (mResponseHead) {
     // Don't return an entity if the server sent the following header:
     // Accept-Ranges: none
@@ -1742,16 +1738,32 @@ static PLDHashOperator
 CopyProperties(const nsAString& aKey, nsIVariant *aData, void *aClosure)
 {
   nsIWritablePropertyBag* bag = static_cast<nsIWritablePropertyBag*>
                                            (aClosure);
   bag->SetProperty(aKey, aData);
   return PL_DHASH_NEXT;
 }
 
+bool
+HttpBaseChannel::ShouldRewriteRedirectToGET(uint32_t httpStatus,
+                                            nsHttpRequestHead::ParsedMethodType method)
+{
+  // for 301 and 302, only rewrite POST
+  if (httpStatus == 301 || httpStatus == 302)
+    return method == nsHttpRequestHead::kMethod_Post;
+
+  // rewrite for 303 unless it was HEAD
+  if (httpStatus == 303)
+    return method != nsHttpRequestHead::kMethod_Head;
+
+  // otherwise, such as for 307, do not rewrite
+  return false;
+}
+
 nsresult
 HttpBaseChannel::SetupReplacementChannel(nsIURI       *newURI,
                                          nsIChannel   *newChannel,
                                          bool          preserveMethod)
 {
   LOG(("HttpBaseChannel::SetupReplacementChannel "
      "[this=%p newChannel=%p preserveMethod=%d]",
      this, newChannel, preserveMethod));
@@ -1802,17 +1814,17 @@ HttpBaseChannel::SetupReplacementChannel
       if (uploadChannel2) {
         const char *ctype = mRequestHead.PeekHeader(nsHttp::Content_Type);
         if (!ctype)
           ctype = "";
         const char *clen  = mRequestHead.PeekHeader(nsHttp::Content_Length);
         int64_t len = clen ? nsCRT::atoll(clen) : -1;
         uploadChannel2->ExplicitSetUploadStream(
                                   mUploadStream, nsDependentCString(ctype), len,
-                                  nsDependentCString(mRequestHead.Method()),
+                                  mRequestHead.Method(),
                                   mUploadStreamHasHeaders);
       } else {
         if (mUploadStreamHasHeaders) {
           uploadChannel->SetUploadStream(mUploadStream, EmptyCString(),
                            -1);
         } else {
           const char *ctype =
             mRequestHead.PeekHeader(nsHttp::Content_Type);
@@ -1829,17 +1841,17 @@ HttpBaseChannel::SetupReplacementChannel
         }
       }
     }
     // since preserveMethod is true, we need to ensure that the appropriate
     // request method gets set on the channel, regardless of whether or not
     // we set the upload stream above. This means SetRequestMethod() will
     // be called twice if ExplicitSetUploadStream() gets called above.
 
-    httpChannel->SetRequestMethod(nsDependentCString(mRequestHead.Method()));
+    httpChannel->SetRequestMethod(mRequestHead.Method());
   }
   // convey the referrer if one was used for this channel to the next one
   if (mReferrer)
     httpChannel->SetReferrer(mReferrer);
   // convey the mAllowPipelining flag
   httpChannel->SetAllowPipelining(mAllowPipelining);
   // convey the new redirection limit
   httpChannel->SetRedirectionLimit(mRedirectionLimit - 1);
--- a/netwerk/protocol/http/HttpBaseChannel.h
+++ b/netwerk/protocol/http/HttpBaseChannel.h
@@ -200,16 +200,22 @@ public:
     nsHttpResponseHead * GetResponseHead() const { return mResponseHead; }
     nsHttpRequestHead * GetRequestHead() { return &mRequestHead; }
 
     const NetAddr& GetSelfAddr() { return mSelfAddr; }
     const NetAddr& GetPeerAddr() { return mPeerAddr; }
 
 public: /* Necko internal use only... */
 
+
+    // Return whether upon a redirect code of httpStatus for method, the
+    // request method should be rewritten to GET.
+    static bool ShouldRewriteRedirectToGET(uint32_t httpStatus,
+                                           nsHttpRequestHead::ParsedMethodType method);
+
 protected:
     nsCOMArray<nsISecurityConsoleMessage> mSecurityConsoleMessages;
 
   // Handle notifying listener, removing from loadgroup if request failed.
   void     DoNotifyListener();
   virtual void DoNotifyListenerCleanup() = 0;
 
   // drop reference to listener, its callbacks, and the progress sink
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -781,18 +781,18 @@ HttpChannelChild::Redirect1Begin(const u
     OnRedirectVerifyCallback(rv);
     return;
   }
 
   // We won't get OnStartRequest, set cookies here.
   mResponseHead = new nsHttpResponseHead(responseHead);
   SetCookie(mResponseHead->PeekHeader(nsHttp::Set_Cookie));
 
-  bool rewriteToGET = nsHttp::ShouldRewriteRedirectToGET(
-                               mResponseHead->Status(), mRequestHead.Method());
+  bool rewriteToGET = HttpBaseChannel::ShouldRewriteRedirectToGET(mResponseHead->Status(),
+                                                                  mRequestHead.ParsedMethod());
 
   rv = SetupReplacementChannel(uri, newChannel, !rewriteToGET);
   if (NS_FAILED(rv)) {
     // Veto redirect.  nsHttpChannel decides to cancel or continue.
     OnRedirectVerifyCallback(rv);
     return;
   }
 
--- a/netwerk/protocol/http/HttpChannelParent.cpp
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -142,17 +142,17 @@ HttpChannelParent::GetInterface(const ns
 bool
 HttpChannelParent::DoAsyncOpen(  const URIParams&           aURI,
                                  const OptionalURIParams&   aOriginalURI,
                                  const OptionalURIParams&   aDocURI,
                                  const OptionalURIParams&   aReferrerURI,
                                  const OptionalURIParams&   aAPIRedirectToURI,
                                  const uint32_t&            loadFlags,
                                  const RequestHeaderTuples& requestHeaders,
-                                 const nsHttpAtom&          requestMethod,
+                                 const nsCString&           requestMethod,
                                  const OptionalInputStreamParams& uploadStream,
                                  const bool&              uploadStreamHasHeaders,
                                  const uint16_t&            priority,
                                  const uint8_t&             redirectionLimit,
                                  const bool&              allowPipelining,
                                  const bool&              forceAllowThirdPartyCookie,
                                  const bool&                doResumeAt,
                                  const uint64_t&            startPos,
--- a/netwerk/protocol/http/HttpChannelParent.h
+++ b/netwerk/protocol/http/HttpChannelParent.h
@@ -73,17 +73,17 @@ protected:
 
   bool DoAsyncOpen(const URIParams&           uri,
                    const OptionalURIParams&   originalUri,
                    const OptionalURIParams&   docUri,
                    const OptionalURIParams&   referrerUri,
                    const OptionalURIParams&   internalRedirectUri,
                    const uint32_t&            loadFlags,
                    const RequestHeaderTuples& requestHeaders,
-                   const nsHttpAtom&          requestMethod,
+                   const nsCString&           requestMethod,
                    const OptionalInputStreamParams& uploadStream,
                    const bool&                uploadStreamHasHeaders,
                    const uint16_t&            priority,
                    const uint8_t&             redirectionLimit,
                    const bool&                allowPipelining,
                    const bool&                forceAllowThirdPartyCookie,
                    const bool&                doResumeAt,
                    const uint64_t&            startPos,
--- a/netwerk/protocol/http/SpdyStream3.cpp
+++ b/netwerk/protocol/http/SpdyStream3.cpp
@@ -284,17 +284,17 @@ SpdyStream3::ParseHttpRequestHeaders(con
   mTransaction->RequestHead()->GetHeader(nsHttp::Host, hostHeader);
 
   CreatePushHashKey(NS_LITERAL_CSTRING("https"),
                     hostHeader, mSession->Serial(),
                     mTransaction->RequestHead()->RequestURI(),
                     mOrigin, hashkey);
 
   // check the push cache for GET
-  if (mTransaction->RequestHead()->Method() == nsHttp::Get) {
+  if (mTransaction->RequestHead()->IsGet()) {
     // from :scheme, :host, :path
     nsILoadGroupConnectionInfo *loadGroupCI = mTransaction->LoadGroupConnectionInfo();
     SpdyPushCache *cache = nullptr;
     if (loadGroupCI)
       loadGroupCI->GetSpdyPushCache(&cache);
 
     SpdyPushedStream3 *pushedStream = nullptr;
     // we remove the pushedstream from the push cache so that
@@ -478,28 +478,28 @@ SpdyStream3::ParseHttpRequestHeaders(con
   NetworkEndian::writeUint32(mTxInlineFrame + 1 * sizeof(uint32_t),
                              mTxInlineFrameUsed - 8);
 
   MOZ_ASSERT(!mTxInlineFrame[4], "Size greater than 24 bits");
 
   // Determine whether to put the fin bit on the syn stream frame or whether
   // to wait for a data packet to put it on.
 
-  if (mTransaction->RequestHead()->Method() == nsHttp::Get ||
-      mTransaction->RequestHead()->Method() == nsHttp::Connect ||
-      mTransaction->RequestHead()->Method() == nsHttp::Head) {
+  if (mTransaction->RequestHead()->IsGet() ||
+      mTransaction->RequestHead()->IsConnect() ||
+      mTransaction->RequestHead()->IsHead()) {
     // for GET, CONNECT, and HEAD place the fin bit right on the
     // syn stream packet
 
     mSentFinOnData = 1;
     mTxInlineFrame[4] = SpdySession3::kFlag_Data_FIN;
   }
-  else if (mTransaction->RequestHead()->Method() == nsHttp::Post ||
-           mTransaction->RequestHead()->Method() == nsHttp::Put ||
-           mTransaction->RequestHead()->Method() == nsHttp::Options) {
+  else if (mTransaction->RequestHead()->IsPost() ||
+           mTransaction->RequestHead()->IsPut() ||
+           mTransaction->RequestHead()->IsOptions()) {
     // place fin in a data frame even for 0 length messages, I've seen
     // the google gateway be unhappy with fin-on-syn for 0 length POST
   }
   else if (!mRequestBodyLenRemaining) {
     // for other HTTP extension methods, rely on the content-length
     // to determine whether or not to put fin on syn
     mSentFinOnData = 1;
     mTxInlineFrame[4] = SpdySession3::kFlag_Data_FIN;
--- a/netwerk/protocol/http/SpdyStream31.cpp
+++ b/netwerk/protocol/http/SpdyStream31.cpp
@@ -290,17 +290,17 @@ SpdyStream31::ParseHttpRequestHeaders(co
   mTransaction->RequestHead()->GetHeader(nsHttp::Host, hostHeader);
 
   CreatePushHashKey(NS_LITERAL_CSTRING("https"),
                     hostHeader, mSession->Serial(),
                     mTransaction->RequestHead()->RequestURI(),
                     mOrigin, hashkey);
 
   // check the push cache for GET
-  if (mTransaction->RequestHead()->Method() == nsHttp::Get) {
+  if (mTransaction->RequestHead()->IsGet()) {
     // from :scheme, :host, :path
     nsILoadGroupConnectionInfo *loadGroupCI = mTransaction->LoadGroupConnectionInfo();
     SpdyPushCache *cache = nullptr;
     if (loadGroupCI)
       loadGroupCI->GetSpdyPushCache(&cache);
 
     SpdyPushedStream31 *pushedStream = nullptr;
     // we remove the pushedstream from the push cache so that
@@ -485,28 +485,28 @@ SpdyStream31::ParseHttpRequestHeaders(co
   (reinterpret_cast<uint32_t *>(mTxInlineFrame.get()))[1] =
     PR_htonl(mTxInlineFrameUsed - 8);
 
   MOZ_ASSERT(!mTxInlineFrame[4], "Size greater than 24 bits");
 
   // Determine whether to put the fin bit on the syn stream frame or whether
   // to wait for a data packet to put it on.
 
-  if (mTransaction->RequestHead()->Method() == nsHttp::Get ||
-      mTransaction->RequestHead()->Method() == nsHttp::Connect ||
-      mTransaction->RequestHead()->Method() == nsHttp::Head) {
+  if (mTransaction->RequestHead()->IsGet() ||
+      mTransaction->RequestHead()->IsConnect() ||
+      mTransaction->RequestHead()->IsHead()) {
     // for GET, CONNECT, and HEAD place the fin bit right on the
     // syn stream packet
 
     mSentFinOnData = 1;
     mTxInlineFrame[4] = SpdySession31::kFlag_Data_FIN;
   }
-  else if (mTransaction->RequestHead()->Method() == nsHttp::Post ||
-           mTransaction->RequestHead()->Method() == nsHttp::Put ||
-           mTransaction->RequestHead()->Method() == nsHttp::Options) {
+  else if (mTransaction->RequestHead()->IsPost() ||
+           mTransaction->RequestHead()->IsPut() ||
+           mTransaction->RequestHead()->IsOptions()) {
     // place fin in a data frame even for 0 length messages, I've seen
     // the google gateway be unhappy with fin-on-syn for 0 length POST
   }
   else if (!mRequestBodyLenRemaining) {
     // for other HTTP extension methods, rely on the content-length
     // to determine whether or not to put fin on syn
     mSentFinOnData = 1;
     mTxInlineFrame[4] = SpdySession31::kFlag_Data_FIN;
--- a/netwerk/protocol/http/nsHttp.cpp
+++ b/netwerk/protocol/http/nsHttp.cpp
@@ -290,39 +290,10 @@ nsHttp::ParseInt64(const char *input, co
 }
 
 bool
 nsHttp::IsPermanentRedirect(uint32_t httpStatus)
 {
   return httpStatus == 301 || httpStatus == 308;
 }
 
-bool
-nsHttp::ShouldRewriteRedirectToGET(uint32_t httpStatus, nsHttpAtom method)
-{
-  // for 301 and 302, only rewrite POST
-  if (httpStatus == 301 || httpStatus == 302)
-    return method == nsHttp::Post;
-
-  // rewrite for 303 unless it was HEAD
-  if (httpStatus == 303)
-    return method != nsHttp::Head;
-
-  // otherwise, such as for 307, do not rewrite
-  return false;
-}
-
-bool
-nsHttp::IsSafeMethod(nsHttpAtom method)
-{
-  // This code will need to be extended for new safe methods, otherwise
-  // they'll default to "not safe".
-  return method == nsHttp::Get ||
-         method == nsHttp::Head ||
-         method == nsHttp::Options ||
-         method == nsHttp::Propfind ||
-         method == nsHttp::Report ||
-         method == nsHttp::Search ||
-         method == nsHttp::Trace;
-}
-
 } // namespace mozilla::net
 } // namespace mozilla
--- a/netwerk/protocol/http/nsHttp.h
+++ b/netwerk/protocol/http/nsHttp.h
@@ -155,24 +155,16 @@ struct nsHttp
     static inline bool ParseInt64(const char *input, int64_t *result) {
         const char *next;
         return ParseInt64(input, &next, result) && *next == '\0';
     }
 
     // Return whether the HTTP status code represents a permanent redirect
     static bool IsPermanentRedirect(uint32_t httpStatus);
 
-    // Return whether upon a redirect code of httpStatus for method, the
-    // request method should be rewritten to GET.
-    static bool ShouldRewriteRedirectToGET(uint32_t httpStatus, nsHttpAtom method);
-
-    // Return whether the specified method is safe as per RFC 2616,
-    // Section 9.1.1.
-    static bool IsSafeMethod(nsHttpAtom method);
-
     // Declare all atoms
     //
     // The atom names and values are stored in nsHttpAtomList.h and are brought
     // to you by the magic of C preprocessing.  Add new atoms to nsHttpAtomList
     // and all support logic will be auto-generated.
     //
 #define HTTP_ATOM(_name, _value) static nsHttpAtom _name;
 #include "nsHttpAtomList.h"
--- a/netwerk/protocol/http/nsHttpAtomList.h
+++ b/netwerk/protocol/http/nsHttpAtomList.h
@@ -81,32 +81,9 @@ HTTP_ATOM(URI,                       "UR
 HTTP_ATOM(Upgrade,                   "Upgrade")
 HTTP_ATOM(User_Agent,                "User-Agent")
 HTTP_ATOM(Vary,                      "Vary")
 HTTP_ATOM(Version,                   "Version")
 HTTP_ATOM(WWW_Authenticate,          "WWW-Authenticate")
 HTTP_ATOM(Warning,                   "Warning")
 HTTP_ATOM(X_Firefox_Spdy,            "X-Firefox-Spdy")
 
-// methods are atoms too.
-//
-// Note: winnt.h defines DELETE macro, so we'll just keep the methods mixedcase
-// even though they're normally written all uppercase. -- darin
-
-HTTP_ATOM(Connect,                   "CONNECT")
-HTTP_ATOM(Copy,                      "COPY")
-HTTP_ATOM(Delete,                    "DELETE")
-HTTP_ATOM(Get,                       "GET")
-HTTP_ATOM(Head,                      "HEAD")
-HTTP_ATOM(Index,                     "INDEX")
-HTTP_ATOM(Lock,                      "LOCK")
-HTTP_ATOM(M_Post,                    "M-POST")
-HTTP_ATOM(Mkcol,                     "MKCOL")
-HTTP_ATOM(Move,                      "MOVE")
-HTTP_ATOM(Options,                   "OPTIONS")
-HTTP_ATOM(Post,                      "POST")
-HTTP_ATOM(Propfind,                  "PROPFIND")
-HTTP_ATOM(Proppatch,                 "PROPPATCH")
-HTTP_ATOM(Put,                       "PUT")
-HTTP_ATOM(Report,                    "REPORT")
-HTTP_ATOM(Search,                    "SEARCH")
-HTTP_ATOM(Trace,                     "TRACE")
-HTTP_ATOM(Unlock,                    "UNLOCK")
+// methods are case sensitive and do not use atom table
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -617,16 +617,34 @@ nsHttpChannel::RetrieveSSLOptions()
                                                        "falsestart-rsa", &perm);
     if (NS_SUCCEEDED(rv) && perm == nsIPermissionManager::ALLOW_ACTION) {
         LOG(("nsHttpChannel::RetrieveSSLOptions [this=%p] "
              "falsestart-rsa permission found\n", this));
         mCaps |= NS_HTTP_ALLOW_RSA_FALSESTART;
     }
 }
 
+static bool
+SafeForPipelining(nsHttpRequestHead::ParsedMethodType method,
+                  const nsCString &methodString)
+{
+    if (method == nsHttpRequestHead::kMethod_Get ||
+        method == nsHttpRequestHead::kMethod_Head ||
+        method == nsHttpRequestHead::kMethod_Options) {
+        return true;
+    }
+
+    if (method != nsHttpRequestHead::kMethod_Custom) {
+        return false;
+    }
+
+    return (!strcmp(methodString.get(), "PROPFIND") ||
+            !strcmp(methodString.get(), "PROPPATCH"));
+}
+
 nsresult
 nsHttpChannel::SetupTransaction()
 {
     LOG(("nsHttpChannel::SetupTransaction [this=%p]\n", this));
 
     NS_ENSURE_TRUE(!mTransaction, NS_ERROR_ALREADY_INITIALIZED);
 
     nsresult rv;
@@ -637,21 +655,17 @@ nsHttpChannel::SetupTransaction()
         //   (1) pipelining has been disabled by config
         //   (2) pipelining has been disabled by connection mgr info
         //   (3) request corresponds to a top-level document load (link click)
         //   (4) request method is non-idempotent
         //   (5) request is marked slow (e.g XHR)
         //
         if (!mAllowPipelining ||
            (mLoadFlags & (LOAD_INITIAL_DOCUMENT_URI | INHIBIT_PIPELINE)) ||
-            !(mRequestHead.Method() == nsHttp::Get ||
-              mRequestHead.Method() == nsHttp::Head ||
-              mRequestHead.Method() == nsHttp::Options ||
-              mRequestHead.Method() == nsHttp::Propfind ||
-              mRequestHead.Method() == nsHttp::Proppatch)) {
+            !SafeForPipelining(mRequestHead.ParsedMethod(), mRequestHead.Method())) {
             LOG(("  pipelining disallowed\n"));
             mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
         }
     }
 
     if (!mAllowSpdy)
         mCaps |= NS_HTTP_DISALLOW_SPDY;
 
@@ -2478,34 +2492,33 @@ nsHttpChannel::OpenCacheEntry(bool using
     AutoCacheWaitFlags waitFlags(this);
 
     // Drop this flag here
     mConcurentCacheAccess = 0;
 
     nsresult rv;
 
     mLoadedFromApplicationCache = false;
-    mHasQueryString = HasQueryString(mRequestHead.Method(), mURI);
+    mHasQueryString = HasQueryString(mRequestHead.ParsedMethod(), mURI);
 
     LOG(("nsHttpChannel::OpenCacheEntry [this=%p]", this));
 
     // make sure we're not abusing this function
     NS_PRECONDITION(!mCacheEntry, "cache entry already open");
 
     nsAutoCString cacheKey;
 
-    if (mRequestHead.Method() == nsHttp::Post) {
+    if (mRequestHead.IsPost()) {
         // If the post id is already set then this is an attempt to replay
         // a post transaction via the cache.  Otherwise, we need a unique
         // post id for this transaction.
         if (mPostID == 0)
             mPostID = gHttpHandler->GenerateUniqueID();
     }
-    else if ((mRequestHead.Method() != nsHttp::Get) &&
-             (mRequestHead.Method() != nsHttp::Head)) {
+    else if (!mRequestHead.IsGet() && !mRequestHead.IsHead()) {
         // don't use the cache for other types of requests
         return NS_OK;
     }
 
     if (mResuming) {
         // We don't support caching for requests initiated
         // via nsIResumableChannel.
         return NS_OK;
@@ -2608,17 +2621,17 @@ bypassCacheEntryOpen:
         return NS_OK;
     }
 
     if (mLoadFlags & INHIBIT_CACHING) {
         // respect demand not to cache
         return NS_OK;
     }
 
-    if (mRequestHead.Method() != nsHttp::Get) {
+    if (!mRequestHead.IsGet()) {
         // only cache complete documents offline
         return NS_OK;
     }
 
     rv = cacheStorageService->AppCacheStorage(info, mApplicationCacheForWrite,
                                               getter_AddRefs(cacheStorage));
     NS_ENSURE_SUCCESS(rv, rv);
 
@@ -2680,22 +2693,25 @@ nsHttpChannel::OnCacheEntryCheck(nsICach
     mCachedContentIsValid = false;
 
     nsXPIDLCString buf;
 
     // Get the method that was used to generate the cached response
     rv = entry->GetMetaDataElement("request-method", getter_Copies(buf));
     NS_ENSURE_SUCCESS(rv, rv);
 
-    nsHttpAtom method = nsHttp::ResolveAtom(buf);
-    if (method == nsHttp::Head) {
+    bool methodWasHead = buf.EqualsLiteral("HEAD");
+    bool methodWasGet = buf.EqualsLiteral("GET");
+
+    if (methodWasHead) {
         // The cached response does not contain an entity.  We can only reuse
         // the response if the current request is also HEAD.
-        if (mRequestHead.Method() != nsHttp::Head)
+        if (!mRequestHead.IsHead()) {
             return NS_OK;
+        }
     }
     buf.Adopt(0);
 
     // We'll need this value in later computations...
     uint32_t lastModifiedTime;
     rv = entry->GetLastModified(&lastModifiedTime);
     NS_ENSURE_SUCCESS(rv, rv);
 
@@ -2734,17 +2750,17 @@ nsHttpChannel::OnCacheEntryCheck(nsICach
             mCachedContentIsValid = true;
             entry->MaybeMarkValid();
         }
         return rv;
     }
 
     bool wantCompleteEntry = false;
 
-    if (method != nsHttp::Head && !isCachedRedirect) {
+    if (!methodWasHead && !isCachedRedirect) {
         // If the cached content-length is set and it does not match the data
         // size of the cached content, then the cached response is partial...
         // either we need to issue a byte range request or we need to refetch
         // the entire document.
         //
         // We exclude redirects from this check because we (usually) strip the
         // entity when we store the cache entry, and even if we didn't, we
         // always ignore a cached redirect's entity anyway. See bug 759043.
@@ -2865,17 +2881,17 @@ nsHttpChannel::OnCacheEntryCheck(nsICach
         }
         else
             doValidation = true;
 
         LOG(("%salidating based on expiration time\n", doValidation ? "V" : "Not v"));
     }
 
     if (!doValidation && mRequestHead.PeekHeader(nsHttp::If_Match) &&
-        (method == nsHttp::Get || method == nsHttp::Head)) {
+        (methodWasGet || methodWasHead)) {
         const char *requestedETag, *cachedETag;
         cachedETag = mCachedResponseHead->PeekHeader(nsHttp::ETag);
         requestedETag = mRequestHead.PeekHeader(nsHttp::If_Match);
         if (cachedETag && (!strncmp(cachedETag, "W/", 2) ||
             strcmp(requestedETag, cachedETag))) {
             // User has defined If-Match header, if the cached entry is not
             // matching the provided header value or the cached ETag is weak,
             // force validation.
@@ -2934,18 +2950,17 @@ nsHttpChannel::OnCacheEntryCheck(nsICach
         // do not attempt to validate no-store content, since servers will not
         // expect it to be cached.  (we only keep it in our cache for the
         // purposes of back/forward, etc.)
         //
         // the request method MUST be either GET or HEAD (see bug 175641).
         //
         // do not override conditional headers when consumer has defined its own
         if (!mCachedResponseHead->NoStore() &&
-            (mRequestHead.Method() == nsHttp::Get ||
-             mRequestHead.Method() == nsHttp::Head) &&
+            (mRequestHead.IsGet() || mRequestHead.IsHead()) &&
              !mCustomConditionalRequest) {
 
             if (mConcurentCacheAccess) {
                 // In case of concurrent read and also validation request we
                 // must wait for the current writer to close the output stream
                 // first.  Otherwise, when the writer's job would have been interrupted
                 // before all the data were downloaded, we'd have to do a range request
                 // which would be a second request in line during this channel's
@@ -3289,23 +3304,24 @@ nsHttpChannel::UpdateExpirationTime()
         rv = mOfflineCacheEntry->SetExpirationTime(expirationTime);
         NS_ENSURE_SUCCESS(rv, rv);
     }
 
     return NS_OK;
 }
 
 /*static*/ inline bool
-nsHttpChannel::HasQueryString(nsHttpAtom method, nsIURI * uri)
+nsHttpChannel::HasQueryString(nsHttpRequestHead::ParsedMethodType method, nsIURI * uri)
 {
     // Must be called on the main thread because nsIURI does not implement
     // thread-safe QueryInterface.
     MOZ_ASSERT(NS_IsMainThread());
 
-    if (method != nsHttp::Get && method != nsHttp::Head)
+    if (method != nsHttpRequestHead::kMethod_Get &&
+        method != nsHttpRequestHead::kMethod_Head)
         return false;
 
     nsAutoCString query;
     nsCOMPtr<nsIURL> url = do_QueryInterface(uri);
     nsresult rv = url->GetQuery(query);
     return NS_SUCCEEDED(rv) && !query.IsEmpty();
 }
 
@@ -4174,22 +4190,21 @@ nsHttpChannel::ContinueProcessRedirectio
         mURI->GetRef(ref);
         if (!ref.IsEmpty()) {
             // NOTE: SetRef will fail if mRedirectURI is immutable
             // (e.g. an about: URI)... Oh well.
             mRedirectURI->SetRef(ref);
         }
     }
 
-    bool rewriteToGET = nsHttp::ShouldRewriteRedirectToGET(
-                                    mRedirectType, mRequestHead.Method());
+    bool rewriteToGET = ShouldRewriteRedirectToGET(mRedirectType,
+                                                   mRequestHead.ParsedMethod());
 
     // prompt if the method is not safe (such as POST, PUT, DELETE, ...)
-    if (!rewriteToGET &&
-        !nsHttp::IsSafeMethod(mRequestHead.Method())) {
+    if (!rewriteToGET && !mRequestHead.IsSafeMethod()) {
         rv = PromptTempRedirect();
         if (NS_FAILED(rv)) return rv;
     }
 
     nsCOMPtr<nsIIOService> ioService;
     rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
     if (NS_FAILED(rv)) return rv;
 
@@ -6049,23 +6064,21 @@ nsHttpChannel::OnLookupComplete(nsICance
 void
 nsHttpChannel::MaybeInvalidateCacheEntryForSubsequentGet()
 {
     // See RFC 2616 section 5.1.1. These are considered valid
     // methods which DO NOT invalidate cache-entries for the
     // referred resource. POST, PUT and DELETE as well as any
     // other method not listed here will potentially invalidate
     // any cached copy of the resource
-    if (mRequestHead.Method() == nsHttp::Options ||
-       mRequestHead.Method() == nsHttp::Get ||
-       mRequestHead.Method() == nsHttp::Head ||
-       mRequestHead.Method() == nsHttp::Trace ||
-       mRequestHead.Method() == nsHttp::Connect)
+    if (mRequestHead.IsGet() || mRequestHead.IsOptions() ||
+        mRequestHead.IsHead() || mRequestHead.IsTrace() ||
+        mRequestHead.IsConnect()) {
         return;
-
+    }
 
     // Invalidate the request-uri.
 #ifdef PR_LOGGING
     nsAutoCString key;
     mURI->GetAsciiSpec(key);
     LOG(("MaybeInvalidateCacheEntryForSubsequentGet [this=%p uri=%s]\n",
         this, key.get()));
 #endif
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -311,17 +311,17 @@ private:
                rv == NS_ERROR_UNKNOWN_PROTOCOL      ||
                rv == NS_ERROR_MALFORMED_URI;
     }
 
     // Create a aggregate set of the current notification callbacks
     // and ensure the transaction is updated to use it.
     void UpdateAggregateCallbacks();
 
-    static bool HasQueryString(nsHttpAtom method, nsIURI * uri);
+    static bool HasQueryString(nsHttpRequestHead::ParsedMethodType method, nsIURI * uri);
     bool ResponseWouldVary(nsICacheEntry* entry) const;
     bool MustValidateBasedOnQueryUrl() const;
     bool IsResumable(int64_t partialLen, int64_t contentLength,
                      bool ignoreMissingPartialLen = false) const;
     nsresult MaybeSetupByteRangeRequest(int64_t partialLen, int64_t contentLength);
     nsresult SetupByteRangeRequest(int64_t partialLen);
     nsresult OpenCacheInputStream(nsICacheEntry* cacheEntry, bool startBuffering);
 
--- a/netwerk/protocol/http/nsHttpConnection.cpp
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -1572,17 +1572,17 @@ nsHttpConnection::SetupProxyConnect()
     nsAutoCString buf;
     nsresult rv = nsHttpHandler::GenerateHostPort(
             nsDependentCString(mConnInfo->Host()), mConnInfo->Port(), buf);
     if (NS_FAILED(rv))
         return rv;
 
     // CONNECT host:port HTTP/1.1
     nsHttpRequestHead request;
-    request.SetMethod(nsHttp::Connect);
+    request.SetMethod(NS_LITERAL_CSTRING("CONNECT"));
     request.SetVersion(gHttpHandler->HttpVersion());
     request.SetRequestURI(buf);
     request.SetHeader(nsHttp::User_Agent, gHttpHandler->UserAgent());
 
     // a CONNECT is always persistent
     request.SetHeader(nsHttp::Proxy_Connection, NS_LITERAL_CSTRING("keep-alive"));
     request.SetHeader(nsHttp::Connection, NS_LITERAL_CSTRING("keep-alive"));
 
--- a/netwerk/protocol/http/nsHttpRequestHead.cpp
+++ b/netwerk/protocol/http/nsHttpRequestHead.cpp
@@ -10,22 +10,69 @@
 
 //-----------------------------------------------------------------------------
 // nsHttpRequestHead
 //-----------------------------------------------------------------------------
 
 namespace mozilla {
 namespace net {
 
+nsHttpRequestHead::nsHttpRequestHead()
+    : mMethod(NS_LITERAL_CSTRING("GET"))
+    , mVersion(NS_HTTP_VERSION_1_1)
+    , mParsedMethod(kMethod_Get)
+{
+}
+
+void
+nsHttpRequestHead::SetMethod(const nsACString &method)
+{
+    mParsedMethod = kMethod_Custom;
+    mMethod = method;
+    if (!strcmp(mMethod.get(), "GET")) {
+        mParsedMethod = kMethod_Get;
+    } else if (!strcmp(mMethod.get(), "POST")) {
+        mParsedMethod = kMethod_Post;
+    } else if (!strcmp(mMethod.get(), "OPTIONS")) {
+        mParsedMethod = kMethod_Options;
+    } else if (!strcmp(mMethod.get(), "CONNECT")) {
+        mParsedMethod = kMethod_Connect;
+    } else if (!strcmp(mMethod.get(), "HEAD")) {
+        mParsedMethod = kMethod_Head;
+    } else if (!strcmp(mMethod.get(), "PUT")) {
+        mParsedMethod = kMethod_Put;
+    } else if (!strcmp(mMethod.get(), "TRACE")) {
+        mParsedMethod = kMethod_Trace;
+    }
+}
+
+bool
+nsHttpRequestHead::IsSafeMethod() const
+{
+  // This code will need to be extended for new safe methods, otherwise
+  // they'll default to "not safe".
+    if (IsGet() || IsHead() || IsOptions() || IsTrace()) {
+        return true;
+    }
+
+    if (mParsedMethod != kMethod_Custom) {
+        return false;
+    }
+
+    return (!strcmp(mMethod.get(), "PROPFIND") ||
+            !strcmp(mMethod.get(), "REPORT") ||
+            !strcmp(mMethod.get(), "SEARCH"));
+}
+
 void
 nsHttpRequestHead::Flatten(nsACString &buf, bool pruneProxyHeaders)
 {
     // note: the first append is intentional.
 
-    buf.Append(mMethod.get());
+    buf.Append(mMethod);
     buf.Append(' ');
     buf.Append(mRequestURI);
     buf.AppendLiteral(" HTTP/");
 
     switch (mVersion) {
     case NS_HTTP_VERSION_1_1:
         buf.AppendLiteral("1.1");
         break;
--- a/netwerk/protocol/http/nsHttpRequestHead.h
+++ b/netwerk/protocol/http/nsHttpRequestHead.h
@@ -15,25 +15,25 @@ namespace mozilla { namespace net {
 //-----------------------------------------------------------------------------
 // nsHttpRequestHead represents the request line and headers from an HTTP
 // request.
 //-----------------------------------------------------------------------------
 
 class nsHttpRequestHead
 {
 public:
-    nsHttpRequestHead() : mMethod(nsHttp::Get), mVersion(NS_HTTP_VERSION_1_1) {}
+    nsHttpRequestHead();
 
-    void SetMethod(nsHttpAtom method) { mMethod = method; }
+    void SetMethod(const nsACString &method);
     void SetVersion(nsHttpVersion version) { mVersion = version; }
     void SetRequestURI(const nsCSubstring &s) { mRequestURI = s; }
 
     const nsHttpHeaderArray &Headers() const { return mHeaders; }
     nsHttpHeaderArray & Headers()          { return mHeaders; }
-    nsHttpAtom          Method()     const { return mMethod; }
+    const nsCString &Method()        const { return mMethod; }
     nsHttpVersion       Version()    const { return mVersion; }
     const nsCSubstring &RequestURI() const { return mRequestURI; }
 
     const char *PeekHeader(nsHttpAtom h) const
     {
         return mHeaders.PeekHeader(h);
     }
     nsresult SetHeader(nsHttpAtom h, const nsACString &v, bool m=false) { return mHeaders.SetHeader(h, v, m); }
@@ -58,19 +58,44 @@ public:
     // Don't allow duplicate values
     nsresult SetHeaderOnce(nsHttpAtom h, const char *v, bool merge = false)
     {
         if (!merge || !HasHeaderValue(h, v))
             return mHeaders.SetHeader(h, nsDependentCString(v), merge);
         return NS_OK;
     }
 
+    bool IsSafeMethod() const;
+
+    enum ParsedMethodType
+    {
+        kMethod_Custom,
+        kMethod_Get,
+        kMethod_Post,
+        kMethod_Options,
+        kMethod_Connect,
+        kMethod_Head,
+        kMethod_Put,
+        kMethod_Trace
+    };
+
+    ParsedMethodType ParsedMethod() const { return mParsedMethod; }
+    bool EqualsMethod(ParsedMethodType aType) const { return mParsedMethod == aType; }
+    bool IsGet() const { return EqualsMethod(kMethod_Get); }
+    bool IsPost() const { return EqualsMethod(kMethod_Post); }
+    bool IsOptions() const { return EqualsMethod(kMethod_Options); }
+    bool IsConnect() const { return EqualsMethod(kMethod_Connect); }
+    bool IsHead() const { return EqualsMethod(kMethod_Head); }
+    bool IsPut() const { return EqualsMethod(kMethod_Put); }
+    bool IsTrace() const { return EqualsMethod(kMethod_Trace); }
+
 private:
     // All members must be copy-constructable and assignable
     nsHttpHeaderArray mHeaders;
-    nsHttpAtom        mMethod;
+    nsCString         mMethod;
     nsHttpVersion     mVersion;
     nsCString         mRequestURI;
+    ParsedMethodType  mParsedMethod;
 };
 
 }} // namespace mozilla::net
 
 #endif // nsHttpRequestHead_h__
--- a/netwerk/protocol/http/nsHttpTransaction.cpp
+++ b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -269,32 +269,33 @@ nsHttpTransaction::Init(uint32_t caps,
                                         !activityDistributorActive);
     if (NS_FAILED(rv)) return rv;
 
     NS_ADDREF(mConnInfo = cinfo);
     mCallbacks = callbacks;
     mConsumerTarget = target;
     mCaps = caps;
 
-    if (requestHead->Method() == nsHttp::Head)
+    if (requestHead->IsHead()) {
         mNoContent = true;
+    }
 
     // Make sure that there is "Content-Length: 0" header in the requestHead
     // in case of POST and PUT methods when there is no requestBody and
     // requestHead doesn't contain "Transfer-Encoding" header.
     //
     // RFC1945 section 7.2.2:
     //   HTTP/1.0 requests containing an entity body must include a valid
     //   Content-Length header field.
     //
     // RFC2616 section 4.4:
     //   For compatibility with HTTP/1.0 applications, HTTP/1.1 requests
     //   containing a message-body MUST include a valid Content-Length header
     //   field unless the server is known to be HTTP/1.1 compliant.
-    if ((requestHead->Method() == nsHttp::Post || requestHead->Method() == nsHttp::Put) &&
+    if ((requestHead->IsPost() || requestHead->IsPut()) &&
         !requestBody && !requestHead->PeekHeader(nsHttp::Transfer_Encoding)) {
         requestHead->SetHeader(nsHttp::Content_Length, NS_LITERAL_CSTRING("0"));
     }
 
     // grab a weak reference to the request head
     mRequestHead = requestHead;
 
     // make sure we eliminate any proxy specific headers from
@@ -1309,17 +1310,17 @@ nsHttpTransaction::ParseHead(char *buf,
         // to skip over a response body that the server may have sent even
         // though it wasn't allowed.
         if (!mConnection || !mConnection->LastTransactionExpectedNoContent()) {
             // tolerate only minor junk before the status line
             mHttpResponseMatched = true;
             char *p = LocateHttpStart(buf, std::min<uint32_t>(count, 11), true);
             if (!p) {
                 // Treat any 0.9 style response of a put as a failure.
-                if (mRequestHead->Method() == nsHttp::Put)
+                if (mRequestHead->IsPut())
                     return NS_ERROR_ABORT;
 
                 mResponseHead->ParseStatusLine("");
                 mHaveStatusLine = true;
                 mHaveAllHeaders = true;
                 return NS_OK;
             }
             if (p > buf) {
@@ -1492,17 +1493,17 @@ nsHttpTransaction::HandleContentStart()
             return NS_ERROR_ABORT;
         }
     }
 
     mDidContentStart = true;
 
     // The verifier only initializes itself once (from the first iteration of
     // a transaction that gets far enough to have response headers)
-    if (mRequestHead->Method() == nsHttp::Get)
+    if (mRequestHead->IsGet())
         mRestartInProgressVerifier.Set(mContentLength, mResponseHead);
 
     return NS_OK;
 }
 
 // called on the socket thread
 nsresult
 nsHttpTransaction::HandleContent(char *buf,
new file mode 100644
--- /dev/null
+++ b/netwerk/test/mochitests/method.sjs
@@ -0,0 +1,12 @@
+
+function handleRequest(request, response)
+{
+    // avoid confusing cache behaviors
+    response.setHeader("Cache-Control", "no-cache", false);
+    response.setHeader("Content-Type", "text/plain", false);
+    response.setHeader("Access-Control-Allow-Origin", "*", false);
+
+    // echo method
+    response.write(request.method);
+}
+
--- a/netwerk/test/mochitests/mochitest.ini
+++ b/netwerk/test/mochitests/mochitest.ini
@@ -1,12 +1,14 @@
 [DEFAULT]
 skip-if = buildapp == 'b2g' || e10s
 support-files =
+  method.sjs
   partial_content.sjs
   user_agent.sjs
   user_agent_update.sjs
 
 [test_partially_cached_content.html]
 [test_uri_scheme.html]
 [test_user_agent_overrides.html]
 [test_user_agent_updates.html]
 [test_user_agent_updates_reset.html]
+[test_xhr_method_case.html]
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/netwerk/test/mochitests/test_xhr_method_case.html
@@ -0,0 +1,66 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+XHR uppercases certain method names, but not others
+-->
+<head>
+  <title>Test for XHR Method casing</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<script type="text/javascript">
+
+const testMethods = [
+// these methods should be normalized
+ ["get", "GET"],
+ ["GET", "GET"],
+ ["GeT", "GET"],
+ ["geT", "GET"],
+ ["GEt", "GET"],
+ ["post", "POST"],
+ ["POST", "POST"],
+ ["delete", "DELETE"],
+ ["DELETE", "DELETE"],
+ ["options", "OPTIONS"],
+ ["OPTIONS", "OPTIONS"],
+ ["put", "PUT"],
+ ["PUT", "PUT"],
+// HEAD is not tested because we use the resposne body as part of the test
+// ["head", "HEAD"],
+// ["HEAD", "HEAD"],
+
+// other custom methods should not be normalized
+ ["Foo", "Foo"],
+ ["bAR", "bAR"],
+ ["foobar", "foobar"],
+ ["FOOBAR", "FOOBAR"]
+]
+
+function doIter(index)
+{
+  var xhr = new XMLHttpRequest();
+  xhr.open(testMethods[index][0], 'method.sjs', false); // sync request
+  xhr.send();
+  is(xhr.status, 200, 'transaction failed');
+  is(xhr.response, testMethods[index][1], 'unexpected method');
+}
+
+function dotest()
+{
+  SimpleTest.waitForExplicitFinish();
+  for (var i = 0; i < testMethods.length; i++) {
+    doIter(i);
+  }
+  SimpleTest.finish();
+}
+
+</script>
+</head>
+<body onload="dotest();">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+</pre>
+</body>
+</html>
+
--- a/netwerk/test/unit/test_bug412945.js
+++ b/netwerk/test/unit/test_bug412945.js
@@ -22,17 +22,17 @@ function run_test() {
   // make request
   var channel =
       Components.classes["@mozilla.org/network/io-service;1"].
       getService(Components.interfaces.nsIIOService).
       newChannel("http://localhost:" + httpserv.identity.primaryPort +
                  "/bug412945", null, null);
 
   channel.QueryInterface(Components.interfaces.nsIHttpChannel);
-  channel.requestMethod = "post";
+  channel.requestMethod = "POST";
   channel.asyncOpen(new TestListener(), null);
 
   do_test_pending();
 }
 
 function bug412945(metadata, response) {
   if (!metadata.hasHeader("Content-Length") ||
       metadata.getHeader("Content-Length") != "0")
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_bug477578.js
@@ -0,0 +1,51 @@
+// test that methods are not normalized
+
+const testMethods = [
+  ["GET"],
+  ["get"],
+  ["Get"],
+  ["gET"],
+  ["gEt"],
+  ["post"],
+  ["POST"],
+  ["head"],
+  ["HEAD"],
+  ["put"],
+  ["PUT"],
+  ["delete"],
+  ["DELETE"],
+  ["connect"],
+  ["CONNECT"],
+  ["options"],
+  ["trace"],
+  ["track"],
+  ["copy"],
+  ["index"],
+  ["lock"],
+  ["m-post"],
+  ["mkcol"],
+  ["move"],
+  ["propfind"],
+  ["proppatch"],
+  ["unlock"],
+  ["link"],
+  ["LINK"],
+  ["foo"],
+  ["foO"],
+  ["fOo"],
+  ["Foo"]
+]
+
+function run_test() {
+  var ios =
+    Cc["@mozilla.org/network/io-service;1"].
+    getService(Ci.nsIIOService);
+
+  var chan = ios.newChannel("http://localhost/", null, null)
+                  .QueryInterface(Components.interfaces.nsIHttpChannel);
+
+  for (var i = 0; i < testMethods.length; i++) {
+    chan.requestMethod = testMethods[i];
+    do_check_eq(chan.requestMethod, testMethods[i]);
+  }
+}
--- a/netwerk/test/unit/test_bug618835.js
+++ b/netwerk/test/unit/test_bug618835.js
@@ -26,32 +26,32 @@ function setupChannel(path) {
 function InitialListener() { }
 InitialListener.prototype = {
     onStartRequest: function(request, context) { },
     onStopRequest: function(request, context, status) {
         do_check_eq(1, numberOfCLHandlerCalls);
         do_execute_soon(function() {
             var channel = setupChannel("http://localhost:" +
                                        httpserv.identity.primaryPort + "/post");
-            channel.requestMethod = "post";
+            channel.requestMethod = "POST";
             channel.asyncOpen(new RedirectingListener(), null);
         });
     }
 };
 
 // Verify that Location-URI has been loaded once, reload post_target
 function RedirectingListener() { }
 RedirectingListener.prototype = {
     onStartRequest: function(request, context) { },
     onStopRequest: function(request, context, status) {
         do_check_eq(1, numberOfHandlerCalls);
         do_execute_soon(function() {
             var channel = setupChannel("http://localhost:" +
                                        httpserv.identity.primaryPort + "/post");
-            channel.requestMethod = "post";
+            channel.requestMethod = "POST";
             channel.asyncOpen(new VerifyingListener(), null);
         });
     }
 };
 
 // Verify that Location-URI has been loaded twice (cached entry invalidated),
 // reload Content-Location-URI
 function VerifyingListener() { }
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -106,16 +106,17 @@ skip-if = "FTP channel implementation ne
 [test_bug455598.js]
 [test_bug468426.js]
 # Bug 675039: intermittent fail on Android-armv6 
 skip-if = os == "android"
 [test_bug468594.js]
 # Bug 675039: intermittent fail on Android-armv6 
 skip-if = os == "android"
 [test_bug470716.js]
+[test_bug477578.js]
 [test_bug479413.js]
 [test_bug479485.js]
 [test_bug482601.js]
 # Bug 675039: intermittent fail on Android-armv6 
 skip-if = os == "android"
 [test_bug484684.js]
 # Bug 675039: intermittent fail on Android-armv6 
 #skip-if = os == "android"