b7da373bcdbe4004be6fae0dea9d07616fec8114
[WebKit.git] / Source / WebCore / platform / network / curl / CurlCacheEntry.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 "CurlCacheEntry.h"
32
33 #include "HTTPHeaderMap.h"
34 #include "HTTPHeaderNames.h"
35 #include "HTTPParsers.h"
36 #include "Logging.h"
37 #include "ResourceHandle.h"
38 #include "ResourceHandleClient.h"
39 #include "ResourceHandleInternal.h"
40 #include "ResourceRequest.h"
41 #include "ResourceResponse.h"
42 #include "SharedBuffer.h"
43 #include <wtf/CurrentTime.h>
44 #include <wtf/DateMath.h>
45 #include <wtf/HexNumber.h>
46 #include <wtf/MD5.h>
47
48 namespace WebCore {
49
50 CurlCacheEntry::CurlCacheEntry(const String& url, ResourceHandle* job, const String& cacheDir)
51     : m_headerFilename(cacheDir)
52     , m_contentFilename(cacheDir)
53     , m_contentFile(FileSystem::invalidPlatformFileHandle)
54     , m_entrySize(0)
55     , m_expireDate(-1)
56     , m_headerParsed(false)
57     , m_isLoading(false)
58     , m_job(job)
59 {
60     generateBaseFilename(url.latin1());
61
62     m_headerFilename.append(m_basename);
63     m_headerFilename.append(".header");
64
65     m_contentFilename.append(m_basename);
66     m_contentFilename.append(".content");
67 }
68
69 CurlCacheEntry::~CurlCacheEntry()
70 {
71     closeContentFile();
72 }
73
74 bool CurlCacheEntry::isLoading() const
75 {
76     return m_isLoading;
77 }
78
79 // Cache manager should invalidate the entry on false
80 bool CurlCacheEntry::isCached()
81 {
82     if (!FileSystem::fileExists(m_contentFilename) || !FileSystem::fileExists(m_headerFilename))
83         return false;
84
85     if (!m_headerParsed) {
86         if (!loadResponseHeaders())
87             return false;
88     }
89
90     if (m_expireDate < currentTimeMS()) {
91         m_headerParsed = false;
92         return false;
93     }
94
95     if (!entrySize())
96         return false;
97
98     return true;
99 }
100
101 bool CurlCacheEntry::saveCachedData(const char* data, size_t size)
102 {
103     if (!openContentFile())
104         return false;
105
106     // Append
107     FileSystem::writeToFile(m_contentFile, data, size);
108
109     return true;
110 }
111
112 bool CurlCacheEntry::readCachedData(ResourceHandle* job)
113 {
114     ASSERT(job->client());
115
116     Vector<char> buffer;
117     if (!loadFileToBuffer(m_contentFilename, buffer))
118         return false;
119
120     if (buffer.size())
121         job->getInternal()->client()->didReceiveBuffer(job, SharedBuffer::create(buffer.data(), buffer.size()), buffer.size());
122
123     return true;
124 }
125
126 bool CurlCacheEntry::saveResponseHeaders(const ResourceResponse& response)
127 {
128     FileSystem::PlatformFileHandle headerFile = FileSystem::openFile(m_headerFilename, FileSystem::FileOpenMode::Write);
129     if (!FileSystem::isHandleValid(headerFile)) {
130         LOG(Network, "Cache Error: Could not open %s for write\n", m_headerFilename.latin1().data());
131         return false;
132     }
133
134     // Headers
135     HTTPHeaderMap::const_iterator it = response.httpHeaderFields().begin();
136     HTTPHeaderMap::const_iterator end = response.httpHeaderFields().end();
137     while (it != end) {
138         String headerField = it->key;
139         headerField.append(": ");
140         headerField.append(it->value);
141         headerField.append("\n");
142         CString headerFieldLatin1 = headerField.latin1();
143         FileSystem::writeToFile(headerFile, headerFieldLatin1.data(), headerFieldLatin1.length());
144         m_cachedResponse.setHTTPHeaderField(it->key, it->value);
145         ++it;
146     }
147
148     FileSystem::closeFile(headerFile);
149     return true;
150 }
151
152 bool CurlCacheEntry::loadResponseHeaders()
153 {
154     Vector<char> buffer;
155     if (!loadFileToBuffer(m_headerFilename, buffer))
156         return false;
157
158     String headerContent = String(buffer.data(), buffer.size());
159     Vector<String> headerFields;
160     headerContent.split('\n', headerFields);
161
162     Vector<String>::const_iterator it = headerFields.begin();
163     Vector<String>::const_iterator end = headerFields.end();
164     while (it != end) {
165         size_t splitPosition = it->find(":");
166         if (splitPosition != notFound)
167             m_cachedResponse.setHTTPHeaderField(it->left(splitPosition), it->substring(splitPosition+1).stripWhiteSpace());
168         ++it;
169     }
170
171     return parseResponseHeaders(m_cachedResponse);
172 }
173
174 // Set response headers from memory
175 void CurlCacheEntry::setResponseFromCachedHeaders(ResourceResponse& response)
176 {
177     response.setHTTPStatusCode(304);
178     response.setSource(ResourceResponseBase::Source::DiskCache);
179
180     // Integrate the headers in the response with the cached ones.
181     HTTPHeaderMap::const_iterator it = m_cachedResponse.httpHeaderFields().begin();
182     HTTPHeaderMap::const_iterator end = m_cachedResponse.httpHeaderFields().end();
183     while (it != end) {
184         if (response.httpHeaderField(it->key).isNull())
185             response.setHTTPHeaderField(it->key, it->value);
186         ++it;
187     }
188
189     // Try to parse expected content length
190     long long contentLength = -1;
191     if (!response.httpHeaderField(HTTPHeaderName::ContentLength).isNull()) {
192         bool success = false;
193         long long parsedContentLength = response.httpHeaderField(HTTPHeaderName::ContentLength).toInt64(&success);
194         if (success)
195             contentLength = parsedContentLength;
196     }
197     response.setExpectedContentLength(contentLength); // -1 on parse error or null
198
199     response.setMimeType(extractMIMETypeFromMediaType(response.httpHeaderField(HTTPHeaderName::ContentType)));
200     response.setTextEncodingName(extractCharsetFromMediaType(response.httpHeaderField(HTTPHeaderName::ContentType)));
201 }
202
203 void CurlCacheEntry::didFail()
204 {
205     // The cache manager will call invalidate()
206     setIsLoading(false);
207 }
208
209 void CurlCacheEntry::didFinishLoading()
210 {
211     setIsLoading(false);
212 }
213
214 void CurlCacheEntry::generateBaseFilename(const CString& url)
215 {
216     MD5 md5;
217     md5.addBytes(reinterpret_cast<const uint8_t*>(url.data()), url.length());
218
219     MD5::Digest sum;
220     md5.checksum(sum);
221     uint8_t* rawdata = sum.data();
222
223     for (size_t i = 0; i < MD5::hashSize; i++)
224         appendByteAsHex(rawdata[i], m_basename, Lowercase);
225 }
226
227 bool CurlCacheEntry::loadFileToBuffer(const String& filepath, Vector<char>& buffer)
228 {
229     // Open the file
230     FileSystem::PlatformFileHandle inputFile = FileSystem::openFile(filepath, FileSystem::FileOpenMode::Read);
231     if (!FileSystem::isHandleValid(inputFile)) {
232         LOG(Network, "Cache Error: Could not open %s for read\n", filepath.latin1().data());
233         return false;
234     }
235
236     long long filesize = -1;
237     if (!FileSystem::getFileSize(filepath, filesize)) {
238         LOG(Network, "Cache Error: Could not get file size of %s\n", filepath.latin1().data());
239         FileSystem::closeFile(inputFile);
240         return false;
241     }
242
243     // Load the file content into buffer
244     buffer.resize(filesize);
245     int bufferPosition = 0;
246     int bufferReadSize = 4096;
247     int bytesRead = 0;
248     while (filesize > bufferPosition) {
249         if (filesize - bufferPosition < bufferReadSize)
250             bufferReadSize = filesize - bufferPosition;
251
252         bytesRead = FileSystem::readFromFile(inputFile, buffer.data() + bufferPosition, bufferReadSize);
253         if (bytesRead != bufferReadSize) {
254             LOG(Network, "Cache Error: Could not read from %s\n", filepath.latin1().data());
255             FileSystem::closeFile(inputFile);
256             return false;
257         }
258
259         bufferPosition += bufferReadSize;
260     }
261     FileSystem::closeFile(inputFile);
262     return true;
263 }
264
265 void CurlCacheEntry::invalidate()
266 {
267     closeContentFile();
268     FileSystem::deleteFile(m_headerFilename);
269     FileSystem::deleteFile(m_contentFilename);
270     LOG(Network, "Cache: invalidated %s\n", m_basename.latin1().data());
271 }
272
273 bool CurlCacheEntry::parseResponseHeaders(const ResourceResponse& response)
274 {
275     using namespace std::chrono;
276
277     if (response.cacheControlContainsNoCache() || response.cacheControlContainsNoStore() || !response.hasCacheValidatorFields())
278         return false;
279
280     double fileTime;
281     time_t fileModificationDate;
282
283     if (FileSystem::getFileModificationTime(m_headerFilename, fileModificationDate))
284         fileTime = difftime(fileModificationDate, 0) * 1000.0;
285     else
286         fileTime = currentTimeMS(); // GMT
287
288     auto maxAge = response.cacheControlMaxAge();
289     auto lastModificationDate = response.lastModified();
290     auto responseDate = response.date();
291     auto expirationDate = response.expires();
292
293     if (maxAge && !response.cacheControlContainsMustRevalidate()) {
294         // When both the cache entry and the response contain max-age, the lesser one takes priority
295         auto maxAgeMS = duration_cast<milliseconds>(maxAge.value()).count();
296         double expires = fileTime + maxAgeMS;
297         if (m_expireDate == -1 || m_expireDate > expires)
298             m_expireDate = expires;
299     } else if (responseDate && expirationDate) {
300         auto expirationDateMS = duration_cast<milliseconds>(expirationDate.value().time_since_epoch()).count();
301         auto responseDateMS = duration_cast<milliseconds>(responseDate.value().time_since_epoch()).count();
302         if (expirationDateMS >= responseDateMS)
303             m_expireDate = fileTime + (expirationDateMS - responseDateMS);
304     }
305     // If there is no lifetime information
306     if (m_expireDate == -1) {
307         if (lastModificationDate) {
308             auto lastModificationDateMS = duration_cast<milliseconds>(lastModificationDate.value().time_since_epoch()).count();
309             m_expireDate = fileTime + (fileTime - lastModificationDateMS) * 0.1;
310         }
311         else
312             m_expireDate = 0;
313     }
314
315     String etag = response.httpHeaderField(HTTPHeaderName::ETag);
316     if (!etag.isNull())
317         m_requestHeaders.set(HTTPHeaderName::IfNoneMatch, etag);
318
319     String lastModified = response.httpHeaderField(HTTPHeaderName::LastModified);
320     if (!lastModified.isNull())
321         m_requestHeaders.set(HTTPHeaderName::IfModifiedSince, lastModified);
322
323     if (etag.isNull() && lastModified.isNull())
324         return false;
325
326     m_headerParsed = true;
327     return true;
328 }
329
330 void CurlCacheEntry::setIsLoading(bool isLoading)
331 {
332     m_isLoading = isLoading;
333     if (m_isLoading)
334         openContentFile();
335     else
336         closeContentFile();
337 }
338
339 size_t CurlCacheEntry::entrySize()
340 {
341     if (!m_entrySize) {
342         long long headerFileSize;
343         long long contentFileSize;
344
345         if (!FileSystem::getFileSize(m_headerFilename, headerFileSize)) {
346             LOG(Network, "Cache Error: Could not get file size of %s\n", m_headerFilename.latin1().data());
347             return m_entrySize;
348         }
349         if (!FileSystem::getFileSize(m_contentFilename, contentFileSize)) {
350             LOG(Network, "Cache Error: Could not get file size of %s\n", m_contentFilename.latin1().data());
351             return m_entrySize;
352         }
353
354         m_entrySize = headerFileSize + contentFileSize;
355     }
356
357     return m_entrySize;
358 }
359
360
361 bool CurlCacheEntry::openContentFile()
362 {
363     if (FileSystem::isHandleValid(m_contentFile))
364         return true;
365     
366     m_contentFile = FileSystem::openFile(m_contentFilename, FileSystem::FileOpenMode::Write);
367
368     if (FileSystem::isHandleValid(m_contentFile))
369         return true;
370     
371     LOG(Network, "Cache Error: Could not open %s for write\n", m_contentFilename.latin1().data());
372     return false;
373 }
374
375 bool CurlCacheEntry::closeContentFile()
376 {
377     if (!FileSystem::isHandleValid(m_contentFile))
378         return true;
379
380     FileSystem::closeFile(m_contentFile);
381     m_contentFile = FileSystem::invalidPlatformFileHandle;
382
383     return true;
384 }
385
386 }
387
388 #endif