зеркало из https://github.com/mozilla/gecko-dev.git
466 строки
17 KiB
C++
466 строки
17 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
*
|
|
* The contents of this file are subject to the Netscape Public
|
|
* License Version 1.1 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.mozilla.org/NPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS
|
|
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
|
* implied. See the License for the specific language governing
|
|
* rights and limitations under the License.
|
|
*
|
|
* The Original Code is mozilla.org code.
|
|
*
|
|
* The Initial Developer of the Original Code is Netscape
|
|
* Communications Corporation. Portions created by Netscape are
|
|
* Copyright (C) 1998 Netscape Communications Corporation. All
|
|
* Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
*/
|
|
|
|
#include "nsMultiMixedConv.h"
|
|
#include "nsIAllocator.h"
|
|
#include "plstr.h"
|
|
#include "nsIStringStream.h"
|
|
#include "nsIHTTPChannel.h"
|
|
#include "nsIAtom.h"
|
|
#include "nsIServiceManager.h"
|
|
#include "nsIGenericFactory.h"
|
|
#include "nsCOMPtr.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsXPIDLString.h"
|
|
static NS_DEFINE_CID(kComponentManagerCID, NS_COMPONENTMANAGER_CID);
|
|
static NS_DEFINE_CID(kIOServiceCID, NS_IOSERVICE_CID);
|
|
|
|
// other converters for the converter export factory fuctions
|
|
#include "nsFTPDirListingConv.h"
|
|
|
|
|
|
// nsISupports implementation
|
|
NS_IMPL_ISUPPORTS3(nsMultiMixedConv, nsIStreamConverter, nsIStreamListener, nsIStreamObserver);
|
|
|
|
|
|
// nsIStreamConverter implementation
|
|
|
|
// No syncronous conversion at this time.
|
|
NS_IMETHODIMP
|
|
nsMultiMixedConv::Convert(nsIInputStream *aFromStream,
|
|
const PRUnichar *aFromType,
|
|
const PRUnichar *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 PRUnichar *aFromType, const PRUnichar *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;
|
|
}
|
|
|
|
// nsIStreamListener implementation
|
|
NS_IMETHODIMP
|
|
nsMultiMixedConv::OnDataAvailable(nsIChannel *channel, nsISupports *context,
|
|
nsIInputStream *inStr, PRUint32 sourceOffset, PRUint32 count) {
|
|
nsresult rv;
|
|
char *buffer = nsnull, *rootMemPtr = nsnull;
|
|
PRUint32 bufLen, read;
|
|
|
|
NS_ASSERTION(channel, "multimixed converter needs a channel");
|
|
|
|
if (!mBoundaryCStr) {
|
|
char *bndry = nsnull;
|
|
nsXPIDLCString delimiter;
|
|
|
|
// ask the HTTP channel for the content-type and extract the boundary from it.
|
|
nsCOMPtr<nsIHTTPChannel> httpChannel;
|
|
rv = channel->QueryInterface(NS_GET_IID(nsIHTTPChannel), getter_AddRefs(httpChannel));
|
|
if (NS_SUCCEEDED(rv)) {
|
|
nsCOMPtr<nsIAtom> header = NS_NewAtom("content-type");
|
|
if (!header) return NS_ERROR_OUT_OF_MEMORY;
|
|
rv = httpChannel->GetResponseHeader(header, getter_Copies(delimiter));
|
|
if (NS_FAILED(rv)) return rv;
|
|
} else {
|
|
// try asking the channel directly
|
|
rv = channel->GetContentType(getter_Copies(delimiter));
|
|
if (NS_FAILED(rv)) return rv;
|
|
}
|
|
if (!delimiter) return NS_ERROR_FAILURE;
|
|
bndry = PL_strstr(delimiter, "boundary");
|
|
if (!bndry) return NS_ERROR_FAILURE;
|
|
|
|
bndry = PL_strchr(bndry, '=');
|
|
if (!bndry) return NS_ERROR_FAILURE;
|
|
|
|
bndry++; // move past the equals sign
|
|
|
|
nsCAutoString boundaryString(bndry);
|
|
boundaryString.StripWhitespace();
|
|
// we're not using the beginning "--" to delimit boundaries
|
|
// so just make them part of the boundary delimiter.
|
|
boundaryString.Insert("--", 0, 2); ;
|
|
mBoundaryCStr = boundaryString.ToNewCString();
|
|
if (!mBoundaryCStr) return NS_ERROR_OUT_OF_MEMORY;
|
|
mBoundaryStrLen = boundaryString.Length();
|
|
}
|
|
|
|
|
|
rv = inStr->Available(&bufLen);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
rootMemPtr = buffer = (char*)nsAllocator::Alloc(bufLen + 1);
|
|
if (!buffer) return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
rv = inStr->Read(buffer, bufLen, &read);
|
|
if (NS_FAILED(rv) || read == 0) return rv;
|
|
buffer[bufLen] = '\0';
|
|
|
|
if (mBufferedData.Length() > 0) {
|
|
mBufferedData.Append(buffer);
|
|
nsAllocator::Free(buffer);
|
|
buffer = mBufferedData.ToNewCString();
|
|
}
|
|
char *cursor = buffer;
|
|
PRBool done = PR_FALSE;
|
|
|
|
while (cursor) {
|
|
char *boundary = PL_strstr(cursor, mBoundaryCStr);
|
|
if (boundary) {
|
|
mFoundBoundary = PR_TRUE;
|
|
if (cursor == boundary) {
|
|
if (!mNewPart) {
|
|
// we were processing a part and ran into a boundary
|
|
// thus that makes it an ending boundary. finish this
|
|
// part and move on.
|
|
rv = mFinalListener->OnStopRequest(mPartChannel, context, NS_OK, nsnull);
|
|
if (NS_FAILED(rv)) break;
|
|
|
|
// Remove the channel from its load group (if any)
|
|
nsCOMPtr<nsILoadGroup> loadGroup;
|
|
|
|
(void) mPartChannel->GetLoadGroup(getter_AddRefs(loadGroup));
|
|
if (loadGroup) {
|
|
loadGroup->RemoveChannel(mPartChannel, context, NS_OK, nsnull);
|
|
}
|
|
|
|
mPartChannel = 0;
|
|
}
|
|
// the boundary occurs at the beginning of the data.
|
|
// we obviously don't want to set it to null, instead
|
|
// set the cursor to the beginnning of the real data
|
|
// and look for another boundary after that.
|
|
cursor += mBoundaryStrLen;
|
|
if (*cursor == '-') {
|
|
// we've reached the end of the line
|
|
done = PR_TRUE;
|
|
} else {
|
|
// we just passed a boundary, therefore we're guaranteed to be
|
|
// processing a new part.
|
|
mNewPart = PR_TRUE;
|
|
// see if there's another boundary.
|
|
boundary = PL_strstr(cursor, mBoundaryCStr);
|
|
if (boundary) *boundary = '\0';
|
|
}
|
|
|
|
if ( (*cursor == '\r') || (*cursor == '\n') ) {
|
|
if (cursor[1] == '\n')
|
|
cursor++; // if it's a CRLF, move two places.
|
|
cursor++;
|
|
}
|
|
} else {
|
|
*boundary = '\0';
|
|
}
|
|
} else if (!mFoundBoundary) {
|
|
// we haven't seen a boundary yet, buffer this up.
|
|
mBufferedData = cursor;
|
|
break; // XXX do more here.
|
|
}
|
|
if (mNewPart) {
|
|
mLineFeedIncrement = 1;
|
|
// check for a newline char, then figure out if
|
|
// we're dealing w/ CRLFs as line delimiters.
|
|
// unfortunately we can see both :(
|
|
char *newLine = PL_strchr(cursor, '\n');
|
|
if (newLine) {
|
|
if ( (newLine > cursor) && (newLine[-1] == '\r') ) {
|
|
// CRLF
|
|
mLineFeedIncrement = 2;
|
|
newLine--;
|
|
}
|
|
}
|
|
char *headerStart = cursor;
|
|
while (newLine) {
|
|
*newLine = '\0';
|
|
char *colon = PL_strchr(headerStart, ':');
|
|
if (colon) {
|
|
// Header name
|
|
*colon = '\0';
|
|
nsCAutoString headerStr(headerStart);
|
|
headerStr.StripWhitespace();
|
|
headerStr.ToLowerCase();
|
|
nsCOMPtr<nsIAtom> header = NS_NewAtom(headerStr.GetBuffer());
|
|
if (!header) {
|
|
nsAllocator::Free(buffer);
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
*colon = ':';
|
|
// END Header name
|
|
|
|
// Header value
|
|
nsCAutoString headerVal(colon + 1);
|
|
headerVal.StripWhitespace();
|
|
// END Header value
|
|
|
|
// examine header
|
|
if (headerStr.Equals("content-type")) {
|
|
mContentType = headerVal;
|
|
} else if (headerStr.Equals("content-length")) {
|
|
mContentLength = atoi(headerVal);
|
|
} else if (headerStr.Equals("set-cookie")) {
|
|
// setting headers on the HTTP channel
|
|
// causes HTTP to notify, again if necessary,
|
|
// it's header observers.
|
|
nsCOMPtr<nsIHTTPChannel> httpChannel = do_QueryInterface(channel, &rv);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
rv = httpChannel->SetResponseHeader(header, headerVal);
|
|
if (NS_FAILED(rv)) {
|
|
nsAllocator::Free(buffer);
|
|
return rv;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ( (newLine[mLineFeedIncrement] == '\n')
|
|
|| (newLine[mLineFeedIncrement] == '\r')
|
|
|| (newLine == cursor) ) {
|
|
nsCOMPtr<nsILoadGroup> loadGroup;
|
|
|
|
// that's it we've processed all the headers and
|
|
// this is no longer a mNewPart
|
|
mNewPart = PR_FALSE;
|
|
|
|
// move the newLine beyond the double linefeed marker
|
|
newLine += mLineFeedIncrement;
|
|
|
|
// First build up a dummy uri.
|
|
nsCOMPtr<nsIURI> partURI;
|
|
rv = BuildURI(channel, getter_AddRefs(partURI));
|
|
if (NS_FAILED(rv)) {
|
|
nsAllocator::Free(buffer);
|
|
return rv;
|
|
}
|
|
(void) channel->GetLoadGroup(getter_AddRefs(loadGroup));
|
|
|
|
if (mContentType.Length() < 1)
|
|
mContentType = "text/html"; // default to text/html, that's all we'll ever see anyway
|
|
rv = NS_NewInputStreamChannel(partURI, mContentType.GetBuffer(), mContentLength,
|
|
nsnull, // inStr
|
|
loadGroup, // loadGroup
|
|
nsnull, // notificationCallbacks
|
|
nsIChannel::LOAD_NORMAL,
|
|
nsnull, // originalURI
|
|
0, 0,
|
|
getter_AddRefs(mPartChannel));
|
|
if (NS_FAILED(rv)) {
|
|
nsAllocator::Free(buffer);
|
|
return rv;
|
|
}
|
|
|
|
// Add the new channel to the load group (if any)
|
|
if (loadGroup) {
|
|
loadGroup->AddChannel(mPartChannel, nsnull);
|
|
}
|
|
|
|
// 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.
|
|
rv = mFinalListener->OnStartRequest(mPartChannel, context);
|
|
if (NS_FAILED(rv)) {
|
|
nsAllocator::Free(buffer);
|
|
return rv;
|
|
}
|
|
break;
|
|
}
|
|
headerStart = newLine + mLineFeedIncrement;
|
|
newLine = PL_strchr(headerStart, '\n');
|
|
if (newLine) {
|
|
// we're catching LF, LFLF, and CRLF
|
|
if ( (newLine > cursor) && (newLine[-1] == '\r') ) {
|
|
mLineFeedIncrement = 2;
|
|
newLine--;
|
|
}
|
|
}
|
|
} // end while (newLine)
|
|
|
|
if (mNewPart) {
|
|
// we broke out because we have incomplete headers
|
|
// buffer the data and fall out.
|
|
if (*headerStart)
|
|
mBufferedData = headerStart;
|
|
break;
|
|
} else {
|
|
mPartCount++;
|
|
// we broke out because we reached two linefeeds (noting
|
|
// the end of the header section. move the cursor over the
|
|
// linefeeds into the data and start processing it.
|
|
if (cursor == newLine) {
|
|
// all we got was a newline in this read. kick out
|
|
break;
|
|
} else {
|
|
cursor = newLine + mLineFeedIncrement;
|
|
}
|
|
}
|
|
} // end mNewPart
|
|
|
|
// after processing headers, it's possible that cursor points to
|
|
// a null byte. If it does don't send it off because it's meaningless.
|
|
if (!*cursor)
|
|
break; // XXX more to do here.
|
|
|
|
// Check for a completed part
|
|
if (!done) {
|
|
// If we've gotten this far we know we're processing a hunk of data
|
|
// and that all the headers for this part have been processed.
|
|
// Just send off the data.
|
|
SendData(cursor, mPartChannel, context);
|
|
}
|
|
|
|
if (boundary) {
|
|
// We know this is the end of a part. Set mNewPart to TRUE
|
|
// so anymore data we receive gets kicked into the mNewPart
|
|
// cycle, tell the listener we're done w/ this part, reset
|
|
// the partChannel (mNewPart will create a new one if we
|
|
// get that far), and see if we're completely done.
|
|
mNewPart = PR_TRUE;
|
|
rv = mFinalListener->OnStopRequest(mPartChannel, context, NS_OK, nsnull);
|
|
if (NS_FAILED(rv)) break;
|
|
|
|
// Remove the channel from its load group (if any)
|
|
nsCOMPtr<nsILoadGroup> loadGroup;
|
|
|
|
(void) mPartChannel->GetLoadGroup(getter_AddRefs(loadGroup));
|
|
if (loadGroup) {
|
|
loadGroup->RemoveChannel(mPartChannel, context, NS_OK, nsnull);
|
|
}
|
|
|
|
mPartChannel = 0; // kill this channel. it's done
|
|
if (done || (*(boundary+mBoundaryStrLen+1) == '-') ) {
|
|
// it's completely over
|
|
break;
|
|
} else {
|
|
cursor = boundary + mBoundaryStrLen;
|
|
}
|
|
if ( (*cursor == '\r') || (*cursor == '\n') ){
|
|
if (cursor[1] == '\n')
|
|
cursor++; // if it's a CRLF move two places.
|
|
cursor++;
|
|
}
|
|
} else {
|
|
// no more data. kick out
|
|
break;
|
|
}
|
|
} // end while (cursor)
|
|
nsAllocator::Free(buffer);
|
|
|
|
return rv;
|
|
}
|
|
|
|
|
|
// nsIStreamObserver implementation
|
|
NS_IMETHODIMP
|
|
nsMultiMixedConv::OnStartRequest(nsIChannel *channel, nsISupports *ctxt) {
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsMultiMixedConv::OnStopRequest(nsIChannel *channel, nsISupports *ctxt,
|
|
nsresult status, const PRUnichar *errorMsg) {
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
// nsMultiMixedConv methods
|
|
nsMultiMixedConv::nsMultiMixedConv() {
|
|
NS_INIT_ISUPPORTS();
|
|
mBoundaryCStr = nsnull;
|
|
mBoundaryStrLen = mPartCount = 0;
|
|
mNewPart = PR_TRUE;
|
|
mFoundBoundary = PR_FALSE;
|
|
mContentLength = -1;
|
|
mLineFeedIncrement = 1;
|
|
}
|
|
|
|
nsMultiMixedConv::~nsMultiMixedConv() {
|
|
if (mBoundaryCStr) nsAllocator::Free(mBoundaryCStr);
|
|
}
|
|
|
|
nsresult
|
|
nsMultiMixedConv::Init() {
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsMultiMixedConv::SendData(const char *aBuffer, nsIChannel *aChannel, nsISupports *aCtxt) {
|
|
|
|
nsresult rv;
|
|
nsCOMPtr<nsISupports> inStreamSup;
|
|
rv = NS_NewStringInputStream(getter_AddRefs(inStreamSup), aBuffer);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsCOMPtr<nsIInputStream> inStream = do_QueryInterface(inStreamSup, &rv);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
PRUint32 len;
|
|
rv = inStream->Available(&len);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
return mFinalListener->OnDataAvailable(aChannel, aCtxt, inStream, 0, len);
|
|
}
|
|
|
|
nsresult
|
|
nsMultiMixedConv::BuildURI(nsIChannel *aChannel, nsIURI **_retval) {
|
|
nsresult rv;
|
|
nsXPIDLCString uriSpec;
|
|
nsCOMPtr<nsIURI> rootURI;
|
|
rv = aChannel->GetURI(getter_AddRefs(rootURI));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
rv = rootURI->GetSpec(getter_Copies(uriSpec));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsCAutoString dummyURIStr(uriSpec);
|
|
dummyURIStr.Append("##");
|
|
dummyURIStr.Append(mPartCount);
|
|
|
|
NS_WITH_SERVICE(nsIIOService, serv, kIOServiceCID, &rv);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
return serv->NewURI(dummyURIStr.GetBuffer(), nsnull, _retval);
|
|
}
|
|
|
|
nsresult
|
|
NS_NewMultiMixedConv(nsMultiMixedConv** aMultiMixedConv)
|
|
{
|
|
NS_PRECONDITION(aMultiMixedConv != nsnull, "null ptr");
|
|
if (! aMultiMixedConv)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
*aMultiMixedConv = new nsMultiMixedConv();
|
|
if (! *aMultiMixedConv)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
NS_ADDREF(*aMultiMixedConv);
|
|
return (*aMultiMixedConv)->Init();
|
|
}
|