diff --git a/mailnews/base/util/nsMsgDBFolder.cpp b/mailnews/base/util/nsMsgDBFolder.cpp index c759168e0f5d..249ab5189979 100644 --- a/mailnews/base/util/nsMsgDBFolder.cpp +++ b/mailnews/base/util/nsMsgDBFolder.cpp @@ -539,7 +539,11 @@ NS_IMETHODIMP nsMsgDBFolder::GetOfflineFileTransport(nsMsgKey msgKey, PRUint32 * if (NS_FAILED(rv)) return rv; - rv = fts->CreateTransport(localStore, PR_RDWR | PR_CREATE_FILE, 0664, aFileChannel); + rv = fts->CreateTransport(localStore, + PR_RDWR | PR_CREATE_FILE, + 0664, + PR_TRUE, + aFileChannel); if (NS_SUCCEEDED(rv)) { diff --git a/mailnews/base/util/nsMsgProtocol.cpp b/mailnews/base/util/nsMsgProtocol.cpp index a50f3b894f45..a35b4f2bda02 100644 --- a/mailnews/base/util/nsMsgProtocol.cpp +++ b/mailnews/base/util/nsMsgProtocol.cpp @@ -234,7 +234,7 @@ nsresult nsMsgProtocol::OpenFileSocket(nsIURI * aURL, PRUint32 aStartPosition, P if (NS_FAILED(rv)) return rv; //we are always using this file socket to read data from the mailbox. rv = fts->CreateTransport(file, PR_RDONLY, - 0664, getter_AddRefs(m_transport)); + 0664, PR_TRUE, getter_AddRefs(m_transport)); m_socketIsOpen = PR_FALSE; return rv; @@ -880,7 +880,7 @@ nsresult nsMsgFilePostHelper::Init(nsIOutputStream * aOutStream, nsMsgAsyncWrite if (NS_FAILED(rv)) return rv; nsCOMPtr transport; - rv = fts->CreateTransport(aFileToPost, PR_RDONLY, 0664, getter_AddRefs(transport)); + rv = fts->CreateTransport(aFileToPost, PR_RDONLY, 0664, PR_TRUE, getter_AddRefs(transport)); if (transport) { rv = transport->AsyncRead(this, nsnull, 0, PRUint32(-1), 0, getter_AddRefs(mPostFileRequest)); diff --git a/modules/libjar/nsJARChannel.cpp b/modules/libjar/nsJARChannel.cpp index 1f208ffbea82..3e843fc80c91 100644 --- a/modules/libjar/nsJARChannel.cpp +++ b/modules/libjar/nsJARChannel.cpp @@ -339,7 +339,7 @@ nsJARChannel::AsyncReadJARElement() if (NS_FAILED(rv)) return rv; nsCOMPtr jarTransport; - rv = fts->CreateTransportFromStreamIO(this, getter_AddRefs(jarTransport)); + rv = fts->CreateTransportFromStreamIO(this, PR_TRUE, getter_AddRefs(jarTransport)); if (NS_FAILED(rv)) return rv; if (mCallbacks) { diff --git a/netwerk/base/public/nsIFileTransportService.idl b/netwerk/base/public/nsIFileTransportService.idl index 2f262a30624a..b7ec5d2285ed 100644 --- a/netwerk/base/public/nsIFileTransportService.idl +++ b/netwerk/base/public/nsIFileTransportService.idl @@ -49,7 +49,8 @@ interface nsIFileTransportService : nsISupports { nsITransport createTransport(in nsIFile file, in long ioFlags, - in long perm); + in long perm, + in boolean closeStreamWhenDone); // This version can be used with an existing input stream to serve // as a data pump: @@ -60,7 +61,8 @@ interface nsIFileTransportService : nsISupports in long contentLength, in boolean closeStreamWhenDone); - nsITransport createTransportFromStreamIO(in nsIStreamIO io); + nsITransport createTransportFromStreamIO(in nsIStreamIO io, + in boolean closeStreamWhenDone); void dispatchRequest(in nsIRunnable runnable); void processPendingRequests(); diff --git a/netwerk/base/src/nsFileStreams.cpp b/netwerk/base/src/nsFileStreams.cpp index 2578d06c4e6f..ec48f2611b22 100644 --- a/netwerk/base/src/nsFileStreams.cpp +++ b/netwerk/base/src/nsFileStreams.cpp @@ -124,6 +124,10 @@ nsFileIO::nsFileIO() nsFileIO::~nsFileIO() { (void)Close(NS_OK); + if (mFD) { + PR_Close(mFD); + mFD = nsnull; + } #ifdef PR_LOGGING if (mSpec) nsCRT::free(mSpec); #endif @@ -185,11 +189,18 @@ nsFileIO::Open() rv = localFile->OpenNSPRFileDesc(mIOFlags, mPerm, &mFD); if (NS_FAILED(rv)) { + mFD = nsnull; // just in case +#ifdef PR_LOGGING + nsresult openError = rv; +#endif + // maybe we can't open this because it is a directory... PRBool isDir; rv = localFile->IsDirectory(&isDir); if (NS_SUCCEEDED(rv) && isDir) { return NS_OK; } + PR_LOG(gFileIOLog, PR_LOG_DEBUG, + ("nsFileIO: OpenNSPRFileDesc failed [rv=%x]\n", openError)); return NS_ERROR_FILE_NOT_FOUND; } @@ -294,8 +305,10 @@ nsFileIO::GetInputStream(nsIInputStream * *aInputStream) return rv; if (isDir) { - if (mFD) + if (mFD) { PR_Close(mFD); + mFD = nsnull; + } rv = nsDirectoryIndexStream::Create(mFile, aInputStream); PR_LOG(gFileIOLog, PR_LOG_DEBUG, ("nsFileIO: opening local dir %s for input (%x)", @@ -307,7 +320,7 @@ nsFileIO::GetInputStream(nsIInputStream * *aInputStream) if (fileIn == nsnull) return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(fileIn); - rv = fileIn->InitWithFileDescriptor(mFD, mFile, PR_FALSE); + rv = fileIn->InitWithFileDescriptor(mFD, this); if (NS_SUCCEEDED(rv)) { #ifdef NS_NO_INPUT_BUFFERING *aInputStream = fileIn; @@ -351,7 +364,7 @@ nsFileIO::GetOutputStream(nsIOutputStream * *aOutputStream) if (fileOut == nsnull) return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(fileOut); - rv = fileOut->InitWithFileDescriptor(mFD, mFile); + rv = fileOut->InitWithFileDescriptor(mFD, this); if (NS_SUCCEEDED(rv)) { nsCOMPtr bufStr; #ifdef NS_NO_OUTPUT_BUFFERING @@ -391,22 +404,40 @@ nsFileIO::GetName(nsACString &aName) nsFileStream::nsFileStream() : mFD(nsnull) + , mCloseFD(PR_TRUE) { NS_INIT_REFCNT(); } nsFileStream::~nsFileStream() { - Close(); + if (mCloseFD) + Close(); } NS_IMPL_THREADSAFE_ISUPPORTS1(nsFileStream, nsISeekableStream) +nsresult +nsFileStream::InitWithFileDescriptor(PRFileDesc* fd, nsISupports* parent) +{ + NS_ENSURE_TRUE(mFD == nsnull, NS_ERROR_ALREADY_INITIALIZED); + // + // this file stream is dependent on its parent to keep the + // file descriptor valid. an owning reference to the parent + // prevents the file descriptor from going away prematurely. + // + mFD = fd; + mCloseFD = PR_FALSE; + mParent = parent; + return NS_OK; +} + nsresult nsFileStream::Close() { if (mFD) { - PR_Close(mFD); + if (mCloseFD) + PR_Close(mFD); mFD = nsnull; } return NS_OK; @@ -505,6 +536,8 @@ nsFileInputStream::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult) NS_IMETHODIMP nsFileInputStream::Init(nsIFile* file, PRInt32 ioFlags, PRInt32 perm, PRBool deleteOnClose) { + NS_ENSURE_TRUE(mFD == nsnull, NS_ERROR_ALREADY_INITIALIZED); + nsresult rv = NS_OK; nsCOMPtr localFile = do_QueryInterface(file, &rv); if (NS_FAILED(rv)) return rv; @@ -516,18 +549,7 @@ nsFileInputStream::Init(nsIFile* file, PRInt32 ioFlags, PRInt32 perm, PRBool del PRFileDesc* fd; rv = localFile->OpenNSPRFileDesc(ioFlags, perm, &fd); if (NS_FAILED(rv)) return rv; - - return InitWithFileDescriptor(fd, file, deleteOnClose); -} -nsresult -nsFileInputStream::InitWithFileDescriptor(PRFileDesc* fd, nsIFile* file, PRBool deleteOnClose) -{ - NS_ASSERTION(mFD == nsnull, "already inited"); - if (mFD || !fd) - return NS_ERROR_FAILURE; - - mLineBuffer = nsnull; mFD = fd; if (deleteOnClose) { @@ -609,6 +631,7 @@ nsFileInputStream::ReadSegments(nsWriteSegmentFun writer, void * closure, PRUint if (NS_SUCCEEDED(rv)) { rv = writer(this, closure, readBuf, 0, nBytes, _retval); NS_ASSERTION(NS_SUCCEEDED(rv) ? nBytes == *_retval : PR_TRUE, "Didn't write all Data."); + // XXX this assertion is invalid! } nsMemory::Free(readBuf); @@ -647,6 +670,8 @@ nsFileOutputStream::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult) NS_IMETHODIMP nsFileOutputStream::Init(nsIFile* file, PRInt32 ioFlags, PRInt32 perm) { + NS_ENSURE_TRUE(mFD == nsnull, NS_ERROR_ALREADY_INITIALIZED); + nsresult rv; nsCOMPtr localFile = do_QueryInterface(file, &rv); if (NS_FAILED(rv)) return rv; @@ -654,20 +679,11 @@ nsFileOutputStream::Init(nsIFile* file, PRInt32 ioFlags, PRInt32 perm) ioFlags = PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE; if (perm <= 0) perm = 0664; + PRFileDesc* fd; rv = localFile->OpenNSPRFileDesc(ioFlags, perm, &fd); if (NS_FAILED(rv)) return rv; - return InitWithFileDescriptor(fd, file); -} - - -nsresult -nsFileOutputStream::InitWithFileDescriptor(PRFileDesc* fd, nsIFile* file) -{ - NS_ASSERTION(mFD == nsnull, "already inited"); - if (mFD || !fd) - return NS_ERROR_FAILURE; mFD = fd; return NS_OK; } diff --git a/netwerk/base/src/nsFileStreams.h b/netwerk/base/src/nsFileStreams.h index f7b129a901eb..e3425e92c784 100644 --- a/netwerk/base/src/nsFileStreams.h +++ b/netwerk/base/src/nsFileStreams.h @@ -88,9 +88,13 @@ public: virtual ~nsFileStream(); nsresult Close(); + nsresult InitWithFileDescriptor(PRFileDesc* fd, nsISupports* parent); protected: - PRFileDesc* mFD; + PRFileDesc* mFD; + nsCOMPtr mParent; // strong reference to parent nsFileIO, + // which ensures mFD remains valid. + PRBool mCloseFD; }; //////////////////////////////////////////////////////////////////////////////// @@ -116,7 +120,7 @@ public: static NS_METHOD Create(nsISupports *aOuter, REFNSIID aIID, void **aResult); - nsresult InitWithFileDescriptor(PRFileDesc* fd, nsIFile* file, PRBool deleteOnClose); + protected: nsLineBuffer *mLineBuffer; nsCOMPtr mFileToDelete; @@ -137,7 +141,6 @@ public: static NS_METHOD Create(nsISupports *aOuter, REFNSIID aIID, void **aResult); - nsresult InitWithFileDescriptor(PRFileDesc* fd, nsIFile* file); }; //////////////////////////////////////////////////////////////////////////////// diff --git a/netwerk/base/src/nsFileTransport.cpp b/netwerk/base/src/nsFileTransport.cpp index 6660220e11cd..73ad37218029 100644 --- a/netwerk/base/src/nsFileTransport.cpp +++ b/netwerk/base/src/nsFileTransport.cpp @@ -234,14 +234,18 @@ nsFileTransport::nsFileTransport() } nsresult -nsFileTransport::Init(nsFileTransportService *aService, nsIFile* file, PRInt32 ioFlags, PRInt32 perm) +nsFileTransport::Init(nsFileTransportService *aService, + nsIFile *file, + PRInt32 ioFlags, + PRInt32 perm, + PRBool closeStreamWhenDone) { nsresult rv; nsCOMPtr io; rv = NS_NewFileIO(getter_AddRefs(io), file, ioFlags, perm); if (NS_FAILED(rv)) return rv; - return Init(aService, io); + return Init(aService, io, closeStreamWhenDone); } nsresult @@ -258,12 +262,13 @@ nsFileTransport::Init(nsFileTransportService *aService, rv = NS_NewInputStreamIO(getter_AddRefs(io), name, inStr, contentType, contentCharset, contentLength); if (NS_FAILED(rv)) return rv; - mCloseStreamWhenDone = closeStreamWhenDone; - return Init(aService, io); + return Init(aService, io, closeStreamWhenDone); } nsresult -nsFileTransport::Init(nsFileTransportService *aService, nsIStreamIO* io) +nsFileTransport::Init(nsFileTransportService *aService, + nsIStreamIO* io, + PRBool closeStreamWhenDone) { nsresult rv = NS_OK; if (mLock == nsnull) { @@ -275,6 +280,8 @@ nsFileTransport::Init(nsFileTransportService *aService, nsIStreamIO* io) rv = mStreamIO->GetName(mStreamName); NS_ASSERTION(NS_SUCCEEDED(rv), "GetName failed"); + mCloseStreamWhenDone = closeStreamWhenDone; + NS_ADDREF(mService = aService); PR_AtomicIncrement(&mService->mTotalTransports); diff --git a/netwerk/base/src/nsFileTransport.h b/netwerk/base/src/nsFileTransport.h index 2a1720779afd..b67be33daadb 100644 --- a/netwerk/base/src/nsFileTransport.h +++ b/netwerk/base/src/nsFileTransport.h @@ -88,14 +88,17 @@ public: nsresult Init(nsFileTransportService *aService, nsIFile* file, PRInt32 ioFlags, - PRInt32 perm); + PRInt32 perm, + PRBool closeStreamWhenDone); nsresult Init(nsFileTransportService *aService, const nsACString &name, nsIInputStream *fromStream, const nsACString &contentType, const nsACString &contentCharset, PRInt32 contentLength, PRBool closeStreamWhenDone); - nsresult Init(nsFileTransportService *aService, nsIStreamIO* io); + nsresult Init(nsFileTransportService *aService, + nsIStreamIO* io, + PRBool closeStreamWhenDone); void Process(nsIProgressEventSink *); void DoClose(void); diff --git a/netwerk/base/src/nsFileTransportService.cpp b/netwerk/base/src/nsFileTransportService.cpp index 3b7efcb8cb4f..be5c41e487a9 100644 --- a/netwerk/base/src/nsFileTransportService.cpp +++ b/netwerk/base/src/nsFileTransportService.cpp @@ -127,6 +127,7 @@ NS_IMETHODIMP nsFileTransportService::CreateTransport(nsIFile* file, PRInt32 ioFlags, PRInt32 perm, + PRBool closeStreamWhenDone, nsITransport** result) { nsresult rv; @@ -134,7 +135,7 @@ nsFileTransportService::CreateTransport(nsIFile* file, if (trans == nsnull) return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(trans); - rv = trans->Init(this, file, ioFlags, perm); + rv = trans->Init(this, file, ioFlags, perm, closeStreamWhenDone); if (NS_FAILED(rv)) { NS_RELEASE(trans); return rv; @@ -170,6 +171,7 @@ nsFileTransportService::CreateTransportFromStream(const nsACString &name, NS_IMETHODIMP nsFileTransportService::CreateTransportFromStreamIO(nsIStreamIO *io, + PRBool closeStreamWhenDone, nsITransport **result) { nsresult rv; @@ -177,7 +179,7 @@ nsFileTransportService::CreateTransportFromStreamIO(nsIStreamIO *io, if (trans == nsnull) return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(trans); - rv = trans->Init(this, io); + rv = trans->Init(this, io, closeStreamWhenDone); if (NS_FAILED(rv)) { NS_RELEASE(trans); return rv; diff --git a/netwerk/base/src/nsInputStreamChannel.cpp b/netwerk/base/src/nsInputStreamChannel.cpp index 5348bc089432..668a06c30cc8 100644 --- a/netwerk/base/src/nsInputStreamChannel.cpp +++ b/netwerk/base/src/nsInputStreamChannel.cpp @@ -311,9 +311,10 @@ nsStreamIOChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctxt) do_GetService(kFileTransportServiceCID, &rv); if (NS_FAILED(rv)) goto done; - rv = fts->CreateTransportFromStreamIO(mStreamIO, getter_AddRefs(mFileTransport)); - if (NS_FAILED(rv)) goto done; - } + rv = fts->CreateTransportFromStreamIO(mStreamIO, PR_TRUE, + getter_AddRefs(mFileTransport)); + if (NS_FAILED(rv)) goto done; + } // Hook up the notification callbacks InterfaceRequestor... { diff --git a/netwerk/cache/src/nsDiskCacheDevice.cpp b/netwerk/cache/src/nsDiskCacheDevice.cpp index d2450b109c81..29324dacb1e9 100644 --- a/netwerk/cache/src/nsDiskCacheDevice.cpp +++ b/netwerk/cache/src/nsDiskCacheDevice.cpp @@ -580,7 +580,7 @@ nsDiskCacheDevice::GetTransportForEntry(nsCacheEntry * entry, break; } - rv = gFileTransportService->CreateTransport(file, ioFlags, PR_IRUSR | PR_IWUSR, result); + rv = gFileTransportService->CreateTransport(file, ioFlags, PR_IRUSR | PR_IWUSR, PR_FALSE, result); return rv; } diff --git a/netwerk/mime/src/nsXMLMIMEDataSource.cpp b/netwerk/mime/src/nsXMLMIMEDataSource.cpp index 277d51eb9ce3..3c91ff01b755 100644 --- a/netwerk/mime/src/nsXMLMIMEDataSource.cpp +++ b/netwerk/mime/src/nsXMLMIMEDataSource.cpp @@ -308,7 +308,11 @@ nsXMLMIMEDataSource::Serialize() { do_GetService(kFileTransportServiceCID, &rv) ; if(NS_FAILED(rv)) return rv ; - rv = fts->CreateTransport(mFile, PR_WRONLY|PR_CREATE_FILE, PR_IRWXU, getter_AddRefs(transport)) ; + rv = fts->CreateTransport(mFile, + PR_WRONLY|PR_CREATE_FILE, + PR_IRWXU, + PR_TRUE, + getter_AddRefs(transport)) ; if(NS_FAILED(rv)) return rv ; @@ -726,7 +730,11 @@ nsXMLMIMEDataSource::InitFromFile( nsIFile* aFile ) do_GetService(kFileTransportServiceCID, &rv) ; if(NS_FAILED(rv)) return rv ; // Made second parameter 0 since I really don't know what it is used for - rv = fts->CreateTransport(aFile, PR_RDONLY, PR_IRWXU, getter_AddRefs(transport)) ; + rv = fts->CreateTransport(aFile, + PR_RDONLY, + PR_IRWXU, + PR_TRUE, + getter_AddRefs(transport)) ; if(NS_FAILED(rv)) return rv ; diff --git a/netwerk/protocol/file/src/nsFileChannel.cpp b/netwerk/protocol/file/src/nsFileChannel.cpp index f5f787ae4f44..69e5025fdfb9 100644 --- a/netwerk/protocol/file/src/nsFileChannel.cpp +++ b/netwerk/protocol/file/src/nsFileChannel.cpp @@ -231,7 +231,7 @@ nsFileChannel::EnsureTransport() do_GetService(kFileTransportServiceCID, &rv); if (NS_FAILED(rv)) return rv; - rv = fts->CreateTransport(mFile, mIOFlags, mPerm, + rv = fts->CreateTransport(mFile, mIOFlags, mPerm, PR_TRUE, getter_AddRefs(mFileTransport)); if (NS_FAILED(rv)) return rv; diff --git a/netwerk/protocol/http/src/nsHttpChannel.cpp b/netwerk/protocol/http/src/nsHttpChannel.cpp index fab81ec6d915..c0b47f85841b 100644 --- a/netwerk/protocol/http/src/nsHttpChannel.cpp +++ b/netwerk/protocol/http/src/nsHttpChannel.cpp @@ -59,6 +59,7 @@ nsHttpChannel::nsHttpChannel() , mConnectionInfo(nsnull) , mLoadFlags(LOAD_NORMAL) , mStatus(NS_OK) + , mLogicalOffset(0) , mCapabilities(0) , mReferrerType(REFERRER_NONE) , mCachedResponseHead(nsnull) @@ -70,6 +71,7 @@ nsHttpChannel::nsHttpChannel() , mApplyConversion(PR_TRUE) , mFromCacheOnly(PR_FALSE) , mCachedContentIsValid(PR_FALSE) + , mCachedContentIsPartial(PR_FALSE) , mResponseHeadersModified(PR_FALSE) , mCanceled(PR_FALSE) , mUploadStreamHasHeaders(PR_FALSE) @@ -389,7 +391,7 @@ nsHttpChannel::SetupTransaction() if (mConnectionInfo->UsingSSL() || !mConnectionInfo->UsingHttpProxy()) { rv = mURI->GetPath(path); if (NS_FAILED(rv)) return rv; - // path may contain UTF-8 characters, so ensure that their escaped. + // path may contain UTF-8 characters, so ensure that they're escaped. if (NS_EscapeURL(path.get(), path.Length(), esc_OnlyNonASCII, buf)) requestURI = buf.get(); else @@ -489,10 +491,15 @@ nsHttpChannel::ProcessResponse() switch (httpStatus) { case 200: case 203: - case 206: // these can normally be cached rv = ProcessNormal(); break; + case 206: + if (mCachedContentIsPartial) // an internal byte range request... + rv = ProcessPartialContent(); + else + rv = ProcessNormal(); + break; case 300: case 301: case 302: @@ -596,6 +603,160 @@ nsHttpChannel::ProcessNormal() return rv; } +//----------------------------------------------------------------------------- +// nsHttpChannel +//----------------------------------------------------------------------------- + +nsresult +nsHttpChannel::SetupByteRangeRequest(PRUint32 partialLen) +{ + // cached content has been found to be partial, add necessary request + // headers to complete cache entry. + + // use strongest validator available... + const char *val = mCachedResponseHead->PeekHeader(nsHttp::ETag); + if (!val) + val = mCachedResponseHead->PeekHeader(nsHttp::Last_Modified); + if (!val) { + // if we hit this code it means mCachedResponseHead->IsResumable() is + // either broken or not being called. + NS_NOTREACHED("no cache validator"); + return NS_ERROR_FAILURE; + } + + char buf[32]; + PR_snprintf(buf, sizeof(buf), "bytes=%u-", partialLen); + + mRequestHead.SetHeader(nsHttp::Range, nsDependentCString(buf)); + mRequestHead.SetHeader(nsHttp::If_Range, nsDependentCString(val)); + + return NS_OK; +} + +nsresult +nsHttpChannel::ProcessPartialContent() +{ + nsresult rv; + + // ok, we've just received a 206 + // + // we need to stream whatever data is in the cache out first, and then + // pick up whatever data is on the wire, writing it into the cache. + + LOG(("nsHttpChannel::ProcessPartialContent [this=%x]\n", this)); + + NS_ENSURE_TRUE(mCachedResponseHead, NS_ERROR_NOT_INITIALIZED); + NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_NOT_INITIALIZED); + + // suspend the current transaction (may still get an OnDataAvailable) + rv = mTransaction->Suspend(); + if (NS_FAILED(rv)) return rv; + + // merge any new headers with the cached response headers + rv = mCachedResponseHead->UpdateHeaders(mResponseHead->Headers()); + if (NS_FAILED(rv)) return rv; + + // update the cached response head + nsCAutoString head; + mCachedResponseHead->Flatten(head, PR_TRUE); + rv = mCacheEntry->SetMetaDataElement("response-head", head.get()); + if (NS_FAILED(rv)) return rv; + + // make the cached response be the current response + delete mResponseHead; + mResponseHead = mCachedResponseHead; + mCachedResponseHead = 0; + + rv = UpdateExpirationTime(); + if (NS_FAILED(rv)) return rv; + + // the cached content is valid, although incomplete. + mCachedContentIsValid = PR_TRUE; + return ReadFromCache(); +} + +nsresult +nsHttpChannel::BufferPartialContent(nsIInputStream *input, PRUint32 count) +{ + nsresult rv; + + LOG(("nsHttpChannel::BufferPartialContent [this=%x count=%u]\n", this, count)); + + if (!mBufferOut) { + LOG(("creating pipe...\n")); + // + // create a pipe for buffering network data (the size of this + // pipe must be equal to or greater than the size of the pipe + // used to proxy data from the socket transport thread). + // + rv = NS_NewPipe(getter_AddRefs(mBufferIn), + getter_AddRefs(mBufferOut), + NS_HTTP_SEGMENT_SIZE, + NS_HTTP_BUFFER_SIZE, + PR_TRUE, + PR_TRUE); + if (NS_FAILED(rv)) return rv; + } + + PRUint32 bytesWritten = 0; + rv = mBufferOut->WriteFrom(input, count, &bytesWritten); + if (NS_FAILED(rv) || (bytesWritten != count)) { + LOG(("writing to pipe failed [rv=%s bytes-written=%u]\n", rv, bytesWritten)); + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +nsresult +nsHttpChannel::OnDoneReadingPartialCacheEntry(PRBool *streamDone) +{ + nsresult rv; + + LOG(("nsHttpChannel::OnDoneReadingPartialCacheEntry [this=%x]", this)); + + // by default, assume we would have streamed all data or failed... + *streamDone = PR_TRUE; + + // setup cache listener to append to cache entry + PRUint32 size; + rv = mCacheEntry->GetDataSize(&size); + if (NS_FAILED(rv)) return rv; + + rv = InstallCacheListener(size); + if (NS_FAILED(rv)) return rv; + + // process any buffered data + if (mBufferIn) { + PRUint32 avail; + rv = mBufferIn->Available(&avail); + if (NS_FAILED(rv)) return rv; + + rv = mListener->OnDataAvailable(this, mListenerContext, mBufferIn, size, avail); + if (NS_FAILED(rv)) return rv; + + // done with the pipe + mBufferIn = 0; + mBufferOut = 0; + } + + // need to track the logical offset of the data being sent to our listener + mLogicalOffset = size; + + // we're now completing the cached content, so we can clear this flag. + // this puts us in the state of a regular download. + mCachedContentIsPartial = PR_FALSE; + + // resume the transaction if it exists, otherwise the pipe contained the + // remaining part of the document and we've now streamed all of the data. + if (mTransaction) { + rv = mTransaction->Resume(); + if (NS_SUCCEEDED(rv)) + *streamDone = PR_FALSE; + } + return rv; +} + //----------------------------------------------------------------------------- // nsHttpChannel //----------------------------------------------------------------------------- @@ -664,7 +825,10 @@ nsHttpChannel::OpenCacheEntry(PRBool *delayed) return NS_OK; } else if (mRequestHead.PeekHeader(nsHttp::Range)) { - // we don't support caching for byte range requests + // we don't support caching for byte range requests initiated + // by our clients. + // XXX perhaps we could munge their byte range into the cache + // key to make caching sort'a work. return NS_OK; } @@ -850,18 +1014,24 @@ nsHttpChannel::CheckCache() } // If the cached content-length is set and it does not match the data size - // of the cached content, then refetch. - PRInt32 contentLength = mCachedResponseHead->ContentLength(); - if (contentLength != -1) { + // of the cached content, then the cached response is partial... + // either we need to issue a byte range request or we need to refetch the + // entire document. + PRUint32 contentLength = (PRUint32) mCachedResponseHead->ContentLength(); + if (contentLength != PRUint32(-1)) { PRUint32 size; rv = mCacheEntry->GetDataSize(&size); if (NS_FAILED(rv)) return rv; - if (size != (PRUint32) contentLength) { + if (size != contentLength) { LOG(("Cached data size does not match the Content-Length header " "[content-length=%u size=%u]\n", contentLength, size)); - // looks like a partial entry. - // XXX must re-fetch until we learn how to do byte range requests. + if ((size < contentLength) && mCachedResponseHead->IsResumable()) { + // looks like a partial entry. + rv = SetupByteRangeRequest(size); + if (NS_FAILED(rv)) return rv; + mCachedContentIsPartial = PR_TRUE; + } return NS_OK; } } @@ -1002,7 +1172,7 @@ nsHttpChannel::ReadFromCache() if (!mSecurityInfo) mCacheEntry->GetSecurityInfo(getter_AddRefs(mSecurityInfo)); - if (mCacheAccess & nsICache::ACCESS_WRITE) { + if ((mCacheAccess & nsICache::ACCESS_WRITE) && !mCachedContentIsPartial) { // We have write access to the cache, but we don't need to go to the // server to validate at this time, so just mark the cache entry as // valid in order to allow others access to this cache entry. @@ -1150,17 +1320,22 @@ nsHttpChannel::FinalizeCacheEntry() // Open an output stream to the cache entry and insert a listener tee into // the chain of response listeners. nsresult -nsHttpChannel::InstallCacheListener() +nsHttpChannel::InstallCacheListener(PRUint32 offset) { nsresult rv; LOG(("Preparing to write data into the cache [uri=%s]\n", mSpec.get())); - rv = mCacheEntry->GetTransport(getter_AddRefs(mCacheTransport)); - if (NS_FAILED(rv)) return rv; + NS_ASSERTION(mCacheEntry, "no cache entry"); + NS_ASSERTION(mListener, "no listener"); + + if (!mCacheTransport) { + rv = mCacheEntry->GetTransport(getter_AddRefs(mCacheTransport)); + if (NS_FAILED(rv)) return rv; + } nsCOMPtr out; - rv = mCacheTransport->OpenOutputStream(0, PRUint32(-1), 0, getter_AddRefs(out)); + rv = mCacheTransport->OpenOutputStream(offset, PRUint32(-1), 0, getter_AddRefs(out)); if (NS_FAILED(rv)) return rv; // XXX disk cache does not support overlapped i/o yet @@ -2536,14 +2711,17 @@ nsHttpChannel::GetContentEncodings(nsISimpleEnumerator** aEncodings) NS_IMETHODIMP nsHttpChannel::OnStartRequest(nsIRequest *request, nsISupports *ctxt) { - // capture the request's status, so our consumers will know ASAP of any - // connection failures, etc - bug 93581 - request->GetStatus(&mStatus); + if (!(mCanceled || NS_FAILED(mStatus))) { + // capture the request's status, so our consumers will know ASAP of any + // connection failures, etc - bug 93581 + request->GetStatus(&mStatus); + } LOG(("nsHttpChannel::OnStartRequest [this=%x request=%x status=%x]\n", this, request, mStatus)); - if (mTransaction) { + // don't enter this block if we're reading from the cache... + if (NS_SUCCEEDED(mStatus) && !mCacheReadRequest && mTransaction) { // grab the security info from the connection object; the transaction // is guaranteed to own a reference to the connection. mSecurityInfo = mTransaction->SecurityInfo(); @@ -2578,6 +2756,23 @@ nsHttpChannel::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult st mPrevTransaction = nsnull; } + if (mCachedContentIsPartial && NS_SUCCEEDED(status)) { + if (request == mTransaction) { + // byte-range transaction finished before we got around to streaming it. + NS_ASSERTION(mCacheReadRequest, "should be reading from cache right now"); + NS_RELEASE(mTransaction); + mTransaction = nsnull; + return NS_OK; + } + if (request == mCacheReadRequest) { + PRBool streamDone; + status = OnDoneReadingPartialCacheEntry(&streamDone); + if (NS_SUCCEEDED(status) && !streamDone) + return status; + // otherwise, fall through and fire OnStopRequest... + } + } + // if the request is for something we no longer reference, then simply // drop this event. if ((request != mTransaction) && (request != mCacheReadRequest)) @@ -2586,8 +2781,11 @@ nsHttpChannel::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult st mIsPending = PR_FALSE; mStatus = status; - // at this point, we're done with the transaction + PRBool isPartial = PR_FALSE; if (mTransaction) { + // find out if the transaction ran to completion... + isPartial = !mTransaction->ResponseIsComplete(); + // at this point, we're done with the transaction NS_RELEASE(mTransaction); mTransaction = nsnull; } @@ -2609,12 +2807,21 @@ nsHttpChannel::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult st } if (mCacheEntry) { - // we don't want to discard the cache entry if canceled and - // reading from the cache. - if (mCanceled && (request == mCacheReadRequest)) - CloseCacheEntry(NS_OK); - else - CloseCacheEntry(status); + nsresult closeStatus = status; + if (mCanceled) { + // we don't want to discard the cache entry if canceled and + // reading from the cache. + if (request == mCacheReadRequest) + closeStatus = NS_OK; + // we also don't want to discard the cache entry if the + // server supports byte range requests, because we could always + // complete the download at a later time. + else if (isPartial && mResponseHead && mResponseHead->IsResumable()) { + LOG(("keeping partial response that is resumable!\n")); + closeStatus = NS_OK; + } + } + CloseCacheEntry(closeStatus); } if (mLoadGroup) @@ -2635,6 +2842,11 @@ nsHttpChannel::OnDataAvailable(nsIRequest *request, nsISupports *ctxt, LOG(("nsHttpChannel::OnDataAvailable [this=%x request=%x offset=%u count=%u]\n", this, request, offset, count)); + if (mCachedContentIsPartial && (request == mTransaction)) { + // XXX we can eliminate this buffer once bug 93055 is resolved. + return BufferPartialContent(input, count); + } + // if the request is for something we no longer reference, then simply // drop this event. if ((request != mTransaction) && (request != mCacheReadRequest)) { @@ -2642,8 +2854,21 @@ nsHttpChannel::OnDataAvailable(nsIRequest *request, nsISupports *ctxt, return NS_BASE_STREAM_CLOSED; } - if (mListener) - return mListener->OnDataAvailable(this, mListenerContext, input, offset, count); + if (mListener) { + // + // we have to manually keep the logical offset of the stream up-to-date. + // we cannot depend soley on the offset provided, since we may have + // already streamed some data from another source (see, for example, + // OnDoneReadingPartialCacheEntry). + // + nsresult rv = mListener->OnDataAvailable(this, + mListenerContext, + input, + mLogicalOffset, + count); + mLogicalOffset += count; + return rv; + } return NS_BASE_STREAM_CLOSED; } diff --git a/netwerk/protocol/http/src/nsHttpChannel.h b/netwerk/protocol/http/src/nsHttpChannel.h index 2237fee6db0d..e57af3aad696 100644 --- a/netwerk/protocol/http/src/nsHttpChannel.h +++ b/netwerk/protocol/http/src/nsHttpChannel.h @@ -40,10 +40,12 @@ #include "nsICacheListener.h" #include "nsITransport.h" #include "nsIUploadChannel.h" +#include "nsISimpleEnumerator.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" #include "nsCOMPtr.h" #include "nsXPIDLString.h" #include "nsHttpConnection.h" -#include "nsISimpleEnumerator.h" class nsHttpTransaction; class nsHttpResponseHead; @@ -106,7 +108,13 @@ private: nsresult InitCacheEntry(); nsresult StoreAuthorizationMetaData(); nsresult FinalizeCacheEntry(); - nsresult InstallCacheListener(); + nsresult InstallCacheListener(PRUint32 offset = 0); + + // byte range request specific methods + nsresult SetupByteRangeRequest(PRUint32 partialLen); + nsresult ProcessPartialContent(); + nsresult BufferPartialContent(nsIInputStream *, PRUint32 count); + nsresult OnDoneReadingPartialCacheEntry(PRBool *streamDone); // auth specific methods nsresult GetCredentials(const char *challenges, PRBool proxyAuth, nsAFlatCString &creds); @@ -149,6 +157,7 @@ private: PRUint32 mLoadFlags; PRUint32 mStatus; + PRUint32 mLogicalOffset; PRUint8 mCapabilities; PRUint8 mReferrerType; @@ -161,6 +170,10 @@ private: PRUint32 mPostID; PRUint32 mRequestTime; + // byte-range specific data + nsCOMPtr mBufferIn; + nsCOMPtr mBufferOut; + // auth specific data nsXPIDLString mUser; nsXPIDLString mPass; @@ -174,6 +187,7 @@ private: PRPackedBool mApplyConversion; PRPackedBool mFromCacheOnly; PRPackedBool mCachedContentIsValid; + PRPackedBool mCachedContentIsPartial; PRPackedBool mResponseHeadersModified; PRPackedBool mCanceled; PRPackedBool mUploadStreamHasHeaders; diff --git a/netwerk/protocol/http/src/nsHttpResponseHead.cpp b/netwerk/protocol/http/src/nsHttpResponseHead.cpp index 7084c3159fb0..66965201596f 100644 --- a/netwerk/protocol/http/src/nsHttpResponseHead.cpp +++ b/netwerk/protocol/http/src/nsHttpResponseHead.cpp @@ -363,6 +363,17 @@ nsHttpResponseHead::MustValidateIfExpired() return val && PL_strcasestr(val, "must-revalidate"); } +PRBool +nsHttpResponseHead::IsResumable() +{ + // even though some HTTP/1.0 servers may support byte range requests, we're not + // going to bother with them, since those servers wouldn't understand If-Range. + return mVersion >= NS_HTTP_VERSION_1_1 && + PeekHeader(nsHttp::Content_Length) && + (PeekHeader(nsHttp::ETag) || PeekHeader(nsHttp::Last_Modified)) && + PL_strcasestr(PeekHeader(nsHttp::Accept_Ranges), "bytes"); +} + PRBool nsHttpResponseHead::ExpiresInPast() { diff --git a/netwerk/protocol/http/src/nsHttpResponseHead.h b/netwerk/protocol/http/src/nsHttpResponseHead.h index 8e26b4edc4a0..04366e6a8d8b 100644 --- a/netwerk/protocol/http/src/nsHttpResponseHead.h +++ b/netwerk/protocol/http/src/nsHttpResponseHead.h @@ -87,6 +87,9 @@ public: PRBool MustValidate(); PRBool MustValidateIfExpired(); + // returns true if the server appears to support byte range requests. + PRBool IsResumable(); + // returns true if the Expires header has a value in the past relative to the // value of the Date header. PRBool ExpiresInPast(); diff --git a/netwerk/protocol/http/src/nsHttpTransaction.cpp b/netwerk/protocol/http/src/nsHttpTransaction.cpp index 4d5cef5fb17a..6d177d276912 100644 --- a/netwerk/protocol/http/src/nsHttpTransaction.cpp +++ b/netwerk/protocol/http/src/nsHttpTransaction.cpp @@ -95,6 +95,7 @@ nsHttpTransaction::nsHttpTransaction(nsIStreamListener *listener, , mCapabilities(caps) , mHaveStatusLine(PR_FALSE) , mHaveAllHeaders(PR_FALSE) + , mResponseIsComplete(PR_FALSE) , mFiredOnStart(PR_FALSE) , mNoContent(PR_FALSE) , mPrematureEOF(PR_FALSE) @@ -617,6 +618,7 @@ nsHttpTransaction::HandleContent(char *buf, // OnTransactionComplete is fired only once! PRInt32 priorVal = PR_AtomicSet(&mTransactionDone, 1); if (priorVal == 0) { + mResponseIsComplete = PR_TRUE; // let the connection know that we are done with it; this should // result in OnStopTransaction being fired. return mConnection->OnTransactionComplete(this, NS_OK); diff --git a/netwerk/protocol/http/src/nsHttpTransaction.h b/netwerk/protocol/http/src/nsHttpTransaction.h index cb9f148267f0..0fdc690eb507 100644 --- a/netwerk/protocol/http/src/nsHttpTransaction.h +++ b/netwerk/protocol/http/src/nsHttpTransaction.h @@ -78,6 +78,9 @@ public: // will drop any reference to the response headers after this call. nsHttpResponseHead *TakeResponseHead(); + // Called to find out if the transaction generated a complete response. + PRBool ResponseIsComplete() { return mResponseIsComplete; } + // nsAHttpTransaction methods: void SetConnection(nsAHttpConnection *conn) { NS_IF_ADDREF(mConnection = conn); } void SetSecurityInfo(nsISupports *info) { mSecurityInfo = info; } @@ -133,6 +136,7 @@ private: PRPackedBool mHaveStatusLine; PRPackedBool mHaveAllHeaders; + PRPackedBool mResponseIsComplete; PRPackedBool mFiredOnStart; PRPackedBool mNoContent; // expecting an empty entity body? PRPackedBool mPrematureEOF; diff --git a/netwerk/protocol/jar/src/nsJARChannel.cpp b/netwerk/protocol/jar/src/nsJARChannel.cpp index 1f208ffbea82..3e843fc80c91 100644 --- a/netwerk/protocol/jar/src/nsJARChannel.cpp +++ b/netwerk/protocol/jar/src/nsJARChannel.cpp @@ -339,7 +339,7 @@ nsJARChannel::AsyncReadJARElement() if (NS_FAILED(rv)) return rv; nsCOMPtr jarTransport; - rv = fts->CreateTransportFromStreamIO(this, getter_AddRefs(jarTransport)); + rv = fts->CreateTransportFromStreamIO(this, PR_TRUE, getter_AddRefs(jarTransport)); if (NS_FAILED(rv)) return rv; if (mCallbacks) { diff --git a/netwerk/test/TestFileInput.cpp b/netwerk/test/TestFileInput.cpp index e91d8d86b9c6..3cfbcd5db3ca 100644 --- a/netwerk/test/TestFileInput.cpp +++ b/netwerk/test/TestFileInput.cpp @@ -366,7 +366,7 @@ ParallelReadTest(char* dirName, nsIFileTransportService* fts) NS_ASSERTION(listener, "QI failed"); nsITransport* trans; - rv = fts->CreateTransport(file, PR_RDONLY, 0, &trans); + rv = fts->CreateTransport(file, PR_RDONLY, 0, PR_TRUE, &trans); NS_ASSERTION(NS_SUCCEEDED(rv), "create failed"); nsCOMPtr request; rv = trans->AsyncRead(nsnull, listener, 0, -1, 0, getter_AddRefs(request)); diff --git a/netwerk/test/TestFileTransport.cpp b/netwerk/test/TestFileTransport.cpp index 5f41e3636237..bc8784c42722 100644 --- a/netwerk/test/TestFileTransport.cpp +++ b/netwerk/test/TestFileTransport.cpp @@ -204,7 +204,7 @@ TestAsyncRead(const char* fileName, PRUint32 offset, PRInt32 length) nsCOMPtr file; rv = NS_NewLocalFile(fileName, PR_FALSE, getter_AddRefs(file)); if (NS_FAILED(rv)) return rv; - rv = fts->CreateTransport(file, PR_RDONLY, 0, &fileTrans); + rv = fts->CreateTransport(file, PR_RDONLY, 0, PR_TRUE, &fileTrans); if (NS_FAILED(rv)) return rv; MyListener* listener = new MyListener(); @@ -260,7 +260,7 @@ TestAsyncWrite(const char* fileName, PRUint32 offset, PRInt32 length) if (NS_FAILED(rv)) return rv; rv = fts->CreateTransport(file, PR_CREATE_FILE | PR_WRONLY | PR_TRUNCATE, - 0664, &fileTrans); + 0664, PR_TRUE, &fileTrans); if (NS_FAILED(rv)) return rv; MyListener* listener = new MyListener(); diff --git a/netwerk/test/TestWriteStream.cpp b/netwerk/test/TestWriteStream.cpp index dc740cb40e9e..cb3465711387 100644 --- a/netwerk/test/TestWriteStream.cpp +++ b/netwerk/test/TestWriteStream.cpp @@ -133,7 +133,7 @@ TestSyncWrite(char* filename, PRUint32 startPosition, PRInt32 length) if (NS_FAILED(rv)) return rv ; nsCOMPtr transport; - rv = fts->CreateTransport(fs, PR_RDWR | PR_CREATE_FILE, 0664, + rv = fts->CreateTransport(fs, PR_RDWR | PR_CREATE_FILE, 0664, PR_TRUE, getter_AddRefs(transport)) ; if (NS_FAILED(rv)) return rv ; diff --git a/uriloader/exthandler/nsExternalHelperAppService.cpp b/uriloader/exthandler/nsExternalHelperAppService.cpp index d2d5c4a3c056..c10e48f3a416 100644 --- a/uriloader/exthandler/nsExternalHelperAppService.cpp +++ b/uriloader/exthandler/nsExternalHelperAppService.cpp @@ -1030,10 +1030,14 @@ nsresult nsExternalAppHandler::SetUpTempFile(nsIChannel * aChannel) if (NS_FAILED(rv)) return rv; nsCOMPtr fileTransport; - rv = fts->CreateTransport(mTempFile, PR_WRONLY | PR_CREATE_FILE, 0600, getter_AddRefs(fileTransport)); + rv = fts->CreateTransport(mTempFile, + PR_WRONLY | PR_CREATE_FILE, + 0600, + PR_TRUE, + getter_AddRefs(fileTransport)); if (NS_FAILED(rv)) return rv; - rv = fileTransport->OpenOutputStream(0, -1, 0, getter_AddRefs(mOutStream)); + rv = fileTransport->OpenOutputStream(0, PRUint32(-1), 0, getter_AddRefs(mOutStream)); #ifdef XP_MAC nsXPIDLCString contentType; diff --git a/xpfe/components/xfer/src/nsStreamXferOp.cpp b/xpfe/components/xfer/src/nsStreamXferOp.cpp index dedfd41e94c9..7b38ca759fb1 100644 --- a/xpfe/components/xfer/src/nsStreamXferOp.cpp +++ b/xpfe/components/xfer/src/nsStreamXferOp.cpp @@ -168,6 +168,7 @@ nsStreamXferOp::Start( void ) { rv = fts->CreateTransport( mOutputFile, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 0664, + PR_TRUE, getter_AddRefs( mOutputTransport ) ); if ( NS_SUCCEEDED( rv ) ) { diff --git a/xpinstall/src/nsXPInstallManager.cpp b/xpinstall/src/nsXPInstallManager.cpp index 94446a953837..693048570ebc 100644 --- a/xpinstall/src/nsXPInstallManager.cpp +++ b/xpinstall/src/nsXPInstallManager.cpp @@ -777,6 +777,7 @@ nsXPInstallManager::OnStartRequest(nsIRequest* request, nsISupports *ctxt) rv = fts->CreateTransport(mItem->mFile, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 0664, + PR_TRUE, getter_AddRefs( outTransport)); if (NS_SUCCEEDED(rv))