gecko-dev/netwerk/streamconv/converters/nsMultiMixedConv.cpp
Bogdan Tara 80377e90ee Backed out 6 changesets (bug 1552176) for causing Hazard bustages CLOSED TREE
Backed out changeset c79b90bae420 (bug 1552176)
Backed out changeset 4b970cc771ca (bug 1552176)
Backed out changeset de7aa0eaf4c8 (bug 1552176)
Backed out changeset c8e692a40cd3 (bug 1552176)
Backed out changeset 68882d1eccac (bug 1552176)
Backed out changeset 692e5e51e19e (bug 1552176)
2019-11-11 12:11:36 +02:00

1020 lines
30 KiB
C++

/* -*- 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);
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;
}