michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsMultiMixedConv.h" michael@0: #include "plstr.h" michael@0: #include "nsIHttpChannel.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsMimeTypes.h" michael@0: #include "nsIStringStream.h" michael@0: #include "nsCRT.h" michael@0: #include "nsIHttpChannelInternal.h" michael@0: #include "nsURLHelper.h" michael@0: #include "nsIStreamConverterService.h" michael@0: #include michael@0: michael@0: // michael@0: // Helper function for determining the length of data bytes up to michael@0: // the next multipart token. A token is usually preceded by a LF michael@0: // or CRLF delimiter. michael@0: // michael@0: static uint32_t michael@0: LengthToToken(const char *cursor, const char *token) michael@0: { michael@0: uint32_t len = token - cursor; michael@0: // Trim off any LF or CRLF preceding the token michael@0: if (len && *(token-1) == '\n') { michael@0: --len; michael@0: if (len && *(token-2) == '\r') michael@0: --len; michael@0: } michael@0: return len; michael@0: } michael@0: michael@0: nsPartChannel::nsPartChannel(nsIChannel *aMultipartChannel, uint32_t aPartID, michael@0: nsIStreamListener* aListener) : michael@0: mMultipartChannel(aMultipartChannel), michael@0: mListener(aListener), michael@0: mStatus(NS_OK), michael@0: mContentLength(UINT64_MAX), michael@0: mIsByteRangeRequest(false), michael@0: mByteRangeStart(0), michael@0: mByteRangeEnd(0), michael@0: mPartID(aPartID), michael@0: mIsLastPart(false) michael@0: { michael@0: mMultipartChannel = aMultipartChannel; michael@0: michael@0: // Inherit the load flags from the original channel... michael@0: mMultipartChannel->GetLoadFlags(&mLoadFlags); michael@0: michael@0: mMultipartChannel->GetLoadGroup(getter_AddRefs(mLoadGroup)); michael@0: } michael@0: michael@0: nsPartChannel::~nsPartChannel() michael@0: { michael@0: } michael@0: michael@0: void nsPartChannel::InitializeByteRange(int64_t aStart, int64_t aEnd) michael@0: { michael@0: mIsByteRangeRequest = true; michael@0: michael@0: mByteRangeStart = aStart; michael@0: mByteRangeEnd = aEnd; michael@0: } michael@0: michael@0: nsresult nsPartChannel::SendOnStartRequest(nsISupports* aContext) michael@0: { michael@0: return mListener->OnStartRequest(this, aContext); michael@0: } michael@0: michael@0: nsresult nsPartChannel::SendOnDataAvailable(nsISupports* aContext, michael@0: nsIInputStream* aStream, michael@0: uint64_t aOffset, uint32_t aLen) michael@0: { michael@0: return mListener->OnDataAvailable(this, aContext, aStream, aOffset, aLen); michael@0: } michael@0: michael@0: nsresult nsPartChannel::SendOnStopRequest(nsISupports* aContext, michael@0: nsresult aStatus) michael@0: { michael@0: // Drop the listener michael@0: nsCOMPtr listener; michael@0: listener.swap(mListener); michael@0: return listener->OnStopRequest(this, aContext, aStatus); michael@0: } michael@0: michael@0: void nsPartChannel::SetContentDisposition(const nsACString& aContentDispositionHeader) michael@0: { michael@0: mContentDispositionHeader = aContentDispositionHeader; michael@0: nsCOMPtr uri; michael@0: GetURI(getter_AddRefs(uri)); michael@0: NS_GetFilenameFromDisposition(mContentDispositionFilename, michael@0: mContentDispositionHeader, uri); michael@0: mContentDisposition = NS_GetContentDispositionFromHeader(mContentDispositionHeader, this); michael@0: } michael@0: michael@0: // michael@0: // nsISupports implementation... michael@0: // michael@0: michael@0: NS_IMPL_ADDREF(nsPartChannel) michael@0: NS_IMPL_RELEASE(nsPartChannel) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(nsPartChannel) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIChannel) michael@0: NS_INTERFACE_MAP_ENTRY(nsIRequest) michael@0: NS_INTERFACE_MAP_ENTRY(nsIChannel) michael@0: NS_INTERFACE_MAP_ENTRY(nsIByteRangeRequest) michael@0: NS_INTERFACE_MAP_ENTRY(nsIMultiPartChannel) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: // michael@0: // nsIRequest implementation... michael@0: // michael@0: michael@0: NS_IMETHODIMP michael@0: nsPartChannel::GetName(nsACString &aResult) michael@0: { michael@0: return mMultipartChannel->GetName(aResult); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPartChannel::IsPending(bool *aResult) michael@0: { michael@0: // For now, consider the active lifetime of each part the same as michael@0: // the underlying multipart channel... This is not exactly right, michael@0: // but it is good enough :-) michael@0: return mMultipartChannel->IsPending(aResult); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPartChannel::GetStatus(nsresult *aResult) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: michael@0: if (NS_FAILED(mStatus)) { michael@0: *aResult = mStatus; michael@0: } else { michael@0: rv = mMultipartChannel->GetStatus(aResult); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPartChannel::Cancel(nsresult aStatus) michael@0: { michael@0: // Cancelling an individual part must not cancel the underlying michael@0: // multipart channel... michael@0: // XXX but we should stop sending data for _this_ part channel! michael@0: mStatus = aStatus; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPartChannel::Suspend(void) michael@0: { michael@0: // Suspending an individual part must not suspend the underlying michael@0: // multipart channel... michael@0: // XXX why not? michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPartChannel::Resume(void) michael@0: { michael@0: // Resuming an individual part must not resume the underlying michael@0: // multipart channel... michael@0: // XXX why not? michael@0: return NS_OK; michael@0: } michael@0: michael@0: // michael@0: // nsIChannel implementation michael@0: // michael@0: michael@0: NS_IMETHODIMP michael@0: nsPartChannel::GetOriginalURI(nsIURI * *aURI) michael@0: { michael@0: return mMultipartChannel->GetOriginalURI(aURI); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPartChannel::SetOriginalURI(nsIURI *aURI) michael@0: { michael@0: return mMultipartChannel->SetOriginalURI(aURI); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPartChannel::GetURI(nsIURI * *aURI) michael@0: { michael@0: return mMultipartChannel->GetURI(aURI); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPartChannel::Open(nsIInputStream **result) michael@0: { michael@0: // This channel cannot be opened! michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPartChannel::AsyncOpen(nsIStreamListener *aListener, nsISupports *aContext) michael@0: { michael@0: // This channel cannot be opened! michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPartChannel::GetLoadFlags(nsLoadFlags *aLoadFlags) michael@0: { michael@0: *aLoadFlags = mLoadFlags; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPartChannel::SetLoadFlags(nsLoadFlags aLoadFlags) michael@0: { michael@0: mLoadFlags = aLoadFlags; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPartChannel::GetLoadGroup(nsILoadGroup* *aLoadGroup) michael@0: { michael@0: *aLoadGroup = mLoadGroup; michael@0: NS_IF_ADDREF(*aLoadGroup); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPartChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) michael@0: { michael@0: mLoadGroup = aLoadGroup; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPartChannel::GetOwner(nsISupports* *aOwner) michael@0: { michael@0: return mMultipartChannel->GetOwner(aOwner); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPartChannel::SetOwner(nsISupports* aOwner) michael@0: { michael@0: return mMultipartChannel->SetOwner(aOwner); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPartChannel::GetNotificationCallbacks(nsIInterfaceRequestor* *aCallbacks) michael@0: { michael@0: return mMultipartChannel->GetNotificationCallbacks(aCallbacks); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPartChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks) michael@0: { michael@0: return mMultipartChannel->SetNotificationCallbacks(aCallbacks); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPartChannel::GetSecurityInfo(nsISupports * *aSecurityInfo) michael@0: { michael@0: return mMultipartChannel->GetSecurityInfo(aSecurityInfo); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPartChannel::GetContentType(nsACString &aContentType) michael@0: { michael@0: aContentType = mContentType; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPartChannel::SetContentType(const nsACString &aContentType) michael@0: { michael@0: bool dummy; michael@0: net_ParseContentType(aContentType, mContentType, mContentCharset, &dummy); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPartChannel::GetContentCharset(nsACString &aContentCharset) michael@0: { michael@0: aContentCharset = mContentCharset; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPartChannel::SetContentCharset(const nsACString &aContentCharset) michael@0: { michael@0: mContentCharset = aContentCharset; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPartChannel::GetContentLength(int64_t *aContentLength) michael@0: { michael@0: *aContentLength = mContentLength; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPartChannel::SetContentLength(int64_t aContentLength) michael@0: { michael@0: mContentLength = aContentLength; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPartChannel::GetContentDisposition(uint32_t *aContentDisposition) michael@0: { michael@0: if (mContentDispositionHeader.IsEmpty()) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: *aContentDisposition = mContentDisposition; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPartChannel::SetContentDisposition(uint32_t aContentDisposition) michael@0: { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPartChannel::GetContentDispositionFilename(nsAString &aContentDispositionFilename) michael@0: { michael@0: if (mContentDispositionFilename.IsEmpty()) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: aContentDispositionFilename = mContentDispositionFilename; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPartChannel::SetContentDispositionFilename(const nsAString &aContentDispositionFilename) michael@0: { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsPartChannel::GetContentDispositionHeader(nsACString &aContentDispositionHeader) michael@0: { michael@0: if (mContentDispositionHeader.IsEmpty()) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: aContentDispositionHeader = mContentDispositionHeader; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPartChannel::GetPartID(uint32_t *aPartID) michael@0: { michael@0: *aPartID = mPartID; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPartChannel::GetIsLastPart(bool *aIsLastPart) michael@0: { michael@0: *aIsLastPart = mIsLastPart; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // michael@0: // nsIByteRangeRequest implementation... michael@0: // michael@0: michael@0: NS_IMETHODIMP michael@0: nsPartChannel::GetIsByteRangeRequest(bool *aIsByteRangeRequest) michael@0: { michael@0: *aIsByteRangeRequest = mIsByteRangeRequest; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsPartChannel::GetStartRange(int64_t *aStartRange) michael@0: { michael@0: *aStartRange = mByteRangeStart; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPartChannel::GetEndRange(int64_t *aEndRange) michael@0: { michael@0: *aEndRange = mByteRangeEnd; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPartChannel::GetBaseChannel(nsIChannel ** aReturn) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aReturn); michael@0: michael@0: *aReturn = mMultipartChannel; michael@0: NS_IF_ADDREF(*aReturn); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: // nsISupports implementation michael@0: NS_IMPL_ISUPPORTS(nsMultiMixedConv, michael@0: nsIStreamConverter, michael@0: nsIStreamListener, michael@0: nsIRequestObserver) michael@0: michael@0: michael@0: // nsIStreamConverter implementation michael@0: michael@0: // No syncronous conversion at this time. michael@0: NS_IMETHODIMP michael@0: nsMultiMixedConv::Convert(nsIInputStream *aFromStream, michael@0: const char *aFromType, michael@0: const char *aToType, michael@0: nsISupports *aCtxt, nsIInputStream **_retval) { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: // Stream converter service calls this to initialize the actual stream converter (us). michael@0: NS_IMETHODIMP michael@0: nsMultiMixedConv::AsyncConvertData(const char *aFromType, const char *aToType, michael@0: nsIStreamListener *aListener, nsISupports *aCtxt) { michael@0: NS_ASSERTION(aListener && aFromType && aToType, "null pointer passed into multi mixed converter"); michael@0: michael@0: // hook up our final listener. this guy gets the various On*() calls we want to throw michael@0: // at him. michael@0: // michael@0: // WARNING: this listener must be able to handle multiple OnStartRequest, OnDataAvail() michael@0: // and OnStopRequest() call combinations. We call of series of these for each sub-part michael@0: // in the raw stream. michael@0: mFinalListener = aListener; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // AutoFree implementation to prevent memory leaks michael@0: class AutoFree michael@0: { michael@0: public: michael@0: AutoFree() : mBuffer(nullptr) {} michael@0: michael@0: AutoFree(char *buffer) : mBuffer(buffer) {} michael@0: michael@0: ~AutoFree() { michael@0: free(mBuffer); michael@0: } michael@0: michael@0: AutoFree& operator=(char *buffer) { michael@0: mBuffer = buffer; michael@0: return *this; michael@0: } michael@0: michael@0: operator char*() const { michael@0: return mBuffer; michael@0: } michael@0: private: michael@0: char *mBuffer; michael@0: }; michael@0: michael@0: // nsIStreamListener implementation michael@0: NS_IMETHODIMP michael@0: nsMultiMixedConv::OnDataAvailable(nsIRequest *request, nsISupports *context, michael@0: nsIInputStream *inStr, uint64_t sourceOffset, michael@0: uint32_t count) { michael@0: michael@0: if (mToken.IsEmpty()) // no token, no love. michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsresult rv = NS_OK; michael@0: AutoFree buffer = nullptr; michael@0: uint32_t bufLen = 0, read = 0; michael@0: michael@0: NS_ASSERTION(request, "multimixed converter needs a request"); michael@0: michael@0: nsCOMPtr channel = do_QueryInterface(request, &rv); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // fill buffer michael@0: { michael@0: bufLen = count + mBufLen; michael@0: NS_ENSURE_TRUE((bufLen >= count) && (bufLen >= mBufLen), michael@0: NS_ERROR_FAILURE); michael@0: buffer = (char *) malloc(bufLen); michael@0: if (!buffer) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: if (mBufLen) { michael@0: // incorporate any buffered data into the parsing michael@0: memcpy(buffer, mBuffer, mBufLen); michael@0: free(mBuffer); michael@0: mBuffer = 0; michael@0: mBufLen = 0; michael@0: } michael@0: michael@0: rv = inStr->Read(buffer + (bufLen - count), count, &read); michael@0: michael@0: if (NS_FAILED(rv) || read == 0) return rv; michael@0: NS_ASSERTION(read == count, "poor data size assumption"); michael@0: } michael@0: michael@0: char *cursor = buffer; michael@0: michael@0: if (mFirstOnData) { michael@0: // this is the first OnData() for this request. some servers michael@0: // don't bother sending a token in the first "part." This is michael@0: // illegal, but we'll handle the case anyway by shoving the michael@0: // boundary token in for the server. michael@0: mFirstOnData = false; michael@0: NS_ASSERTION(!mBufLen, "this is our first time through, we can't have buffered data"); michael@0: const char * token = mToken.get(); michael@0: michael@0: PushOverLine(cursor, bufLen); michael@0: michael@0: if (bufLen < mTokenLen+2) { michael@0: // we don't have enough data yet to make this comparison. michael@0: // skip this check, and try again the next time OnData() michael@0: // is called. michael@0: mFirstOnData = true; michael@0: } michael@0: else if (!PL_strnstr(cursor, token, mTokenLen+2)) { michael@0: buffer = (char *) realloc(buffer, bufLen + mTokenLen + 1); michael@0: if (!buffer) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: memmove(buffer + mTokenLen + 1, buffer, bufLen); michael@0: memcpy(buffer, token, mTokenLen); michael@0: buffer[mTokenLen] = '\n'; michael@0: michael@0: bufLen += (mTokenLen + 1); michael@0: michael@0: // need to reset cursor to the buffer again (bug 100595) michael@0: cursor = buffer; michael@0: } michael@0: } michael@0: michael@0: char *token = nullptr; michael@0: michael@0: if (mProcessingHeaders) { michael@0: // we were not able to process all the headers michael@0: // for this "part" given the previous buffer given to michael@0: // us in the previous OnDataAvailable callback. michael@0: bool done = false; michael@0: rv = ParseHeaders(channel, cursor, bufLen, &done); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: if (done) { michael@0: mProcessingHeaders = false; michael@0: rv = SendStart(channel); michael@0: if (NS_FAILED(rv)) return rv; michael@0: } michael@0: } michael@0: michael@0: int32_t tokenLinefeed = 1; michael@0: while ( (token = FindToken(cursor, bufLen)) ) { michael@0: michael@0: if (((token + mTokenLen) < (cursor + bufLen)) && michael@0: (*(token + mTokenLen + 1) == '-')) { michael@0: // This was the last delimiter so we can stop processing michael@0: rv = SendData(cursor, LengthToToken(cursor, token)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: return SendStop(NS_OK); michael@0: } michael@0: michael@0: if (!mNewPart && token > cursor) { michael@0: // headers are processed, we're pushing data now. michael@0: NS_ASSERTION(!mProcessingHeaders, "we should be pushing raw data"); michael@0: rv = SendData(cursor, LengthToToken(cursor, token)); michael@0: bufLen -= token - cursor; michael@0: if (NS_FAILED(rv)) return rv; michael@0: } michael@0: // XXX else NS_ASSERTION(token == cursor, "?"); michael@0: token += mTokenLen; michael@0: bufLen -= mTokenLen; michael@0: tokenLinefeed = PushOverLine(token, bufLen); michael@0: michael@0: if (mNewPart) { michael@0: // parse headers michael@0: mNewPart = false; michael@0: cursor = token; michael@0: bool done = false; michael@0: rv = ParseHeaders(channel, cursor, bufLen, &done); michael@0: if (NS_FAILED(rv)) return rv; michael@0: if (done) { michael@0: rv = SendStart(channel); michael@0: if (NS_FAILED(rv)) return rv; michael@0: } michael@0: else { michael@0: // we haven't finished processing header info. michael@0: // we'll break out and try to process later. michael@0: mProcessingHeaders = true; michael@0: break; michael@0: } michael@0: } michael@0: else { michael@0: mNewPart = true; michael@0: // Reset state so we don't carry it over from part to part michael@0: mContentType.Truncate(); michael@0: mContentLength = UINT64_MAX; michael@0: mContentDisposition.Truncate(); michael@0: mIsByteRangeRequest = false; michael@0: mByteRangeStart = 0; michael@0: mByteRangeEnd = 0; michael@0: michael@0: rv = SendStop(NS_OK); michael@0: if (NS_FAILED(rv)) return rv; michael@0: // reset the token to front. this allows us to treat michael@0: // the token as a starting token. michael@0: token -= mTokenLen + tokenLinefeed; michael@0: bufLen += mTokenLen + tokenLinefeed; michael@0: cursor = token; michael@0: } michael@0: } michael@0: michael@0: // at this point, we want to buffer up whatever amount (bufLen) michael@0: // we have leftover. However, we *always* want to ensure that michael@0: // we buffer enough data to handle a broken token. michael@0: michael@0: // carry over michael@0: uint32_t bufAmt = 0; michael@0: if (mProcessingHeaders) michael@0: bufAmt = bufLen; michael@0: else if (bufLen) { michael@0: // if the data ends in a linefeed, and we're in the middle michael@0: // of a "part" (ie. mPartChannel exists) don't bother michael@0: // buffering, go ahead and send the data we have. Otherwise michael@0: // if we don't have a channel already, then we don't even michael@0: // have enough info to start a part, go ahead and buffer michael@0: // enough to collect a boundary token. michael@0: if (!mPartChannel || !(cursor[bufLen-1] == nsCRT::LF) ) michael@0: bufAmt = std::min(mTokenLen - 1, bufLen); michael@0: } michael@0: michael@0: if (bufAmt) { michael@0: rv = BufferData(cursor + (bufLen - bufAmt), bufAmt); michael@0: if (NS_FAILED(rv)) return rv; michael@0: bufLen -= bufAmt; michael@0: } michael@0: michael@0: if (bufLen) { michael@0: rv = SendData(cursor, bufLen); michael@0: if (NS_FAILED(rv)) return rv; michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: // nsIRequestObserver implementation michael@0: NS_IMETHODIMP michael@0: nsMultiMixedConv::OnStartRequest(nsIRequest *request, nsISupports *ctxt) { michael@0: // we're assuming the content-type is available at this stage michael@0: NS_ASSERTION(mToken.IsEmpty(), "a second on start???"); michael@0: const char *bndry = nullptr; michael@0: nsAutoCString delimiter; michael@0: nsresult rv = NS_OK; michael@0: mContext = ctxt; michael@0: michael@0: mFirstOnData = true; michael@0: mTotalSent = 0; michael@0: michael@0: nsCOMPtr channel = do_QueryInterface(request, &rv); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // ask the HTTP channel for the content-type and extract the boundary from it. michael@0: nsCOMPtr httpChannel = do_QueryInterface(channel, &rv); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("content-type"), delimiter); michael@0: if (NS_FAILED(rv)) return rv; michael@0: } else { michael@0: // try asking the channel directly michael@0: rv = channel->GetContentType(delimiter); michael@0: if (NS_FAILED(rv)) return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: bndry = strstr(delimiter.BeginWriting(), "boundary"); michael@0: if (!bndry) return NS_ERROR_FAILURE; michael@0: michael@0: bndry = strchr(bndry, '='); michael@0: if (!bndry) return NS_ERROR_FAILURE; michael@0: michael@0: bndry++; // move past the equals sign michael@0: michael@0: char *attrib = (char *) strchr(bndry, ';'); michael@0: if (attrib) *attrib = '\0'; michael@0: michael@0: nsAutoCString boundaryString(bndry); michael@0: if (attrib) *attrib = ';'; michael@0: michael@0: boundaryString.Trim(" \""); michael@0: michael@0: mToken = boundaryString; michael@0: mTokenLen = boundaryString.Length(); michael@0: michael@0: if (mTokenLen == 0) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMultiMixedConv::OnStopRequest(nsIRequest *request, nsISupports *ctxt, michael@0: nsresult aStatus) { michael@0: michael@0: if (mToken.IsEmpty()) // no token, no love. michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: if (mPartChannel) { michael@0: mPartChannel->SetIsLastPart(); michael@0: michael@0: // we've already called SendStart() (which sets up the mPartChannel, michael@0: // and fires an OnStart()) send any data left over, and then fire the stop. michael@0: if (mBufLen > 0 && mBuffer) { michael@0: (void) SendData(mBuffer, mBufLen); michael@0: // don't bother checking the return value here, if the send failed michael@0: // we're done anyway as we're in the OnStop() callback. michael@0: free(mBuffer); michael@0: mBuffer = nullptr; michael@0: mBufLen = 0; michael@0: } michael@0: (void) SendStop(aStatus); michael@0: } else if (NS_FAILED(aStatus)) { michael@0: // underlying data production problem. we should not be in michael@0: // the middle of sending data. if we were, mPartChannel, michael@0: // above, would have been true. michael@0: michael@0: // if we send the start, the URI Loader's m_targetStreamListener, may michael@0: // be pointing at us causing a nice stack overflow. So, don't call michael@0: // OnStartRequest! - This breaks necko's semantecs. michael@0: //(void) mFinalListener->OnStartRequest(request, ctxt); michael@0: michael@0: (void) mFinalListener->OnStopRequest(request, ctxt, aStatus); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: // nsMultiMixedConv methods michael@0: nsMultiMixedConv::nsMultiMixedConv() : michael@0: mCurrentPartID(0) michael@0: { michael@0: mTokenLen = 0; michael@0: mNewPart = true; michael@0: mContentLength = UINT64_MAX; michael@0: mBuffer = nullptr; michael@0: mBufLen = 0; michael@0: mProcessingHeaders = false; michael@0: mByteRangeStart = 0; michael@0: mByteRangeEnd = 0; michael@0: mTotalSent = 0; michael@0: mIsByteRangeRequest = false; michael@0: } michael@0: michael@0: nsMultiMixedConv::~nsMultiMixedConv() { michael@0: NS_ASSERTION(!mBuffer, "all buffered data should be gone"); michael@0: if (mBuffer) { michael@0: free(mBuffer); michael@0: mBuffer = nullptr; michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsMultiMixedConv::BufferData(char *aData, uint32_t aLen) { michael@0: NS_ASSERTION(!mBuffer, "trying to over-write buffer"); michael@0: michael@0: char *buffer = (char *) malloc(aLen); michael@0: if (!buffer) return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: memcpy(buffer, aData, aLen); michael@0: mBuffer = buffer; michael@0: mBufLen = aLen; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsMultiMixedConv::SendStart(nsIChannel *aChannel) { michael@0: nsresult rv = NS_OK; michael@0: michael@0: nsCOMPtr partListener(mFinalListener); michael@0: if (mContentType.IsEmpty()) { michael@0: mContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE); michael@0: nsCOMPtr serv = michael@0: do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: nsCOMPtr converter; michael@0: rv = serv->AsyncConvertData(UNKNOWN_CONTENT_TYPE, michael@0: "*/*", michael@0: mFinalListener, michael@0: mContext, michael@0: getter_AddRefs(converter)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: partListener = converter; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // if we already have an mPartChannel, that means we never sent a Stop() michael@0: // before starting up another "part." that would be bad. michael@0: NS_ASSERTION(!mPartChannel, "tisk tisk, shouldn't be overwriting a channel"); michael@0: michael@0: nsPartChannel *newChannel; michael@0: newChannel = new nsPartChannel(aChannel, mCurrentPartID++, partListener); michael@0: if (!newChannel) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: if (mIsByteRangeRequest) { michael@0: newChannel->InitializeByteRange(mByteRangeStart, mByteRangeEnd); michael@0: } michael@0: michael@0: mTotalSent = 0; michael@0: michael@0: // Set up the new part channel... michael@0: mPartChannel = newChannel; michael@0: michael@0: rv = mPartChannel->SetContentType(mContentType); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = mPartChannel->SetContentLength(mContentLength); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: mPartChannel->SetContentDisposition(mContentDisposition); michael@0: michael@0: nsLoadFlags loadFlags = 0; michael@0: mPartChannel->GetLoadFlags(&loadFlags); michael@0: loadFlags |= nsIChannel::LOAD_REPLACE; michael@0: mPartChannel->SetLoadFlags(loadFlags); michael@0: michael@0: nsCOMPtr loadGroup; michael@0: (void)mPartChannel->GetLoadGroup(getter_AddRefs(loadGroup)); michael@0: michael@0: // Add the new channel to the load group (if any) michael@0: if (loadGroup) { michael@0: rv = loadGroup->AddRequest(mPartChannel, nullptr); michael@0: if (NS_FAILED(rv)) return rv; michael@0: } michael@0: michael@0: // Let's start off the load. NOTE: we don't forward on the channel passed michael@0: // into our OnDataAvailable() as it's the root channel for the raw stream. michael@0: return mPartChannel->SendOnStartRequest(mContext); michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsMultiMixedConv::SendStop(nsresult aStatus) { michael@0: michael@0: nsresult rv = NS_OK; michael@0: if (mPartChannel) { michael@0: rv = mPartChannel->SendOnStopRequest(mContext, aStatus); michael@0: // don't check for failure here, we need to remove the channel from michael@0: // the loadgroup. michael@0: michael@0: // Remove the channel from its load group (if any) michael@0: nsCOMPtr loadGroup; michael@0: (void) mPartChannel->GetLoadGroup(getter_AddRefs(loadGroup)); michael@0: if (loadGroup) michael@0: (void) loadGroup->RemoveRequest(mPartChannel, mContext, aStatus); michael@0: } michael@0: michael@0: mPartChannel = 0; michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsMultiMixedConv::SendData(char *aBuffer, uint32_t aLen) { michael@0: michael@0: nsresult rv = NS_OK; michael@0: michael@0: if (!mPartChannel) return NS_ERROR_FAILURE; // something went wrong w/ processing michael@0: michael@0: if (mContentLength != UINT64_MAX) { michael@0: // make sure that we don't send more than the mContentLength michael@0: // XXX why? perhaps the Content-Length header was actually wrong!! michael@0: if ((uint64_t(aLen) + mTotalSent) > mContentLength) michael@0: aLen = static_cast(mContentLength - mTotalSent); michael@0: michael@0: if (aLen == 0) michael@0: return NS_OK; michael@0: } michael@0: michael@0: uint64_t offset = mTotalSent; michael@0: mTotalSent += aLen; michael@0: michael@0: nsCOMPtr ss( michael@0: do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: rv = ss->ShareData(aBuffer, aLen); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: nsCOMPtr inStream(do_QueryInterface(ss, &rv)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: return mPartChannel->SendOnDataAvailable(mContext, inStream, offset, aLen); michael@0: } michael@0: michael@0: int32_t michael@0: nsMultiMixedConv::PushOverLine(char *&aPtr, uint32_t &aLen) { michael@0: int32_t chars = 0; michael@0: if ((aLen > 0) && (*aPtr == nsCRT::CR || *aPtr == nsCRT::LF)) { michael@0: if ((aLen > 1) && (aPtr[1] == nsCRT::LF)) michael@0: chars++; michael@0: chars++; michael@0: aPtr += chars; michael@0: aLen -= chars; michael@0: } michael@0: return chars; michael@0: } michael@0: michael@0: nsresult michael@0: nsMultiMixedConv::ParseHeaders(nsIChannel *aChannel, char *&aPtr, michael@0: uint32_t &aLen, bool *_retval) { michael@0: // NOTE: this data must be ascii. michael@0: // NOTE: aPtr is NOT null terminated! michael@0: nsresult rv = NS_OK; michael@0: char *cursor = aPtr, *newLine = nullptr; michael@0: uint32_t cursorLen = aLen; michael@0: bool done = false; michael@0: uint32_t lineFeedIncrement = 1; michael@0: michael@0: mContentLength = UINT64_MAX; // XXX what if we were already called? michael@0: while (cursorLen && (newLine = (char *) memchr(cursor, nsCRT::LF, cursorLen))) { michael@0: // adjust for linefeeds michael@0: if ((newLine > cursor) && (newLine[-1] == nsCRT::CR) ) { // CRLF michael@0: lineFeedIncrement = 2; michael@0: newLine--; michael@0: } michael@0: else michael@0: lineFeedIncrement = 1; // reset michael@0: michael@0: if (newLine == cursor) { michael@0: // move the newLine beyond the linefeed marker michael@0: NS_ASSERTION(cursorLen >= lineFeedIncrement, "oops!"); michael@0: michael@0: cursor += lineFeedIncrement; michael@0: cursorLen -= lineFeedIncrement; michael@0: michael@0: done = true; michael@0: break; michael@0: } michael@0: michael@0: char tmpChar = *newLine; michael@0: *newLine = '\0'; // cursor is now null terminated michael@0: char *colon = (char *) strchr(cursor, ':'); michael@0: if (colon) { michael@0: *colon = '\0'; michael@0: nsAutoCString headerStr(cursor); michael@0: headerStr.CompressWhitespace(); michael@0: *colon = ':'; michael@0: michael@0: nsAutoCString headerVal(colon + 1); michael@0: headerVal.CompressWhitespace(); michael@0: michael@0: // examine header michael@0: if (headerStr.LowerCaseEqualsLiteral("content-type")) { michael@0: mContentType = headerVal; michael@0: } else if (headerStr.LowerCaseEqualsLiteral("content-length")) { michael@0: mContentLength = nsCRT::atoll(headerVal.get()); michael@0: } else if (headerStr.LowerCaseEqualsLiteral("content-disposition")) { michael@0: mContentDisposition = headerVal; michael@0: } else if (headerStr.LowerCaseEqualsLiteral("set-cookie")) { michael@0: nsCOMPtr httpInternal = michael@0: do_QueryInterface(aChannel); michael@0: if (httpInternal) { michael@0: httpInternal->SetCookie(headerVal.get()); michael@0: } michael@0: } else if (headerStr.LowerCaseEqualsLiteral("content-range") || michael@0: headerStr.LowerCaseEqualsLiteral("range") ) { michael@0: // something like: Content-range: bytes 7000-7999/8000 michael@0: char* tmpPtr; michael@0: michael@0: tmpPtr = (char *) strchr(colon + 1, '/'); michael@0: if (tmpPtr) michael@0: *tmpPtr = '\0'; michael@0: michael@0: // pass the bytes-unit and the SP michael@0: char *range = (char *) strchr(colon + 2, ' '); michael@0: if (!range) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: do { michael@0: range++; michael@0: } while (*range == ' '); michael@0: michael@0: if (range[0] == '*'){ michael@0: mByteRangeStart = mByteRangeEnd = 0; michael@0: } michael@0: else { michael@0: tmpPtr = (char *) strchr(range, '-'); michael@0: if (!tmpPtr) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: tmpPtr[0] = '\0'; michael@0: michael@0: mByteRangeStart = nsCRT::atoll(range); michael@0: tmpPtr++; michael@0: mByteRangeEnd = nsCRT::atoll(tmpPtr); michael@0: } michael@0: michael@0: mIsByteRangeRequest = true; michael@0: if (mContentLength == UINT64_MAX) michael@0: mContentLength = uint64_t(mByteRangeEnd - mByteRangeStart + 1); michael@0: } michael@0: } michael@0: *newLine = tmpChar; michael@0: newLine += lineFeedIncrement; michael@0: cursorLen -= (newLine - cursor); michael@0: cursor = newLine; michael@0: } michael@0: michael@0: aPtr = cursor; michael@0: aLen = cursorLen; michael@0: michael@0: *_retval = done; michael@0: return rv; michael@0: } michael@0: michael@0: char * michael@0: nsMultiMixedConv::FindToken(char *aCursor, uint32_t aLen) { michael@0: // strnstr without looking for null termination michael@0: const char *token = mToken.get(); michael@0: char *cur = aCursor; michael@0: michael@0: if (!(token && aCursor && *token)) { michael@0: NS_WARNING("bad data"); michael@0: return nullptr; michael@0: } michael@0: michael@0: for (; aLen >= mTokenLen; aCursor++, aLen--) { michael@0: if (!memcmp(aCursor, token, mTokenLen) ) { michael@0: if ((aCursor - cur) >= 2) { michael@0: // back the cursor up over a double dash for backwards compat. michael@0: if ((*(aCursor-1) == '-') && (*(aCursor-2) == '-')) { michael@0: aCursor -= 2; michael@0: aLen += 2; michael@0: michael@0: // we're playing w/ double dash tokens, adjust. michael@0: mToken.Assign(aCursor, mTokenLen + 2); michael@0: mTokenLen = mToken.Length(); michael@0: } michael@0: } michael@0: return aCursor; michael@0: } michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: nsresult michael@0: NS_NewMultiMixedConv(nsMultiMixedConv** aMultiMixedConv) michael@0: { michael@0: NS_PRECONDITION(aMultiMixedConv != nullptr, "null ptr"); michael@0: if (! aMultiMixedConv) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: *aMultiMixedConv = new nsMultiMixedConv(); michael@0: if (! *aMultiMixedConv) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: NS_ADDREF(*aMultiMixedConv); michael@0: return NS_OK; michael@0: } michael@0: