diff options
Diffstat (limited to 'netwerk/streamconv/converters/nsMultiMixedConv.cpp')
-rw-r--r-- | netwerk/streamconv/converters/nsMultiMixedConv.cpp | 1121 |
1 files changed, 1121 insertions, 0 deletions
diff --git a/netwerk/streamconv/converters/nsMultiMixedConv.cpp b/netwerk/streamconv/converters/nsMultiMixedConv.cpp new file mode 100644 index 000000000..4ebd61d9c --- /dev/null +++ b/netwerk/streamconv/converters/nsMultiMixedConv.cpp @@ -0,0 +1,1121 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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" + +// +// Helper function for determining the length of data bytes up to +// the next multipart token. A token is usually preceded by a LF +// or CRLF delimiter. +// +static uint32_t +LengthToToken(const char *cursor, const char *token) +{ + uint32_t len = token - cursor; + // Trim off any LF or CRLF preceding the token + if (len && *(token-1) == '\n') { + --len; + if (len && *(token-2) == '\r') + --len; + } + return len; +} + +nsPartChannel::nsPartChannel(nsIChannel *aMultipartChannel, uint32_t aPartID, + nsIStreamListener* aListener) : + mMultipartChannel(aMultipartChannel), + mListener(aListener), + mStatus(NS_OK), + 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)); +} + +nsPartChannel::~nsPartChannel() +{ +} + +void nsPartChannel::InitializeByteRange(int64_t aStart, int64_t aEnd) +{ + mIsByteRangeRequest = true; + + mByteRangeStart = aStart; + mByteRangeEnd = aEnd; +} + +nsresult nsPartChannel::SendOnStartRequest(nsISupports* aContext) +{ + return mListener->OnStartRequest(this, aContext); +} + +nsresult nsPartChannel::SendOnDataAvailable(nsISupports* aContext, + nsIInputStream* aStream, + uint64_t aOffset, uint32_t aLen) +{ + return mListener->OnDataAvailable(this, aContext, aStream, aOffset, aLen); +} + +nsresult nsPartChannel::SendOnStopRequest(nsISupports* aContext, + nsresult aStatus) +{ + // Drop the listener + nsCOMPtr<nsIStreamListener> listener; + listener.swap(mListener); + return listener->OnStopRequest(this, aContext, 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 **result) +{ + // This channel cannot be opened! + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsPartChannel::Open2(nsIInputStream** aStream) +{ + nsCOMPtr<nsIStreamListener> listener; + nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener); + NS_ENSURE_SUCCESS(rv, rv); + return Open(aStream); +} + +NS_IMETHODIMP +nsPartChannel::AsyncOpen(nsIStreamListener *aListener, nsISupports *aContext) +{ + // This channel cannot be opened! + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsPartChannel::AsyncOpen2(nsIStreamListener *aListener) +{ + nsCOMPtr<nsIStreamListener> listener = aListener; + nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener); + NS_ENSURE_SUCCESS(rv, rv); + return AsyncOpen(listener, nullptr); +} + +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::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) +{ + 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; +} + +// AutoFree implementation to prevent memory leaks +class AutoFree +{ +public: + AutoFree() : mBuffer(nullptr) {} + + explicit AutoFree(char *buffer) : mBuffer(buffer) {} + + ~AutoFree() { + free(mBuffer); + } + + AutoFree& operator=(char *buffer) { + mBuffer = buffer; + return *this; + } + + operator char*() const { + return mBuffer; + } +private: + char *mBuffer; +}; + +// nsIStreamListener implementation +NS_IMETHODIMP +nsMultiMixedConv::OnDataAvailable(nsIRequest *request, nsISupports *context, + nsIInputStream *inStr, uint64_t sourceOffset, + uint32_t count) { + nsresult rv = NS_OK; + AutoFree buffer(nullptr); + uint32_t bufLen = 0, read = 0; + + NS_ASSERTION(request, "multimixed converter needs a request"); + + nsCOMPtr<nsIChannel> channel = do_QueryInterface(request, &rv); + if (NS_FAILED(rv)) return rv; + + // fill buffer + { + bufLen = count + mBufLen; + NS_ENSURE_TRUE((bufLen >= count) && (bufLen >= mBufLen), + NS_ERROR_FAILURE); + buffer = (char *) malloc(bufLen); + if (!buffer) + return NS_ERROR_OUT_OF_MEMORY; + + if (mBufLen) { + // incorporate any buffered data into the parsing + memcpy(buffer, mBuffer, mBufLen); + free(mBuffer); + mBuffer = 0; + mBufLen = 0; + } + + rv = inStr->Read(buffer + (bufLen - count), count, &read); + + if (NS_FAILED(rv) || read == 0) return rv; + NS_ASSERTION(read == count, "poor data size assumption"); + } + + char *cursor = buffer; + + if (mFirstOnData) { + // this is the first OnData() for this request. some servers + // don't bother sending a token in the first "part." This is + // illegal, but we'll handle the case anyway by shoving the + // boundary token in for the server. + mFirstOnData = false; + NS_ASSERTION(!mBufLen, "this is our first time through, we can't have buffered data"); + const char * token = mToken.get(); + + PushOverLine(cursor, bufLen); + + bool needMoreChars = bufLen < mTokenLen + 2; + nsAutoCString firstBuffer(buffer, bufLen); + int32_t posCR = firstBuffer.Find("\r"); + + if (needMoreChars || (posCR == kNotFound)) { + // we don't have enough data yet to make this comparison. + // skip this check, and try again the next time OnData() + // is called. + mFirstOnData = true; + } else if (!PL_strnstr(cursor, token, mTokenLen + 2)) { + char *newBuffer = (char *) realloc(buffer, bufLen + mTokenLen + 1); + if (!newBuffer) + return NS_ERROR_OUT_OF_MEMORY; + buffer = newBuffer; + + memmove(buffer + mTokenLen + 1, buffer, bufLen); + memcpy(buffer, token, mTokenLen); + buffer[mTokenLen] = '\n'; + + bufLen += (mTokenLen + 1); + + // need to reset cursor to the buffer again (bug 100595) + cursor = buffer; + } + } + + char *token = nullptr; + + if (mProcessingHeaders) { + // we were not able to process all the headers + // for this "part" given the previous buffer given to + // us in the previous OnDataAvailable callback. + bool done = false; + rv = ParseHeaders(channel, cursor, bufLen, &done); + if (NS_FAILED(rv)) return rv; + + if (done) { + mProcessingHeaders = false; + rv = SendStart(channel); + if (NS_FAILED(rv)) return rv; + } + } + + int32_t tokenLinefeed = 1; + while ( (token = FindToken(cursor, bufLen)) ) { + + if (((token + mTokenLen + 1) < (cursor + bufLen)) && + (*(token + mTokenLen + 1) == '-')) { + // This was the last delimiter so we can stop processing + rv = SendData(cursor, LengthToToken(cursor, token)); + if (NS_FAILED(rv)) return rv; + if (mPartChannel) { + mPartChannel->SetIsLastPart(); + } + return SendStop(NS_OK); + } + + if (!mNewPart && token > cursor) { + // headers are processed, we're pushing data now. + NS_ASSERTION(!mProcessingHeaders, "we should be pushing raw data"); + rv = SendData(cursor, LengthToToken(cursor, token)); + bufLen -= token - cursor; + if (NS_FAILED(rv)) return rv; + } + // XXX else NS_ASSERTION(token == cursor, "?"); + token += mTokenLen; + bufLen -= mTokenLen; + tokenLinefeed = PushOverLine(token, bufLen); + + if (mNewPart) { + // parse headers + mNewPart = false; + cursor = token; + bool done = false; + rv = ParseHeaders(channel, cursor, bufLen, &done); + if (NS_FAILED(rv)) return rv; + + if (done) { + rv = SendStart(channel); + if (NS_FAILED(rv)) return rv; + } + else { + // we haven't finished processing header info. + // we'll break out and try to process later. + mProcessingHeaders = true; + break; + } + } + else { + mNewPart = true; + // Reset state so we don't carry it over from part to part + mContentType.Truncate(); + mContentLength = UINT64_MAX; + mContentDisposition.Truncate(); + mIsByteRangeRequest = false; + mByteRangeStart = 0; + mByteRangeEnd = 0; + + rv = SendStop(NS_OK); + if (NS_FAILED(rv)) return rv; + // reset the token to front. this allows us to treat + // the token as a starting token. + token -= mTokenLen + tokenLinefeed; + bufLen += mTokenLen + tokenLinefeed; + cursor = token; + } + } + + // at this point, we want to buffer up whatever amount (bufLen) + // we have leftover. However, we *always* want to ensure that + // we buffer enough data to handle a broken token. + + // carry over + uint32_t bufAmt = 0; + if (mProcessingHeaders) + bufAmt = bufLen; + else if (bufLen) { + // if the data ends in a linefeed, and we're in the middle + // of a "part" (ie. mPartChannel exists) don't bother + // buffering, go ahead and send the data we have. Otherwise + // if we don't have a channel already, then we don't even + // have enough info to start a part, go ahead and buffer + // enough to collect a boundary token. + if (!mPartChannel || !(cursor[bufLen-1] == nsCRT::LF) ) + bufAmt = std::min(mTokenLen - 1, bufLen); + } + + if (bufAmt) { + rv = BufferData(cursor + (bufLen - bufAmt), bufAmt); + if (NS_FAILED(rv)) return rv; + bufLen -= bufAmt; + } + + if (bufLen) { + rv = SendData(cursor, bufLen); + if (NS_FAILED(rv)) return rv; + } + + return rv; +} + + +// nsIRequestObserver implementation +NS_IMETHODIMP +nsMultiMixedConv::OnStartRequest(nsIRequest *request, nsISupports *ctxt) { + // we're assuming the content-type is available at this stage + NS_ASSERTION(mToken.IsEmpty(), "a second on start???"); + const char *bndry = nullptr; + nsAutoCString delimiter; + nsresult rv = NS_OK; + mContext = ctxt; + + mFirstOnData = true; + mTotalSent = 0; + + nsCOMPtr<nsIChannel> channel = do_QueryInterface(request, &rv); + if (NS_FAILED(rv)) return rv; + + // ask the HTTP channel for the content-type and extract the boundary from it. + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel, &rv); + if (NS_SUCCEEDED(rv)) { + rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("content-type"), delimiter); + if (NS_FAILED(rv)) { + return rv; + } + } else { + // try asking the channel directly + rv = channel->GetContentType(delimiter); + if (NS_FAILED(rv)) { + return NS_ERROR_FAILURE; + } + } + + bndry = strstr(delimiter.BeginWriting(), "boundary"); + + if (!bndry) { + return NS_ERROR_FAILURE; + } + + bndry = strchr(bndry, '='); + if (!bndry) return NS_ERROR_FAILURE; + + bndry++; // move past the equals sign + + char *attrib = (char *) strchr(bndry, ';'); + if (attrib) *attrib = '\0'; + + nsAutoCString boundaryString(bndry); + if (attrib) *attrib = ';'; + + boundaryString.Trim(" \""); + + mToken = boundaryString; + mTokenLen = boundaryString.Length(); + + if (mTokenLen == 0) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMultiMixedConv::OnStopRequest(nsIRequest *request, nsISupports *ctxt, + nsresult aStatus) +{ + if (mToken.IsEmpty()) { // no token, no love. + return NS_ERROR_FAILURE; + } + + if (mPartChannel) { + mPartChannel->SetIsLastPart(); + + // we've already called SendStart() (which sets up the mPartChannel, + // and fires an OnStart()) send any data left over, and then fire the stop. + if (mBufLen > 0 && mBuffer) { + (void) SendData(mBuffer, mBufLen); + // don't bother checking the return value here, if the send failed + // we're done anyway as we're in the OnStop() callback. + free(mBuffer); + mBuffer = nullptr; + mBufLen = 0; + } + (void) SendStop(aStatus); + } else if (NS_FAILED(aStatus)) { + // underlying data production problem. we should not be in + // the middle of sending data. if we were, mPartChannel, + // above, would have been true. + + // if we send the start, the URI Loader's m_targetStreamListener, may + // be pointing at us causing a nice stack overflow. So, don't call + // OnStartRequest! - This breaks necko's semantecs. + //(void) mFinalListener->OnStartRequest(request, ctxt); + + (void) mFinalListener->OnStopRequest(request, ctxt, aStatus); + } + + return NS_OK; +} + + +// nsMultiMixedConv methods +nsMultiMixedConv::nsMultiMixedConv() : + mCurrentPartID(0) +{ + mTokenLen = 0; + mNewPart = true; + mContentLength = UINT64_MAX; + mBuffer = nullptr; + mBufLen = 0; + mProcessingHeaders = false; + mByteRangeStart = 0; + mByteRangeEnd = 0; + mTotalSent = 0; + mIsByteRangeRequest = false; +} + +nsMultiMixedConv::~nsMultiMixedConv() { + NS_ASSERTION(!mBuffer, "all buffered data should be gone"); + if (mBuffer) { + free(mBuffer); + mBuffer = nullptr; + } +} + +nsresult +nsMultiMixedConv::BufferData(char *aData, uint32_t aLen) { + NS_ASSERTION(!mBuffer, "trying to over-write buffer"); + + char *buffer = (char *) malloc(aLen); + if (!buffer) return NS_ERROR_OUT_OF_MEMORY; + + memcpy(buffer, aData, aLen); + mBuffer = buffer; + mBufLen = aLen; + return NS_OK; +} + + +nsresult +nsMultiMixedConv::SendStart(nsIChannel *aChannel) { + 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. + NS_ASSERTION(!mPartChannel, "tisk tisk, shouldn't be overwriting a channel"); + + nsPartChannel *newChannel; + newChannel = new nsPartChannel(aChannel, 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); + + 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; + } + + // 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) { + + nsresult rv = NS_OK; + 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; +} + +nsresult +nsMultiMixedConv::SendData(char *aBuffer, uint32_t aLen) { + + nsresult rv = NS_OK; + + 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(aLen) + mTotalSent) > mContentLength) + aLen = static_cast<uint32_t>(mContentLength - mTotalSent); + + if (aLen == 0) + return NS_OK; + } + + uint64_t offset = mTotalSent; + mTotalSent += aLen; + + nsCOMPtr<nsIStringInputStream> ss( + do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv)); + if (NS_FAILED(rv)) + return rv; + + rv = ss->ShareData(aBuffer, aLen); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIInputStream> inStream(do_QueryInterface(ss, &rv)); + if (NS_FAILED(rv)) return rv; + + return mPartChannel->SendOnDataAvailable(mContext, inStream, offset, aLen); +} + +int32_t +nsMultiMixedConv::PushOverLine(char *&aPtr, uint32_t &aLen) { + int32_t chars = 0; + if ((aLen > 0) && (*aPtr == nsCRT::CR || *aPtr == nsCRT::LF)) { + if ((aLen > 1) && (aPtr[1] == nsCRT::LF)) + chars++; + chars++; + aPtr += chars; + aLen -= chars; + } + return chars; +} + +nsresult +nsMultiMixedConv::ParseHeaders(nsIChannel *aChannel, char *&aPtr, + uint32_t &aLen, bool *_retval) { + // NOTE: this data must be ascii. + // NOTE: aPtr is NOT null terminated! + nsresult rv = NS_OK; + char *cursor = aPtr, *newLine = nullptr; + uint32_t cursorLen = aLen; + bool done = false; + uint32_t lineFeedIncrement = 1; + + mContentLength = UINT64_MAX; // XXX what if we were already called? + while (cursorLen && (newLine = (char *) memchr(cursor, nsCRT::LF, cursorLen))) { + // adjust for linefeeds + if ((newLine > cursor) && (newLine[-1] == nsCRT::CR) ) { // CRLF + lineFeedIncrement = 2; + newLine--; + } + else + lineFeedIncrement = 1; // reset + + if (newLine == cursor) { + // move the newLine beyond the linefeed marker + NS_ASSERTION(cursorLen >= lineFeedIncrement, "oops!"); + + cursor += lineFeedIncrement; + cursorLen -= lineFeedIncrement; + + done = true; + break; + } + + char tmpChar = *newLine; + *newLine = '\0'; // cursor is now null terminated + + char *colon = (char *) strchr(cursor, ':'); + if (colon) { + *colon = '\0'; + nsAutoCString headerStr(cursor); + headerStr.CompressWhitespace(); + *colon = ':'; + + nsAutoCString headerVal(colon + 1); + headerVal.CompressWhitespace(); + + // examine header + if (headerStr.LowerCaseEqualsLiteral("content-type")) { + mContentType = headerVal; + } else if (headerStr.LowerCaseEqualsLiteral("content-length")) { + mContentLength = nsCRT::atoll(headerVal.get()); + } else if (headerStr.LowerCaseEqualsLiteral("content-disposition")) { + mContentDisposition = headerVal; + } else if (headerStr.LowerCaseEqualsLiteral("set-cookie")) { + nsCOMPtr<nsIHttpChannelInternal> httpInternal = + do_QueryInterface(aChannel); + if (httpInternal) { + httpInternal->SetCookie(headerVal.get()); + } + } else if (headerStr.LowerCaseEqualsLiteral("content-range") || + headerStr.LowerCaseEqualsLiteral("range") ) { + // something like: Content-range: bytes 7000-7999/8000 + char* tmpPtr; + + tmpPtr = (char *) strchr(colon + 1, '/'); + if (tmpPtr) + *tmpPtr = '\0'; + + // pass the bytes-unit and the SP + char *range = (char *) strchr(colon + 2, ' '); + if (!range) + return NS_ERROR_FAILURE; + + do { + range++; + } while (*range == ' '); + + if (range[0] == '*'){ + mByteRangeStart = mByteRangeEnd = 0; + } + else { + tmpPtr = (char *) strchr(range, '-'); + if (!tmpPtr) + return NS_ERROR_FAILURE; + + tmpPtr[0] = '\0'; + + mByteRangeStart = nsCRT::atoll(range); + tmpPtr++; + mByteRangeEnd = nsCRT::atoll(tmpPtr); + } + + mIsByteRangeRequest = true; + if (mContentLength == UINT64_MAX) + mContentLength = uint64_t(mByteRangeEnd - mByteRangeStart + 1); + } + } + *newLine = tmpChar; + newLine += lineFeedIncrement; + cursorLen -= (newLine - cursor); + cursor = newLine; + } + + aPtr = cursor; + aLen = cursorLen; + + *_retval = done; + return rv; +} + +char * +nsMultiMixedConv::FindToken(char *aCursor, uint32_t aLen) { + // strnstr without looking for null termination + const char *token = mToken.get(); + char *cur = aCursor; + + if (!(token && aCursor && *token)) { + NS_WARNING("bad data"); + return nullptr; + } + + for (; aLen >= mTokenLen; aCursor++, aLen--) { + if (!memcmp(aCursor, token, mTokenLen) ) { + if ((aCursor - cur) >= 2) { + // back the cursor up over a double dash for backwards compat. + if ((*(aCursor-1) == '-') && (*(aCursor-2) == '-')) { + aCursor -= 2; + aLen += 2; + + // we're playing w/ double dash tokens, adjust. + mToken.Assign(aCursor, mTokenLen + 2); + mTokenLen = mToken.Length(); + } + } + return aCursor; + } + } + + return nullptr; +} + +nsresult +NS_NewMultiMixedConv(nsMultiMixedConv** aMultiMixedConv) +{ + NS_PRECONDITION(aMultiMixedConv != nullptr, "null ptr"); + if (! aMultiMixedConv) + return NS_ERROR_NULL_POINTER; + + *aMultiMixedConv = new nsMultiMixedConv(); + if (! *aMultiMixedConv) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(*aMultiMixedConv); + return NS_OK; +} |