summaryrefslogtreecommitdiffstats
path: root/netwerk/cache/nsCacheEntryDescriptor.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/cache/nsCacheEntryDescriptor.cpp')
-rw-r--r--netwerk/cache/nsCacheEntryDescriptor.cpp1475
1 files changed, 1475 insertions, 0 deletions
diff --git a/netwerk/cache/nsCacheEntryDescriptor.cpp b/netwerk/cache/nsCacheEntryDescriptor.cpp
new file mode 100644
index 000000000..b53ee3058
--- /dev/null
+++ b/netwerk/cache/nsCacheEntryDescriptor.cpp
@@ -0,0 +1,1475 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsICache.h"
+#include "nsCache.h"
+#include "nsCacheService.h"
+#include "nsCacheEntryDescriptor.h"
+#include "nsCacheEntry.h"
+#include "nsReadableUtils.h"
+#include "nsIOutputStream.h"
+#include "nsCRT.h"
+#include "nsThreadUtils.h"
+#include <algorithm>
+
+#define kMinDecompressReadBufLen 1024
+#define kMinCompressWriteBufLen 1024
+
+
+/******************************************************************************
+ * nsAsyncDoomEvent
+ *****************************************************************************/
+
+class nsAsyncDoomEvent : public mozilla::Runnable {
+public:
+ nsAsyncDoomEvent(nsCacheEntryDescriptor *descriptor,
+ nsICacheListener *listener)
+ {
+ mDescriptor = descriptor;
+ mListener = listener;
+ mThread = do_GetCurrentThread();
+ // We addref the listener here and release it in nsNotifyDoomListener
+ // on the callers thread. If posting of nsNotifyDoomListener event fails
+ // we leak the listener which is better than releasing it on a wrong
+ // thread.
+ NS_IF_ADDREF(mListener);
+ }
+
+ NS_IMETHOD Run() override
+ {
+ nsresult status = NS_OK;
+
+ {
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSASYNCDOOMEVENT_RUN));
+
+ if (mDescriptor->mCacheEntry) {
+ status = nsCacheService::gService->DoomEntry_Internal(
+ mDescriptor->mCacheEntry, true);
+ } else if (!mDescriptor->mDoomedOnClose) {
+ status = NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+
+ if (mListener) {
+ mThread->Dispatch(new nsNotifyDoomListener(mListener, status),
+ NS_DISPATCH_NORMAL);
+ // posted event will release the reference on the correct thread
+ mListener = nullptr;
+ }
+
+ return NS_OK;
+ }
+
+private:
+ RefPtr<nsCacheEntryDescriptor> mDescriptor;
+ nsICacheListener *mListener;
+ nsCOMPtr<nsIThread> mThread;
+};
+
+
+NS_IMPL_ISUPPORTS(nsCacheEntryDescriptor,
+ nsICacheEntryDescriptor,
+ nsICacheEntryInfo)
+
+nsCacheEntryDescriptor::nsCacheEntryDescriptor(nsCacheEntry * entry,
+ nsCacheAccessMode accessGranted)
+ : mCacheEntry(entry),
+ mAccessGranted(accessGranted),
+ mOutputWrapper(nullptr),
+ mLock("nsCacheEntryDescriptor.mLock"),
+ mAsyncDoomPending(false),
+ mDoomedOnClose(false),
+ mClosingDescriptor(false)
+{
+ PR_INIT_CLIST(this);
+ NS_ADDREF(nsCacheService::GlobalInstance()); // ensure it lives for the lifetime of the descriptor
+}
+
+
+nsCacheEntryDescriptor::~nsCacheEntryDescriptor()
+{
+ // No need to close if the cache entry has already been severed. This
+ // helps avoid a shutdown assertion (bug 285519) that is caused when
+ // consumers end up holding onto these objects past xpcom-shutdown. It's
+ // okay for them to do that because the cache service calls our Close
+ // method during xpcom-shutdown, so we don't need to complain about it.
+ if (mCacheEntry)
+ Close();
+
+ NS_ASSERTION(mInputWrappers.IsEmpty(),
+ "We have still some input wrapper!");
+ NS_ASSERTION(!mOutputWrapper, "We have still an output wrapper!");
+
+ nsCacheService * service = nsCacheService::GlobalInstance();
+ NS_RELEASE(service);
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetClientID(char ** result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETCLIENTID));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ return ClientIDFromCacheKey(*(mCacheEntry->Key()), result);
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetDeviceID(char ** aDeviceID)
+{
+ NS_ENSURE_ARG_POINTER(aDeviceID);
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETDEVICEID));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ const char* deviceID = mCacheEntry->GetDeviceID();
+ if (!deviceID) {
+ *aDeviceID = nullptr;
+ return NS_OK;
+ }
+
+ *aDeviceID = NS_strdup(deviceID);
+ return *aDeviceID ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetKey(nsACString &result)
+{
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETKEY));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ return ClientKeyFromCacheKey(*(mCacheEntry->Key()), result);
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetFetchCount(int32_t *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETFETCHCOUNT));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *result = mCacheEntry->FetchCount();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetLastFetched(uint32_t *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETLASTFETCHED));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *result = mCacheEntry->LastFetched();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetLastModified(uint32_t *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETLASTMODIFIED));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *result = mCacheEntry->LastModified();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetExpirationTime(uint32_t *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETEXPIRATIONTIME));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *result = mCacheEntry->ExpirationTime();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::SetExpirationTime(uint32_t expirationTime)
+{
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_SETEXPIRATIONTIME));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ mCacheEntry->SetExpirationTime(expirationTime);
+ mCacheEntry->MarkEntryDirty();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsCacheEntryDescriptor::IsStreamBased(bool *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_ISSTREAMBASED));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *result = mCacheEntry->IsStreamData();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCacheEntryDescriptor::GetPredictedDataSize(int64_t *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETPREDICTEDDATASIZE));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *result = mCacheEntry->PredictedDataSize();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCacheEntryDescriptor::SetPredictedDataSize(int64_t
+ predictedSize)
+{
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_SETPREDICTEDDATASIZE));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ mCacheEntry->SetPredictedDataSize(predictedSize);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCacheEntryDescriptor::GetDataSize(uint32_t *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETDATASIZE));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ const char* val = mCacheEntry->GetMetaDataElement("uncompressed-len");
+ if (!val) {
+ *result = mCacheEntry->DataSize();
+ } else {
+ *result = atol(val);
+ }
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsCacheEntryDescriptor::GetStorageDataSize(uint32_t *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETSTORAGEDATASIZE));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *result = mCacheEntry->DataSize();
+
+ return NS_OK;
+}
+
+
+nsresult
+nsCacheEntryDescriptor::RequestDataSizeChange(int32_t deltaSize)
+{
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_REQUESTDATASIZECHANGE));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ nsresult rv;
+ rv = nsCacheService::OnDataSizeChange(mCacheEntry, deltaSize);
+ if (NS_SUCCEEDED(rv)) {
+ // XXX review for signed/unsigned math errors
+ uint32_t newDataSize = mCacheEntry->DataSize() + deltaSize;
+ mCacheEntry->SetDataSize(newDataSize);
+ mCacheEntry->TouchData();
+ }
+ return rv;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::SetDataSize(uint32_t dataSize)
+{
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_SETDATASIZE));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ // XXX review for signed/unsigned math errors
+ int32_t deltaSize = dataSize - mCacheEntry->DataSize();
+
+ nsresult rv;
+ rv = nsCacheService::OnDataSizeChange(mCacheEntry, deltaSize);
+ // this had better be NS_OK, this call instance is advisory for memory cache objects
+ if (NS_SUCCEEDED(rv)) {
+ // XXX review for signed/unsigned math errors
+ uint32_t newDataSize = mCacheEntry->DataSize() + deltaSize;
+ mCacheEntry->SetDataSize(newDataSize);
+ mCacheEntry->TouchData();
+ } else {
+ NS_WARNING("failed SetDataSize() on memory cache object!");
+ }
+
+ return rv;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::OpenInputStream(uint32_t offset, nsIInputStream ** result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+
+ nsInputStreamWrapper* cacheInput = nullptr;
+ {
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_OPENINPUTSTREAM));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+ if (!mCacheEntry->IsStreamData()) return NS_ERROR_CACHE_DATA_IS_NOT_STREAM;
+
+ // Don't open any new stream when closing descriptor or clearing entries
+ if (mClosingDescriptor || nsCacheService::GetClearingEntries())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // ensure valid permissions
+ if (!(mAccessGranted & nsICache::ACCESS_READ))
+ return NS_ERROR_CACHE_READ_ACCESS_DENIED;
+
+ const char *val;
+ val = mCacheEntry->GetMetaDataElement("uncompressed-len");
+ if (val) {
+ cacheInput = new nsDecompressInputStreamWrapper(this, offset);
+ } else {
+ cacheInput = new nsInputStreamWrapper(this, offset);
+ }
+ if (!cacheInput) return NS_ERROR_OUT_OF_MEMORY;
+
+ mInputWrappers.AppendElement(cacheInput);
+ }
+
+ NS_ADDREF(*result = cacheInput);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::OpenOutputStream(uint32_t offset, nsIOutputStream ** result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+
+ nsOutputStreamWrapper* cacheOutput = nullptr;
+ {
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_OPENOUTPUTSTREAM));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+ if (!mCacheEntry->IsStreamData()) return NS_ERROR_CACHE_DATA_IS_NOT_STREAM;
+
+ // Don't open any new stream when closing descriptor or clearing entries
+ if (mClosingDescriptor || nsCacheService::GetClearingEntries())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // ensure valid permissions
+ if (!(mAccessGranted & nsICache::ACCESS_WRITE))
+ return NS_ERROR_CACHE_WRITE_ACCESS_DENIED;
+
+ int32_t compressionLevel = nsCacheService::CacheCompressionLevel();
+ const char *val;
+ val = mCacheEntry->GetMetaDataElement("uncompressed-len");
+ if ((compressionLevel > 0) && val) {
+ cacheOutput = new nsCompressOutputStreamWrapper(this, offset);
+ } else {
+ // clear compression flag when compression disabled - see bug 715198
+ if (val) {
+ mCacheEntry->SetMetaDataElement("uncompressed-len", nullptr);
+ }
+ cacheOutput = new nsOutputStreamWrapper(this, offset);
+ }
+ if (!cacheOutput) return NS_ERROR_OUT_OF_MEMORY;
+
+ mOutputWrapper = cacheOutput;
+ }
+
+ NS_ADDREF(*result = cacheOutput);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetCacheElement(nsISupports ** result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETCACHEELEMENT));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+ if (mCacheEntry->IsStreamData()) return NS_ERROR_CACHE_DATA_IS_STREAM;
+
+ NS_IF_ADDREF(*result = mCacheEntry->Data());
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::SetCacheElement(nsISupports * cacheElement)
+{
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_SETCACHEELEMENT));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+ if (mCacheEntry->IsStreamData()) return NS_ERROR_CACHE_DATA_IS_STREAM;
+
+ return nsCacheService::SetCacheElement(mCacheEntry, cacheElement);
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetAccessGranted(nsCacheAccessMode *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = mAccessGranted;
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetStoragePolicy(nsCacheStoragePolicy *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETSTORAGEPOLICY));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *result = mCacheEntry->StoragePolicy();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::SetStoragePolicy(nsCacheStoragePolicy policy)
+{
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_SETSTORAGEPOLICY));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+ // XXX validate policy against session?
+
+ bool storageEnabled = false;
+ storageEnabled = nsCacheService::IsStorageEnabledForPolicy_Locked(policy);
+ if (!storageEnabled) return NS_ERROR_FAILURE;
+
+ // Don't change the storage policy of entries we can't write
+ if (!(mAccessGranted & nsICache::ACCESS_WRITE))
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // Don't allow a cache entry to move from memory-only to anything else
+ if (mCacheEntry->StoragePolicy() == nsICache::STORE_IN_MEMORY &&
+ policy != nsICache::STORE_IN_MEMORY)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ mCacheEntry->SetStoragePolicy(policy);
+ mCacheEntry->MarkEntryDirty();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetFile(nsIFile ** result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETFILE));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ return nsCacheService::GetFileForEntry(mCacheEntry, result);
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetSecurityInfo(nsISupports ** result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETSECURITYINFO));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ *result = mCacheEntry->SecurityInfo();
+ NS_IF_ADDREF(*result);
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::SetSecurityInfo(nsISupports * securityInfo)
+{
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_SETSECURITYINFO));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ mCacheEntry->SetSecurityInfo(securityInfo);
+ mCacheEntry->MarkEntryDirty();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::Doom()
+{
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_DOOM));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ return nsCacheService::DoomEntry(mCacheEntry);
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::DoomAndFailPendingRequests(nsresult status)
+{
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_DOOMANDFAILPENDINGREQUESTS));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::AsyncDoom(nsICacheListener *listener)
+{
+ bool asyncDoomPending;
+ {
+ mozilla::MutexAutoLock lock(mLock);
+ asyncDoomPending = mAsyncDoomPending;
+ mAsyncDoomPending = true;
+ }
+
+ if (asyncDoomPending) {
+ // AsyncDoom was already called. Notify listener if it is non-null,
+ // otherwise just return success.
+ if (listener) {
+ nsresult rv = NS_DispatchToCurrentThread(
+ new nsNotifyDoomListener(listener, NS_ERROR_NOT_AVAILABLE));
+ if (NS_SUCCEEDED(rv))
+ NS_IF_ADDREF(listener);
+ return rv;
+ }
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIRunnable> event = new nsAsyncDoomEvent(this, listener);
+ return nsCacheService::DispatchToCacheIOThread(event);
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::MarkValid()
+{
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_MARKVALID));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ nsresult rv = nsCacheService::ValidateEntry(mCacheEntry);
+ return rv;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::Close()
+{
+ RefPtr<nsOutputStreamWrapper> outputWrapper;
+ nsTArray<RefPtr<nsInputStreamWrapper> > inputWrappers;
+
+ {
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_CLOSE));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ // Make sure no other stream can be opened
+ mClosingDescriptor = true;
+ outputWrapper = mOutputWrapper;
+ for (size_t i = 0; i < mInputWrappers.Length(); i++)
+ inputWrappers.AppendElement(mInputWrappers[i]);
+ }
+
+ // Call Close() on the streams outside the lock since it might need to call
+ // methods that grab the cache service lock, e.g. compressed output stream
+ // when it finalizes the entry
+ if (outputWrapper) {
+ if (NS_FAILED(outputWrapper->Close())) {
+ NS_WARNING("Dooming entry because Close() failed!!!");
+ Doom();
+ }
+ outputWrapper = nullptr;
+ }
+
+ for (uint32_t i = 0 ; i < inputWrappers.Length() ; i++)
+ inputWrappers[i]->Close();
+
+ inputWrappers.Clear();
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_CLOSE));
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ // XXX perhaps closing descriptors should clear/sever transports
+
+ // tell nsCacheService we're going away
+ nsCacheService::CloseDescriptor(this);
+ NS_ASSERTION(mCacheEntry == nullptr, "mCacheEntry not null");
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::GetMetaDataElement(const char *key, char **result)
+{
+ NS_ENSURE_ARG_POINTER(key);
+ *result = nullptr;
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_GETMETADATAELEMENT));
+ NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_NOT_AVAILABLE);
+
+ const char *value;
+
+ value = mCacheEntry->GetMetaDataElement(key);
+ if (!value) return NS_ERROR_NOT_AVAILABLE;
+
+ *result = NS_strdup(value);
+ if (!*result) return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::SetMetaDataElement(const char *key, const char *value)
+{
+ NS_ENSURE_ARG_POINTER(key);
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_SETMETADATAELEMENT));
+ NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_NOT_AVAILABLE);
+
+ // XXX allow null value, for clearing key?
+
+ nsresult rv = mCacheEntry->SetMetaDataElement(key, value);
+ if (NS_SUCCEEDED(rv))
+ mCacheEntry->TouchMetaData();
+ return rv;
+}
+
+
+NS_IMETHODIMP
+nsCacheEntryDescriptor::VisitMetaData(nsICacheMetaDataVisitor * visitor)
+{
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSCACHEENTRYDESCRIPTOR_VISITMETADATA));
+ // XXX check callers, we're calling out of module
+ NS_ENSURE_ARG_POINTER(visitor);
+ if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ return mCacheEntry->VisitMetaDataElements(visitor);
+}
+
+
+/******************************************************************************
+ * nsCacheInputStream - a wrapper for nsIInputStream keeps the cache entry
+ * open while referenced.
+ ******************************************************************************/
+
+NS_IMPL_ADDREF(nsCacheEntryDescriptor::nsInputStreamWrapper)
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsCacheEntryDescriptor::nsInputStreamWrapper::Release()
+{
+ // Holding a reference to descriptor ensures that cache service won't go
+ // away. Do not grab cache service lock if there is no descriptor.
+ RefPtr<nsCacheEntryDescriptor> desc;
+
+ {
+ mozilla::MutexAutoLock lock(mLock);
+ desc = mDescriptor;
+ }
+
+ if (desc)
+ nsCacheService::Lock(LOCK_TELEM(NSINPUTSTREAMWRAPPER_RELEASE));
+
+ nsrefcnt count;
+ NS_PRECONDITION(0 != mRefCnt, "dup release");
+ count = --mRefCnt;
+ NS_LOG_RELEASE(this, count, "nsCacheEntryDescriptor::nsInputStreamWrapper");
+
+ if (0 == count) {
+ // don't use desc here since mDescriptor might be already nulled out
+ if (mDescriptor) {
+ NS_ASSERTION(mDescriptor->mInputWrappers.Contains(this),
+ "Wrapper not found in array!");
+ mDescriptor->mInputWrappers.RemoveElement(this);
+ }
+
+ if (desc)
+ nsCacheService::Unlock();
+
+ mRefCnt = 1;
+ delete (this);
+ return 0;
+ }
+
+ if (desc)
+ nsCacheService::Unlock();
+
+ return count;
+}
+
+NS_INTERFACE_MAP_BEGIN(nsCacheEntryDescriptor::nsInputStreamWrapper)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+nsresult nsCacheEntryDescriptor::
+nsInputStreamWrapper::LazyInit()
+{
+ // Check if we have the descriptor. If not we can't even grab the cache
+ // lock since it is not ensured that the cache service still exists.
+ if (!mDescriptor)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSINPUTSTREAMWRAPPER_LAZYINIT));
+
+ nsCacheAccessMode mode;
+ nsresult rv = mDescriptor->GetAccessGranted(&mode);
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ENSURE_TRUE(mode & nsICache::ACCESS_READ, NS_ERROR_UNEXPECTED);
+
+ nsCacheEntry* cacheEntry = mDescriptor->CacheEntry();
+ if (!cacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ rv = nsCacheService::OpenInputStreamForEntry(cacheEntry, mode,
+ mStartOffset,
+ getter_AddRefs(mInput));
+
+ CACHE_LOG_DEBUG(("nsInputStreamWrapper::LazyInit "
+ "[entry=%p, wrapper=%p, mInput=%p, rv=%d]",
+ mDescriptor, this, mInput.get(), int(rv)));
+
+ if (NS_FAILED(rv)) return rv;
+
+ mInitialized = true;
+ return NS_OK;
+}
+
+nsresult nsCacheEntryDescriptor::
+nsInputStreamWrapper::EnsureInit()
+{
+ if (mInitialized) {
+ NS_ASSERTION(mDescriptor, "Bad state");
+ return NS_OK;
+ }
+
+ return LazyInit();
+}
+
+void nsCacheEntryDescriptor::
+nsInputStreamWrapper::CloseInternal()
+{
+ mLock.AssertCurrentThreadOwns();
+ if (!mDescriptor) {
+ NS_ASSERTION(!mInitialized, "Bad state");
+ NS_ASSERTION(!mInput, "Bad state");
+ return;
+ }
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSINPUTSTREAMWRAPPER_CLOSEINTERNAL));
+
+ if (mDescriptor) {
+ mDescriptor->mInputWrappers.RemoveElement(this);
+ nsCacheService::ReleaseObject_Locked(mDescriptor);
+ mDescriptor = nullptr;
+ }
+ mInitialized = false;
+ mInput = nullptr;
+}
+
+nsresult nsCacheEntryDescriptor::
+nsInputStreamWrapper::Close()
+{
+ mozilla::MutexAutoLock lock(mLock);
+
+ return Close_Locked();
+}
+
+nsresult nsCacheEntryDescriptor::
+nsInputStreamWrapper::Close_Locked()
+{
+ nsresult rv = EnsureInit();
+ if (NS_SUCCEEDED(rv)) {
+ rv = mInput->Close();
+ } else {
+ NS_ASSERTION(!mInput,
+ "Shouldn't have mInput when EnsureInit() failed");
+ }
+
+ // Call CloseInternal() even when EnsureInit() failed, e.g. in case we are
+ // closing streams with nsCacheService::CloseAllStream()
+ CloseInternal();
+ return rv;
+}
+
+nsresult nsCacheEntryDescriptor::
+nsInputStreamWrapper::Available(uint64_t *avail)
+{
+ mozilla::MutexAutoLock lock(mLock);
+
+ nsresult rv = EnsureInit();
+ if (NS_FAILED(rv)) return rv;
+
+ return mInput->Available(avail);
+}
+
+nsresult nsCacheEntryDescriptor::
+nsInputStreamWrapper::Read(char *buf, uint32_t count, uint32_t *countRead)
+{
+ mozilla::MutexAutoLock lock(mLock);
+
+ return Read_Locked(buf, count, countRead);
+}
+
+nsresult nsCacheEntryDescriptor::
+nsInputStreamWrapper::Read_Locked(char *buf, uint32_t count, uint32_t *countRead)
+{
+ nsresult rv = EnsureInit();
+ if (NS_SUCCEEDED(rv))
+ rv = mInput->Read(buf, count, countRead);
+
+ CACHE_LOG_DEBUG(("nsInputStreamWrapper::Read "
+ "[entry=%p, wrapper=%p, mInput=%p, rv=%d]",
+ mDescriptor, this, mInput.get(), rv));
+
+ return rv;
+}
+
+nsresult nsCacheEntryDescriptor::
+nsInputStreamWrapper::ReadSegments(nsWriteSegmentFun writer, void *closure,
+ uint32_t count, uint32_t *countRead)
+{
+ // cache stream not buffered
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsCacheEntryDescriptor::
+nsInputStreamWrapper::IsNonBlocking(bool *result)
+{
+ // cache streams will never return NS_BASE_STREAM_WOULD_BLOCK
+ *result = false;
+ return NS_OK;
+}
+
+
+/******************************************************************************
+ * nsDecompressInputStreamWrapper - an input stream wrapper that decompresses
+ ******************************************************************************/
+
+NS_IMPL_ADDREF(nsCacheEntryDescriptor::nsDecompressInputStreamWrapper)
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsCacheEntryDescriptor::nsDecompressInputStreamWrapper::Release()
+{
+ // Holding a reference to descriptor ensures that cache service won't go
+ // away. Do not grab cache service lock if there is no descriptor.
+ RefPtr<nsCacheEntryDescriptor> desc;
+
+ {
+ mozilla::MutexAutoLock lock(mLock);
+ desc = mDescriptor;
+ }
+
+ if (desc)
+ nsCacheService::Lock(LOCK_TELEM(
+ NSDECOMPRESSINPUTSTREAMWRAPPER_RELEASE));
+
+ nsrefcnt count;
+ NS_PRECONDITION(0 != mRefCnt, "dup release");
+ count = --mRefCnt;
+ NS_LOG_RELEASE(this, count,
+ "nsCacheEntryDescriptor::nsDecompressInputStreamWrapper");
+
+ if (0 == count) {
+ // don't use desc here since mDescriptor might be already nulled out
+ if (mDescriptor) {
+ NS_ASSERTION(mDescriptor->mInputWrappers.Contains(this),
+ "Wrapper not found in array!");
+ mDescriptor->mInputWrappers.RemoveElement(this);
+ }
+
+ if (desc)
+ nsCacheService::Unlock();
+
+ mRefCnt = 1;
+ delete (this);
+ return 0;
+ }
+
+ if (desc)
+ nsCacheService::Unlock();
+
+ return count;
+}
+
+NS_INTERFACE_MAP_BEGIN(nsCacheEntryDescriptor::nsDecompressInputStreamWrapper)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+NS_IMETHODIMP nsCacheEntryDescriptor::
+nsDecompressInputStreamWrapper::Read(char * buf,
+ uint32_t count,
+ uint32_t *countRead)
+{
+ mozilla::MutexAutoLock lock(mLock);
+
+ int zerr = Z_OK;
+ nsresult rv = NS_OK;
+
+ if (!mStreamInitialized) {
+ rv = InitZstream();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ mZstream.next_out = (Bytef*)buf;
+ mZstream.avail_out = count;
+
+ if (mReadBufferLen < count) {
+ // Allocate a buffer for reading from the input stream. This will
+ // determine the max number of compressed bytes read from the
+ // input stream at one time. Making the buffer size proportional
+ // to the request size is not necessary, but helps minimize the
+ // number of read requests to the input stream.
+ uint32_t newBufLen = std::max(count, (uint32_t)kMinDecompressReadBufLen);
+ unsigned char* newBuf;
+ newBuf = (unsigned char*)moz_xrealloc(mReadBuffer,
+ newBufLen);
+ if (newBuf) {
+ mReadBuffer = newBuf;
+ mReadBufferLen = newBufLen;
+ }
+ if (!mReadBuffer) {
+ mReadBufferLen = 0;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ // read and inflate data until the output buffer is full, or
+ // there is no more data to read
+ while (NS_SUCCEEDED(rv) &&
+ zerr == Z_OK &&
+ mZstream.avail_out > 0 &&
+ count > 0) {
+ if (mZstream.avail_in == 0) {
+ rv = nsInputStreamWrapper::Read_Locked((char*)mReadBuffer,
+ mReadBufferLen,
+ &mZstream.avail_in);
+ if (NS_FAILED(rv) || !mZstream.avail_in) {
+ break;
+ }
+ mZstream.next_in = mReadBuffer;
+ }
+ zerr = inflate(&mZstream, Z_NO_FLUSH);
+ if (zerr == Z_STREAM_END) {
+ // The compressed data may have been stored in multiple
+ // chunks/streams. To allow for this case, re-initialize
+ // the inflate stream and continue decompressing from
+ // the next byte.
+ Bytef * saveNextIn = mZstream.next_in;
+ unsigned int saveAvailIn = mZstream.avail_in;
+ Bytef * saveNextOut = mZstream.next_out;
+ unsigned int saveAvailOut = mZstream.avail_out;
+ inflateReset(&mZstream);
+ mZstream.next_in = saveNextIn;
+ mZstream.avail_in = saveAvailIn;
+ mZstream.next_out = saveNextOut;
+ mZstream.avail_out = saveAvailOut;
+ zerr = Z_OK;
+ } else if (zerr != Z_OK) {
+ rv = NS_ERROR_INVALID_CONTENT_ENCODING;
+ }
+ }
+ if (NS_SUCCEEDED(rv)) {
+ *countRead = count - mZstream.avail_out;
+ }
+ return rv;
+}
+
+nsresult nsCacheEntryDescriptor::
+nsDecompressInputStreamWrapper::Close()
+{
+ mozilla::MutexAutoLock lock(mLock);
+
+ if (!mDescriptor)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ EndZstream();
+ if (mReadBuffer) {
+ free(mReadBuffer);
+ mReadBuffer = 0;
+ mReadBufferLen = 0;
+ }
+ return nsInputStreamWrapper::Close_Locked();
+}
+
+nsresult nsCacheEntryDescriptor::
+nsDecompressInputStreamWrapper::InitZstream()
+{
+ if (!mDescriptor)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ if (mStreamEnded)
+ return NS_ERROR_FAILURE;
+
+ // Initialize zlib inflate stream
+ mZstream.zalloc = Z_NULL;
+ mZstream.zfree = Z_NULL;
+ mZstream.opaque = Z_NULL;
+ mZstream.next_out = Z_NULL;
+ mZstream.avail_out = 0;
+ mZstream.next_in = Z_NULL;
+ mZstream.avail_in = 0;
+ if (inflateInit(&mZstream) != Z_OK) {
+ return NS_ERROR_FAILURE;
+ }
+ mStreamInitialized = true;
+ return NS_OK;
+}
+
+nsresult nsCacheEntryDescriptor::
+nsDecompressInputStreamWrapper::EndZstream()
+{
+ if (mStreamInitialized && !mStreamEnded) {
+ inflateEnd(&mZstream);
+ mStreamInitialized = false;
+ mStreamEnded = true;
+ }
+ return NS_OK;
+}
+
+
+/******************************************************************************
+ * nsOutputStreamWrapper - a wrapper for nsIOutputstream to track the amount of
+ * data written to a cache entry.
+ * - also keeps the cache entry open while referenced.
+ ******************************************************************************/
+
+NS_IMPL_ADDREF(nsCacheEntryDescriptor::nsOutputStreamWrapper)
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsCacheEntryDescriptor::nsOutputStreamWrapper::Release()
+{
+ // Holding a reference to descriptor ensures that cache service won't go
+ // away. Do not grab cache service lock if there is no descriptor.
+ RefPtr<nsCacheEntryDescriptor> desc;
+
+ {
+ mozilla::MutexAutoLock lock(mLock);
+ desc = mDescriptor;
+ }
+
+ if (desc)
+ nsCacheService::Lock(LOCK_TELEM(NSOUTPUTSTREAMWRAPPER_RELEASE));
+
+ nsrefcnt count;
+ NS_PRECONDITION(0 != mRefCnt, "dup release");
+ count = --mRefCnt;
+ NS_LOG_RELEASE(this, count,
+ "nsCacheEntryDescriptor::nsOutputStreamWrapper");
+
+ if (0 == count) {
+ // don't use desc here since mDescriptor might be already nulled out
+ if (mDescriptor)
+ mDescriptor->mOutputWrapper = nullptr;
+
+ if (desc)
+ nsCacheService::Unlock();
+
+ mRefCnt = 1;
+ delete (this);
+ return 0;
+ }
+
+ if (desc)
+ nsCacheService::Unlock();
+
+ return count;
+}
+
+NS_INTERFACE_MAP_BEGIN(nsCacheEntryDescriptor::nsOutputStreamWrapper)
+ NS_INTERFACE_MAP_ENTRY(nsIOutputStream)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+nsresult nsCacheEntryDescriptor::
+nsOutputStreamWrapper::LazyInit()
+{
+ // Check if we have the descriptor. If not we can't even grab the cache
+ // lock since it is not ensured that the cache service still exists.
+ if (!mDescriptor)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSOUTPUTSTREAMWRAPPER_LAZYINIT));
+
+ nsCacheAccessMode mode;
+ nsresult rv = mDescriptor->GetAccessGranted(&mode);
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ENSURE_TRUE(mode & nsICache::ACCESS_WRITE, NS_ERROR_UNEXPECTED);
+
+ nsCacheEntry* cacheEntry = mDescriptor->CacheEntry();
+ if (!cacheEntry) return NS_ERROR_NOT_AVAILABLE;
+
+ NS_ASSERTION(mOutput == nullptr, "mOutput set in LazyInit");
+
+ nsCOMPtr<nsIOutputStream> stream;
+ rv = nsCacheService::OpenOutputStreamForEntry(cacheEntry, mode, mStartOffset,
+ getter_AddRefs(stream));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCacheDevice* device = cacheEntry->CacheDevice();
+ if (device) {
+ // the entry has been truncated to mStartOffset bytes, inform device
+ int32_t size = cacheEntry->DataSize();
+ rv = device->OnDataSizeChange(cacheEntry, mStartOffset - size);
+ if (NS_SUCCEEDED(rv))
+ cacheEntry->SetDataSize(mStartOffset);
+ } else {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // If anything above failed, clean up internal state and get out of here
+ // (see bug #654926)...
+ if (NS_FAILED(rv)) {
+ nsCacheService::ReleaseObject_Locked(stream.forget().take());
+ mDescriptor->mOutputWrapper = nullptr;
+ nsCacheService::ReleaseObject_Locked(mDescriptor);
+ mDescriptor = nullptr;
+ mInitialized = false;
+ return rv;
+ }
+
+ mOutput = stream;
+ mInitialized = true;
+ return NS_OK;
+}
+
+nsresult nsCacheEntryDescriptor::
+nsOutputStreamWrapper::EnsureInit()
+{
+ if (mInitialized) {
+ NS_ASSERTION(mDescriptor, "Bad state");
+ return NS_OK;
+ }
+
+ return LazyInit();
+}
+
+nsresult nsCacheEntryDescriptor::
+nsOutputStreamWrapper::OnWrite(uint32_t count)
+{
+ if (count > INT32_MAX) return NS_ERROR_UNEXPECTED;
+ return mDescriptor->RequestDataSizeChange((int32_t)count);
+}
+
+void nsCacheEntryDescriptor::
+nsOutputStreamWrapper::CloseInternal()
+{
+ mLock.AssertCurrentThreadOwns();
+ if (!mDescriptor) {
+ NS_ASSERTION(!mInitialized, "Bad state");
+ NS_ASSERTION(!mOutput, "Bad state");
+ return;
+ }
+
+ nsCacheServiceAutoLock lock(LOCK_TELEM(NSOUTPUTSTREAMWRAPPER_CLOSEINTERNAL));
+
+ if (mDescriptor) {
+ mDescriptor->mOutputWrapper = nullptr;
+ nsCacheService::ReleaseObject_Locked(mDescriptor);
+ mDescriptor = nullptr;
+ }
+ mInitialized = false;
+ mOutput = nullptr;
+}
+
+
+NS_IMETHODIMP nsCacheEntryDescriptor::
+nsOutputStreamWrapper::Close()
+{
+ mozilla::MutexAutoLock lock(mLock);
+
+ return Close_Locked();
+}
+
+nsresult nsCacheEntryDescriptor::
+nsOutputStreamWrapper::Close_Locked()
+{
+ nsresult rv = EnsureInit();
+ if (NS_SUCCEEDED(rv)) {
+ rv = mOutput->Close();
+ } else {
+ NS_ASSERTION(!mOutput,
+ "Shouldn't have mOutput when EnsureInit() failed");
+ }
+
+ // Call CloseInternal() even when EnsureInit() failed, e.g. in case we are
+ // closing streams with nsCacheService::CloseAllStream()
+ CloseInternal();
+ return rv;
+}
+
+NS_IMETHODIMP nsCacheEntryDescriptor::
+nsOutputStreamWrapper::Flush()
+{
+ mozilla::MutexAutoLock lock(mLock);
+
+ nsresult rv = EnsureInit();
+ if (NS_FAILED(rv)) return rv;
+
+ return mOutput->Flush();
+}
+
+NS_IMETHODIMP nsCacheEntryDescriptor::
+nsOutputStreamWrapper::Write(const char * buf,
+ uint32_t count,
+ uint32_t * result)
+{
+ mozilla::MutexAutoLock lock(mLock);
+ return Write_Locked(buf, count, result);
+}
+
+nsresult nsCacheEntryDescriptor::
+nsOutputStreamWrapper::Write_Locked(const char * buf,
+ uint32_t count,
+ uint32_t * result)
+{
+ nsresult rv = EnsureInit();
+ if (NS_FAILED(rv)) return rv;
+
+ rv = OnWrite(count);
+ if (NS_FAILED(rv)) return rv;
+
+ return mOutput->Write(buf, count, result);
+}
+
+NS_IMETHODIMP nsCacheEntryDescriptor::
+nsOutputStreamWrapper::WriteFrom(nsIInputStream * inStr,
+ uint32_t count,
+ uint32_t * result)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsCacheEntryDescriptor::
+nsOutputStreamWrapper::WriteSegments(nsReadSegmentFun reader,
+ void * closure,
+ uint32_t count,
+ uint32_t * result)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsCacheEntryDescriptor::
+nsOutputStreamWrapper::IsNonBlocking(bool *result)
+{
+ // cache streams will never return NS_BASE_STREAM_WOULD_BLOCK
+ *result = false;
+ return NS_OK;
+}
+
+
+/******************************************************************************
+ * nsCompressOutputStreamWrapper - an output stream wrapper that compresses
+ * data before it is written
+ ******************************************************************************/
+
+NS_IMPL_ADDREF(nsCacheEntryDescriptor::nsCompressOutputStreamWrapper)
+NS_IMETHODIMP_(MozExternalRefCountType)
+nsCacheEntryDescriptor::nsCompressOutputStreamWrapper::Release()
+{
+ // Holding a reference to descriptor ensures that cache service won't go
+ // away. Do not grab cache service lock if there is no descriptor.
+ RefPtr<nsCacheEntryDescriptor> desc;
+
+ {
+ mozilla::MutexAutoLock lock(mLock);
+ desc = mDescriptor;
+ }
+
+ if (desc)
+ nsCacheService::Lock(LOCK_TELEM(NSCOMPRESSOUTPUTSTREAMWRAPPER_RELEASE));
+
+ nsrefcnt count;
+ NS_PRECONDITION(0 != mRefCnt, "dup release");
+ count = --mRefCnt;
+ NS_LOG_RELEASE(this, count,
+ "nsCacheEntryDescriptor::nsCompressOutputStreamWrapper");
+
+ if (0 == count) {
+ // don't use desc here since mDescriptor might be already nulled out
+ if (mDescriptor)
+ mDescriptor->mOutputWrapper = nullptr;
+
+ if (desc)
+ nsCacheService::Unlock();
+
+ mRefCnt = 1;
+ delete (this);
+ return 0;
+ }
+
+ if (desc)
+ nsCacheService::Unlock();
+
+ return count;
+}
+
+NS_INTERFACE_MAP_BEGIN(nsCacheEntryDescriptor::nsCompressOutputStreamWrapper)
+ NS_INTERFACE_MAP_ENTRY(nsIOutputStream)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+NS_IMETHODIMP nsCacheEntryDescriptor::
+nsCompressOutputStreamWrapper::Write(const char * buf,
+ uint32_t count,
+ uint32_t * result)
+{
+ mozilla::MutexAutoLock lock(mLock);
+
+ int zerr = Z_OK;
+ nsresult rv = NS_OK;
+
+ if (!mStreamInitialized) {
+ rv = InitZstream();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ if (!mWriteBuffer) {
+ // Once allocated, this buffer is referenced by the zlib stream and
+ // cannot be grown. We use 2x(initial write request) to approximate
+ // a stream buffer size proportional to request buffers.
+ mWriteBufferLen = std::max(count*2, (uint32_t)kMinCompressWriteBufLen);
+ mWriteBuffer = (unsigned char*)moz_xmalloc(mWriteBufferLen);
+ if (!mWriteBuffer) {
+ mWriteBufferLen = 0;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ mZstream.next_out = mWriteBuffer;
+ mZstream.avail_out = mWriteBufferLen;
+ }
+
+ // Compress (deflate) the requested buffer. Keep going
+ // until the entire buffer has been deflated.
+ mZstream.avail_in = count;
+ mZstream.next_in = (Bytef*)buf;
+ while (mZstream.avail_in > 0) {
+ zerr = deflate(&mZstream, Z_NO_FLUSH);
+ if (zerr == Z_STREAM_ERROR) {
+ deflateEnd(&mZstream);
+ mStreamEnded = true;
+ mStreamInitialized = false;
+ return NS_ERROR_FAILURE;
+ }
+ // Note: Z_BUF_ERROR is non-fatal and sometimes expected here.
+
+ // If the compression stream output buffer is filled, write
+ // it out to the underlying stream wrapper.
+ if (mZstream.avail_out == 0) {
+ rv = WriteBuffer();
+ if (NS_FAILED(rv)) {
+ deflateEnd(&mZstream);
+ mStreamEnded = true;
+ mStreamInitialized = false;
+ return rv;
+ }
+ }
+ }
+ *result = count;
+ mUncompressedCount += *result;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCacheEntryDescriptor::
+nsCompressOutputStreamWrapper::Close()
+{
+ mozilla::MutexAutoLock lock(mLock);
+
+ if (!mDescriptor)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsresult retval = NS_OK;
+ nsresult rv;
+ int zerr = 0;
+
+ if (mStreamInitialized) {
+ // complete compression of any data remaining in the zlib stream
+ do {
+ zerr = deflate(&mZstream, Z_FINISH);
+ rv = WriteBuffer();
+ if (NS_FAILED(rv))
+ retval = rv;
+ } while (zerr == Z_OK && rv == NS_OK);
+ deflateEnd(&mZstream);
+ mStreamInitialized = false;
+ }
+ // Do not allow to initialize stream after calling Close().
+ mStreamEnded = true;
+
+ if (mDescriptor->CacheEntry()) {
+ nsAutoCString uncompressedLenStr;
+ rv = mDescriptor->GetMetaDataElement("uncompressed-len",
+ getter_Copies(uncompressedLenStr));
+ if (NS_SUCCEEDED(rv)) {
+ int32_t oldCount = uncompressedLenStr.ToInteger(&rv);
+ if (NS_SUCCEEDED(rv)) {
+ mUncompressedCount += oldCount;
+ }
+ }
+ uncompressedLenStr.Adopt(0);
+ uncompressedLenStr.AppendInt(mUncompressedCount);
+ rv = mDescriptor->SetMetaDataElement("uncompressed-len",
+ uncompressedLenStr.get());
+ if (NS_FAILED(rv))
+ retval = rv;
+ }
+
+ if (mWriteBuffer) {
+ free(mWriteBuffer);
+ mWriteBuffer = 0;
+ mWriteBufferLen = 0;
+ }
+
+ rv = nsOutputStreamWrapper::Close_Locked();
+ if (NS_FAILED(rv))
+ retval = rv;
+
+ return retval;
+}
+
+nsresult nsCacheEntryDescriptor::
+nsCompressOutputStreamWrapper::InitZstream()
+{
+ if (!mDescriptor)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ if (mStreamEnded)
+ return NS_ERROR_FAILURE;
+
+ // Determine compression level: Aggressive compression
+ // may impact performance on mobile devices, while a
+ // lower compression level still provides substantial
+ // space savings for many text streams.
+ int32_t compressionLevel = nsCacheService::CacheCompressionLevel();
+
+ // Initialize zlib deflate stream
+ mZstream.zalloc = Z_NULL;
+ mZstream.zfree = Z_NULL;
+ mZstream.opaque = Z_NULL;
+ if (deflateInit2(&mZstream, compressionLevel, Z_DEFLATED,
+ MAX_WBITS, 8, Z_DEFAULT_STRATEGY) != Z_OK) {
+ return NS_ERROR_FAILURE;
+ }
+ mZstream.next_in = Z_NULL;
+ mZstream.avail_in = 0;
+
+ mStreamInitialized = true;
+
+ return NS_OK;
+}
+
+nsresult nsCacheEntryDescriptor::
+nsCompressOutputStreamWrapper::WriteBuffer()
+{
+ uint32_t bytesToWrite = mWriteBufferLen - mZstream.avail_out;
+ uint32_t result = 0;
+ nsresult rv = nsCacheEntryDescriptor::nsOutputStreamWrapper::Write_Locked(
+ (const char *)mWriteBuffer, bytesToWrite, &result);
+ mZstream.next_out = mWriteBuffer;
+ mZstream.avail_out = mWriteBufferLen;
+ return rv;
+}
+