Add WTF::move()
[WebKit-https.git] / Source / WebCore / platform / network / curl / CurlCacheManager.cpp
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
44 namespace WebCore {
45
46 CurlCacheManager& CurlCacheManager::getInstance()
47 {
48     static CurlCacheManager instance;
49     return instance;
50 }
51
52 CurlCacheManager::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
60 CurlCacheManager::~CurlCacheManager()
61 {
62     if (m_disabled)
63         return;
64
65     saveIndex();
66 }
67
68 void 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
90 void CurlCacheManager::setStorageSizeLimit(size_t sizeLimit)
91 {
92     m_storageSizeLimit = sizeLimit;
93 }
94
95 void 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
156 void 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
183 void 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
194 void 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
227 void 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
239 bool CurlCacheManager::isCached(const String& url)
240 {
241     if (m_disabled)
242         return false;
243
244     auto it = m_index.find(url);
245     if (it != m_index.end()) {
246         if (it->value->isCached())
247             return !it->value->isLoading();
248
249         invalidateCacheEntry(url);
250     }
251     return false;
252 }
253
254 HTTPHeaderMap& CurlCacheManager::requestHeaders(const String& url)
255 {
256     ASSERT(isCached(url));
257     return m_index.find(url)->value->requestHeaders();
258 }
259
260 bool CurlCacheManager::getCachedResponse(const String& url, ResourceResponse& response)
261 {
262     auto it = m_index.find(url);
263     if (it != m_index.end()) {
264         it->value->setResponseFromCachedHeaders(response);
265         return true;
266     }
267     return false;
268 }
269
270 void CurlCacheManager::didReceiveData(ResourceHandle& job, const char* data, size_t size)
271 {
272     if (m_disabled)
273         return;
274
275     const String& url = job.firstRequest().url().string();
276
277     auto it = m_index.find(url);
278     if (it != m_index.end()) {
279         if (it->value->getJob() != &job)
280             return;
281
282         if (!it->value->saveCachedData(data, size))
283             invalidateCacheEntry(url);
284
285         else {
286             m_currentStorageSize += size;
287             m_LRUEntryList.prependOrMoveToFirst(url);
288             makeRoomForNewEntry();
289         }
290     }
291 }
292
293 void CurlCacheManager::saveResponseHeaders(const String& url, ResourceResponse& response)
294 {
295     if (m_disabled)
296         return;
297
298     auto it = m_index.find(url);
299     if (it != m_index.end())
300         if (!it->value->saveResponseHeaders(response))
301             invalidateCacheEntry(url);
302 }
303
304 void CurlCacheManager::invalidateCacheEntry(const String& url)
305 {
306     if (m_disabled)
307         return;
308
309     auto it = m_index.find(url);
310     if (it != m_index.end()) {
311         if (m_currentStorageSize < it->value->entrySize())
312             m_currentStorageSize = 0;
313         else
314             m_currentStorageSize -= it->value->entrySize();
315
316         it->value->invalidate();
317         m_index.remove(url);
318     }
319     m_LRUEntryList.remove(url);
320 }
321
322 void CurlCacheManager::didFail(ResourceHandle &job)
323 {
324     const String& url = job.firstRequest().url().string();
325
326     invalidateCacheEntry(url);
327 }
328
329 void CurlCacheManager::readCachedData(const String& url, ResourceHandle* job, ResourceResponse& response)
330 {
331     if (m_disabled)
332         return;
333
334     auto it = m_index.find(url);
335     if (it != m_index.end()) {
336         it->value->setResponseFromCachedHeaders(response);
337         m_LRUEntryList.prependOrMoveToFirst(url);
338         if (!it->value->readCachedData(job))
339             invalidateCacheEntry(url);
340     }
341 }
342
343 }
344
345 #endif