blob: e2b4b5dfbda62a3c0cb6bf9181bbd68fd7a4a089 [file] [log] [blame]
/*
* Copyright (C) 2013 University of Szeged
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY UNIVERSITY OF SZEGED ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL UNIVERSITY OF SZEGED OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#if USE(CURL)
#include "CurlCacheManager.h"
#include "HTTPHeaderMap.h"
#include "Logging.h"
#include "ResourceHandleClient.h"
#include "ResourceHandleInternal.h"
#include "ResourceRequest.h"
#include <wtf/FileSystem.h>
#include <wtf/HashMap.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/text/CString.h>
#define IO_BUFFERSIZE 4096
namespace WebCore {
CurlCacheManager& CurlCacheManager::singleton()
{
static NeverDestroyed<CurlCacheManager> sharedInstance;
return sharedInstance;
}
CurlCacheManager::CurlCacheManager()
: m_disabled(true)
, m_currentStorageSize(0)
, m_storageSizeLimit(52428800) // 50 * 1024 * 1024 bytes
{
// Call setCacheDirectory() to enable the Cache Manager
}
CurlCacheManager::~CurlCacheManager()
{
if (m_disabled)
return;
saveIndex();
}
void CurlCacheManager::setCacheDirectory(const String& directory)
{
m_cacheDir = directory;
if (m_cacheDir.isEmpty()) {
LOG(Network, "Cache Error: Cache location is not set! CacheManager disabled.\n");
m_disabled = true;
return;
}
if (!FileSystem::fileExists(m_cacheDir)) {
if (!FileSystem::makeAllDirectories(m_cacheDir)) {
LOG(Network, "Cache Error: Could not open or create cache directory! CacheManager disabled.\n");
m_disabled = true;
return;
}
}
m_cacheDir.append("/");
m_disabled = false;
loadIndex();
}
void CurlCacheManager::setStorageSizeLimit(size_t sizeLimit)
{
m_storageSizeLimit = sizeLimit;
}
void CurlCacheManager::loadIndex()
{
if (m_disabled)
return;
String indexFilePath(m_cacheDir);
indexFilePath.append("index.dat");
FileSystem::PlatformFileHandle indexFile = FileSystem::openFile(indexFilePath, FileSystem::FileOpenMode::Read);
if (!FileSystem::isHandleValid(indexFile)) {
LOG(Network, "Cache Warning: Could not open %s for read\n", indexFilePath.latin1().data());
return;
}
long long filesize = -1;
if (!FileSystem::getFileSize(indexFilePath, filesize)) {
LOG(Network, "Cache Error: Could not get file size of %s\n", indexFilePath.latin1().data());
return;
}
// Load the file content into buffer
Vector<char> buffer;
buffer.resize(filesize);
int bufferPosition = 0;
int bufferReadSize = IO_BUFFERSIZE;
while (filesize > bufferPosition) {
if (filesize - bufferPosition < bufferReadSize)
bufferReadSize = filesize - bufferPosition;
FileSystem::readFromFile(indexFile, buffer.data() + bufferPosition, bufferReadSize);
bufferPosition += bufferReadSize;
}
FileSystem::closeFile(indexFile);
// Create strings from buffer
String headerContent = String(buffer.data(), buffer.size());
Vector<String> indexURLs = headerContent.split('\n');
buffer.clear();
// Add entries to index
Vector<String>::const_iterator it = indexURLs.begin();
Vector<String>::const_iterator end = indexURLs.end();
if (indexURLs.size() > 1)
--end; // Last line is empty
while (it != end) {
String url = it->stripWhiteSpace();
auto cacheEntry = makeUnique<CurlCacheEntry>(url, nullptr, m_cacheDir);
if (cacheEntry->isCached() && cacheEntry->entrySize() < m_storageSizeLimit) {
m_currentStorageSize += cacheEntry->entrySize();
makeRoomForNewEntry();
m_LRUEntryList.prependOrMoveToFirst(url);
m_index.set(url, WTFMove(cacheEntry));
} else
cacheEntry->invalidate();
++it;
}
}
void CurlCacheManager::saveIndex()
{
if (m_disabled)
return;
String indexFilePath(m_cacheDir);
indexFilePath.append("index.dat");
FileSystem::deleteFile(indexFilePath);
FileSystem::PlatformFileHandle indexFile = FileSystem::openFile(indexFilePath, FileSystem::FileOpenMode::Write);
if (!FileSystem::isHandleValid(indexFile)) {
LOG(Network, "Cache Error: Could not open %s for write\n", indexFilePath.latin1().data());
return;
}
auto it = m_LRUEntryList.begin();
const auto& end = m_LRUEntryList.end();
while (it != end) {
const CString& urlLatin1 = it->latin1();
FileSystem::writeToFile(indexFile, urlLatin1.data(), urlLatin1.length());
FileSystem::writeToFile(indexFile, "\n", 1);
++it;
}
FileSystem::closeFile(indexFile);
}
void CurlCacheManager::makeRoomForNewEntry()
{
if (m_disabled)
return;
while ((m_currentStorageSize > m_storageSizeLimit) && m_LRUEntryList.size() > 0) {
ASSERT(m_index.find(m_LRUEntryList.last()) != m_index.end());
invalidateCacheEntry(m_LRUEntryList.last());
}
}
void CurlCacheManager::didReceiveResponse(ResourceHandle& job, ResourceResponse& response)
{
if (m_disabled)
return;
const String& url = job.firstRequest().url().string();
removeCacheEntryClient(url, &job);
if (response.source() == ResourceResponseBase::Source::DiskCache) {
readCachedData(url, &job, response);
m_LRUEntryList.prependOrMoveToFirst(url);
}
else if (response.httpStatusCode() == 200) {
auto it = m_index.find(url);
if (it != m_index.end() && (it->value->isLoading() || it->value->hasClients()))
return;
invalidateCacheEntry(url); // Invalidate existing entry on 200
auto cacheEntry = makeUnique<CurlCacheEntry>(url, &job, m_cacheDir);
bool cacheable = cacheEntry->parseResponseHeaders(response);
if (cacheable) {
cacheEntry->setIsLoading(true);
m_LRUEntryList.prependOrMoveToFirst(url);
m_index.set(url, WTFMove(cacheEntry));
saveResponseHeaders(url, response);
}
} else
invalidateCacheEntry(url);
}
void CurlCacheManager::didFinishLoading(ResourceHandle& job)
{
if (m_disabled)
return;
const String& url = job.firstRequest().url().string();
auto it = m_index.find(url);
if (it != m_index.end())
it->value->didFinishLoading();
}
bool CurlCacheManager::isCached(const String& url) const
{
if (m_disabled)
return false;
auto it = m_index.find(url);
if (it != m_index.end())
return it->value->isCached() && !it->value->isLoading();
return false;
}
HTTPHeaderMap& CurlCacheManager::requestHeaders(const String& url)
{
ASSERT(isCached(url));
return m_index.find(url)->value->requestHeaders();
}
bool CurlCacheManager::getCachedResponse(const String& url, ResourceResponse& response)
{
auto it = m_index.find(url);
if (it != m_index.end()) {
it->value->setResponseFromCachedHeaders(response);
return true;
}
return false;
}
void CurlCacheManager::didReceiveData(ResourceHandle& job, const char* data, size_t size)
{
if (m_disabled)
return;
const String& url = job.firstRequest().url().string();
auto it = m_index.find(url);
if (it != m_index.end()) {
if (it->value->getJob() != &job)
return;
if (!it->value->saveCachedData(data, size))
invalidateCacheEntry(url);
else {
m_currentStorageSize += size;
m_LRUEntryList.prependOrMoveToFirst(url);
makeRoomForNewEntry();
}
}
}
void CurlCacheManager::saveResponseHeaders(const String& url, ResourceResponse& response)
{
if (m_disabled)
return;
auto it = m_index.find(url);
if (it != m_index.end())
if (!it->value->saveResponseHeaders(response))
invalidateCacheEntry(url);
}
void CurlCacheManager::invalidateCacheEntry(const String& url)
{
if (m_disabled)
return;
auto it = m_index.find(url);
if (it != m_index.end()) {
if (m_currentStorageSize < it->value->entrySize())
m_currentStorageSize = 0;
else
m_currentStorageSize -= it->value->entrySize();
it->value->invalidate();
m_index.remove(url);
}
m_LRUEntryList.remove(url);
}
void CurlCacheManager::didFail(ResourceHandle &job)
{
const String& url = job.firstRequest().url().string();
invalidateCacheEntry(url);
}
void CurlCacheManager::addCacheEntryClient(const String& url, ResourceHandle* job)
{
if (m_disabled)
return;
auto it = m_index.find(url);
if (it != m_index.end())
it->value->addClient(job);
}
void CurlCacheManager::removeCacheEntryClient(const String& url, ResourceHandle* job)
{
if (m_disabled)
return;
auto it = m_index.find(url);
if (it != m_index.end())
it->value->removeClient(job);
}
void CurlCacheManager::readCachedData(const String& url, ResourceHandle* job, ResourceResponse& response)
{
if (m_disabled)
return;
auto it = m_index.find(url);
if (it != m_index.end()) {
it->value->setResponseFromCachedHeaders(response);
m_LRUEntryList.prependOrMoveToFirst(url);
if (!it->value->readCachedData(job))
invalidateCacheEntry(url);
}
}
}
#endif