/* -*- 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 "nsMemory.h"
#include "plstr.h"
#include "nsIHttpChannel.h"
#include "nsIAtom.h"
#include "nsIServiceManager.h"
#include "nsNetUtil.h"
#include "nsMimeTypes.h"
#include "nsIByteArrayInputStream.h"


//
// nsPartChannel is a "dummy" channel which represents an individual part of
// a multipart/mixed stream...
//
// Instances on this channel are passed out to the consumer through the
// nsIStreamListener interface.
//
class nsPartChannel : public nsIChannel,
                      public nsIByteRangeRequest
{
public:
  nsPartChannel(nsIChannel *aMultipartChannel);

  void InitializeByteRange(PRInt32 aStart, PRInt32 aEnd);

  NS_DECL_ISUPPORTS
  NS_DECL_NSIREQUEST
  NS_DECL_NSICHANNEL
  NS_DECL_NSIBYTERANGEREQUEST

protected:
  virtual ~nsPartChannel();

protected:
  nsCOMPtr<nsIChannel>    mMultipartChannel;
  
  nsresult                mStatus;
  nsLoadFlags             mLoadFlags;

  nsCOMPtr<nsILoadGroup>  mLoadGroup;

  nsCString               mContentType;
  PRInt32                 mContentLength;

  PRBool                  mIsByteRangeRequest;
  PRInt32                 mByteRangeStart;
  PRInt32                 mByteRangeEnd;
};

nsPartChannel::nsPartChannel(nsIChannel *aMultipartChannel) :
  mStatus(NS_OK),
  mContentLength(-1),
  mIsByteRangeRequest(PR_FALSE),
  mByteRangeStart(0),
  mByteRangeEnd(0)
{
    NS_INIT_ISUPPORTS();

    mMultipartChannel = aMultipartChannel;

    // Inherit the load flags from the original channel...
    mMultipartChannel->GetLoadFlags(&mLoadFlags);

    mMultipartChannel->GetLoadGroup(getter_AddRefs(mLoadGroup));
}

nsPartChannel::~nsPartChannel()
{
}

void nsPartChannel::InitializeByteRange(PRInt32 aStart, PRInt32 aEnd)
{
    mIsByteRangeRequest = PR_TRUE;
    
    mByteRangeStart = aStart;
    mByteRangeEnd   = aEnd;
}


//
// 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_END

//
// nsIRequest implementation...
//

NS_IMETHODIMP
nsPartChannel::GetName(PRUnichar * *aResult)
{
    return mMultipartChannel->GetName(aResult);
}

NS_IMETHODIMP
nsPartChannel::IsPending(PRBool *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...
    mStatus = aStatus;
    return NS_OK;
}

NS_IMETHODIMP
nsPartChannel::Suspend(void)
{
    // Suspending an individual part must not suspend the underlying
    // multipart channel...
    return NS_OK;
}

NS_IMETHODIMP
nsPartChannel::Resume(void)
{
    // Resuming an individual part must not resume the underlying
    // multipart channel...
    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::AsyncOpen(nsIStreamListener *aListener, nsISupports *aContext)
{
    // 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::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::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(char * *aContentType)
{
    *aContentType = mContentType.ToNewCString();
    return NS_OK;
}

NS_IMETHODIMP
nsPartChannel::SetContentType(const char *aContentType)
{
    mContentType.Assign(aContentType);
    return NS_OK;
}

NS_IMETHODIMP
nsPartChannel::GetContentLength(PRInt32 *aContentLength)
{
    *aContentLength = mContentLength;
    return NS_OK;
}

NS_IMETHODIMP
nsPartChannel::SetContentLength(PRInt32 aContentLength)
{
    mContentLength = aContentLength;
    return NS_OK;
}


//
// nsIByteRangeRequest implementation...
//

NS_IMETHODIMP 
nsPartChannel::GetIsByteRangeRequest(PRBool *aIsByteRangeRequest)
{
    *aIsByteRangeRequest = mIsByteRangeRequest;

    return NS_OK;
}


NS_IMETHODIMP 
nsPartChannel::GetStartRange(PRInt32 *aStartRange)
{
    *aStartRange = mByteRangeStart;

    return NS_OK;
}

NS_IMETHODIMP 
nsPartChannel::GetEndRange(PRInt32 *aEndRange)
{
    *aEndRange = mByteRangeEnd;
    return NS_OK;
}





// nsISupports implementation
NS_IMPL_THREADSAFE_ISUPPORTS3(nsMultiMixedConv,
                              nsIStreamConverter, 
                              nsIStreamListener,
                              nsIRequestObserver);


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

#define ERR_OUT { nsMemory::Free(buffer); return rv; }

// nsIStreamListener implementation
NS_IMETHODIMP
nsMultiMixedConv::OnDataAvailable(nsIRequest *request, nsISupports *context,
                                  nsIInputStream *inStr, PRUint32 sourceOffset, PRUint32 count) {

    if (!mToken.get()) // no token, no love.
        return NS_ERROR_FAILURE;

    nsresult rv = NS_OK;
    char *buffer = nsnull;
    PRUint32 bufLen = count, read;

    NS_ASSERTION(request, "multimixed converter needs a request");

    nsCOMPtr<nsIChannel> channel = do_QueryInterface(request, &rv);
    if (NS_FAILED(rv)) return rv;
    
    buffer = (char*)nsMemory::Alloc(bufLen);
    if (!buffer) return NS_ERROR_OUT_OF_MEMORY;

    rv = inStr->Read(buffer, bufLen, &read);
    if (NS_FAILED(rv) || read == 0) return rv;
    NS_ASSERTION(read == bufLen, "poor data size assumption");
    
    if (mBufLen) {
		// incorporate any buffered data into the parsing
        char *tmp = (char*)nsMemory::Alloc(mBufLen + bufLen);
        if (!tmp) {
            nsMemory::Free(buffer);
            return NS_ERROR_OUT_OF_MEMORY;
        }
        nsCRT::memcpy(tmp, mBuffer, mBufLen);
        nsMemory::Free(mBuffer);
        mBuffer = nsnull;
        nsCRT::memcpy(tmp+mBufLen, buffer, bufLen);
        nsMemory::Free(buffer);
        buffer = tmp;
        bufLen += mBufLen;
        mBufLen = 0;
    }

    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 = PR_FALSE;
        NS_ASSERTION(!mBufLen, "this is our first time through, we can't have buffered data");
        const char * token = mToken;
           
        PushOverLine(cursor, bufLen);

        if (bufLen < mTokenLen+2) {
            // we don't have enough data yet to make this comparison.
            // skip this check, and try again the next time OnData()
            // is called.
            mFirstOnData = PR_TRUE;
        } else if (!PL_strnstr(cursor, token, mTokenLen+2)) {
            bufLen = read + mTokenLen + 1;
            char *tmp = (char*)nsMemory::Alloc(bufLen);
            if (!tmp) {
                nsMemory::Free(buffer);
                return NS_ERROR_OUT_OF_MEMORY;
            }
            nsCRT::memcpy(tmp, token, mTokenLen);
            nsCRT::memcpy(tmp+mTokenLen, "\n", 1);
            nsCRT::memcpy(tmp+mTokenLen+1, cursor, read);
            nsMemory::Free(buffer);
            buffer = tmp;
        }
    }

    char *token = nsnull;

    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.
        PRBool done = PR_FALSE;
		rv = ParseHeaders(channel, cursor, bufLen, &done);
		if (NS_FAILED(rv)) ERR_OUT

        if (done) {
            mProcessingHeaders = PR_FALSE;
            rv = SendStart(channel);
            if (NS_FAILED(rv)) ERR_OUT
        }
    }

    PRInt32 tokenLinefeed = 1;
    while ( (token = FindToken(cursor, bufLen)) ) {

        if (*(token+mTokenLen+1) == '-') {
			// This was the last delimiter so we can stop processing
            bufLen = token - cursor;
            rv = SendData(cursor, bufLen);
            nsMemory::Free(buffer);
            if (NS_FAILED(rv)) return rv;
            buffer = nsnull;
            bufLen = 0;
            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, token - cursor);
            bufLen -= token - cursor;
            if (NS_FAILED(rv)) ERR_OUT
        }
        token += mTokenLen;
        bufLen -= mTokenLen;
        tokenLinefeed = PushOverLine(token, bufLen);

        if (mNewPart) {
            // parse headers
            mNewPart = PR_FALSE;
            cursor = token;
            PRBool done = PR_FALSE; 
            rv = ParseHeaders(channel, cursor, bufLen, &done);
            if (NS_FAILED(rv)) ERR_OUT
            if (done) {
                rv = SendStart(channel);
                if (NS_FAILED(rv)) ERR_OUT
            } else {
                // we haven't finished processing header info.
                // we'll break out and try to process later.
                mProcessingHeaders = PR_TRUE;
                break;
            }
        } else {
            mNewPart = PR_TRUE;
            rv = SendStop(NS_OK);
            if (NS_FAILED(rv)) ERR_OUT
            // reset to 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
    PRUint32 bufAmt = 0;
    if (mProcessingHeaders) {
        bufAmt = bufLen;
    } else {
        // 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 = PR_MIN(mTokenLen - 1, bufLen);
    }

    if (bufAmt) {
        rv = BufferData(cursor + (bufLen - bufAmt), bufAmt);
        if (NS_FAILED(rv)) ERR_OUT
        bufLen -= bufAmt;
    }

    if (bufLen) {
        rv = SendData(cursor, bufLen);
        if (NS_FAILED(rv)) ERR_OUT
    }

    nsMemory::Free(buffer);

    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, "a second on start???");
    char *bndry = nsnull;
    nsXPIDLCString delimiter;
    nsresult rv = NS_OK;
    mContext = ctxt;

    mFirstOnData = PR_TRUE;

    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("content-type", getter_Copies(delimiter));
        if (NS_FAILED(rv)) return rv;
    } else {
        // try asking the channel directly
        rv = channel->GetContentType(getter_Copies(delimiter));
        if (!delimiter || NS_FAILED(rv)) return NS_ERROR_FAILURE;
    }

    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

    char *attrib = PL_strchr(bndry, ';');
    if (attrib) *attrib = '\0';

    nsCAutoString boundaryString(bndry);
    if (attrib) *attrib = ';';

    boundaryString.Trim(" \"");

    mToken.Adopt(nsCRT::strdup(boundaryString.get()));
    mTokenLen = boundaryString.Length();
    
    if (mTokenLen == 0 || !boundaryString.get())
        return NS_ERROR_FAILURE;

    return NS_OK;
}

NS_IMETHODIMP
nsMultiMixedConv::OnStopRequest(nsIRequest *request, nsISupports *ctxt,
                                nsresult aStatus) {

    if (!mToken.get())  // no token, no love.
        return NS_ERROR_FAILURE;

    if (mPartChannel) {
        // 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.
            nsMemory::Free(mBuffer);
            mBuffer = nsnull;
            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() {
    NS_INIT_ISUPPORTS();
    mTokenLen           = 0;
    mNewPart            = PR_TRUE;
    mContentLength      = -1;
    mBuffer             = nsnull;
    mBufLen             = 0;
    mProcessingHeaders  = PR_FALSE;
    mByteRangeStart     = 0;
    mByteRangeEnd       = 0;
    mIsByteRangeRequest = PR_FALSE;
}

nsMultiMixedConv::~nsMultiMixedConv() {
    NS_ASSERTION(!mBuffer, "all buffered data should be gone");
    if (mBuffer) {
        nsMemory::Free(mBuffer);
        mBuffer = nsnull;
    }
}

nsresult
nsMultiMixedConv::BufferData(char *aData, PRUint32 aLen) {
    NS_ASSERTION(!mBuffer, "trying to over-write buffer");

    char *buffer = (char*)nsMemory::Alloc(aLen);
    if (!buffer) return NS_ERROR_OUT_OF_MEMORY;

    nsCRT::memcpy(buffer, aData, aLen);
    mBuffer = buffer;
    mBufLen = aLen;
    return NS_OK;
}


nsresult
nsMultiMixedConv::SendStart(nsIChannel *aChannel) {
    nsresult rv = NS_OK;

    if (mContentType.IsEmpty())
        mContentType = UNKNOWN_CONTENT_TYPE;

    // 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);
    if (!newChannel)
        return NS_ERROR_OUT_OF_MEMORY;

    if (mIsByteRangeRequest) {
        newChannel->InitializeByteRange(mByteRangeStart, mByteRangeEnd);
    }

    // Set up the new part channel...
    mPartChannel = newChannel;

    rv = mPartChannel->SetContentType(mContentType);
    if (NS_FAILED(rv)) return rv;

    mPartChannel->SetContentLength(mContentLength);
    if (NS_FAILED(rv)) return rv;

    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, nsnull);
        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 mFinalListener->OnStartRequest(mPartChannel, mContext);
}


nsresult
nsMultiMixedConv::SendStop(nsresult aStatus) {
    
    nsresult rv = NS_OK;
    if (mPartChannel) {
        rv = mFinalListener->OnStopRequest(mPartChannel, 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 = 0;
    return rv;
}

nsresult
nsMultiMixedConv::SendData(char *aBuffer, PRUint32 aLen) {

    nsresult rv = NS_OK;
    
    if (!mPartChannel) return NS_ERROR_FAILURE; // something went wrong w/ processing

    char *tmp = (char*)nsMemory::Alloc(aLen); // byteArray stream owns this mem
    if (!tmp) return NS_ERROR_OUT_OF_MEMORY;
    
    nsCRT::memcpy(tmp, aBuffer, aLen);
    
    nsCOMPtr<nsIByteArrayInputStream> byteArrayStream;
    
    rv = NS_NewByteArrayInputStream(getter_AddRefs(byteArrayStream), tmp, aLen);
    if (NS_FAILED(rv)) {
        nsMemory::Free(tmp);
        return rv;
    }

    nsCOMPtr<nsIInputStream> inStream = do_QueryInterface(byteArrayStream, &rv);
    if (NS_FAILED(rv)) return rv;

    PRUint32 len;
    rv = inStream->Available(&len);
    if (NS_FAILED(rv)) return rv;

    return mFinalListener->OnDataAvailable(mPartChannel, mContext, inStream, 0, len);
}

PRInt32
nsMultiMixedConv::PushOverLine(char *&aPtr, PRUint32 &aLen) {
    PRInt32 chars = 0;
    if (*aPtr == nsCRT::CR || *aPtr == nsCRT::LF) {
        if (aPtr[1] == nsCRT::LF)
            chars++;
        chars++;
    }
    aPtr += chars;
    aLen -= chars;
    return chars;
}

nsresult
nsMultiMixedConv::ParseHeaders(nsIChannel *aChannel, char *&aPtr, 
                               PRUint32 &aLen, PRBool *_retval) {
    // NOTE: this data must be ascii.
	nsresult rv = NS_OK;
    char *cursor = aPtr, *newLine = nsnull;
    PRUint32 cursorLen = aLen;
    PRBool done = PR_FALSE;
    PRUint32 lineFeedIncrement = 1;

    while ( (cursorLen > 0) 
            && (newLine = PL_strchr(cursor, nsCRT::LF)) ) {
        // adjust for linefeeds
        if ( (newLine > cursor) && (newLine[-1] == nsCRT::CR) ) { // CRLF
             lineFeedIncrement = 2;
             newLine--;
        }

        if (newLine == cursor) {
            // move the newLine beyond the double linefeed marker
            if ( (newLine - cursor) < ((PRInt32)cursorLen - lineFeedIncrement) ) {
                newLine += lineFeedIncrement;
            }
            cursorLen -= (newLine - cursor);
            cursor = newLine;

            done = PR_TRUE;
            break;
        }

        char tmpChar = *newLine;
        *newLine = '\0';
        char *colon = PL_strchr(cursor, ':');
        if (colon) {
            *colon = '\0';
            nsCAutoString headerStr(cursor);
            headerStr.CompressWhitespace();
            *colon = ':';

            nsCAutoString headerVal(colon + 1);
            headerVal.CompressWhitespace();

            // examine header
            if (headerStr.EqualsIgnoreCase("content-type")) {
                char *tmpString = headerVal.ToNewCString();
                ParseContentType(tmpString);
                nsCRT::free(tmpString);
            } else if (headerStr.EqualsIgnoreCase("content-length")) {
                mContentLength = atoi(headerVal.get());
            } else if (headerStr.EqualsIgnoreCase("set-cookie")) {
                // setting headers on the HTTP channel
                // causes HTTP to notify, again if necessary,
                // it's header observers.
                nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
                if (httpChannel) {
                    rv = httpChannel->SetResponseHeader(headerStr, headerVal);
                    if (NS_FAILED(rv)) return rv;
                }
            } else if (headerStr.EqualsIgnoreCase("content-range")) {
                // something like: Content-range: bytes 7000-7999/8000
                char* tmpPtr;

                tmpPtr = PL_strchr(colon + 1, '/');
                if (tmpPtr) 
                    *tmpPtr = '\0';

                // pass the bytes-unit and the SP
                char *range = PL_strchr(colon + 2, ' ');

                if (!range)
                    return NS_ERROR_FAILURE;

                if (range[0] == '*'){
                    mByteRangeStart = mByteRangeEnd = 0;
                }
                else {
                    tmpPtr = PL_strchr(range, '-');
                    if (!tmpPtr)
                        return NS_ERROR_FAILURE;
                    
                    tmpPtr[0] = '\0';
                    
                    mByteRangeStart = atoi(range);
                    tmpPtr++;
                    mByteRangeEnd = atoi(tmpPtr);
                }

                mIsByteRangeRequest = PR_TRUE;
            }
        }
        *newLine = tmpChar;
        newLine += lineFeedIncrement;
        cursorLen -= (newLine - cursor);
        cursor = newLine;
    } // end while (newLine)

    aPtr = cursor;
    aLen = cursorLen;

	*_retval = done;
    return rv;
}

// This code is duplicated in nsHttpResponseHead.cpp.  If you change it
// here, change it there, too!

nsresult
nsMultiMixedConv::ParseContentType(char *type)
{
    char *p = PL_strchr(type, '(');

    if (p)
        // we don't care about comments (although they are invalid here)
        *p = 0;

    // check if the content-type has additional fields...
    if ((p = PL_strchr(type, ';')) != nsnull) {
        char *p2, *p3;
        // is there a charset field?
        if ((p2 = PL_strcasestr(p, "charset=")) != nsnull) {
            p2 += 8;

            // check end of charset parameter
            if ((p3 = PL_strchr(p2, ';')) == nsnull)
                p3 = p2 + PL_strlen(p2);

            // trim any trailing whitespace
            do {
                --p3;
            } while ((*p3 == ' ') || (*p3 == '\t'));
            *++p3 = 0; // overwrite first char after the charset field

            mContentCharset = p2;
        }
    }
    else
        p = type + PL_strlen(type);

    // trim any trailing whitespace
    while (--p >= type && ((*p == ' ') || (*p == '\t')))
        ;
    *++p = 0; // overwrite first char after the media type

    // force the content-type to lowercase
    while (--p >= type)
        *p = nsCRT::ToLower(*p);

    mContentType = type;

    return NS_OK;
}

char *
nsMultiMixedConv::FindToken(char *aCursor, PRUint32 aLen) {
    // strnstr without looking for null termination
    const char *token = mToken;
    char *cur = aCursor;

    NS_ASSERTION(token && aCursor && *token, "bad data");

    if( mTokenLen > aLen ) return nsnull;

    for( ; aLen >= mTokenLen; aCursor++, aLen-- )
        if( *token == *aCursor)
            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.
                        nsCString newToken("--");
                        newToken.Append(mToken);
                        mToken.Adopt(nsCRT::strdup(newToken.get()));
                        mTokenLen += 2;
                    }
                }
                return aCursor;
            }

    return nsnull;
}

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 NS_OK;
}

