netwerk/streamconv/converters/nsMultiMixedConv.cpp
author Christoph Kerschbaumer <ckerschb@christophkerschbaumer.com>
Thu, 14 Mar 2019 11:38:23 +0000
changeset 521871 d8b7c29dcee0aca2593d1e0f015515ce185d208b
parent 519443 20fcf9d9494ac77d9c8166444e5fa8346ff13be1
child 521904 858a58a6aa93e044e04de5ccdad4aae4b9840657
permissions -rw-r--r--
Bug 1533159: Add MOZ_RELEASE_ASSERT to all SetLoadInfo implementations to ensure loadinfo is never null. r=valentin Differential Revision: https://phabricator.services.mozilla.com/D23302

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 "nsMultiMixedConv.h"
#include "plstr.h"
#include "nsIHttpChannel.h"
#include "nsNetCID.h"
#include "nsMimeTypes.h"
#include "nsIStringStream.h"
#include "nsCRT.h"
#include "nsIHttpChannelInternal.h"
#include "nsURLHelper.h"
#include "nsIStreamConverterService.h"
#include "nsICacheInfoChannel.h"
#include <algorithm>
#include "nsContentSecurityManager.h"
#include "nsHttp.h"
#include "nsNetUtil.h"
#include "nsIURI.h"
#include "nsHttpHeaderArray.h"
#include "mozilla/AutoRestore.h"

nsPartChannel::nsPartChannel(nsIChannel *aMultipartChannel, uint32_t aPartID,
                             nsIStreamListener *aListener)
    : mMultipartChannel(aMultipartChannel),
      mListener(aListener),
      mStatus(NS_OK),
      mLoadFlags(0),
      mContentDisposition(0),
      mContentLength(UINT64_MAX),
      mIsByteRangeRequest(false),
      mByteRangeStart(0),
      mByteRangeEnd(0),
      mPartID(aPartID),
      mIsLastPart(false) {
  // Inherit the load flags from the original channel...
  mMultipartChannel->GetLoadFlags(&mLoadFlags);

  mMultipartChannel->GetLoadGroup(getter_AddRefs(mLoadGroup));
}

void nsPartChannel::InitializeByteRange(int64_t aStart, int64_t aEnd) {
  mIsByteRangeRequest = true;

  mByteRangeStart = aStart;
  mByteRangeEnd = aEnd;
}

nsresult nsPartChannel::SendOnStartRequest(nsISupports *aContext) {
  return mListener->OnStartRequest(this);
}

nsresult nsPartChannel::SendOnDataAvailable(nsISupports *aContext,
                                            nsIInputStream *aStream,
                                            uint64_t aOffset, uint32_t aLen) {
  return mListener->OnDataAvailable(this, aStream, aOffset, aLen);
}

nsresult nsPartChannel::SendOnStopRequest(nsISupports *aContext,
                                          nsresult aStatus) {
  // Drop the listener
  nsCOMPtr<nsIStreamListener> listener;
  listener.swap(mListener);
  return listener->OnStopRequest(this, aStatus);
}

void nsPartChannel::SetContentDisposition(
    const nsACString &aContentDispositionHeader) {
  mContentDispositionHeader = aContentDispositionHeader;
  nsCOMPtr<nsIURI> uri;
  GetURI(getter_AddRefs(uri));
  NS_GetFilenameFromDisposition(mContentDispositionFilename,
                                mContentDispositionHeader, uri);
  mContentDisposition =
      NS_GetContentDispositionFromHeader(mContentDispositionHeader, this);
}

//
// nsISupports implementation...
//

NS_IMPL_ADDREF(nsPartChannel)
NS_IMPL_RELEASE(nsPartChannel)

NS_INTERFACE_MAP_BEGIN(nsPartChannel)
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIChannel)
  NS_INTERFACE_MAP_ENTRY(nsIRequest)
  NS_INTERFACE_MAP_ENTRY(nsIChannel)
  NS_INTERFACE_MAP_ENTRY(nsIByteRangeRequest)
  NS_INTERFACE_MAP_ENTRY(nsIMultiPartChannel)
NS_INTERFACE_MAP_END

//
// nsIRequest implementation...
//

NS_IMETHODIMP
nsPartChannel::GetName(nsACString &aResult) {
  return mMultipartChannel->GetName(aResult);
}

NS_IMETHODIMP
nsPartChannel::IsPending(bool *aResult) {
  // For now, consider the active lifetime of each part the same as
  // the underlying multipart channel...  This is not exactly right,
  // but it is good enough :-)
  return mMultipartChannel->IsPending(aResult);
}

NS_IMETHODIMP
nsPartChannel::GetStatus(nsresult *aResult) {
  nsresult rv = NS_OK;

  if (NS_FAILED(mStatus)) {
    *aResult = mStatus;
  } else {
    rv = mMultipartChannel->GetStatus(aResult);
  }

  return rv;
}

NS_IMETHODIMP
nsPartChannel::Cancel(nsresult aStatus) {
  // Cancelling an individual part must not cancel the underlying
  // multipart channel...
  // XXX but we should stop sending data for _this_ part channel!
  mStatus = aStatus;
  return NS_OK;
}

NS_IMETHODIMP
nsPartChannel::Suspend(void) {
  // Suspending an individual part must not suspend the underlying
  // multipart channel...
  // XXX why not?
  return NS_OK;
}

NS_IMETHODIMP
nsPartChannel::Resume(void) {
  // Resuming an individual part must not resume the underlying
  // multipart channel...
  // XXX why not?
  return NS_OK;
}

//
// nsIChannel implementation
//

NS_IMETHODIMP
nsPartChannel::GetOriginalURI(nsIURI **aURI) {
  return mMultipartChannel->GetOriginalURI(aURI);
}

NS_IMETHODIMP
nsPartChannel::SetOriginalURI(nsIURI *aURI) {
  return mMultipartChannel->SetOriginalURI(aURI);
}

NS_IMETHODIMP
nsPartChannel::GetURI(nsIURI **aURI) { return mMultipartChannel->GetURI(aURI); }

NS_IMETHODIMP
nsPartChannel::Open(nsIInputStream **aStream) {
  nsCOMPtr<nsIStreamListener> listener;
  nsresult rv =
      nsContentSecurityManager::doContentSecurityCheck(this, listener);
  NS_ENSURE_SUCCESS(rv, rv);

  // This channel cannot be opened!
  return NS_ERROR_FAILURE;
}

NS_IMETHODIMP
nsPartChannel::AsyncOpen(nsIStreamListener *aListener) {
  nsCOMPtr<nsIStreamListener> listener = aListener;
  nsresult rv =
      nsContentSecurityManager::doContentSecurityCheck(this, listener);
  NS_ENSURE_SUCCESS(rv, rv);

  // This channel cannot be opened!
  return NS_ERROR_FAILURE;
}

NS_IMETHODIMP
nsPartChannel::GetLoadFlags(nsLoadFlags *aLoadFlags) {
  *aLoadFlags = mLoadFlags;
  return NS_OK;
}

NS_IMETHODIMP
nsPartChannel::SetLoadFlags(nsLoadFlags aLoadFlags) {
  mLoadFlags = aLoadFlags;
  return NS_OK;
}

NS_IMETHODIMP
nsPartChannel::GetIsDocument(bool *aIsDocument) {
  return NS_GetIsDocumentChannel(this, aIsDocument);
}

NS_IMETHODIMP
nsPartChannel::GetLoadGroup(nsILoadGroup **aLoadGroup) {
  *aLoadGroup = mLoadGroup;
  NS_IF_ADDREF(*aLoadGroup);

  return NS_OK;
}

NS_IMETHODIMP
nsPartChannel::SetLoadGroup(nsILoadGroup *aLoadGroup) {
  mLoadGroup = aLoadGroup;

  return NS_OK;
}

NS_IMETHODIMP
nsPartChannel::GetOwner(nsISupports **aOwner) {
  return mMultipartChannel->GetOwner(aOwner);
}

NS_IMETHODIMP
nsPartChannel::SetOwner(nsISupports *aOwner) {
  return mMultipartChannel->SetOwner(aOwner);
}

NS_IMETHODIMP
nsPartChannel::GetLoadInfo(nsILoadInfo **aLoadInfo) {
  return mMultipartChannel->GetLoadInfo(aLoadInfo);
}

NS_IMETHODIMP
nsPartChannel::SetLoadInfo(nsILoadInfo *aLoadInfo) {
  MOZ_RELEASE_ASSERT(aLoadInfo, "loadinfo can't be null");
  return mMultipartChannel->SetLoadInfo(aLoadInfo);
}

NS_IMETHODIMP
nsPartChannel::GetNotificationCallbacks(nsIInterfaceRequestor **aCallbacks) {
  return mMultipartChannel->GetNotificationCallbacks(aCallbacks);
}

NS_IMETHODIMP
nsPartChannel::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks) {
  return mMultipartChannel->SetNotificationCallbacks(aCallbacks);
}

NS_IMETHODIMP
nsPartChannel::GetSecurityInfo(nsISupports **aSecurityInfo) {
  return mMultipartChannel->GetSecurityInfo(aSecurityInfo);
}

NS_IMETHODIMP
nsPartChannel::GetContentType(nsACString &aContentType) {
  aContentType = mContentType;
  return NS_OK;
}

NS_IMETHODIMP
nsPartChannel::SetContentType(const nsACString &aContentType) {
  bool dummy;
  net_ParseContentType(aContentType, mContentType, mContentCharset, &dummy);
  return NS_OK;
}

NS_IMETHODIMP
nsPartChannel::GetContentCharset(nsACString &aContentCharset) {
  aContentCharset = mContentCharset;
  return NS_OK;
}

NS_IMETHODIMP
nsPartChannel::SetContentCharset(const nsACString &aContentCharset) {
  mContentCharset = aContentCharset;
  return NS_OK;
}

NS_IMETHODIMP
nsPartChannel::GetContentLength(int64_t *aContentLength) {
  *aContentLength = mContentLength;
  return NS_OK;
}

NS_IMETHODIMP
nsPartChannel::SetContentLength(int64_t aContentLength) {
  mContentLength = aContentLength;
  return NS_OK;
}

NS_IMETHODIMP
nsPartChannel::GetContentDisposition(uint32_t *aContentDisposition) {
  if (mContentDispositionHeader.IsEmpty()) return NS_ERROR_NOT_AVAILABLE;

  *aContentDisposition = mContentDisposition;
  return NS_OK;
}

NS_IMETHODIMP
nsPartChannel::SetContentDisposition(uint32_t aContentDisposition) {
  return NS_ERROR_NOT_AVAILABLE;
}

NS_IMETHODIMP
nsPartChannel::GetContentDispositionFilename(
    nsAString &aContentDispositionFilename) {
  if (mContentDispositionFilename.IsEmpty()) return NS_ERROR_NOT_AVAILABLE;

  aContentDispositionFilename = mContentDispositionFilename;
  return NS_OK;
}

NS_IMETHODIMP
nsPartChannel::SetContentDispositionFilename(
    const nsAString &aContentDispositionFilename) {
  return NS_ERROR_NOT_AVAILABLE;
}

NS_IMETHODIMP
nsPartChannel::GetContentDispositionHeader(
    nsACString &aContentDispositionHeader) {
  if (mContentDispositionHeader.IsEmpty()) return NS_ERROR_NOT_AVAILABLE;

  aContentDispositionHeader = mContentDispositionHeader;
  return NS_OK;
}

NS_IMETHODIMP
nsPartChannel::GetPartID(uint32_t *aPartID) {
  *aPartID = mPartID;
  return NS_OK;
}

NS_IMETHODIMP
nsPartChannel::GetIsLastPart(bool *aIsLastPart) {
  *aIsLastPart = mIsLastPart;
  return NS_OK;
}

//
// nsIByteRangeRequest implementation...
//

NS_IMETHODIMP
nsPartChannel::GetIsByteRangeRequest(bool *aIsByteRangeRequest) {
  *aIsByteRangeRequest = mIsByteRangeRequest;

  return NS_OK;
}

NS_IMETHODIMP
nsPartChannel::GetStartRange(int64_t *aStartRange) {
  *aStartRange = mByteRangeStart;

  return NS_OK;
}

NS_IMETHODIMP
nsPartChannel::GetEndRange(int64_t *aEndRange) {
  *aEndRange = mByteRangeEnd;
  return NS_OK;
}

NS_IMETHODIMP
nsPartChannel::GetBaseChannel(nsIChannel **aReturn) {
  NS_ENSURE_ARG_POINTER(aReturn);

  *aReturn = mMultipartChannel;
  NS_IF_ADDREF(*aReturn);
  return NS_OK;
}

// nsISupports implementation
NS_IMPL_ISUPPORTS(nsMultiMixedConv, nsIStreamConverter, nsIStreamListener,
                  nsIRequestObserver)

// nsIStreamConverter implementation

// No syncronous conversion at this time.
NS_IMETHODIMP
nsMultiMixedConv::Convert(nsIInputStream *aFromStream, const char *aFromType,
                          const char *aToType, nsISupports *aCtxt,
                          nsIInputStream **_retval) {
  return NS_ERROR_NOT_IMPLEMENTED;
}

// Stream converter service calls this to initialize the actual stream converter
// (us).
NS_IMETHODIMP
nsMultiMixedConv::AsyncConvertData(const char *aFromType, const char *aToType,
                                   nsIStreamListener *aListener,
                                   nsISupports *aCtxt) {
  NS_ASSERTION(aListener && aFromType && aToType,
               "null pointer passed into multi mixed converter");

  // hook up our final listener. this guy gets the various On*() calls we want
  // to throw at him.
  //
  // WARNING: this listener must be able to handle multiple OnStartRequest,
  // OnDataAvail() and OnStopRequest() call combinations. We call of series
  // of these for each sub-part in the raw stream.
  mFinalListener = aListener;

  return NS_OK;
}

// nsIRequestObserver implementation
NS_IMETHODIMP
nsMultiMixedConv::OnStartRequest(nsIRequest *request) {
  // we're assuming the content-type is available at this stage
  NS_ASSERTION(mBoundary.IsEmpty(), "a second on start???");

  nsresult rv;

  mTotalSent = 0;
  mChannel = do_QueryInterface(request, &rv);
  if (NS_FAILED(rv)) return rv;

  nsAutoCString contentType;

  // ask the HTTP channel for the content-type and extract the boundary from it.
  nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel, &rv);
  if (NS_SUCCEEDED(rv)) {
    rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("content-type"),
                                        contentType);
    if (NS_FAILED(rv)) {
      return rv;
    }
    nsCString csp;
    rv = httpChannel->GetResponseHeader(
        NS_LITERAL_CSTRING("content-security-policy"), csp);
    if (NS_SUCCEEDED(rv)) {
      mRootContentSecurityPolicy = csp;
    }
  } else {
    // try asking the channel directly
    rv = mChannel->GetContentType(contentType);
    if (NS_FAILED(rv)) {
      return NS_ERROR_FAILURE;
    }
  }

  Tokenizer p(contentType);
  p.SkipUntil(Token::Char(';'));
  if (!p.CheckChar(';')) {
    return NS_ERROR_CORRUPTED_CONTENT;
  }
  p.SkipWhites();
  if (!p.CheckWord("boundary")) {
    return NS_ERROR_CORRUPTED_CONTENT;
  }
  p.SkipWhites();
  if (!p.CheckChar('=')) {
    return NS_ERROR_CORRUPTED_CONTENT;
  }
  p.SkipWhites();
  Unused << p.ReadUntil(Token::Char(';'), mBoundary);
  mBoundary.Trim(
      " \"");  // ignoring potential quoted string formatting violations
  if (mBoundary.IsEmpty()) {
    return NS_ERROR_CORRUPTED_CONTENT;
  }

  mHeaderTokens[HEADER_CONTENT_TYPE] = mTokenizer.AddCustomToken(
      "content-type", mTokenizer.CASE_INSENSITIVE, false);
  mHeaderTokens[HEADER_CONTENT_LENGTH] = mTokenizer.AddCustomToken(
      "content-length", mTokenizer.CASE_INSENSITIVE, false);
  mHeaderTokens[HEADER_CONTENT_DISPOSITION] = mTokenizer.AddCustomToken(
      "content-disposition", mTokenizer.CASE_INSENSITIVE, false);
  mHeaderTokens[HEADER_SET_COOKIE] = mTokenizer.AddCustomToken(
      "set-cookie", mTokenizer.CASE_INSENSITIVE, false);
  mHeaderTokens[HEADER_CONTENT_RANGE] = mTokenizer.AddCustomToken(
      "content-range", mTokenizer.CASE_INSENSITIVE, false);
  mHeaderTokens[HEADER_RANGE] =
      mTokenizer.AddCustomToken("range", mTokenizer.CASE_INSENSITIVE, false);
  mHeaderTokens[HEADER_CONTENT_SECURITY_POLICY] = mTokenizer.AddCustomToken(
      "content-security-policy", mTokenizer.CASE_INSENSITIVE, false);

  mLFToken = mTokenizer.AddCustomToken("\n", mTokenizer.CASE_SENSITIVE, false);
  mCRLFToken =
      mTokenizer.AddCustomToken("\r\n", mTokenizer.CASE_SENSITIVE, false);

  SwitchToControlParsing();

  mBoundaryToken =
      mTokenizer.AddCustomToken(mBoundary, mTokenizer.CASE_SENSITIVE);
  mBoundaryTokenWithDashes = mTokenizer.AddCustomToken(
      NS_LITERAL_CSTRING("--") + mBoundary, mTokenizer.CASE_SENSITIVE);

  return NS_OK;
}

// nsIStreamListener implementation
NS_IMETHODIMP
nsMultiMixedConv::OnDataAvailable(nsIRequest *request,
                                  nsIInputStream *inStr, uint64_t sourceOffset,
                                  uint32_t count) {
  // Failing these assertions may indicate that some of the target listeners of
  // this converter is looping the thead queue, which is harmful to how we
  // collect the raw (content) data.
  MOZ_DIAGNOSTIC_ASSERT(!mInOnDataAvailable,
                        "nsMultiMixedConv::OnDataAvailable reentered!");
  MOZ_DIAGNOSTIC_ASSERT(
      !mRawData, "There are unsent data from the previous tokenizer feed!");

  if (mInOnDataAvailable) {
    // The multipart logic is incapable of being reentered.
    return NS_ERROR_UNEXPECTED;
  }

  mozilla::AutoRestore<bool> restore(mInOnDataAvailable);
  mInOnDataAvailable = true;

  nsresult rv_feed = mTokenizer.FeedInput(inStr, count);
  // We must do this every time.  Regardless if something has failed during the
  // parsing process.  Otherwise the raw data reference would not be thrown
  // away.
  nsresult rv_send = SendData();

  return NS_FAILED(rv_send) ? rv_send : rv_feed;
}

NS_IMETHODIMP
nsMultiMixedConv::OnStopRequest(nsIRequest *request,
                                nsresult aStatus) {
  nsresult rv;

  if (mBoundary.IsEmpty()) {  // no token, no love.
    return NS_ERROR_FAILURE;
  }

  if (mPartChannel) {
    mPartChannel->SetIsLastPart();

    MOZ_DIAGNOSTIC_ASSERT(
        !mRawData, "There are unsent data from the previous tokenizer feed!");

    rv = mTokenizer.FinishInput();
    if (NS_SUCCEEDED(aStatus)) {
      aStatus = rv;
    }
    rv = SendData();
    if (NS_SUCCEEDED(aStatus)) {
      aStatus = rv;
    }

    (void)SendStop(aStatus);
  } else if (NS_FAILED(aStatus) && !mRequestListenerNotified) {
    // underlying data production problem. we should not be in
    // the middle of sending data. if we were, mPartChannel,
    // above, would have been non-null.

    (void)mFinalListener->OnStartRequest(request);
    (void)mFinalListener->OnStopRequest(request, aStatus);
  }

  return NS_OK;
}

nsresult nsMultiMixedConv::ConsumeToken(Token const &token) {
  nsresult rv;

  switch (mParserState) {
    case PREAMBLE:
      if (token.Equals(mBoundaryTokenWithDashes)) {
        // The server first used boundary '--boundary'.  Hence, we no longer
        // accept plain 'boundary' token as a delimiter.
        mTokenizer.RemoveCustomToken(mBoundaryToken);
        mParserState = BOUNDARY_CRLF;
        break;
      }
      if (token.Equals(mBoundaryToken)) {
        // And here the opposite from the just above block...
        mTokenizer.RemoveCustomToken(mBoundaryTokenWithDashes);
        mParserState = BOUNDARY_CRLF;
        break;
      }

      // This is a preamble, just ignore it and wait for the boundary.
      break;

    case BOUNDARY_CRLF:
      if (token.Equals(Token::NewLine())) {
        mParserState = HEADER_NAME;
        mResponseHeader = HEADER_UNKNOWN;
        HeadersToDefault();
        SetHeaderTokensEnabled(true);
        break;
      }
      return NS_ERROR_CORRUPTED_CONTENT;

    case HEADER_NAME:
      SetHeaderTokensEnabled(false);
      if (token.Equals(Token::NewLine())) {
        mParserState = BODY_INIT;
        SwitchToBodyParsing();
        break;
      }
      for (uint32_t h = HEADER_CONTENT_TYPE; h < HEADER_UNKNOWN; ++h) {
        if (token.Equals(mHeaderTokens[h])) {
          mResponseHeader = static_cast<EHeader>(h);
          break;
        }
      }
      mParserState = HEADER_SEP;
      break;

    case HEADER_SEP:
      if (token.Equals(Token::Char(':'))) {
        mParserState = HEADER_VALUE;
        mResponseHeaderValue.Truncate();
        break;
      }
      if (mResponseHeader == HEADER_UNKNOWN) {
        // If the header is not of any we understand, just pass everything till
        // ':'
        break;
      }
      if (token.Equals(Token::Whitespace())) {
        // Accept only header-name traling whitespaces after known headers
        break;
      }
      return NS_ERROR_CORRUPTED_CONTENT;

    case HEADER_VALUE:
      if (token.Equals(Token::Whitespace()) && mResponseHeaderValue.IsEmpty()) {
        // Eat leading whitespaces
        break;
      }
      if (token.Equals(Token::NewLine())) {
        nsresult rv = ProcessHeader();
        if (NS_FAILED(rv)) {
          return rv;
        }
        mParserState = HEADER_NAME;
        mResponseHeader = HEADER_UNKNOWN;
        SetHeaderTokensEnabled(true);
      } else {
        mResponseHeaderValue.Append(token.Fragment());
      }
      break;

    case BODY_INIT:
      rv = SendStart();
      if (NS_FAILED(rv)) {
        return rv;
      }
      mParserState = BODY;
      MOZ_FALLTHROUGH;

    case BODY: {
      if (!token.Equals(mLFToken) && !token.Equals(mCRLFToken)) {
        if (token.Equals(mBoundaryTokenWithDashes) ||
            token.Equals(mBoundaryToken)) {
          // Allow CRLF to NOT be part of the boundary as well
          SwitchToControlParsing();
          mParserState = TRAIL_DASH1;
          break;
        }
        AccumulateData(token);
        break;
      }

      // After CRLF we must explicitly check for boundary.  If found,
      // that CRLF is part of the boundary and must not be send to the
      // data listener.
      Token token2;
      if (!mTokenizer.Next(token2)) {
        // Note: this will give us the CRLF token again when more data
        // or OnStopRequest arrive.  I.e. we will enter BODY case in
        // the very same state as we are now and start this block over.
        mTokenizer.NeedMoreInput();
        break;
      }
      if (token2.Equals(mBoundaryTokenWithDashes) ||
          token2.Equals(mBoundaryToken)) {
        SwitchToControlParsing();
        mParserState = TRAIL_DASH1;
        break;
      }

      AccumulateData(token);
      AccumulateData(token2);
      break;
    }

    case TRAIL_DASH1:
      if (token.Equals(Token::NewLine())) {
        rv = SendStop(NS_OK);
        if (NS_FAILED(rv)) {
          return rv;
        }
        mParserState = BOUNDARY_CRLF;
        mTokenizer.Rollback();
        break;
      }
      if (token.Equals(Token::Char('-'))) {
        mParserState = TRAIL_DASH2;
        break;
      }
      return NS_ERROR_CORRUPTED_CONTENT;

    case TRAIL_DASH2:
      if (token.Equals(Token::Char('-'))) {
        mPartChannel->SetIsLastPart();
        // SendStop calls SendData first.
        rv = SendStop(NS_OK);
        if (NS_FAILED(rv)) {
          return rv;
        }
        mParserState = EPILOGUE;
        break;
      }
      return NS_ERROR_CORRUPTED_CONTENT;

    case EPILOGUE:
      // Just ignore
      break;

    default:
      MOZ_ASSERT(false, "Missing parser state handling branch");
      break;
  }  // switch

  return NS_OK;
}

void nsMultiMixedConv::SetHeaderTokensEnabled(bool aEnable) {
  for (uint32_t h = HEADER_FIRST; h < HEADER_UNKNOWN; ++h) {
    mTokenizer.EnableCustomToken(mHeaderTokens[h], aEnable);
  }
}

void nsMultiMixedConv::SwitchToBodyParsing() {
  mTokenizer.SetTokenizingMode(Tokenizer::Mode::CUSTOM_ONLY);
  mTokenizer.EnableCustomToken(mLFToken, true);
  mTokenizer.EnableCustomToken(mCRLFToken, true);
  mTokenizer.EnableCustomToken(mBoundaryTokenWithDashes, true);
  mTokenizer.EnableCustomToken(mBoundaryToken, true);
}

void nsMultiMixedConv::SwitchToControlParsing() {
  mTokenizer.SetTokenizingMode(Tokenizer::Mode::FULL);
  mTokenizer.EnableCustomToken(mLFToken, false);
  mTokenizer.EnableCustomToken(mCRLFToken, false);
  mTokenizer.EnableCustomToken(mBoundaryTokenWithDashes, false);
  mTokenizer.EnableCustomToken(mBoundaryToken, false);
}

// nsMultiMixedConv methods
nsMultiMixedConv::nsMultiMixedConv()
    : mCurrentPartID(0),
      mInOnDataAvailable(false),
      mResponseHeader(HEADER_UNKNOWN),
      // XXX: This is a hack to bypass the raw pointer to refcounted object in
      // lambda analysis. It should be removed and replaced when the
      // IncrementalTokenizer API is improved to avoid the need for such
      // workarounds.
      //
      // This is safe because `mTokenizer` will not outlive `this`, meaning that
      // this std::bind object will be destroyed before `this` dies.
      mTokenizer(std::bind(&nsMultiMixedConv::ConsumeToken, this,
                           std::placeholders::_1)) {
  mContentLength = UINT64_MAX;
  mByteRangeStart = 0;
  mByteRangeEnd = 0;
  mTotalSent = 0;
  mIsByteRangeRequest = false;
  mParserState = INIT;
  mRawData = nullptr;
  mRequestListenerNotified = false;
}

nsresult nsMultiMixedConv::SendStart() {
  nsresult rv = NS_OK;

  nsCOMPtr<nsIStreamListener> partListener(mFinalListener);
  if (mContentType.IsEmpty()) {
    mContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE);
    nsCOMPtr<nsIStreamConverterService> serv =
        do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
    if (NS_SUCCEEDED(rv)) {
      nsCOMPtr<nsIStreamListener> converter;
      rv = serv->AsyncConvertData(UNKNOWN_CONTENT_TYPE, "*/*", mFinalListener,
                                  mContext, getter_AddRefs(converter));
      if (NS_SUCCEEDED(rv)) {
        partListener = converter;
      }
    }
  }

  // if we already have an mPartChannel, that means we never sent a Stop()
  // before starting up another "part." that would be bad.
  MOZ_ASSERT(!mPartChannel, "tisk tisk, shouldn't be overwriting a channel");

  nsPartChannel *newChannel;
  newChannel = new nsPartChannel(mChannel, mCurrentPartID++, partListener);
  if (!newChannel) return NS_ERROR_OUT_OF_MEMORY;

  if (mIsByteRangeRequest) {
    newChannel->InitializeByteRange(mByteRangeStart, mByteRangeEnd);
  }

  mTotalSent = 0;

  // Set up the new part channel...
  mPartChannel = newChannel;

  rv = mPartChannel->SetContentType(mContentType);
  if (NS_FAILED(rv)) return rv;

  rv = mPartChannel->SetContentLength(mContentLength);
  if (NS_FAILED(rv)) return rv;

  mPartChannel->SetContentDisposition(mContentDisposition);

  // Each part of a multipart/replace response can be used
  // for the top level document.  We must inform upper layers
  // about this by setting the LOAD_REPLACE flag so that certain
  // state assertions are evaluated as positive.
  nsLoadFlags loadFlags = 0;
  mPartChannel->GetLoadFlags(&loadFlags);
  loadFlags |= nsIChannel::LOAD_REPLACE;
  mPartChannel->SetLoadFlags(loadFlags);

  nsCOMPtr<nsILoadGroup> loadGroup;
  (void)mPartChannel->GetLoadGroup(getter_AddRefs(loadGroup));

  // Add the new channel to the load group (if any)
  if (loadGroup) {
    rv = loadGroup->AddRequest(mPartChannel, nullptr);
    if (NS_FAILED(rv)) return rv;
  }

  // This prevents artificial call to OnStart/StopRequest when the root
  // channel fails.  Since now it's ensured to keep with the nsIStreamListener
  // contract every time.
  mRequestListenerNotified = true;

  // Let's start off the load. NOTE: we don't forward on the channel passed
  // into our OnDataAvailable() as it's the root channel for the raw stream.
  return mPartChannel->SendOnStartRequest(mContext);
}

nsresult nsMultiMixedConv::SendStop(nsresult aStatus) {
  // Make sure we send out all accumulcated data prior call to OnStopRequest.
  // If there is no data, this is a no-op.
  nsresult rv = SendData();
  if (NS_SUCCEEDED(aStatus)) {
    aStatus = rv;
  }
  if (mPartChannel) {
    rv = mPartChannel->SendOnStopRequest(mContext, aStatus);
    // don't check for failure here, we need to remove the channel from
    // the loadgroup.

    // Remove the channel from its load group (if any)
    nsCOMPtr<nsILoadGroup> loadGroup;
    (void)mPartChannel->GetLoadGroup(getter_AddRefs(loadGroup));
    if (loadGroup)
      (void)loadGroup->RemoveRequest(mPartChannel, mContext, aStatus);
  }

  mPartChannel = nullptr;
  return rv;
}

void nsMultiMixedConv::AccumulateData(Token const &aToken) {
  if (!mRawData) {
    // This is the first read of raw data during this FeedInput loop
    // of the incremental tokenizer.  All 'raw' tokens are coming from
    // the same linear buffer, hence begining of this loop raw data
    // is begining of the first raw token.  Length of this loop raw
    // data is just sum of all 'raw' tokens we collect during this loop.
    //
    // It's ensured we flush (send to to the listener via OnDataAvailable)
    // and nullify the collected raw data right after FeedInput call.
    // Hence, the reference can't outlive the actual buffer.
    mRawData = aToken.Fragment().BeginReading();
    mRawDataLength = 0;
  }

  mRawDataLength += aToken.Fragment().Length();
}

nsresult nsMultiMixedConv::SendData() {
  nsresult rv;

  if (!mRawData) {
    return NS_OK;
  }

  nsACString::const_char_iterator rawData = mRawData;
  mRawData = nullptr;

  if (!mPartChannel) {
    return NS_ERROR_FAILURE;  // something went wrong w/ processing
  }

  if (mContentLength != UINT64_MAX) {
    // make sure that we don't send more than the mContentLength
    // XXX why? perhaps the Content-Length header was actually wrong!!
    if ((uint64_t(mRawDataLength) + mTotalSent) > mContentLength)
      mRawDataLength = static_cast<uint32_t>(mContentLength - mTotalSent);

    if (mRawDataLength == 0) return NS_OK;
  }

  uint64_t offset = mTotalSent;
  mTotalSent += mRawDataLength;

  nsCOMPtr<nsIStringInputStream> ss(
      do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv));
  if (NS_FAILED(rv)) return rv;

  rv = ss->ShareData(rawData, mRawDataLength);
  mRawData = nullptr;
  if (NS_FAILED(rv)) return rv;

  return mPartChannel->SendOnDataAvailable(mContext, ss, offset,
                                           mRawDataLength);
}

void nsMultiMixedConv::HeadersToDefault() {
  mContentLength = UINT64_MAX;
  mContentType.Truncate();
  mContentDisposition.Truncate();
  mContentSecurityPolicy.Truncate();
  mIsByteRangeRequest = false;
}

nsresult nsMultiMixedConv::ProcessHeader() {
  mozilla::Tokenizer p(mResponseHeaderValue);

  switch (mResponseHeader) {
    case HEADER_CONTENT_TYPE:
      mContentType = mResponseHeaderValue;
      mContentType.CompressWhitespace();
      break;
    case HEADER_CONTENT_LENGTH:
      p.SkipWhites();
      if (!p.ReadInteger(&mContentLength)) {
        return NS_ERROR_CORRUPTED_CONTENT;
      }
      break;
    case HEADER_CONTENT_DISPOSITION:
      mContentDisposition = mResponseHeaderValue;
      mContentDisposition.CompressWhitespace();
      break;
    case HEADER_SET_COOKIE: {
      nsCOMPtr<nsIHttpChannelInternal> httpInternal =
          do_QueryInterface(mChannel);
      mResponseHeaderValue.CompressWhitespace();
      if (httpInternal) {
        DebugOnly<nsresult> rv =
            httpInternal->SetCookie(mResponseHeaderValue.get());
        MOZ_ASSERT(NS_SUCCEEDED(rv));
      }
      break;
    }
    case HEADER_RANGE:
    case HEADER_CONTENT_RANGE: {
      if (!p.CheckWord("bytes") || !p.CheckWhite()) {
        return NS_ERROR_CORRUPTED_CONTENT;
      }
      p.SkipWhites();
      if (p.CheckChar('*')) {
        mByteRangeStart = mByteRangeEnd = 0;
      } else if (!p.ReadInteger(&mByteRangeStart) || !p.CheckChar('-') ||
                 !p.ReadInteger(&mByteRangeEnd)) {
        return NS_ERROR_CORRUPTED_CONTENT;
      }
      mIsByteRangeRequest = true;
      if (mContentLength == UINT64_MAX) {
        mContentLength = uint64_t(mByteRangeEnd - mByteRangeStart + 1);
      }
      break;
    }
    case HEADER_CONTENT_SECURITY_POLICY: {
      mContentSecurityPolicy = mResponseHeaderValue;
      mContentSecurityPolicy.CompressWhitespace();
      nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
      if (httpChannel) {
        nsCString resultCSP = mRootContentSecurityPolicy;
        if (!mContentSecurityPolicy.IsEmpty()) {
          // We are updating the root channel CSP header respectively for
          // each part as: CSP-root + CSP-partN, where N is the part number.
          // Here we append current part's CSP to root CSP and reset CSP
          // header for each part.
          if (!resultCSP.IsEmpty()) {
            resultCSP.Append(";");
          }
          resultCSP.Append(mContentSecurityPolicy);
        }
        nsresult rv = httpChannel->SetResponseHeader(
            NS_LITERAL_CSTRING("Content-Security-Policy"), resultCSP, false);
        if (NS_FAILED(rv)) {
          return NS_ERROR_CORRUPTED_CONTENT;
        }
      }
      break;
    }
    case HEADER_UNKNOWN:
      // We ignore anything else...
      break;
  }

  return NS_OK;
}

nsresult NS_NewMultiMixedConv(nsMultiMixedConv **aMultiMixedConv) {
  MOZ_ASSERT(aMultiMixedConv != nullptr, "null ptr");
  if (!aMultiMixedConv) return NS_ERROR_NULL_POINTER;

  *aMultiMixedConv = new nsMultiMixedConv();

  NS_ADDREF(*aMultiMixedConv);
  return NS_OK;
}