1/* 2 * Copyright (C) 2013 University of Szeged 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY UNIVERSITY OF SZEGED ``AS IS'' AND ANY 15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL UNIVERSITY OF SZEGED OR 18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include "config.h" 28 29#if USE(CURL) 30 31#include "CurlCacheManager.h" 32 33#include "FileSystem.h" 34#include "HTTPHeaderMap.h" 35#include "Logging.h" 36#include "ResourceHandleClient.h" 37#include "ResourceHandleInternal.h" 38#include "ResourceRequest.h" 39#include <wtf/HashMap.h> 40#include <wtf/text/CString.h> 41 42#define IO_BUFFERSIZE 4096 43 44namespace WebCore { 45 46CurlCacheManager& CurlCacheManager::getInstance() 47{ 48 static CurlCacheManager instance; 49 return instance; 50} 51 52CurlCacheManager::CurlCacheManager() 53 : m_disabled(true) 54 , m_currentStorageSize(0) 55 , m_storageSizeLimit(52428800) // 50 * 1024 * 1024 bytes 56{ 57 // Call setCacheDirectory() to enable the Cache Manager 58} 59 60CurlCacheManager::~CurlCacheManager() 61{ 62 if (m_disabled) 63 return; 64 65 saveIndex(); 66} 67 68void CurlCacheManager::setCacheDirectory(const String& directory) 69{ 70 m_cacheDir = directory; 71 m_cacheDir.append("/"); 72 if (m_cacheDir.isEmpty()) { 73 LOG(Network, "Cache Error: Cache location is not set! CacheManager disabled.\n"); 74 m_disabled = true; 75 return; 76 } 77 78 if (!fileExists(m_cacheDir)) { 79 if (!makeAllDirectories(m_cacheDir)) { 80 LOG(Network, "Cache Error: Could not open or create cache directory! CacheManager disabled.\n"); 81 m_disabled = true; 82 return; 83 } 84 } 85 86 m_disabled = false; 87 loadIndex(); 88} 89 90void CurlCacheManager::setStorageSizeLimit(size_t sizeLimit) 91{ 92 m_storageSizeLimit = sizeLimit; 93} 94 95void CurlCacheManager::loadIndex() 96{ 97 if (m_disabled) 98 return; 99 100 String indexFilePath(m_cacheDir); 101 indexFilePath.append("index.dat"); 102 103 PlatformFileHandle indexFile = openFile(indexFilePath, OpenForRead); 104 if (!isHandleValid(indexFile)) { 105 LOG(Network, "Cache Warning: Could not open %s for read\n", indexFilePath.latin1().data()); 106 return; 107 } 108 109 long long filesize = -1; 110 if (!getFileSize(indexFilePath, filesize)) { 111 LOG(Network, "Cache Error: Could not get file size of %s\n", indexFilePath.latin1().data()); 112 return; 113 } 114 115 // Load the file content into buffer 116 Vector<char> buffer; 117 buffer.resize(filesize); 118 int bufferPosition = 0; 119 int bufferReadSize = IO_BUFFERSIZE; 120 while (filesize > bufferPosition) { 121 if (filesize - bufferPosition < bufferReadSize) 122 bufferReadSize = filesize - bufferPosition; 123 124 readFromFile(indexFile, buffer.data() + bufferPosition, bufferReadSize); 125 bufferPosition += bufferReadSize; 126 } 127 closeFile(indexFile); 128 129 // Create strings from buffer 130 String headerContent = String(buffer.data(), buffer.size()); 131 Vector<String> indexURLs; 132 headerContent.split("\n", indexURLs); 133 buffer.clear(); 134 135 // Add entries to index 136 Vector<String>::const_iterator it = indexURLs.begin(); 137 Vector<String>::const_iterator end = indexURLs.end(); 138 if (indexURLs.size() > 1) 139 --end; // Last line is empty 140 while (it != end) { 141 String url = it->stripWhiteSpace(); 142 auto cacheEntry = std::make_unique<CurlCacheEntry>(url, nullptr, m_cacheDir); 143 144 if (cacheEntry->isCached() && cacheEntry->entrySize() < m_storageSizeLimit) { 145 m_currentStorageSize += cacheEntry->entrySize(); 146 makeRoomForNewEntry(); 147 m_LRUEntryList.prependOrMoveToFirst(url); 148 m_index.set(url, WTF::move(cacheEntry)); 149 } else 150 cacheEntry->invalidate(); 151 152 ++it; 153 } 154} 155 156void CurlCacheManager::saveIndex() 157{ 158 if (m_disabled) 159 return; 160 161 String indexFilePath(m_cacheDir); 162 indexFilePath.append("index.dat"); 163 164 deleteFile(indexFilePath); 165 PlatformFileHandle indexFile = openFile(indexFilePath, OpenForWrite); 166 if (!isHandleValid(indexFile)) { 167 LOG(Network, "Cache Error: Could not open %s for write\n", indexFilePath.latin1().data()); 168 return; 169 } 170 171 auto it = m_LRUEntryList.begin(); 172 const auto& end = m_LRUEntryList.end(); 173 while (it != end) { 174 const CString& urlLatin1 = it->latin1(); 175 writeToFile(indexFile, urlLatin1.data(), urlLatin1.length()); 176 writeToFile(indexFile, "\n", 1); 177 ++it; 178 } 179 180 closeFile(indexFile); 181} 182 183void CurlCacheManager::makeRoomForNewEntry() 184{ 185 if (m_disabled) 186 return; 187 188 while ((m_currentStorageSize > m_storageSizeLimit) && m_LRUEntryList.size() > 0) { 189 ASSERT(m_index.find(m_LRUEntryList.last()) != m_index.end()); 190 invalidateCacheEntry(m_LRUEntryList.last()); 191 } 192} 193 194void CurlCacheManager::didReceiveResponse(ResourceHandle& job, ResourceResponse& response) 195{ 196 if (m_disabled) 197 return; 198 199 ResourceHandleInternal* d = job.getInternal(); 200 if (d->m_cancelled) 201 return; 202 203 const String& url = job.firstRequest().url().string(); 204 205 if (response.httpStatusCode() == 304) { 206 readCachedData(url, &job, response); 207 m_LRUEntryList.prependOrMoveToFirst(url); 208 } 209 else if (response.httpStatusCode() == 200) { 210 auto it = m_index.find(url); 211 if (it != m_index.end() && it->value->isLoading()) 212 return; 213 214 invalidateCacheEntry(url); // Invalidate existing entry on 200 215 216 auto cacheEntry = std::make_unique<CurlCacheEntry>(url, &job, m_cacheDir); 217 bool cacheable = cacheEntry->parseResponseHeaders(response); 218 if (cacheable) { 219 m_LRUEntryList.prependOrMoveToFirst(url); 220 m_index.set(url, WTF::move(cacheEntry)); 221 saveResponseHeaders(url, response); 222 } 223 } else 224 invalidateCacheEntry(url); 225} 226 227void CurlCacheManager::didFinishLoading(ResourceHandle& job) 228{ 229 if (m_disabled) 230 return; 231 232 const String& url = job.firstRequest().url().string(); 233 234 auto it = m_index.find(url); 235 if (it != m_index.end()) 236 it->value->didFinishLoading(); 237} 238 239bool CurlCacheManager::isCached(const String& url) const 240{ 241 if (m_disabled) 242 return false; 243 244 auto it = m_index.find(url); 245 if (it != m_index.end()) 246 return it->value->isCached() && !it->value->isLoading(); 247 248 return false; 249} 250 251HTTPHeaderMap& CurlCacheManager::requestHeaders(const String& url) 252{ 253 ASSERT(isCached(url)); 254 return m_index.find(url)->value->requestHeaders(); 255} 256 257bool CurlCacheManager::getCachedResponse(const String& url, ResourceResponse& response) 258{ 259 auto it = m_index.find(url); 260 if (it != m_index.end()) { 261 it->value->setResponseFromCachedHeaders(response); 262 return true; 263 } 264 return false; 265} 266 267void CurlCacheManager::didReceiveData(ResourceHandle& job, const char* data, size_t size) 268{ 269 if (m_disabled) 270 return; 271 272 const String& url = job.firstRequest().url().string(); 273 274 auto it = m_index.find(url); 275 if (it != m_index.end()) { 276 if (it->value->getJob() != &job) 277 return; 278 279 if (!it->value->saveCachedData(data, size)) 280 invalidateCacheEntry(url); 281 282 else { 283 m_currentStorageSize += size; 284 m_LRUEntryList.prependOrMoveToFirst(url); 285 makeRoomForNewEntry(); 286 } 287 } 288} 289 290void CurlCacheManager::saveResponseHeaders(const String& url, ResourceResponse& response) 291{ 292 if (m_disabled) 293 return; 294 295 auto it = m_index.find(url); 296 if (it != m_index.end()) 297 if (!it->value->saveResponseHeaders(response)) 298 invalidateCacheEntry(url); 299} 300 301void CurlCacheManager::invalidateCacheEntry(const String& url) 302{ 303 if (m_disabled) 304 return; 305 306 auto it = m_index.find(url); 307 if (it != m_index.end()) { 308 if (m_currentStorageSize < it->value->entrySize()) 309 m_currentStorageSize = 0; 310 else 311 m_currentStorageSize -= it->value->entrySize(); 312 313 it->value->invalidate(); 314 m_index.remove(url); 315 } 316 m_LRUEntryList.remove(url); 317} 318 319void CurlCacheManager::didFail(ResourceHandle &job) 320{ 321 const String& url = job.firstRequest().url().string(); 322 323 invalidateCacheEntry(url); 324} 325 326void CurlCacheManager::readCachedData(const String& url, ResourceHandle* job, ResourceResponse& response) 327{ 328 if (m_disabled) 329 return; 330 331 auto it = m_index.find(url); 332 if (it != m_index.end()) { 333 it->value->setResponseFromCachedHeaders(response); 334 m_LRUEntryList.prependOrMoveToFirst(url); 335 if (!it->value->readCachedData(job)) 336 invalidateCacheEntry(url); 337 } 338} 339 340} 341 342#endif 343