gecko-dev/netwerk/cache2/CacheFileOutputStream.cpp
Nikhil Marathe 8dbf443150 Bug 1167809 - Add skip size check flag to cache for use with ServiceWorkers. r=mayhemer
For non-e10s Service Worker, we use Cache entries to achieve interception.
While this is a temporary measure, the fact that cache enforces size limits on
cache entries (which make sense for the purpose it was designed) prevents large
files from being served via a Service Worker. This patch adds a skip size check
flag to CacheStorage that is relayed to CacheEntry and CacheFile. It is set to
false by default leading to normal cache behaviour.
The patch also adds nsICacheStorageService.synthesizedCacheStorage() that
retrieves a cache storage with this flag set to true, which is used by
nsHttpChannel in case of possible interception.
2015-09-03 16:05:42 -07:00

467 lines
12 KiB
C++

/* 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 "CacheLog.h"
#include "CacheFileOutputStream.h"
#include "CacheFile.h"
#include "CacheEntry.h"
#include "nsStreamUtils.h"
#include "nsThreadUtils.h"
#include "mozilla/DebugOnly.h"
#include <algorithm>
namespace mozilla {
namespace net {
NS_IMPL_ADDREF(CacheFileOutputStream)
NS_IMETHODIMP_(MozExternalRefCountType)
CacheFileOutputStream::Release()
{
NS_PRECONDITION(0 != mRefCnt, "dup release");
nsrefcnt count = --mRefCnt;
NS_LOG_RELEASE(this, count, "CacheFileOutputStream");
if (0 == count) {
mRefCnt = 1;
{
CacheFileAutoLock lock(mFile);
mFile->RemoveOutput(this, mStatus);
}
delete (this);
return 0;
}
return count;
}
NS_INTERFACE_MAP_BEGIN(CacheFileOutputStream)
NS_INTERFACE_MAP_ENTRY(nsIOutputStream)
NS_INTERFACE_MAP_ENTRY(nsIAsyncOutputStream)
NS_INTERFACE_MAP_ENTRY(nsISeekableStream)
NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileChunkListener)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIOutputStream)
NS_INTERFACE_MAP_END_THREADSAFE
CacheFileOutputStream::CacheFileOutputStream(CacheFile *aFile,
CacheOutputCloseListener *aCloseListener)
: mFile(aFile)
, mCloseListener(aCloseListener)
, mPos(0)
, mClosed(false)
, mStatus(NS_OK)
, mCallbackFlags(0)
{
LOG(("CacheFileOutputStream::CacheFileOutputStream() [this=%p]", this));
MOZ_COUNT_CTOR(CacheFileOutputStream);
}
CacheFileOutputStream::~CacheFileOutputStream()
{
LOG(("CacheFileOutputStream::~CacheFileOutputStream() [this=%p]", this));
MOZ_COUNT_DTOR(CacheFileOutputStream);
}
// nsIOutputStream
NS_IMETHODIMP
CacheFileOutputStream::Close()
{
LOG(("CacheFileOutputStream::Close() [this=%p]", this));
return CloseWithStatus(NS_OK);
}
NS_IMETHODIMP
CacheFileOutputStream::Flush()
{
// TODO do we need to implement flush ???
LOG(("CacheFileOutputStream::Flush() [this=%p]", this));
return NS_OK;
}
NS_IMETHODIMP
CacheFileOutputStream::Write(const char * aBuf, uint32_t aCount,
uint32_t *_retval)
{
CacheFileAutoLock lock(mFile);
LOG(("CacheFileOutputStream::Write() [this=%p, count=%d]", this, aCount));
if (mClosed) {
LOG(("CacheFileOutputStream::Write() - Stream is closed. [this=%p, "
"status=0x%08x]", this, mStatus));
return NS_FAILED(mStatus) ? mStatus : NS_BASE_STREAM_CLOSED;
}
if (!mFile->mSkipSizeCheck && CacheObserver::EntryIsTooBig(mPos + aCount, !mFile->mMemoryOnly)) {
LOG(("CacheFileOutputStream::Write() - Entry is too big, failing and "
"dooming the entry. [this=%p]", this));
mFile->DoomLocked(nullptr);
CloseWithStatusLocked(NS_ERROR_FILE_TOO_BIG);
return NS_ERROR_FILE_TOO_BIG;
}
// We use 64-bit offset when accessing the file, unfortunatelly we use 32-bit
// metadata offset, so we cannot handle data bigger than 4GB.
if (mPos + aCount > PR_UINT32_MAX) {
LOG(("CacheFileOutputStream::Write() - Entry's size exceeds 4GB while it "
"isn't too big according to CacheObserver::EntryIsTooBig(). Failing "
"and dooming the entry. [this=%p]", this));
mFile->DoomLocked(nullptr);
CloseWithStatusLocked(NS_ERROR_FILE_TOO_BIG);
return NS_ERROR_FILE_TOO_BIG;
}
*_retval = aCount;
while (aCount) {
EnsureCorrectChunk(false);
if (NS_FAILED(mStatus)) {
return mStatus;
}
FillHole();
if (NS_FAILED(mStatus)) {
return mStatus;
}
uint32_t chunkOffset = mPos - (mPos / kChunkSize) * kChunkSize;
uint32_t canWrite = kChunkSize - chunkOffset;
uint32_t thisWrite = std::min(static_cast<uint32_t>(canWrite), aCount);
nsresult rv = mChunk->EnsureBufSize(chunkOffset + thisWrite);
if (NS_FAILED(rv)) {
CloseWithStatusLocked(rv);
return rv;
}
memcpy(mChunk->BufForWriting() + chunkOffset, aBuf, thisWrite);
mPos += thisWrite;
aBuf += thisWrite;
aCount -= thisWrite;
mChunk->UpdateDataSize(chunkOffset, thisWrite, false);
}
EnsureCorrectChunk(true);
LOG(("CacheFileOutputStream::Write() - Wrote %d bytes [this=%p]",
*_retval, this));
return NS_OK;
}
NS_IMETHODIMP
CacheFileOutputStream::WriteFrom(nsIInputStream *aFromStream, uint32_t aCount,
uint32_t *_retval)
{
LOG(("CacheFileOutputStream::WriteFrom() - NOT_IMPLEMENTED [this=%p, from=%p"
", count=%d]", this, aFromStream, aCount));
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
CacheFileOutputStream::WriteSegments(nsReadSegmentFun aReader, void *aClosure,
uint32_t aCount, uint32_t *_retval)
{
LOG(("CacheFileOutputStream::WriteSegments() - NOT_IMPLEMENTED [this=%p, "
"count=%d]", this, aCount));
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
CacheFileOutputStream::IsNonBlocking(bool *_retval)
{
*_retval = false;
return NS_OK;
}
// nsIAsyncOutputStream
NS_IMETHODIMP
CacheFileOutputStream::CloseWithStatus(nsresult aStatus)
{
CacheFileAutoLock lock(mFile);
LOG(("CacheFileOutputStream::CloseWithStatus() [this=%p, aStatus=0x%08x]",
this, aStatus));
return CloseWithStatusLocked(aStatus);
}
nsresult
CacheFileOutputStream::CloseWithStatusLocked(nsresult aStatus)
{
LOG(("CacheFileOutputStream::CloseWithStatusLocked() [this=%p, "
"aStatus=0x%08x]", this, aStatus));
if (mClosed) {
MOZ_ASSERT(!mCallback);
return NS_OK;
}
mClosed = true;
mStatus = NS_FAILED(aStatus) ? aStatus : NS_BASE_STREAM_CLOSED;
if (mChunk) {
ReleaseChunk();
}
if (mCallback) {
NotifyListener();
}
mFile->RemoveOutput(this, mStatus);
return NS_OK;
}
NS_IMETHODIMP
CacheFileOutputStream::AsyncWait(nsIOutputStreamCallback *aCallback,
uint32_t aFlags,
uint32_t aRequestedCount,
nsIEventTarget *aEventTarget)
{
CacheFileAutoLock lock(mFile);
LOG(("CacheFileOutputStream::AsyncWait() [this=%p, callback=%p, flags=%d, "
"requestedCount=%d, eventTarget=%p]", this, aCallback, aFlags,
aRequestedCount, aEventTarget));
mCallback = aCallback;
mCallbackFlags = aFlags;
mCallbackTarget = aEventTarget;
if (!mCallback)
return NS_OK;
// The stream is blocking so it is writable at any time
if (mClosed || !(aFlags & WAIT_CLOSURE_ONLY))
NotifyListener();
return NS_OK;
}
// nsISeekableStream
NS_IMETHODIMP
CacheFileOutputStream::Seek(int32_t whence, int64_t offset)
{
CacheFileAutoLock lock(mFile);
LOG(("CacheFileOutputStream::Seek() [this=%p, whence=%d, offset=%lld]",
this, whence, offset));
if (mClosed) {
LOG(("CacheFileOutputStream::Seek() - Stream is closed. [this=%p]", this));
return NS_BASE_STREAM_CLOSED;
}
int64_t newPos = offset;
switch (whence) {
case NS_SEEK_SET:
break;
case NS_SEEK_CUR:
newPos += mPos;
break;
case NS_SEEK_END:
newPos += mFile->mDataSize;
break;
default:
NS_ERROR("invalid whence");
return NS_ERROR_INVALID_ARG;
}
mPos = newPos;
EnsureCorrectChunk(true);
LOG(("CacheFileOutputStream::Seek() [this=%p, pos=%lld]", this, mPos));
return NS_OK;
}
NS_IMETHODIMP
CacheFileOutputStream::Tell(int64_t *_retval)
{
CacheFileAutoLock lock(mFile);
if (mClosed) {
LOG(("CacheFileOutputStream::Tell() - Stream is closed. [this=%p]", this));
return NS_BASE_STREAM_CLOSED;
}
*_retval = mPos;
LOG(("CacheFileOutputStream::Tell() [this=%p, retval=%lld]", this, *_retval));
return NS_OK;
}
NS_IMETHODIMP
CacheFileOutputStream::SetEOF()
{
MOZ_ASSERT(false, "CacheFileOutputStream::SetEOF() not implemented");
// Right now we don't use SetEOF(). If we ever need this method, we need
// to think about what to do with input streams that already points beyond
// new EOF.
return NS_ERROR_NOT_IMPLEMENTED;
}
// CacheFileChunkListener
nsresult
CacheFileOutputStream::OnChunkRead(nsresult aResult, CacheFileChunk *aChunk)
{
MOZ_CRASH("CacheFileOutputStream::OnChunkRead should not be called!");
return NS_ERROR_UNEXPECTED;
}
nsresult
CacheFileOutputStream::OnChunkWritten(nsresult aResult, CacheFileChunk *aChunk)
{
MOZ_CRASH(
"CacheFileOutputStream::OnChunkWritten should not be called!");
return NS_ERROR_UNEXPECTED;
}
nsresult
CacheFileOutputStream::OnChunkAvailable(nsresult aResult,
uint32_t aChunkIdx,
CacheFileChunk *aChunk)
{
MOZ_CRASH(
"CacheFileOutputStream::OnChunkAvailable should not be called!");
return NS_ERROR_UNEXPECTED;
}
nsresult
CacheFileOutputStream::OnChunkUpdated(CacheFileChunk *aChunk)
{
MOZ_CRASH(
"CacheFileOutputStream::OnChunkUpdated should not be called!");
return NS_ERROR_UNEXPECTED;
}
void CacheFileOutputStream::NotifyCloseListener()
{
nsRefPtr<CacheOutputCloseListener> listener;
listener.swap(mCloseListener);
if (!listener)
return;
listener->OnOutputClosed();
}
void
CacheFileOutputStream::ReleaseChunk()
{
LOG(("CacheFileOutputStream::ReleaseChunk() [this=%p, idx=%d]",
this, mChunk->Index()));
mFile->ReleaseOutsideLock(mChunk.forget());
}
void
CacheFileOutputStream::EnsureCorrectChunk(bool aReleaseOnly)
{
mFile->AssertOwnsLock();
LOG(("CacheFileOutputStream::EnsureCorrectChunk() [this=%p, releaseOnly=%d]",
this, aReleaseOnly));
uint32_t chunkIdx = mPos / kChunkSize;
if (mChunk) {
if (mChunk->Index() == chunkIdx) {
// we have a correct chunk
LOG(("CacheFileOutputStream::EnsureCorrectChunk() - Have correct chunk "
"[this=%p, idx=%d]", this, chunkIdx));
return;
}
else {
ReleaseChunk();
}
}
if (aReleaseOnly)
return;
nsresult rv;
rv = mFile->GetChunkLocked(chunkIdx, CacheFile::WRITER, nullptr,
getter_AddRefs(mChunk));
if (NS_FAILED(rv)) {
LOG(("CacheFileOutputStream::EnsureCorrectChunk() - GetChunkLocked failed. "
"[this=%p, idx=%d, rv=0x%08x]", this, chunkIdx, rv));
CloseWithStatusLocked(rv);
}
}
void
CacheFileOutputStream::FillHole()
{
mFile->AssertOwnsLock();
MOZ_ASSERT(mChunk);
MOZ_ASSERT(mPos / kChunkSize == mChunk->Index());
uint32_t pos = mPos - (mPos / kChunkSize) * kChunkSize;
if (mChunk->DataSize() >= pos)
return;
LOG(("CacheFileOutputStream::FillHole() - Zeroing hole in chunk %d, range "
"%d-%d [this=%p]", mChunk->Index(), mChunk->DataSize(), pos - 1, this));
nsresult rv = mChunk->EnsureBufSize(pos);
if (NS_FAILED(rv)) {
CloseWithStatusLocked(rv);
return;
}
memset(mChunk->BufForWriting() + mChunk->DataSize(), 0,
pos - mChunk->DataSize());
mChunk->UpdateDataSize(mChunk->DataSize(), pos - mChunk->DataSize(), false);
}
void
CacheFileOutputStream::NotifyListener()
{
mFile->AssertOwnsLock();
LOG(("CacheFileOutputStream::NotifyListener() [this=%p]", this));
MOZ_ASSERT(mCallback);
if (!mCallbackTarget) {
mCallbackTarget = CacheFileIOManager::IOTarget();
if (!mCallbackTarget) {
LOG(("CacheFileOutputStream::NotifyListener() - Cannot get Cache I/O "
"thread! Using main thread for callback."));
mCallbackTarget = do_GetMainThread();
}
}
nsCOMPtr<nsIOutputStreamCallback> asyncCallback =
NS_NewOutputStreamReadyEvent(mCallback, mCallbackTarget);
mCallback = nullptr;
mCallbackTarget = nullptr;
asyncCallback->OnOutputStreamReady(this);
}
// Memory reporting
size_t
CacheFileOutputStream::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const
{
// Everything the stream keeps a reference to is already reported somewhere else.
// mFile reports itself.
// mChunk reported as part of CacheFile.
// mCloseListener is CacheEntry, already reported.
// mCallback is usually CacheFile or a class that is reported elsewhere.
return mallocSizeOf(this);
}
} // namespace net
} // namespace mozilla