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