mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-13 18:27:35 +00:00
aa84d3c812
Differential Revision: https://phabricator.services.mozilla.com/D32662 --HG-- extra : moz-landing-system : lando
1020 lines
30 KiB
C++
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;
|
|
}
|