Bug 1600254 - P7: Enable ContentSniffer for socket process r=dragana
authorKershaw Chang <kershaw@mozilla.com>
Thu, 30 Jan 2020 13:56:57 +0000
changeset 512157 1d2e243f15816f932d86999b548c73512b49ca1d
parent 512156 0925f61718bbfde97fcd4c3052696be917b73d3c
child 512158 391ad84d82516e086b13535995d20c0b31d4c049
push id106219
push userkjang@mozilla.com
push dateThu, 30 Jan 2020 13:59:40 +0000
treeherderautoland@655675bbf178 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdragana
bugs1600254
milestone74.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 1600254 - P7: Enable ContentSniffer for socket process r=dragana Differential Revision: https://phabricator.services.mozilla.com/D56206
netwerk/protocol/http/HttpTransactionChild.cpp
netwerk/protocol/http/HttpTransactionParent.cpp
netwerk/protocol/http/HttpTransactionParent.h
netwerk/protocol/http/HttpTransactionShell.h
netwerk/protocol/http/PHttpTransaction.ipdl
netwerk/protocol/http/nsHttp.h
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpResponseHead.cpp
netwerk/protocol/http/nsHttpResponseHead.h
netwerk/protocol/http/nsHttpTransaction.cpp
netwerk/test/unit/test_content_sniffer.js
--- a/netwerk/protocol/http/HttpTransactionChild.cpp
+++ b/netwerk/protocol/http/HttpTransactionChild.cpp
@@ -327,16 +327,26 @@ static TimingStructArgs ToTimingStructAr
   args.secureConnectionStart() = aTiming.secureConnectionStart;
   args.connectEnd() = aTiming.connectEnd;
   args.requestStart() = aTiming.requestStart;
   args.responseStart() = aTiming.responseStart;
   args.responseEnd() = aTiming.responseEnd;
   return args;
 }
 
+// The maximum number of bytes to consider when attempting to sniff.
+// See https://mimesniff.spec.whatwg.org/#reading-the-resource-header.
+static const uint32_t MAX_BYTES_SNIFFED = 1445;
+
+static void GetDataForSniffer(void* aClosure, const uint8_t* aData,
+                              uint32_t aCount) {
+  nsTArray<uint8_t>* outData = static_cast<nsTArray<uint8_t>*>(aClosure);
+  outData->AppendElements(aData, std::min(aCount, MAX_BYTES_SNIFFED));
+}
+
 NS_IMETHODIMP
 HttpTransactionChild::OnStartRequest(nsIRequest* aRequest) {
   LOG(("HttpTransactionChild::OnStartRequest start [this=%p] mTransaction=%p\n",
        this, mTransaction.get()));
 
   // Don't bother sending IPC to parent process if already canceled.
   if (mCanceled) {
     return mStatus;
@@ -357,27 +367,36 @@ HttpTransactionChild::OnStartRequest(nsI
     nsCOMPtr<nsISerializable> secInfoSer = do_QueryInterface(secInfoSupp);
     if (secInfoSer) {
       NS_SerializeToString(secInfoSer, serializedSecurityInfoOut);
     }
   }
 
   nsAutoPtr<nsHttpResponseHead> head(mTransaction->TakeResponseHead());
   Maybe<nsHttpResponseHead> optionalHead;
+  nsTArray<uint8_t> dataForSniffer;
   if (head) {
     optionalHead = Some(*head);
+    if (mTransaction->Caps() & NS_HTTP_CALL_CONTENT_SNIFFER) {
+      nsAutoCString contentTypeOptionsHeader;
+      if (head->GetContentTypeOptionsHeader(contentTypeOptionsHeader) &&
+          contentTypeOptionsHeader.EqualsIgnoreCase("nosniff")) {
+        RefPtr<nsInputStreamPump> pump = do_QueryObject(mTransactionPump);
+        pump->PeekStream(GetDataForSniffer, &dataForSniffer);
+      }
+    }
   }
 
   int32_t proxyConnectResponseCode =
       mTransaction->GetProxyConnectResponseCode();
 
   Unused << SendOnStartRequest(status, optionalHead, serializedSecurityInfoOut,
                                mTransaction->ProxyConnectFailed(),
                                ToTimingStructArgs(mTransaction->Timings()),
-                               proxyConnectResponseCode);
+                               proxyConnectResponseCode, dataForSniffer);
   LOG(("HttpTransactionChild::OnStartRequest end [this=%p]\n", this));
   return NS_OK;
 }
 
 NS_IMETHODIMP
 HttpTransactionChild::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
   LOG(("HttpTransactionChild::OnStopRequest [this=%p]\n", this));
 
--- a/netwerk/protocol/http/HttpTransactionParent.cpp
+++ b/netwerk/protocol/http/HttpTransactionParent.cpp
@@ -13,18 +13,22 @@
 #include "mozilla/net/SocketProcessParent.h"
 #include "nsHttpHandler.h"
 #include "nsStreamUtils.h"
 
 namespace mozilla {
 namespace net {
 
 NS_IMPL_ADDREF(HttpTransactionParent)
-NS_IMPL_QUERY_INTERFACE(HttpTransactionParent, nsIRequest,
-                        nsIThreadRetargetableRequest)
+NS_INTERFACE_MAP_BEGIN(HttpTransactionParent)
+  NS_INTERFACE_MAP_ENTRY(nsIRequest)
+  NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableRequest)
+  NS_INTERFACE_MAP_ENTRY_CONCRETE(HttpTransactionParent)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRequest)
+NS_INTERFACE_MAP_END
 
 NS_IMETHODIMP_(MozExternalRefCountType) HttpTransactionParent::Release(void) {
   MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release");
 
   if (!mRefCnt.isThreadSafe) {
     NS_ASSERT_OWNINGTHREAD(HttpTransactionParent);
   }
 
@@ -225,21 +229,22 @@ nsHttpResponseHead* HttpTransactionParen
 nsHttpHeaderArray* HttpTransactionParent::TakeResponseTrailers() {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!mResponseTrailersTaken, "TakeResponseTrailers called 2x");
 
   mResponseTrailersTaken = true;
   return mResponseTrailers.forget();
 }
 
-nsresult HttpTransactionParent::SetSniffedTypeToChannel(
-    nsIRequest* aPump, nsIChannel* aChannel,
-    nsInputStreamPump::PeekSegmentFun aCallTypeSniffers) {
-  // TODO: will be implemented later in bug 1600254.
-  return NS_ERROR_NOT_IMPLEMENTED;
+void HttpTransactionParent::SetSniffedTypeToChannel(
+    nsInputStreamPump::PeekSegmentFun aCallTypeSniffers, nsIChannel* aChannel) {
+  if (!mDataForSniffer.IsEmpty()) {
+    aCallTypeSniffers(aChannel, mDataForSniffer.Elements(),
+                      mDataForSniffer.Length());
+  }
 }
 
 NS_IMETHODIMP
 HttpTransactionParent::GetDeliveryTarget(nsIEventTarget** aNewTarget) {
   nsCOMPtr<nsIEventTarget> target = mTargetThread;
   target.forget(aNewTarget);
   return NS_OK;
 }
@@ -364,24 +369,27 @@ already_AddRefed<nsIEventTarget> HttpTra
   nsCOMPtr<nsIEventTarget> target = GetMainThreadEventTarget();
   return target.forget();
 }
 
 mozilla::ipc::IPCResult HttpTransactionParent::RecvOnStartRequest(
     const nsresult& aStatus, const Maybe<nsHttpResponseHead>& aResponseHead,
     const nsCString& aSecurityInfoSerialization,
     const bool& aProxyConnectFailed, const TimingStructArgs& aTimings,
-    const int32_t& aProxyConnectResponseCode) {
+    const int32_t& aProxyConnectResponseCode,
+    nsTArray<uint8_t>&& aDataForSniffer) {
   mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
       this, [self = UnsafePtr<HttpTransactionParent>(this), aStatus,
              aResponseHead, aSecurityInfoSerialization, aProxyConnectFailed,
-             aTimings, aProxyConnectResponseCode]() {
+             aTimings, aProxyConnectResponseCode,
+             aDataForSniffer{std::move(aDataForSniffer)}]() mutable {
         self->DoOnStartRequest(aStatus, aResponseHead,
                                aSecurityInfoSerialization, aProxyConnectFailed,
-                               aTimings, aProxyConnectResponseCode);
+                               aTimings, aProxyConnectResponseCode,
+                               std::move(aDataForSniffer));
       }));
   return IPC_OK();
 }
 
 static void TimingStructArgsToTimingsStruct(const TimingStructArgs& aArgs,
                                             TimingStruct& aTimings) {
   // If domainLookupStart/End was set by the channel before, we use these
   // timestamps instead the ones from the transaction.
@@ -398,17 +406,18 @@ static void TimingStructArgsToTimingsStr
   aTimings.responseStart = aArgs.responseStart();
   aTimings.responseEnd = aArgs.responseEnd();
 }
 
 void HttpTransactionParent::DoOnStartRequest(
     const nsresult& aStatus, const Maybe<nsHttpResponseHead>& aResponseHead,
     const nsCString& aSecurityInfoSerialization,
     const bool& aProxyConnectFailed, const TimingStructArgs& aTimings,
-    const int32_t& aProxyConnectResponseCode) {
+    const int32_t& aProxyConnectResponseCode,
+    nsTArray<uint8_t>&& aDataForSniffer) {
   LOG(("HttpTransactionParent::DoOnStartRequest [this=%p aStatus=%" PRIx32
        "]\n",
        this, static_cast<uint32_t>(aStatus)));
 
   if (mCanceled) {
     return;
   }
 
@@ -423,16 +432,17 @@ void HttpTransactionParent::DoOnStartReq
 
   if (aResponseHead.isSome()) {
     mResponseHead = new nsHttpResponseHead(aResponseHead.ref());
   }
   mProxyConnectFailed = aProxyConnectFailed;
   TimingStructArgsToTimingsStruct(aTimings, mTimings);
 
   mProxyConnectResponseCode = aProxyConnectResponseCode;
+  mDataForSniffer = std::move(aDataForSniffer);
 
   AutoEventEnqueuer ensureSerialDispatch(mEventQ);
   nsresult rv = mChannel->OnStartRequest(this);
   mOnStartRequestCalled = true;
   if (NS_FAILED(rv)) {
     Cancel(rv);
   }
 }
--- a/netwerk/protocol/http/HttpTransactionParent.h
+++ b/netwerk/protocol/http/HttpTransactionParent.h
@@ -16,37 +16,46 @@
 #include "nsIRequest.h"
 
 namespace mozilla {
 namespace net {
 
 class ChannelEventQueue;
 class nsHttpConnectionInfo;
 
+#define HTTP_TRANSACTION_PARENT_IID                  \
+  {                                                  \
+    0xb83695cb, 0xc24b, 0x4c53, {                    \
+      0x85, 0x9b, 0x77, 0x77, 0x3e, 0xc5, 0x44, 0xe5 \
+    }                                                \
+  }
+
 // HttpTransactionParent plays the role of nsHttpTransaction and delegates the
 // work to the nsHttpTransaction in socket process.
 class HttpTransactionParent final : public PHttpTransactionParent,
                                     public HttpTransactionShell,
                                     public nsIRequest,
                                     public nsIThreadRetargetableRequest {
  public:
   NS_DECL_ISUPPORTS
   NS_DECL_HTTPTRANSACTIONSHELL
   NS_DECL_NSIREQUEST
   NS_DECL_NSITHREADRETARGETABLEREQUEST
+  NS_DECLARE_STATIC_IID_ACCESSOR(HTTP_TRANSACTION_PARENT_IID)
 
   explicit HttpTransactionParent();
 
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
   mozilla::ipc::IPCResult RecvOnStartRequest(
       const nsresult& aStatus, const Maybe<nsHttpResponseHead>& aResponseHead,
       const nsCString& aSecurityInfoSerialization,
       const bool& aProxyConnectFailed, const TimingStructArgs& aTimings,
-      const int32_t& aProxyConnectResponseCode);
+      const int32_t& aProxyConnectResponseCode,
+      nsTArray<uint8_t>&& aDataForSniffer);
   mozilla::ipc::IPCResult RecvOnTransportStatus(const nsresult& aStatus,
                                                 const int64_t& aProgress,
                                                 const int64_t& aProgressMax);
   mozilla::ipc::IPCResult RecvOnDataAvailable(const nsCString& aData,
                                               const uint64_t& aOffset,
                                               const uint32_t& aCount);
   mozilla::ipc::IPCResult RecvOnStopRequest(
       const nsresult& aStatus, const bool& aResponseIsComplete,
@@ -60,27 +69,32 @@ class HttpTransactionParent final : publ
   mozilla::ipc::IPCResult RecvOnInitFailed(const nsresult& aStatus);
 
   mozilla::ipc::IPCResult RecvOnH2PushStream(const uint32_t& aPushedStreamId,
                                              const nsCString& aResourceUrl,
                                              const nsCString& aRequestString);
 
   already_AddRefed<nsIEventTarget> GetNeckoTarget();
 
+  void SetSniffedTypeToChannel(
+      nsInputStreamPump::PeekSegmentFun aCallTypeSniffers,
+      nsIChannel* aChannel);
+
  private:
   virtual ~HttpTransactionParent();
 
   void GetStructFromInfo(nsHttpConnectionInfo* aInfo,
                          HttpConnectionInfoCloneArgs& aArgs);
   void DoOnStartRequest(const nsresult& aStatus,
                         const Maybe<nsHttpResponseHead>& aResponseHead,
                         const nsCString& aSecurityInfoSerialization,
                         const bool& aProxyConnectFailed,
                         const TimingStructArgs& aTimings,
-                        const int32_t& aProxyConnectResponseCode);
+                        const int32_t& aProxyConnectResponseCode,
+                        nsTArray<uint8_t>&& aDataForSniffer);
   void DoOnTransportStatus(const nsresult& aStatus, const int64_t& aProgress,
                            const int64_t& aProgressMax);
   void DoOnDataAvailable(const nsCString& aData, const uint64_t& aOffset,
                          const uint32_t& aCount);
   void DoOnStopRequest(
       const nsresult& aStatus, const bool& aResponseIsComplete,
       const int64_t& aTransferSize, const TimingStructArgs& aTimings,
       const Maybe<nsHttpHeaderArray>& responseTrailers,
@@ -115,14 +129,18 @@ class HttpTransactionParent final : publ
   NetAddr mSelfAddr;
   NetAddr mPeerAddr;
   TimingStruct mTimings;
   TimeStamp mDomainLookupStart;
   TimeStamp mDomainLookupEnd;
   TransactionObserverFunc mTransactionObserver;
   TransactionObserverResult mTransactionObserverResult;
   OnPushCallback mOnPushCallback;
+  nsTArray<uint8_t> mDataForSniffer;
 };
 
+NS_DEFINE_STATIC_IID_ACCESSOR(HttpTransactionParent,
+                              HTTP_TRANSACTION_PARENT_IID)
+
 }  // namespace net
 }  // namespace mozilla
 
 #endif  // nsHttpTransactionParent_h__
--- a/netwerk/protocol/http/HttpTransactionShell.h
+++ b/netwerk/protocol/http/HttpTransactionShell.h
@@ -138,20 +138,16 @@ class HttpTransactionShell : public nsIS
   virtual void DontReuseConnection() = 0;
   virtual bool HasStickyConnection() const = 0;
 
   virtual void SetH2WSConnRefTaken() = 0;
 
   virtual bool ProxyConnectFailed() = 0;
   virtual int32_t GetProxyConnectResponseCode() = 0;
 
-  virtual nsresult SetSniffedTypeToChannel(
-      nsIRequest* aPump, nsIChannel* aChannel,
-      nsInputStreamPump::PeekSegmentFun aCallTypeSniffers) = 0;
-
   virtual void GetTransactionObserverResult(
       TransactionObserverResult& aResult) = 0;
 
   virtual nsHttpTransaction* AsHttpTransaction() = 0;
   virtual HttpTransactionParent* AsHttpTransactionParent() = 0;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(HttpTransactionShell, HTTPTRANSACTIONSHELL_IID)
@@ -198,19 +194,16 @@ NS_DEFINE_STATIC_IID_ACCESSOR(HttpTransa
   virtual int64_t GetTransferSize() override;                                  \
   virtual int64_t GetRequestSize() override;                                   \
   virtual void SetDNSWasRefreshed() override;                                  \
   virtual void DontReuseConnection() override;                                 \
   virtual bool HasStickyConnection() const override;                           \
   virtual void SetH2WSConnRefTaken() override;                                 \
   virtual bool ProxyConnectFailed() override;                                  \
   virtual int32_t GetProxyConnectResponseCode() override;                      \
-  virtual nsresult SetSniffedTypeToChannel(                                    \
-      nsIRequest* aPump, nsIChannel* aChannel,                                 \
-      nsInputStreamPump::PeekSegmentFun aCallTypeSniffers) override;           \
   virtual void GetTransactionObserverResult(                                   \
       TransactionObserverResult& aResult) override;                            \
   virtual nsHttpTransaction* AsHttpTransaction() override;                     \
   virtual HttpTransactionParent* AsHttpTransactionParent() override;
 }  // namespace net
 }  // namespace mozilla
 
 #endif  // HttpTransactionShell_h__
--- a/netwerk/protocol/http/PHttpTransaction.ipdl
+++ b/netwerk/protocol/http/PHttpTransaction.ipdl
@@ -30,17 +30,18 @@ refcounted protocol PHttpTransaction
   manager PSocketProcess;
 
 parent:
   async OnStartRequest(nsresult                   status,
                        nsHttpResponseHead?        responseHead,
                        nsCString                  securityInfoSerialization,
                        bool                       proxyConnectFailed,
                        TimingStructArgs           timings,
-                       int32_t                    proxyConnectResponseCode);
+                       int32_t                    proxyConnectResponseCode,
+                       uint8_t[]                  dataForSniffer);
   async OnTransportStatus(nsresult status,
                           int64_t progress,
                           int64_t progressMax);
   async OnDataAvailable(nsCString data,
                         uint64_t  offset,
                         uint32_t  count);
   async OnStopRequest(nsresult status,
                       bool responseIsComplete,
--- a/netwerk/protocol/http/nsHttp.h
+++ b/netwerk/protocol/http/nsHttp.h
@@ -125,16 +125,19 @@ const char kHttp3VersionHEX[] = "ff00000
 #define NS_HTTP_DISABLE_IPV4 (1 << 17)
 
 // The connection should not use IPv6
 #define NS_HTTP_DISABLE_IPV6 (1 << 18)
 
 // Encodes the TRR mode.
 #define NS_HTTP_TRR_MODE_MASK ((1 << 19) | (1 << 20))
 
+// The connection could bring the peeked data for sniffing
+#define NS_HTTP_CALL_CONTENT_SNIFFER (1 << 21)
+
 #define NS_HTTP_TRR_FLAGS_FROM_MODE(x) ((static_cast<uint32_t>(x) & 3) << 19)
 
 #define NS_HTTP_TRR_MODE_FROM_FLAGS(x) \
   (static_cast<nsIRequest::TRRMode>((((x)&NS_HTTP_TRR_MODE_MASK) >> 19) & 3))
 
 //-----------------------------------------------------------------------------
 // some default values
 //-----------------------------------------------------------------------------
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -1301,16 +1301,20 @@ nsresult nsHttpChannel::SetupTransaction
 
   // Save the mapping of channel id and the channel. We need this mapping for
   // nsIHttpActivityObserver.
   gHttpHandler->AddHttpChannel(mChannelId, ToSupports(this));
 
   // See bug #466080. Transfer LOAD_ANONYMOUS flag to socket-layer.
   if (mLoadFlags & LOAD_ANONYMOUS) mCaps |= NS_HTTP_LOAD_ANONYMOUS;
 
+  if (mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) {
+    mCaps |= NS_HTTP_CALL_CONTENT_SNIFFER;
+  }
+
   if (mTimingEnabled) mCaps |= NS_HTTP_TIMING_ENABLED;
 
   if (mUpgradeProtocolCallback) {
     rv = mRequestHead.SetHeader(nsHttp::Upgrade, mUpgradeProtocol, false);
     MOZ_ASSERT(NS_SUCCEEDED(rv));
     rv = mRequestHead.SetHeaderOnce(nsHttp::Connection, nsHttp::Upgrade.get(),
                                     true);
     MOZ_ASSERT(NS_SUCCEEDED(rv));
@@ -1436,36 +1440,24 @@ nsresult ProcessXCTO(nsHttpChannel* aCha
   if (!aURI || !aResponseHead || !aLoadInfo) {
     // if there is no uri, no response head or no loadInfo, then there is
     // nothing to do
     return NS_OK;
   }
 
   // 1) Query the XCTO header and check if 'nosniff' is the first value.
   nsAutoCString contentTypeOptionsHeader;
-  Unused << aResponseHead->GetHeader(nsHttp::X_Content_Type_Options,
-                                     contentTypeOptionsHeader);
-  if (contentTypeOptionsHeader.IsEmpty()) {
-    // if there is no XCTO header, then there is nothing to do.
-    return NS_OK;
-  }
-  // XCTO header might contain multiple values which are comma separated, so:
-  // a) let's skip all subsequent values
-  //     e.g. "   NoSniFF   , foo " will be "   NoSniFF   "
-  int32_t idx = contentTypeOptionsHeader.Find(",");
-  if (idx > 0) {
-    contentTypeOptionsHeader = Substring(contentTypeOptionsHeader, 0, idx);
-  }
-  // b) let's trim all surrounding whitespace
-  //    e.g. "   NoSniFF   " -> "NoSniFF"
-  nsHttp::TrimHTTPWhitespace(contentTypeOptionsHeader,
-                             contentTypeOptionsHeader);
-  // c) let's compare the header (ignoring case)
-  //    e.g. "NoSniFF" -> "nosniff"
-  //    if it's not 'nosniff' then there is nothing to do here
+  if (!aResponseHead->GetContentTypeOptionsHeader(contentTypeOptionsHeader)) {
+    // if failed to get XCTO header, then there is nothing to do.
+    return NS_OK;
+  }
+
+  // let's compare the header (ignoring case)
+  // e.g. "NoSniFF" -> "nosniff"
+  // if it's not 'nosniff' then there is nothing to do here
   if (!contentTypeOptionsHeader.EqualsIgnoreCase("nosniff")) {
     // since we are getting here, the XCTO header was sent;
     // a non matching value most likely means a mistake happenend;
     // e.g. sending 'nosnif' instead of 'nosniff', let's log a warning.
     AutoTArray<nsString, 1> params;
     CopyUTF8toUTF16(contentTypeOptionsHeader, *params.AppendElement());
     RefPtr<Document> doc;
     aLoadInfo->GetLoadingDocument(getter_AddRefs(doc));
@@ -1848,16 +1840,21 @@ nsresult nsHttpChannel::CallOnStartReque
       typeSniffersCalled =
           NS_SUCCEEDED(mCachePump->PeekStream(CallTypeSniffers, thisChannel));
     }
 
     if (!typeSniffersCalled && mTransactionPump) {
       RefPtr<nsInputStreamPump> pump = do_QueryObject(mTransactionPump);
       if (pump) {
         pump->PeekStream(CallTypeSniffers, thisChannel);
+      } else {
+        MOZ_ASSERT(gIOService->UseSocketProcess());
+        RefPtr<HttpTransactionParent> trans = do_QueryObject(mTransactionPump);
+        MOZ_ASSERT(trans);
+        trans->SetSniffedTypeToChannel(CallTypeSniffers, thisChannel);
       }
     }
   }
 
   bool unknownDecoderStarted = false;
   if (mResponseHead && !mResponseHead->HasContentType()) {
     MOZ_ASSERT(mConnectionInfo, "Should have connection info here");
     if (!mContentTypeHint.IsEmpty())
--- a/netwerk/protocol/http/nsHttpResponseHead.cpp
+++ b/netwerk/protocol/http/nsHttpResponseHead.cpp
@@ -1199,10 +1199,36 @@ bool nsHttpResponseHead::HasContentType(
   return !mContentType.IsEmpty();
 }
 
 bool nsHttpResponseHead::HasContentCharset() {
   RecursiveMutexAutoLock monitor(mRecursiveMutex);
   return !mContentCharset.IsEmpty();
 }
 
+bool nsHttpResponseHead::GetContentTypeOptionsHeader(nsACString& aOutput) {
+  aOutput.Truncate();
+
+  nsAutoCString contentTypeOptionsHeader;
+  Unused << GetHeader(nsHttp::X_Content_Type_Options, contentTypeOptionsHeader);
+  if (contentTypeOptionsHeader.IsEmpty()) {
+    // if there is no XCTO header, then there is nothing to do.
+    return false;
+  }
+
+  // XCTO header might contain multiple values which are comma separated, so:
+  // a) let's skip all subsequent values
+  //     e.g. "   NoSniFF   , foo " will be "   NoSniFF   "
+  int32_t idx = contentTypeOptionsHeader.Find(",");
+  if (idx > 0) {
+    contentTypeOptionsHeader = Substring(contentTypeOptionsHeader, 0, idx);
+  }
+  // b) let's trim all surrounding whitespace
+  //    e.g. "   NoSniFF   " -> "NoSniFF"
+  nsHttp::TrimHTTPWhitespace(contentTypeOptionsHeader,
+                             contentTypeOptionsHeader);
+
+  aOutput.Assign(contentTypeOptionsHeader);
+  return true;
+}
+
 }  // namespace net
 }  // namespace mozilla
--- a/netwerk/protocol/http/nsHttpResponseHead.h
+++ b/netwerk/protocol/http/nsHttpResponseHead.h
@@ -150,16 +150,17 @@ class nsHttpResponseHead {
   // automatically under one lock.
   MOZ_MUST_USE nsresult VisitHeaders(nsIHttpHeaderVisitor* visitor,
                                      nsHttpHeaderArray::VisitorFilter filter);
   MOZ_MUST_USE nsresult GetOriginalHeader(nsHttpAtom aHeader,
                                           nsIHttpHeaderVisitor* aVisitor);
 
   bool HasContentType();
   bool HasContentCharset();
+  bool GetContentTypeOptionsHeader(nsACString& aOutput);
 
  private:
   MOZ_MUST_USE nsresult SetHeader_locked(nsHttpAtom atom, const nsACString& h,
                                          const nsACString& v, bool m = false);
   void AssignDefaultStatusText();
   void ParseVersion(const char*);
   void ParseCacheControl(const char*);
   void ParsePragma(const char*);
--- a/netwerk/protocol/http/nsHttpTransaction.cpp
+++ b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -510,23 +510,16 @@ nsHttpHeaderArray* nsHttpTransaction::Ta
 
   // Lock TakeResponseTrailers() against main thread
   MutexAutoLock lock(*nsHttp::GetLock());
 
   mResponseTrailersTaken = true;
   return mForTakeResponseTrailers.forget();
 }
 
-nsresult nsHttpTransaction::SetSniffedTypeToChannel(
-    nsIRequest* aPump, nsIChannel* aChannel,
-    nsInputStreamPump::PeekSegmentFun aCallTypeSniffers) {
-  // TODO: will be implemented later in bug 1600254.
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
 void nsHttpTransaction::SetProxyConnectFailed() { mProxyConnectFailed = true; }
 
 nsHttpRequestHead* nsHttpTransaction::RequestHead() { return mRequestHead; }
 
 uint32_t nsHttpTransaction::Http1xTransactionCount() { return 1; }
 
 nsresult nsHttpTransaction::TakeSubTransactions(
     nsTArray<RefPtr<nsAHttpTransaction> >& outTransactions) {
--- a/netwerk/test/unit/test_content_sniffer.js
+++ b/netwerk/test/unit/test_content_sniffer.js
@@ -6,16 +6,18 @@ const unknownType = "application/x-unkno
 const sniffedType = "application/x-sniffed";
 
 const snifferCID = Components.ID("{4c93d2db-8a56-48d7-b261-9cf2a8d998eb}");
 const snifferContract = "@mozilla.org/network/unittest/contentsniffer;1";
 const categoryName = "net-content-sniffers";
 
 var sniffing_enabled = true;
 
+var isNosniff = false;
+
 /**
  * This object is both a factory and an nsIContentSniffer implementation (so, it
  * is de-facto a service)
  */
 var sniffer = {
   QueryInterface: ChromeUtils.generateQI(["nsIFactory", "nsIContentSniffer"]),
   createInstance: function sniffer_ci(outer, iid) {
     if (outer) {
@@ -34,17 +36,21 @@ var sniffer = {
 
 var listener = {
   onStartRequest: function test_onStartR(request) {
     try {
       var chan = request.QueryInterface(Ci.nsIChannel);
       if (chan.contentType == unknownType) {
         do_throw("Type should not be unknown!");
       }
-      if (
+      if (isNosniff) {
+        if (chan.contentType == sniffedType) {
+          do_throw("Sniffer called for X-Content-Type-Options:nosniff");
+        }
+      } else if (
         sniffing_enabled &&
         this._iteration > 2 &&
         chan.contentType != sniffedType
       ) {
         do_throw(
           "Expecting <" +
             sniffedType +
             "> but got <" +
@@ -85,37 +91,43 @@ function makeChan(url) {
   return chan;
 }
 
 var httpserv = null;
 var urls = null;
 
 function run_test() {
   httpserv = new HttpServer();
+  httpserv.registerPathHandler("/nosniff", nosniffHandler);
   httpserv.start(-1);
 
   urls = [
     // NOTE: First URL here runs without our content sniffer
     "data:" + unknownType + ", Some text",
     "data:" + unknownType + ", Text", // Make sure sniffing works even if we
     // used the unknown content sniffer too
     "data:text/plain, Some more text",
     "http://localhost:" + httpserv.identity.primaryPort,
+    "http://localhost:" + httpserv.identity.primaryPort + "/nosniff",
   ];
 
   Components.manager.nsIComponentRegistrar.registerFactory(
     snifferCID,
     "Unit test content sniffer",
     snifferContract,
     sniffer
   );
 
   run_test_iteration(1);
 }
 
+function nosniffHandler(request, response) {
+  response.setHeader("X-Content-Type-Options", "nosniff");
+}
+
 function run_test_iteration(index) {
   if (index > urls.length) {
     if (sniffing_enabled) {
       sniffing_enabled = false;
       index = listener._iteration = 1;
     } else {
       do_test_pending();
       httpserv.stop(do_test_finished);
@@ -131,16 +143,18 @@ function run_test_iteration(index) {
     );
     catMan.nsICategoryManager.addCategoryEntry(
       categoryName,
       "unit test",
       snifferContract,
       false,
       true
     );
+  } else if (sniffing_enabled && index == 5) {
+    isNosniff = true;
   }
 
   var chan = makeChan(urls[index - 1]);
 
   listener._iteration++;
   chan.asyncOpen(listener);
 
   do_test_pending();