/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim:set ts=4 sw=4 sts=4 et cin: */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla 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/MPL/
 *
 * 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 the Initial Developer are Copyright (C) 1998
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#include "nsFileChannel.h"
#include "nsDirectoryIndexStream.h"
#include "nsMimeTypes.h"
#include "nsNetUtil.h"
#include "nsEventQueueUtils.h"

#include "nsIServiceManager.h"
#include "nsIStreamConverterService.h"
#include "nsIStreamTransportService.h"
#include "nsITransport.h"
#include "nsIFileURL.h"
#include "nsIMIMEService.h"
#include "nsIPrefService.h"
#include "nsIPrefBranch.h"
#include "nsURLHelper.h"
#include "nsIChannelEventSink.h"
#include "nsIOService.h"
#include "nsIScriptSecurityManager.h"

static NS_DEFINE_CID(kStreamConverterServiceCID, NS_STREAMCONVERTERSERVICE_CID);
static NS_DEFINE_CID(kStreamTransportServiceCID, NS_STREAMTRANSPORTSERVICE_CID);

//-----------------------------------------------------------------------------

nsFileChannel::nsFileChannel()
    : mContentLength(-1)
    , mUploadLength(-1)
    , mLoadFlags(LOAD_NORMAL)
    , mStatus(NS_OK)
    , mIsDir(PR_FALSE)
    , mUploading(PR_FALSE)
    , mWaitingOnAsyncRedirect(PR_FALSE)
{
}

nsresult
nsFileChannel::Init(nsIURI *uri)
{
    nsresult rv = nsHashPropertyBag::Init();
    if (NS_FAILED(rv))
        return rv;
    mURL = do_QueryInterface(uri, &rv);
    return rv;
}

static PLDHashOperator
CopyProperties(const nsAString &key, nsIVariant *data, void *closure)
{
  nsIWritablePropertyBag *bag =
      static_cast<nsIWritablePropertyBag *>(closure);

  bag->SetProperty(key, data);
  return PL_DHASH_NEXT;
}

void
nsFileChannel::HandleRedirect(nsIChannel* newChannel)
{
    if (NS_SUCCEEDED(mStatus)) {
        nsIURI* originalURI = mOriginalURI;
        if (!originalURI)
            originalURI = mURL;
    
        newChannel->SetOriginalURI(originalURI);
        newChannel->SetLoadGroup(mLoadGroup);
        newChannel->SetNotificationCallbacks(mCallbacks);
        newChannel->SetLoadFlags(mLoadFlags | LOAD_REPLACE);

        nsCOMPtr<nsIWritablePropertyBag> bag = do_QueryInterface(newChannel);
        if (bag)
            mPropertyHash.EnumerateRead(CopyProperties, bag.get());

        // Notify consumer, giving chance to cancel redirect.
        PRInt32 redirectFlags = nsIChannelEventSink::REDIRECT_INTERNAL;

        // Global observers. These come first so that other observers don't see
        // redirects that get aborted for security reasons anyway.
        nsresult rv = gIOService->OnChannelRedirect(this, newChannel,
                                                    redirectFlags);
        if (NS_SUCCEEDED(rv)) {
            nsCOMPtr<nsIChannelEventSink> channelEventSink;
            // Give our consumer a chance to observe/block this redirect.
            NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
                                          channelEventSink);
            if (channelEventSink) {
                rv = channelEventSink->OnChannelRedirect(this, newChannel,
                                                         redirectFlags);
                if (NS_SUCCEEDED(rv)) {
                    rv = newChannel->AsyncOpen(mListener, mListenerContext);
                }
            }
        }

        if (NS_FAILED(rv))
            Cancel(rv);
    }

    mWaitingOnAsyncRedirect = PR_FALSE;

    if (NS_FAILED(mStatus)) {
        // Notify our consumer ourselves        
        mListener->OnStartRequest(this, mListenerContext);
        mListener->OnStopRequest(this, mListenerContext, mStatus);
    } else {
        // close down this channel
        Cancel(NS_BINDING_REDIRECTED);
    }

    mListener = nsnull;
    mListenerContext = nsnull;
    
    if (mLoadGroup)
        mLoadGroup->RemoveRequest(this, nsnull, mStatus);

    // Drop notification callbacks to prevent cycles.
    mCallbacks = nsnull;
}

nsresult
nsFileChannel::GetClonedFile(nsIFile **result)
{
    nsresult rv;

    nsCOMPtr<nsIFile> file;
    rv = mURL->GetFile(getter_AddRefs(file));
    if (NS_FAILED(rv)) return rv;

    return file->Clone(result);
}

nsresult
nsFileChannel::EnsureStream()
{
    NS_ENSURE_TRUE(mURL, NS_ERROR_NOT_INITIALIZED);

    nsresult rv;
    nsCOMPtr<nsIFile> file;

    // don't assume nsIFile impl is threadsafe; pass a clone to the stream.
    rv = GetClonedFile(getter_AddRefs(file));
    if (NS_FAILED(rv)) return rv;

    // we accept that this might result in a disk hit to stat the file
    PRBool isDir;
    rv = file->IsDirectory(&isDir);
    if (NS_FAILED(rv)) {
        // canonicalize error message
        if (rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
            rv = NS_ERROR_FILE_NOT_FOUND;
        return rv;
    }
    mIsDir = isDir;

    if (mIsDir)
        rv = nsDirectoryIndexStream::Create(file, getter_AddRefs(mStream));
    else
        rv = NS_NewLocalFileInputStream(getter_AddRefs(mStream), file);

    if (NS_FAILED(rv)) return rv;

    // fixup content length
    if (mStream && (mContentLength < 0))
        mStream->Available((PRUint32 *) &mContentLength);

    return NS_OK;
}

//-----------------------------------------------------------------------------
// nsISupports
//-----------------------------------------------------------------------------

NS_IMPL_ADDREF_INHERITED(nsFileChannel, nsHashPropertyBag)
NS_IMPL_RELEASE_INHERITED(nsFileChannel, nsHashPropertyBag)
NS_INTERFACE_MAP_BEGIN(nsFileChannel)
    NS_INTERFACE_MAP_ENTRY(nsIRequest)
    NS_INTERFACE_MAP_ENTRY(nsIChannel)
    NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
    NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
    NS_INTERFACE_MAP_ENTRY(nsIUploadChannel)
    NS_INTERFACE_MAP_ENTRY(nsIFileChannel)
    NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
NS_INTERFACE_MAP_END_INHERITING(nsHashPropertyBag)

//-----------------------------------------------------------------------------
// nsIRequest
//-----------------------------------------------------------------------------

NS_IMETHODIMP
nsFileChannel::GetName(nsACString &result)
{
    return mURL->GetSpec(result);
}

NS_IMETHODIMP
nsFileChannel::IsPending(PRBool *result)
{
    *result = mRequest || mWaitingOnAsyncRedirect;
    return NS_OK;
}

NS_IMETHODIMP
nsFileChannel::GetStatus(nsresult *status)
{
    if (NS_SUCCEEDED(mStatus) && mRequest)
        mRequest->GetStatus(status);
    else
        *status = mStatus;
    return NS_OK;
}

NS_IMETHODIMP
nsFileChannel::Cancel(nsresult status)
{
    mStatus = status;
    if (mRequest)
        return mRequest->Cancel(status);
    return NS_OK;
}

NS_IMETHODIMP
nsFileChannel::Suspend()
{
    NS_ENSURE_TRUE(mRequest, NS_ERROR_UNEXPECTED);
    return mRequest->Suspend();
}

NS_IMETHODIMP
nsFileChannel::Resume()
{
    NS_ENSURE_TRUE(mRequest, NS_ERROR_UNEXPECTED);
    return mRequest->Resume();
}

NS_IMETHODIMP
nsFileChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
{
    *aLoadFlags = mLoadFlags;
    return NS_OK;
}

NS_IMETHODIMP
nsFileChannel::SetLoadFlags(nsLoadFlags aLoadFlags)
{
    mLoadFlags = aLoadFlags;
    return NS_OK;
}

NS_IMETHODIMP
nsFileChannel::GetLoadGroup(nsILoadGroup **aLoadGroup)
{
    NS_IF_ADDREF(*aLoadGroup = mLoadGroup);
    return NS_OK;
}

NS_IMETHODIMP
nsFileChannel::SetLoadGroup(nsILoadGroup *aLoadGroup)
{
    mLoadGroup = aLoadGroup;
    mProgressSink = nsnull;
    return NS_OK;
}

//-----------------------------------------------------------------------------
// nsIChannel
//-----------------------------------------------------------------------------

NS_IMETHODIMP
nsFileChannel::GetOriginalURI(nsIURI **aURI)
{
    if (mOriginalURI)
        *aURI = mOriginalURI;
    else
        *aURI = mURL;
    NS_IF_ADDREF(*aURI);
    return NS_OK;
}

NS_IMETHODIMP
nsFileChannel::SetOriginalURI(nsIURI *aURI)
{
    mOriginalURI = aURI;
    return NS_OK;
}

NS_IMETHODIMP
nsFileChannel::GetURI(nsIURI **aURI)
{
    NS_IF_ADDREF(*aURI = mURL);
    return NS_OK;
}

NS_IMETHODIMP
nsFileChannel::GetOwner(nsISupports **aOwner)
{
    NS_IF_ADDREF(*aOwner = mOwner);
    return NS_OK;
}

NS_IMETHODIMP
nsFileChannel::SetOwner(nsISupports *aOwner)
{
    mOwner = aOwner;
    return NS_OK;
}

NS_IMETHODIMP
nsFileChannel::GetNotificationCallbacks(nsIInterfaceRequestor **aCallbacks)
{
    NS_IF_ADDREF(*aCallbacks = mCallbacks);
    return NS_OK;
}

NS_IMETHODIMP
nsFileChannel::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks)
{
    mCallbacks = aCallbacks;
    mProgressSink = nsnull;
    return NS_OK;
}

NS_IMETHODIMP 
nsFileChannel::GetSecurityInfo(nsISupports **aSecurityInfo)
{
    *aSecurityInfo = nsnull;
    return NS_OK;
}

NS_IMETHODIMP
nsFileChannel::GetContentType(nsACString &aContentType)
{
    NS_PRECONDITION(mURL, "Why is this being called?");
    
    if (mContentType.IsEmpty()) {
        if (mIsDir) {
            mContentType.AssignLiteral(APPLICATION_HTTP_INDEX_FORMAT);
        } else {
            // Get content type from file extension
            nsCOMPtr<nsIFile> file;
            nsresult rv = mURL->GetFile(getter_AddRefs(file));
            if (NS_FAILED(rv)) return rv;
            
            nsCOMPtr<nsIMIMEService> mime = do_GetService("@mozilla.org/mime;1", &rv);
            if (NS_SUCCEEDED(rv))
                mime->GetTypeFromFile(file, mContentType);

            if (mContentType.IsEmpty())
                mContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE);
        }
    }
    
    aContentType = mContentType;
    return NS_OK;
}

NS_IMETHODIMP
nsFileChannel::SetContentType(const nsACString &aContentType)
{
    // If someone gives us a type hint we should just use that type instead of
    // doing our guessing.  So we don't care when this is being called.

    PRBool dummy;
    net_ParseContentType(aContentType, mContentType, mContentCharset, &dummy);
    return NS_OK;
}

NS_IMETHODIMP
nsFileChannel::GetContentCharset(nsACString &aContentCharset)
{
    aContentCharset = mContentCharset;
    return NS_OK;
}

NS_IMETHODIMP
nsFileChannel::SetContentCharset(const nsACString &aContentCharset)
{
    // If someone gives us a charset hint we should just use that charset.
    // So we don't care when this is being called.
    mContentCharset = aContentCharset;
    return NS_OK;
}

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

NS_IMETHODIMP
nsFileChannel::SetContentLength(PRInt32 aContentLength)
{
    // XXX does this really make any sense at all?
    mContentLength = aContentLength;
    return NS_OK;
}

NS_IMETHODIMP
nsFileChannel::Open(nsIInputStream **result)
{
    NS_ENSURE_TRUE(!mRequest, NS_ERROR_IN_PROGRESS);
    NS_ENSURE_TRUE(!mUploading, NS_ERROR_NOT_IMPLEMENTED); // XXX implement me!

    nsresult rv;

    rv = EnsureStream();
    if (NS_FAILED(rv)) return rv;

    NS_ADDREF(*result = mStream);

    return NS_OK;
}

nsFileChannel::RedirectRunnable::RedirectRunnable(nsFileChannel* originalChannel,
                                                  nsIChannel* newChannel) :
    mOriginalChannel(originalChannel),
    mNewChannel(newChannel)
{
    MOZ_COUNT_CTOR(nsFileChannel::RedirectRunnable);
    PL_InitEvent(this, nsnull, Handle, Destroy);
}    

nsFileChannel::RedirectRunnable::~RedirectRunnable()
{
    MOZ_COUNT_DTOR(nsFileChannel::RedirectRunnable);
}

/* static */ void * PR_CALLBACK
nsFileChannel::RedirectRunnable::Handle(PLEvent* aEvent)
{
  nsFileChannel::RedirectRunnable* evt =
    NS_STATIC_CAST(nsFileChannel::RedirectRunnable*, aEvent);

  evt->mOriginalChannel->HandleRedirect(evt->mNewChannel);

  return nsnull;
}

/* static */ void PR_CALLBACK
nsFileChannel::RedirectRunnable::Destroy(PLEvent* aEvent)
{
  nsFileChannel::RedirectRunnable* evt =
    NS_STATIC_CAST(nsFileChannel::RedirectRunnable*, aEvent);
  delete evt;
}

NS_IMETHODIMP
nsFileChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctx)
{
    NS_ENSURE_TRUE(!mRequest, NS_ERROR_IN_PROGRESS);

    // This file may be a url file
    nsCOMPtr<nsIFile> file;
    nsresult rv = mURL->GetFile(getter_AddRefs(file));
    if (NS_SUCCEEDED(rv)) {
        nsCOMPtr<nsIFileProtocolHandler> fileHandler;
        rv = NS_GetFileProtocolHandler(getter_AddRefs(fileHandler));
        NS_ENSURE_SUCCESS(rv, rv);

        nsCOMPtr<nsIURI> uri;
        rv = fileHandler->ReadURLFile(file, getter_AddRefs(uri));
        if (NS_SUCCEEDED(rv)) {
            // Make sure this is OK to load
            nsCOMPtr<nsIScriptSecurityManager> securityManager = 
                do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
            if (securityManager) {
                rv = securityManager->CheckLoadURI(mURL, uri,
                                                   nsIScriptSecurityManager::DISALLOW_FROM_MAIL |
                                                   nsIScriptSecurityManager::DISALLOW_SCRIPT_OR_DATA);
                if (NS_FAILED(rv)) return rv;
            }
 
            nsCOMPtr<nsIChannel> newChannel;
            rv = NS_NewChannel(getter_AddRefs(newChannel), uri);
            if (NS_SUCCEEDED(rv)) {
                nsCOMPtr<nsIEventQueueService> eventQService =
                    do_GetService("@mozilla.org/event-queue-service;1", &rv);
                NS_ENSURE_TRUE(eventQService, rv);
                
                nsCOMPtr<nsIEventQueue> eventQ;
                rv = eventQService->
                    GetSpecialEventQueue(nsIEventQueueService::UI_THREAD_EVENT_QUEUE,
                                         getter_AddRefs(eventQ));
                NS_ENSURE_TRUE(eventQ, rv);

                RedirectRunnable* evt =
                    new RedirectRunnable(this, newChannel);
                NS_ENSURE_TRUE(evt, NS_ERROR_OUT_OF_MEMORY);

                rv = eventQ->PostEvent(evt);

                if (NS_FAILED(rv)) {
                    PL_DestroyEvent(evt);
                } else {
                    mWaitingOnAsyncRedirect = PR_TRUE;

                    if (mLoadGroup)
                        mLoadGroup->AddRequest(this, nsnull);

                    mListener = listener;
                    mListenerContext = ctx;
                }
                return rv;
            }
        }
    }

    nsCOMPtr<nsIStreamListener> grip;
    nsCOMPtr<nsIEventQueue> currentEventQ;

    rv = NS_GetCurrentEventQ(getter_AddRefs(currentEventQ));
    if (NS_FAILED(rv)) return rv;

    nsCOMPtr<nsIStreamTransportService> sts =
            do_GetService(kStreamTransportServiceCID, &rv);
    if (NS_FAILED(rv)) return rv;

    if (mUploading) {
        //
        // open file output stream.  since output stream will be accessed on a
        // background thread, we should not give it a reference to "our" nsIFile
        // instance.
        //
        nsCOMPtr<nsIFile> file;
        rv = GetClonedFile(getter_AddRefs(file));
        if (NS_FAILED(rv)) return rv;

        nsCOMPtr<nsIOutputStream> fileOut;
        rv = NS_NewLocalFileOutputStream(getter_AddRefs(fileOut), file,
                                         PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE,
                                         PR_IRUSR | PR_IWUSR);
        if (NS_FAILED(rv)) return rv;

        //
        // create asynchronous output stream wrapping file output stream.
        //
        // XXX 64-bit upload length would be nice; it would require a new
        // nsIUploadChannel, though.
        nsCOMPtr<nsITransport> transport;
        rv = sts->CreateOutputTransport(fileOut, nsInt64(-1),
                                        nsInt64(mUploadLength), PR_TRUE,
                                        getter_AddRefs(transport));
        if (NS_FAILED(rv)) return rv;

        rv = transport->SetEventSink(this, currentEventQ);
        if (NS_FAILED(rv)) return rv;

        nsCOMPtr<nsIOutputStream> asyncOut;
        rv = transport->OpenOutputStream(0, 0, 0, getter_AddRefs(asyncOut));
        if (NS_FAILED(rv)) return rv;

        //
        // create async stream copier
        //
        // XXX copier might read more than mUploadLength from mStream!!  not
        // a huge deal, but probably should be fixed.
        //
        nsCOMPtr<nsIAsyncStreamCopier> copier;
        rv = NS_NewAsyncStreamCopier(getter_AddRefs(copier), mStream, asyncOut,
                                     nsnull,   // perform copy using default i/o thread
                                     PR_FALSE, // assume the upload stream is unbuffered
                                     PR_TRUE); // but, the async output stream is buffered!
        if (NS_FAILED(rv)) return rv;

        rv = copier->AsyncCopy(this, nsnull);
        if (NS_FAILED(rv)) return rv;

        mRequest = copier;
    }
    else {
        //
        // create file input stream
        //
        rv = EnsureStream();
        if (NS_FAILED(rv)) return rv;

        //
        // create asynchronous input stream wrapping file input stream.
        //
        nsCOMPtr<nsITransport> transport;
        rv = sts->CreateInputTransport(mStream, nsInt64(-1), nsInt64(-1), PR_TRUE,
                                       getter_AddRefs(transport));
        if (NS_FAILED(rv)) return rv;

        rv = transport->SetEventSink(this, currentEventQ);
        if (NS_FAILED(rv)) return rv;

        nsCOMPtr<nsIInputStream> asyncIn;
        rv = transport->OpenInputStream(0, 0, 0, getter_AddRefs(asyncIn));
        if (NS_FAILED(rv)) return rv;

        //
        // create input stream pump
        //
        nsCOMPtr<nsIInputStreamPump> pump;
        rv = NS_NewInputStreamPump(getter_AddRefs(pump), asyncIn);
        if (NS_FAILED(rv)) return rv;

        rv = pump->AsyncRead(this, nsnull);
        if (NS_FAILED(rv)) return rv;

        mRequest = pump;
    }

    if (mLoadGroup)
        mLoadGroup->AddRequest(this, nsnull);

    mListener = listener;
    mListenerContext = ctx;
    return NS_OK;
}

//-----------------------------------------------------------------------------
// nsIFileChannel
//-----------------------------------------------------------------------------

NS_IMETHODIMP
nsFileChannel::GetFile(nsIFile **file)
{
    return mURL->GetFile(file);
}

//-----------------------------------------------------------------------------
// nsIUploadChannel
//-----------------------------------------------------------------------------

NS_IMETHODIMP
nsFileChannel::SetUploadStream(nsIInputStream *stream,
                               const nsACString &contentType,
                               PRInt32 contentLength)
{
    NS_ENSURE_TRUE(!mRequest, NS_ERROR_IN_PROGRESS);

    mStream = stream;

    if (mStream) {
        mUploading = PR_TRUE;
        mUploadLength = contentLength;

        if (mUploadLength < 0) {
            // make sure we know how much data we are uploading.
            nsresult rv = mStream->Available((PRUint32 *) &mUploadLength);
            if (NS_FAILED(rv)) return rv;
        }
    }
    else {
        mUploading = PR_FALSE;
        mUploadLength = -1;
    }
    return NS_OK;
}

NS_IMETHODIMP
nsFileChannel::GetUploadStream(nsIInputStream **stream)
{
    if (mUploading)
        NS_IF_ADDREF(*stream = mStream);
    else
        *stream = nsnull;
    return NS_OK;
}

//-----------------------------------------------------------------------------
// nsIStreamListener
//-----------------------------------------------------------------------------

NS_IMETHODIMP
nsFileChannel::OnStartRequest(nsIRequest *req, nsISupports *ctx)
{
    return mListener->OnStartRequest(this, mListenerContext);
}

NS_IMETHODIMP
nsFileChannel::OnStopRequest(nsIRequest *req, nsISupports *ctx, nsresult status)
{
    if (NS_SUCCEEDED(mStatus))
        mStatus = status;

    mListener->OnStopRequest(this, mListenerContext, mStatus);
    mListener = 0;
    mListenerContext = 0;

    if (mLoadGroup)
        mLoadGroup->RemoveRequest(this, nsnull, mStatus);

    mRequest = 0;
    mStream = 0;

    // Drop notification callbacks to prevent cycles.
    mCallbacks = 0;
    mProgressSink = 0;

    return NS_OK;
}

NS_IMETHODIMP
nsFileChannel::OnDataAvailable(nsIRequest *req, nsISupports *ctx,
                               nsIInputStream *stream,
                               PRUint32 offset, PRUint32 count)
{
    return mListener->OnDataAvailable(this, mListenerContext, stream, offset, count);
}

//-----------------------------------------------------------------------------
// nsITransportEventSink
//-----------------------------------------------------------------------------

NS_IMETHODIMP
nsFileChannel::OnTransportStatus(nsITransport *trans, nsresult status,
                                 PRUint64 progress, PRUint64 progressMax)
{
    if (!mProgressSink)
        NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, mProgressSink);

    // suppress status notification if channel is no longer pending!
    if (mProgressSink && NS_SUCCEEDED(mStatus) && mRequest && !(mLoadFlags & LOAD_BACKGROUND)) {
        // file channel does not send OnStatus events!
        if (status == nsITransport::STATUS_READING ||
            status == nsITransport::STATUS_WRITING) {
            mProgressSink->OnProgress(this, nsnull, progress, progressMax);
        }
    }
    return NS_OK;
}


