58712d7f49f5a3dc8ab51a187594fb7c41c25020
[WebKit.git] / Source / 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 "HTTPParsers.h"
31 #include "PlatformMemoryInstrumentation.h"
32 #include "ResourceResponse.h"
33 #include <wtf/CurrentTime.h>
34 #include <wtf/MathExtras.h>
35 #include <wtf/MemoryInstrumentationHashMap.h>
36 #include <wtf/StdLibExtras.h>
37
38 using namespace std;
39
40 namespace WebCore {
41
42 static void parseCacheHeader(const String& header, Vector<pair<String, String> >& result);
43
44 inline const ResourceResponse& ResourceResponseBase::asResourceResponse() const
45 {
46     return *static_cast<const ResourceResponse*>(this);
47 }
48
49 ResourceResponseBase::ResourceResponseBase()  
50     : m_expectedContentLength(0)
51     , m_httpStatusCode(0)
52     , m_lastModifiedDate(0)
53     , m_wasCached(false)
54     , m_connectionID(0)
55     , m_connectionReused(false)
56     , m_isNull(true)
57     , m_haveParsedCacheControlHeader(false)
58     , m_haveParsedAgeHeader(false)
59     , m_haveParsedDateHeader(false)
60     , m_haveParsedExpiresHeader(false)
61     , m_haveParsedLastModifiedHeader(false)
62     , m_cacheControlContainsNoCache(false)
63     , m_cacheControlContainsNoStore(false)
64     , m_cacheControlContainsMustRevalidate(false)
65     , m_cacheControlMaxAge(0.0)
66     , m_age(0.0)
67     , m_date(0.0)
68     , m_expires(0.0)
69     , m_lastModified(0.0)
70 {
71 }
72
73 ResourceResponseBase::ResourceResponseBase(const KURL& url, const String& mimeType, long long expectedLength, const String& textEncodingName, const String& filename)
74     : m_url(url)
75     , m_mimeType(mimeType)
76     , m_expectedContentLength(expectedLength)
77     , m_textEncodingName(textEncodingName)
78     , m_suggestedFilename(filename)
79     , m_httpStatusCode(0)
80     , m_lastModifiedDate(0)
81     , m_wasCached(false)
82     , m_connectionID(0)
83     , m_connectionReused(false)
84     , m_isNull(false)
85     , m_haveParsedCacheControlHeader(false)
86     , m_haveParsedAgeHeader(false)
87     , m_haveParsedDateHeader(false)
88     , m_haveParsedExpiresHeader(false)
89     , m_haveParsedLastModifiedHeader(false)
90     , m_cacheControlContainsNoCache(false)
91     , m_cacheControlContainsNoStore(false)
92     , m_cacheControlContainsMustRevalidate(false)
93     , m_cacheControlMaxAge(0.0)
94     , m_age(0.0)
95     , m_date(0.0)
96     , m_expires(0.0)
97     , m_lastModified(0.0)
98 {
99 }
100
101 PassOwnPtr<ResourceResponse> ResourceResponseBase::adopt(PassOwnPtr<CrossThreadResourceResponseData> data)
102 {
103     OwnPtr<ResourceResponse> response = adoptPtr(new ResourceResponse);
104     response->setURL(data->m_url);
105     response->setMimeType(data->m_mimeType);
106     response->setExpectedContentLength(data->m_expectedContentLength);
107     response->setTextEncodingName(data->m_textEncodingName);
108     response->setSuggestedFilename(data->m_suggestedFilename);
109
110     response->setHTTPStatusCode(data->m_httpStatusCode);
111     response->setHTTPStatusText(data->m_httpStatusText);
112
113     response->lazyInit(CommonAndUncommonFields);
114     response->m_httpHeaderFields.adopt(data->m_httpHeaders.release());
115     response->setLastModifiedDate(data->m_lastModifiedDate);
116     response->setResourceLoadTiming(data->m_resourceLoadTiming.release());
117     response->doPlatformAdopt(data);
118     return response.release();
119 }
120
121 PassOwnPtr<CrossThreadResourceResponseData> ResourceResponseBase::copyData() const
122 {
123     OwnPtr<CrossThreadResourceResponseData> data = adoptPtr(new CrossThreadResourceResponseData);
124     data->m_url = url().copy();
125     data->m_mimeType = mimeType().isolatedCopy();
126     data->m_expectedContentLength = expectedContentLength();
127     data->m_textEncodingName = textEncodingName().isolatedCopy();
128     data->m_suggestedFilename = suggestedFilename().isolatedCopy();
129     data->m_httpStatusCode = httpStatusCode();
130     data->m_httpStatusText = httpStatusText().isolatedCopy();
131     data->m_httpHeaders = httpHeaderFields().copyData();
132     data->m_lastModifiedDate = lastModifiedDate();
133     if (m_resourceLoadTiming)
134         data->m_resourceLoadTiming = m_resourceLoadTiming->deepCopy();
135     return asResourceResponse().doPlatformCopyData(data.release());
136 }
137
138 bool ResourceResponseBase::isHTTP() const
139 {
140     lazyInit(CommonFieldsOnly);
141
142     String protocol = m_url.protocol();
143
144     return equalIgnoringCase(protocol, "http")  || equalIgnoringCase(protocol, "https");
145 }
146
147 const KURL& ResourceResponseBase::url() const
148 {
149     lazyInit(CommonFieldsOnly);
150
151     return m_url; 
152 }
153
154 void ResourceResponseBase::setURL(const KURL& url)
155 {
156     lazyInit(CommonFieldsOnly);
157     m_isNull = false;
158
159     m_url = url; 
160 }
161
162 const String& ResourceResponseBase::mimeType() const
163 {
164     lazyInit(CommonFieldsOnly);
165
166     return m_mimeType; 
167 }
168
169 void ResourceResponseBase::setMimeType(const String& mimeType)
170 {
171     lazyInit(CommonFieldsOnly);
172     m_isNull = false;
173
174     m_mimeType = mimeType; 
175 }
176
177 long long ResourceResponseBase::expectedContentLength() const 
178 {
179     lazyInit(CommonFieldsOnly);
180
181     return m_expectedContentLength;
182 }
183
184 void ResourceResponseBase::setExpectedContentLength(long long expectedContentLength)
185 {
186     lazyInit(CommonFieldsOnly);
187     m_isNull = false;
188
189     m_expectedContentLength = expectedContentLength; 
190 }
191
192 const String& ResourceResponseBase::textEncodingName() const
193 {
194     lazyInit(CommonFieldsOnly);
195
196     return m_textEncodingName;
197 }
198
199 void ResourceResponseBase::setTextEncodingName(const String& encodingName)
200 {
201     lazyInit(CommonFieldsOnly);
202     m_isNull = false;
203
204     m_textEncodingName = encodingName; 
205 }
206
207 // FIXME should compute this on the fly
208 const String& ResourceResponseBase::suggestedFilename() const
209 {
210     lazyInit(AllFields);
211
212     return m_suggestedFilename;
213 }
214
215 void ResourceResponseBase::setSuggestedFilename(const String& suggestedName)
216 {
217     lazyInit(AllFields);
218     m_isNull = false;
219
220     m_suggestedFilename = suggestedName; 
221 }
222
223 int ResourceResponseBase::httpStatusCode() const
224 {
225     lazyInit(CommonFieldsOnly);
226
227     return m_httpStatusCode;
228 }
229
230 void ResourceResponseBase::setHTTPStatusCode(int statusCode)
231 {
232     lazyInit(CommonFieldsOnly);
233
234     m_httpStatusCode = statusCode;
235 }
236
237 const String& ResourceResponseBase::httpStatusText() const 
238 {
239     lazyInit(CommonAndUncommonFields);
240
241     return m_httpStatusText; 
242 }
243
244 void ResourceResponseBase::setHTTPStatusText(const String& statusText) 
245 {
246     lazyInit(CommonAndUncommonFields);
247
248     m_httpStatusText = statusText; 
249 }
250
251 String ResourceResponseBase::httpHeaderField(const AtomicString& name) const
252 {
253     lazyInit(CommonFieldsOnly);
254
255     // If we already have the header, just return it instead of consuming memory by grabing all headers.
256     String value = m_httpHeaderFields.get(name);
257     if (!value.isEmpty())        
258         return value;
259
260     lazyInit(CommonAndUncommonFields);
261
262     return m_httpHeaderFields.get(name); 
263 }
264
265 String ResourceResponseBase::httpHeaderField(const char* name) const
266 {
267     lazyInit(CommonFieldsOnly);
268
269     // If we already have the header, just return it instead of consuming memory by grabing all headers.
270     String value = m_httpHeaderFields.get(name);
271     if (!value.isEmpty())
272         return value;
273
274     lazyInit(CommonAndUncommonFields);
275
276     return m_httpHeaderFields.get(name); 
277 }
278
279 void ResourceResponseBase::updateHeaderParsedState(const AtomicString& name)
280 {
281     DEFINE_STATIC_LOCAL(const AtomicString, ageHeader, ("age", AtomicString::ConstructFromLiteral));
282     DEFINE_STATIC_LOCAL(const AtomicString, cacheControlHeader, ("cache-control", AtomicString::ConstructFromLiteral));
283     DEFINE_STATIC_LOCAL(const AtomicString, dateHeader, ("date", AtomicString::ConstructFromLiteral));
284     DEFINE_STATIC_LOCAL(const AtomicString, expiresHeader, ("expires", AtomicString::ConstructFromLiteral));
285     DEFINE_STATIC_LOCAL(const AtomicString, lastModifiedHeader, ("last-modified", AtomicString::ConstructFromLiteral));
286     DEFINE_STATIC_LOCAL(const AtomicString, pragmaHeader, ("pragma", AtomicString::ConstructFromLiteral));
287
288     if (equalIgnoringCase(name, ageHeader))
289         m_haveParsedAgeHeader = false;
290     else if (equalIgnoringCase(name, cacheControlHeader) || equalIgnoringCase(name, pragmaHeader))
291         m_haveParsedCacheControlHeader = false;
292     else if (equalIgnoringCase(name, dateHeader))
293         m_haveParsedDateHeader = false;
294     else if (equalIgnoringCase(name, expiresHeader))
295         m_haveParsedExpiresHeader = false;
296     else if (equalIgnoringCase(name, lastModifiedHeader))
297         m_haveParsedLastModifiedHeader = false;
298 }
299
300 void ResourceResponseBase::setHTTPHeaderField(const AtomicString& name, const String& value)
301 {
302     lazyInit(CommonAndUncommonFields);
303
304     updateHeaderParsedState(name);
305
306     m_httpHeaderFields.set(name, value);
307 }
308
309 void ResourceResponseBase::addHTTPHeaderField(const AtomicString& name, const String& value)
310 {
311     lazyInit(CommonAndUncommonFields);
312
313     updateHeaderParsedState(name);
314
315     HTTPHeaderMap::AddResult result = m_httpHeaderFields.add(name, value);
316     if (!result.isNewEntry)
317         result.iterator->value.append(", " + value);
318 }
319
320 const HTTPHeaderMap& ResourceResponseBase::httpHeaderFields() const
321 {
322     lazyInit(CommonAndUncommonFields);
323
324     return m_httpHeaderFields;
325 }
326
327 void ResourceResponseBase::parseCacheControlDirectives() const
328 {
329     ASSERT(!m_haveParsedCacheControlHeader);
330
331     lazyInit(CommonFieldsOnly);
332
333     m_haveParsedCacheControlHeader = true;
334
335     m_cacheControlContainsMustRevalidate = false;
336     m_cacheControlContainsNoCache = false;
337     m_cacheControlMaxAge = numeric_limits<double>::quiet_NaN();
338
339     DEFINE_STATIC_LOCAL(const AtomicString, cacheControlString, ("cache-control", AtomicString::ConstructFromLiteral));
340     DEFINE_STATIC_LOCAL(const AtomicString, noCacheDirective, ("no-cache", AtomicString::ConstructFromLiteral));
341     DEFINE_STATIC_LOCAL(const AtomicString, noStoreDirective, ("no-store", AtomicString::ConstructFromLiteral));
342     DEFINE_STATIC_LOCAL(const AtomicString, mustRevalidateDirective, ("must-revalidate", AtomicString::ConstructFromLiteral));
343     DEFINE_STATIC_LOCAL(const AtomicString, maxAgeDirective, ("max-age", AtomicString::ConstructFromLiteral));
344
345     String cacheControlValue = m_httpHeaderFields.get(cacheControlString);
346     if (!cacheControlValue.isEmpty()) {
347         Vector<pair<String, String> > directives;
348         parseCacheHeader(cacheControlValue, directives);
349
350         size_t directivesSize = directives.size();
351         for (size_t i = 0; i < directivesSize; ++i) {
352             // RFC2616 14.9.1: A no-cache directive with a value is only meaningful for proxy caches.
353             // It should be ignored by a browser level cache.
354             if (equalIgnoringCase(directives[i].first, noCacheDirective) && directives[i].second.isEmpty())
355                 m_cacheControlContainsNoCache = true;
356             else if (equalIgnoringCase(directives[i].first, noStoreDirective))
357                 m_cacheControlContainsNoStore = true;
358             else if (equalIgnoringCase(directives[i].first, mustRevalidateDirective))
359                 m_cacheControlContainsMustRevalidate = true;
360             else if (equalIgnoringCase(directives[i].first, maxAgeDirective)) {
361                 if (!std::isnan(m_cacheControlMaxAge)) {
362                     // First max-age directive wins if there are multiple ones.
363                     continue;
364                 }
365                 bool ok;
366                 double maxAge = directives[i].second.toDouble(&ok);
367                 if (ok)
368                     m_cacheControlMaxAge = maxAge;
369             }
370         }
371     }
372
373     if (!m_cacheControlContainsNoCache) {
374         // Handle Pragma: no-cache
375         // This is deprecated and equivalent to Cache-control: no-cache
376         // Don't bother tokenizing the value, it is not important
377         DEFINE_STATIC_LOCAL(const AtomicString, pragmaHeader, ("pragma", AtomicString::ConstructFromLiteral));
378         String pragmaValue = m_httpHeaderFields.get(pragmaHeader);
379
380         m_cacheControlContainsNoCache = pragmaValue.lower().contains(noCacheDirective);
381     }
382 }
383     
384 bool ResourceResponseBase::cacheControlContainsNoCache() const
385 {
386     if (!m_haveParsedCacheControlHeader)
387         parseCacheControlDirectives();
388     return m_cacheControlContainsNoCache;
389 }
390
391 bool ResourceResponseBase::cacheControlContainsNoStore() const
392 {
393     if (!m_haveParsedCacheControlHeader)
394         parseCacheControlDirectives();
395     return m_cacheControlContainsNoStore;
396 }
397
398 bool ResourceResponseBase::cacheControlContainsMustRevalidate() const
399 {
400     if (!m_haveParsedCacheControlHeader)
401         parseCacheControlDirectives();
402     return m_cacheControlContainsMustRevalidate;
403 }
404
405 bool ResourceResponseBase::hasCacheValidatorFields() const
406 {
407     lazyInit(CommonFieldsOnly);
408
409     DEFINE_STATIC_LOCAL(const AtomicString, lastModifiedHeader, ("last-modified", AtomicString::ConstructFromLiteral));
410     DEFINE_STATIC_LOCAL(const AtomicString, eTagHeader, ("etag", AtomicString::ConstructFromLiteral));
411     return !m_httpHeaderFields.get(lastModifiedHeader).isEmpty() || !m_httpHeaderFields.get(eTagHeader).isEmpty();
412 }
413
414 double ResourceResponseBase::cacheControlMaxAge() const
415 {
416     if (!m_haveParsedCacheControlHeader)
417         parseCacheControlDirectives();
418     return m_cacheControlMaxAge;
419 }
420
421 static double parseDateValueInHeader(const HTTPHeaderMap& headers, const AtomicString& headerName)
422 {
423     String headerValue = headers.get(headerName);
424     if (headerValue.isEmpty())
425         return std::numeric_limits<double>::quiet_NaN(); 
426     // This handles all date formats required by RFC2616:
427     // Sun, 06 Nov 1994 08:49:37 GMT  ; RFC 822, updated by RFC 1123
428     // Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
429     // Sun Nov  6 08:49:37 1994       ; ANSI C's asctime() format
430     double dateInMilliseconds = parseDate(headerValue);
431     if (!isfinite(dateInMilliseconds))
432         return std::numeric_limits<double>::quiet_NaN();
433     return dateInMilliseconds / 1000;
434 }
435
436 double ResourceResponseBase::date() const
437 {
438     lazyInit(CommonFieldsOnly);
439
440     if (!m_haveParsedDateHeader) {
441         DEFINE_STATIC_LOCAL(const AtomicString, headerName, ("date", AtomicString::ConstructFromLiteral));
442         m_date = parseDateValueInHeader(m_httpHeaderFields, headerName);
443         m_haveParsedDateHeader = true;
444     }
445     return m_date;
446 }
447
448 double ResourceResponseBase::age() const
449 {
450     lazyInit(CommonFieldsOnly);
451
452     if (!m_haveParsedAgeHeader) {
453         DEFINE_STATIC_LOCAL(const AtomicString, headerName, ("age", AtomicString::ConstructFromLiteral));
454         String headerValue = m_httpHeaderFields.get(headerName);
455         bool ok;
456         m_age = headerValue.toDouble(&ok);
457         if (!ok)
458             m_age = std::numeric_limits<double>::quiet_NaN();
459         m_haveParsedAgeHeader = true;
460     }
461     return m_age;
462 }
463
464 double ResourceResponseBase::expires() const
465 {
466     lazyInit(CommonFieldsOnly);
467
468     if (!m_haveParsedExpiresHeader) {
469         DEFINE_STATIC_LOCAL(const AtomicString, headerName, ("expires", AtomicString::ConstructFromLiteral));
470         m_expires = parseDateValueInHeader(m_httpHeaderFields, headerName);
471         m_haveParsedExpiresHeader = true;
472     }
473     return m_expires;
474 }
475
476 double ResourceResponseBase::lastModified() const
477 {
478     lazyInit(CommonFieldsOnly);
479
480     if (!m_haveParsedLastModifiedHeader) {
481         DEFINE_STATIC_LOCAL(const AtomicString, headerName, ("last-modified", AtomicString::ConstructFromLiteral));
482         m_lastModified = parseDateValueInHeader(m_httpHeaderFields, headerName);
483         m_haveParsedLastModifiedHeader = true;
484     }
485     return m_lastModified;
486 }
487
488 bool ResourceResponseBase::isAttachment() const
489 {
490     lazyInit(CommonAndUncommonFields);
491
492     DEFINE_STATIC_LOCAL(const AtomicString, headerName, ("content-disposition", AtomicString::ConstructFromLiteral));
493     String value = m_httpHeaderFields.get(headerName);
494     size_t loc = value.find(';');
495     if (loc != notFound)
496         value = value.left(loc);
497     value = value.stripWhiteSpace();
498     DEFINE_STATIC_LOCAL(const AtomicString, attachmentString, ("attachment", AtomicString::ConstructFromLiteral));
499     return equalIgnoringCase(value, attachmentString);
500 }
501   
502 void ResourceResponseBase::setLastModifiedDate(time_t lastModifiedDate)
503 {
504     lazyInit(CommonAndUncommonFields);
505
506     m_lastModifiedDate = lastModifiedDate;
507 }
508
509 time_t ResourceResponseBase::lastModifiedDate() const
510 {
511     lazyInit(CommonAndUncommonFields);
512
513     return m_lastModifiedDate;
514 }
515
516 bool ResourceResponseBase::wasCached() const
517 {
518     lazyInit(CommonAndUncommonFields);
519
520     return m_wasCached;
521 }
522
523 void ResourceResponseBase::setWasCached(bool value)
524 {
525     m_wasCached = value;
526 }
527
528 bool ResourceResponseBase::connectionReused() const
529 {
530     lazyInit(CommonAndUncommonFields);
531
532     return m_connectionReused;
533 }
534
535 void ResourceResponseBase::setConnectionReused(bool connectionReused)
536 {
537     lazyInit(CommonAndUncommonFields);
538
539     m_connectionReused = connectionReused;
540 }
541
542 unsigned ResourceResponseBase::connectionID() const
543 {
544     lazyInit(CommonAndUncommonFields);
545
546     return m_connectionID;
547 }
548
549 void ResourceResponseBase::setConnectionID(unsigned connectionID)
550 {
551     lazyInit(CommonAndUncommonFields);
552
553     m_connectionID = connectionID;
554 }
555
556 ResourceLoadTiming* ResourceResponseBase::resourceLoadTiming() const
557 {
558     lazyInit(CommonAndUncommonFields);
559
560     return m_resourceLoadTiming.get();
561 }
562
563 void ResourceResponseBase::setResourceLoadTiming(PassRefPtr<ResourceLoadTiming> resourceLoadTiming)
564 {
565     lazyInit(CommonAndUncommonFields);
566
567     m_resourceLoadTiming = resourceLoadTiming;
568 }
569
570 PassRefPtr<ResourceLoadInfo> ResourceResponseBase::resourceLoadInfo() const
571 {
572     lazyInit(CommonAndUncommonFields);
573
574     return m_resourceLoadInfo.get();
575 }
576
577 void ResourceResponseBase::setResourceLoadInfo(PassRefPtr<ResourceLoadInfo> loadInfo)
578 {
579     lazyInit(CommonAndUncommonFields);
580
581     m_resourceLoadInfo = loadInfo;
582 }
583
584 void ResourceResponseBase::lazyInit(InitLevel initLevel) const
585 {
586     const_cast<ResourceResponse*>(static_cast<const ResourceResponse*>(this))->platformLazyInit(initLevel);
587 }
588
589 void ResourceResponseBase::reportMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const
590 {
591     MemoryClassInfo info(memoryObjectInfo, this, PlatformMemoryTypes::Loader);
592     info.addMember(m_url, "url");
593     info.addMember(m_mimeType, "mimeType");
594     info.addMember(m_textEncodingName, "textEncodingName");
595     info.addMember(m_suggestedFilename, "suggestedFilename");
596     info.addMember(m_httpStatusText, "httpStatusText");
597     info.addMember(m_httpHeaderFields, "httpHeaderFields");
598     info.addMember(m_resourceLoadTiming, "resourceLoadTiming");
599     info.addMember(m_resourceLoadInfo, "resourceLoadInfo");
600 }
601     
602 bool ResourceResponseBase::compare(const ResourceResponse& a, const ResourceResponse& b)
603 {
604     if (a.isNull() != b.isNull())
605         return false;  
606     if (a.url() != b.url())
607         return false;
608     if (a.mimeType() != b.mimeType())
609         return false;
610     if (a.expectedContentLength() != b.expectedContentLength())
611         return false;
612     if (a.textEncodingName() != b.textEncodingName())
613         return false;
614     if (a.suggestedFilename() != b.suggestedFilename())
615         return false;
616     if (a.httpStatusCode() != b.httpStatusCode())
617         return false;
618     if (a.httpStatusText() != b.httpStatusText())
619         return false;
620     if (a.httpHeaderFields() != b.httpHeaderFields())
621         return false;
622     if (a.resourceLoadTiming() && b.resourceLoadTiming() && *a.resourceLoadTiming() == *b.resourceLoadTiming())
623         return ResourceResponse::platformCompare(a, b);
624     if (a.resourceLoadTiming() != b.resourceLoadTiming())
625         return false;
626     return ResourceResponse::platformCompare(a, b);
627 }
628
629 static bool isCacheHeaderSeparator(UChar c)
630 {
631     // See RFC 2616, Section 2.2
632     switch (c) {
633         case '(':
634         case ')':
635         case '<':
636         case '>':
637         case '@':
638         case ',':
639         case ';':
640         case ':':
641         case '\\':
642         case '"':
643         case '/':
644         case '[':
645         case ']':
646         case '?':
647         case '=':
648         case '{':
649         case '}':
650         case ' ':
651         case '\t':
652             return true;
653         default:
654             return false;
655     }
656 }
657
658 static bool isControlCharacter(UChar c)
659 {
660     return c < ' ' || c == 127;
661 }
662
663 static inline String trimToNextSeparator(const String& str)
664 {
665     return str.substring(0, str.find(isCacheHeaderSeparator));
666 }
667
668 static void parseCacheHeader(const String& header, Vector<pair<String, String> >& result)
669 {
670     const String safeHeader = header.removeCharacters(isControlCharacter);
671     unsigned max = safeHeader.length();
672     for (unsigned pos = 0; pos < max; /* pos incremented in loop */) {
673         size_t nextCommaPosition = safeHeader.find(',', pos);
674         size_t nextEqualSignPosition = safeHeader.find('=', pos);
675         if (nextEqualSignPosition != notFound && (nextEqualSignPosition < nextCommaPosition || nextCommaPosition == notFound)) {
676             // Get directive name, parse right hand side of equal sign, then add to map
677             String directive = trimToNextSeparator(safeHeader.substring(pos, nextEqualSignPosition - pos).stripWhiteSpace());
678             pos += nextEqualSignPosition - pos + 1;
679
680             String value = safeHeader.substring(pos, max - pos).stripWhiteSpace();
681             if (value[0] == '"') {
682                 // The value is a quoted string
683                 size_t nextDoubleQuotePosition = value.find('"', 1);
684                 if (nextDoubleQuotePosition != notFound) {
685                     // Store the value as a quoted string without quotes
686                     result.append(pair<String, String>(directive, value.substring(1, nextDoubleQuotePosition - 1).stripWhiteSpace()));
687                     pos += (safeHeader.find('"', pos) - pos) + nextDoubleQuotePosition + 1;
688                     // Move past next comma, if there is one
689                     size_t nextCommaPosition2 = safeHeader.find(',', pos);
690                     if (nextCommaPosition2 != notFound)
691                         pos += nextCommaPosition2 - pos + 1;
692                     else
693                         return; // Parse error if there is anything left with no comma
694                 } else {
695                     // Parse error; just use the rest as the value
696                     result.append(pair<String, String>(directive, trimToNextSeparator(value.substring(1, value.length() - 1).stripWhiteSpace())));
697                     return;
698                 }
699             } else {
700                 // The value is a token until the next comma
701                 size_t nextCommaPosition2 = value.find(',');
702                 if (nextCommaPosition2 != notFound) {
703                     // The value is delimited by the next comma
704                     result.append(pair<String, String>(directive, trimToNextSeparator(value.substring(0, nextCommaPosition2).stripWhiteSpace())));
705                     pos += (safeHeader.find(',', pos) - pos) + 1;
706                 } else {
707                     // The rest is the value; no change to value needed
708                     result.append(pair<String, String>(directive, trimToNextSeparator(value)));
709                     return;
710                 }
711             }
712         } else if (nextCommaPosition != notFound && (nextCommaPosition < nextEqualSignPosition || nextEqualSignPosition == notFound)) {
713             // Add directive to map with empty string as value
714             result.append(pair<String, String>(trimToNextSeparator(safeHeader.substring(pos, nextCommaPosition - pos).stripWhiteSpace()), ""));
715             pos += nextCommaPosition - pos + 1;
716         } else {
717             // Add last directive to map with empty string as value
718             result.append(pair<String, String>(trimToNextSeparator(safeHeader.substring(pos, max - pos).stripWhiteSpace()), ""));
719             return;
720         }
721     }
722 }
723
724 }