afd8d1eb70b2ff2d71ac029e30fa7a3c21f744f8
[WebKit-https.git] / WebCore / platform / network / ResourceResponseBase.cpp
1 /*
2  * Copyright (C) 2006, 2008 Apple Inc. All rights reserved.
3  * Copyright (C) 2009 Google Inc. 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 APPLE COMPUTER, INC. ``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 APPLE COMPUTER, INC. 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 #include "ResourceResponseBase.h"
29
30 #include "ResourceResponse.h"
31
32 using namespace std;
33
34 namespace WebCore {
35
36 static void parseCacheHeader(const String& header, Vector<pair<String, String> >& result);
37 static void parseCacheControlDirectiveValues(const String& directives, Vector<String>& result);
38
39 auto_ptr<ResourceResponse> ResourceResponseBase::adopt(auto_ptr<CrossThreadResourceResponseData> data)
40 {
41     auto_ptr<ResourceResponse> response(new ResourceResponse());
42     response->setURL(data->m_url);
43     response->setMimeType(data->m_mimeType);
44     response->setExpectedContentLength(data->m_expectedContentLength);
45     response->setTextEncodingName(data->m_textEncodingName);
46     response->setSuggestedFilename(data->m_suggestedFilename);
47
48     response->setHTTPStatusCode(data->m_httpStatusCode);
49     response->setHTTPStatusText(data->m_httpStatusText);
50
51     response->lazyInit();
52     response->m_httpHeaderFields.adopt(std::auto_ptr<CrossThreadHTTPHeaderMapData>(data->m_httpHeaders.release()));
53
54     response->setExpirationDate(data->m_expirationDate);
55     response->setLastModifiedDate(data->m_lastModifiedDate);
56     response->m_haveParsedCacheControl = data->m_haveParsedCacheControl;
57     response->m_cacheControlContainsMustRevalidate = data->m_cacheControlContainsMustRevalidate;
58     response->m_cacheControlContainsNoCache = data->m_cacheControlContainsNoCache;
59     return response;
60 }
61
62 auto_ptr<CrossThreadResourceResponseData> ResourceResponseBase::copyData() const
63 {
64     auto_ptr<CrossThreadResourceResponseData> data(new CrossThreadResourceResponseData());
65     data->m_url = url().copy();
66     data->m_mimeType = mimeType().copy();
67     data->m_expectedContentLength = expectedContentLength();
68     data->m_textEncodingName = textEncodingName().copy();
69     data->m_suggestedFilename = suggestedFilename().copy();
70     data->m_httpStatusCode = httpStatusCode();
71     data->m_httpStatusText = httpStatusText().copy();
72     data->m_httpHeaders.adopt(httpHeaderFields().copyData());
73     data->m_expirationDate = expirationDate();
74     data->m_lastModifiedDate = lastModifiedDate();
75     data->m_haveParsedCacheControl = m_haveParsedCacheControl;
76     data->m_cacheControlContainsMustRevalidate = m_cacheControlContainsMustRevalidate;
77     data->m_cacheControlContainsNoCache = m_cacheControlContainsNoCache;
78     return data;
79 }
80
81 bool ResourceResponseBase::isHTTP() const
82 {
83     lazyInit();
84
85     String protocol = m_url.protocol();
86
87     return equalIgnoringCase(protocol, "http")  || equalIgnoringCase(protocol, "https");
88 }
89
90 const KURL& ResourceResponseBase::url() const
91 {
92     lazyInit();
93     
94     return m_url; 
95 }
96
97 void ResourceResponseBase::setURL(const KURL& url)
98 {
99     lazyInit();
100     m_isNull = false;
101     
102     m_url = url; 
103 }
104
105 const String& ResourceResponseBase::mimeType() const
106 {
107     lazyInit();
108
109     return m_mimeType; 
110 }
111
112 void ResourceResponseBase::setMimeType(const String& mimeType)
113 {
114     lazyInit();
115     m_isNull = false;
116     
117     m_mimeType = mimeType; 
118 }
119
120 long long ResourceResponseBase::expectedContentLength() const 
121 {
122     lazyInit();
123
124     return m_expectedContentLength;
125 }
126
127 void ResourceResponseBase::setExpectedContentLength(long long expectedContentLength)
128 {
129     lazyInit();
130     m_isNull = false;
131     
132     m_expectedContentLength = expectedContentLength; 
133 }
134
135 const String& ResourceResponseBase::textEncodingName() const
136 {
137     lazyInit();
138
139     return m_textEncodingName;
140 }
141
142 void ResourceResponseBase::setTextEncodingName(const String& encodingName)
143 {
144     lazyInit();
145     m_isNull = false;
146     
147     m_textEncodingName = encodingName; 
148 }
149
150 // FIXME should compute this on the fly
151 const String& ResourceResponseBase::suggestedFilename() const
152 {
153     lazyInit();
154
155     return m_suggestedFilename;
156 }
157
158 void ResourceResponseBase::setSuggestedFilename(const String& suggestedName)
159 {
160     lazyInit();
161     m_isNull = false;
162     
163     m_suggestedFilename = suggestedName; 
164 }
165
166 int ResourceResponseBase::httpStatusCode() const
167 {
168     lazyInit();
169
170     return m_httpStatusCode;
171 }
172
173 void ResourceResponseBase::setHTTPStatusCode(int statusCode)
174 {
175     lazyInit();
176
177     m_httpStatusCode = statusCode;
178 }
179
180 const String& ResourceResponseBase::httpStatusText() const 
181 {
182     lazyInit();
183
184     return m_httpStatusText; 
185 }
186
187 void ResourceResponseBase::setHTTPStatusText(const String& statusText) 
188 {
189     lazyInit();
190
191     m_httpStatusText = statusText; 
192 }
193
194 String ResourceResponseBase::httpHeaderField(const AtomicString& name) const
195 {
196     lazyInit();
197
198     return m_httpHeaderFields.get(name); 
199 }
200
201 void ResourceResponseBase::setHTTPHeaderField(const AtomicString& name, const String& value)
202 {
203     lazyInit();
204
205     if (equalIgnoringCase(name, "cache-control"))
206         m_haveParsedCacheControl = false;
207     m_httpHeaderFields.set(name, value);
208 }
209
210 const HTTPHeaderMap& ResourceResponseBase::httpHeaderFields() const
211 {
212     lazyInit();
213
214     return m_httpHeaderFields;
215 }
216
217 void ResourceResponseBase::parseCacheControlDirectives() const
218 {
219     ASSERT(!m_haveParsedCacheControl);
220
221     lazyInit();
222
223     m_haveParsedCacheControl = true;
224     m_cacheControlContainsMustRevalidate = false;
225     m_cacheControlContainsNoCache = false;
226
227     String cacheControlValue = httpHeaderField("cache-control");
228     if (cacheControlValue.isEmpty())
229         return;
230
231     // FIXME: It would probably be much more efficient to parse this without creating all these data structures.
232
233     Vector<pair<String, String> > directives;
234     parseCacheHeader(cacheControlValue, directives);
235
236     size_t directivesSize = directives.size();
237     for (size_t i = 0; i < directivesSize; ++i) {
238         Vector<String> directiveValues;
239         if ((equalIgnoringCase(directives[i].first, "private") || equalIgnoringCase(directives[i].first, "no-cache")) && !directives[i].second.isEmpty())
240             parseCacheControlDirectiveValues(directives[i].second, directiveValues);
241         else
242             directiveValues.append(directives[i].second);
243         for (size_t i = 0; i < directiveValues.size(); ++i) {
244             if (equalIgnoringCase(directiveValues[i], "no-cache"))
245                 m_cacheControlContainsNoCache = true;
246             else if (equalIgnoringCase(directiveValues[i], "must-revalidate"))
247                 m_cacheControlContainsMustRevalidate = true;
248         }
249     }
250 }
251
252 bool ResourceResponseBase::isAttachment() const
253 {
254     lazyInit();
255
256     String value = m_httpHeaderFields.get("Content-Disposition");
257     int loc = value.find(';');
258     if (loc != -1)
259         value = value.left(loc);
260     value = value.stripWhiteSpace();
261     return equalIgnoringCase(value, "attachment");
262 }
263
264 void ResourceResponseBase::setExpirationDate(time_t expirationDate)
265 {
266     lazyInit();
267
268     m_expirationDate = expirationDate;
269 }
270
271 time_t ResourceResponseBase::expirationDate() const
272 {
273     lazyInit();
274
275     return m_expirationDate; 
276 }
277
278 void ResourceResponseBase::setLastModifiedDate(time_t lastModifiedDate) 
279 {
280     lazyInit();
281
282     m_lastModifiedDate = lastModifiedDate;
283 }
284
285 time_t ResourceResponseBase::lastModifiedDate() const
286 {
287     lazyInit();
288
289     return m_lastModifiedDate;
290 }
291
292 void ResourceResponseBase::lazyInit() const
293 {
294     const_cast<ResourceResponse*>(static_cast<const ResourceResponse*>(this))->platformLazyInit();
295 }
296
297 bool ResourceResponseBase::compare(const ResourceResponse& a, const ResourceResponse& b)
298 {
299     if (a.isNull() != b.isNull())
300         return false;  
301     if (a.url() != b.url())
302         return false;
303     if (a.mimeType() != b.mimeType())
304         return false;
305     if (a.expectedContentLength() != b.expectedContentLength())
306         return false;
307     if (a.textEncodingName() != b.textEncodingName())
308         return false;
309     if (a.suggestedFilename() != b.suggestedFilename())
310         return false;
311     if (a.httpStatusCode() != b.httpStatusCode())
312         return false;
313     if (a.httpStatusText() != b.httpStatusText())
314         return false;
315     if (a.httpHeaderFields() != b.httpHeaderFields())
316         return false;
317     if (a.expirationDate() != b.expirationDate())
318         return false;
319     return ResourceResponse::platformCompare(a, b);
320 }
321
322 static bool isCacheHeaderSeparator(UChar c)
323 {
324     // See RFC 2616, Section 2.2
325     switch (c) {
326         case '(':
327         case ')':
328         case '<':
329         case '>':
330         case '@':
331         case ',':
332         case ';':
333         case ':':
334         case '\\':
335         case '"':
336         case '/':
337         case '[':
338         case ']':
339         case '?':
340         case '=':
341         case '{':
342         case '}':
343         case ' ':
344         case '\t':
345             return true;
346         default:
347             return false;
348     }
349 }
350
351 static bool isControlCharacter(UChar c)
352 {
353     return c < ' ' || c == 127;
354 }
355
356 static inline String trimToNextSeparator(const String& str)
357 {
358     return str.substring(0, str.find(isCacheHeaderSeparator, 0));
359 }
360
361 static void parseCacheHeader(const String& header, Vector<pair<String, String> >& result)
362 {
363     const String safeHeader = header.removeCharacters(isControlCharacter);
364     unsigned max = safeHeader.length();
365     for (unsigned pos = 0; pos < max; /* pos incremented in loop */) {
366         int nextCommaPosition = safeHeader.find(',', pos);
367         int nextEqualSignPosition = safeHeader.find('=', pos);
368         if (nextEqualSignPosition >= 0 && (nextEqualSignPosition < nextCommaPosition || nextCommaPosition < 0)) {
369             // Get directive name, parse right hand side of equal sign, then add to map
370             String directive = trimToNextSeparator(safeHeader.substring(pos, nextEqualSignPosition - pos).stripWhiteSpace());
371             pos += nextEqualSignPosition - pos + 1;
372
373             String value = safeHeader.substring(pos, max - pos).stripWhiteSpace();
374             if (value[0] == '"') {
375                 // The value is a quoted string
376                 int nextDoubleQuotePosition = value.find('"', 1);
377                 if (nextDoubleQuotePosition >= 0) {
378                     // Store the value as a quoted string without quotes
379                     result.append(pair<String, String>(directive, value.substring(1, nextDoubleQuotePosition - 1).stripWhiteSpace()));
380                     pos += (safeHeader.find('"', pos) - pos) + nextDoubleQuotePosition + 1;
381                     // Move past next comma, if there is one
382                     int nextCommaPosition2 = safeHeader.find(',', pos);
383                     if (nextCommaPosition2 >= 0)
384                         pos += nextCommaPosition2 - pos + 1;
385                     else
386                         return; // Parse error if there is anything left with no comma
387                 } else {
388                     // Parse error; just use the rest as the value
389                     result.append(pair<String, String>(directive, trimToNextSeparator(value.substring(1, value.length() - 1).stripWhiteSpace())));
390                     return;
391                 }
392             } else {
393                 // The value is a token until the next comma
394                 int nextCommaPosition2 = value.find(',', 0);
395                 if (nextCommaPosition2 >= 0) {
396                     // The value is delimited by the next comma
397                     result.append(pair<String, String>(directive, trimToNextSeparator(value.substring(0, nextCommaPosition2).stripWhiteSpace())));
398                     pos += (safeHeader.find(',', pos) - pos) + 1;
399                 } else {
400                     // The rest is the value; no change to value needed
401                     result.append(pair<String, String>(directive, trimToNextSeparator(value)));
402                     return;
403                 }
404             }
405         } else if (nextCommaPosition >= 0 && (nextCommaPosition < nextEqualSignPosition || nextEqualSignPosition < 0)) {
406             // Add directive to map with empty string as value
407             result.append(pair<String, String>(trimToNextSeparator(safeHeader.substring(pos, nextCommaPosition - pos).stripWhiteSpace()), ""));
408             pos += nextCommaPosition - pos + 1;
409         } else {
410             // Add last directive to map with empty string as value
411             result.append(pair<String, String>(trimToNextSeparator(safeHeader.substring(pos, max - pos).stripWhiteSpace()), ""));
412             return;
413         }
414     }
415 }
416
417 static void parseCacheControlDirectiveValues(const String& directives, Vector<String>& result)
418 {
419     directives.split(',', false, result);
420     unsigned max = result.size();
421     for (unsigned i = 0; i < max; ++i)
422         result[i] = result[i].stripWhiteSpace();
423 }
424
425 }