The 'global isinf/isnan' compiler quirk required when using clang with libstdc++
[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::setHTTPHeaderField(const AtomicString& name, const String& value)
280 {
281     lazyInit(CommonAndUncommonFields);
282
283     DEFINE_STATIC_LOCAL(const AtomicString, ageHeader, ("age", AtomicString::ConstructFromLiteral));
284     DEFINE_STATIC_LOCAL(const AtomicString, cacheControlHeader, ("cache-control", AtomicString::ConstructFromLiteral));
285     DEFINE_STATIC_LOCAL(const AtomicString, dateHeader, ("date", AtomicString::ConstructFromLiteral));
286     DEFINE_STATIC_LOCAL(const AtomicString, expiresHeader, ("expires", AtomicString::ConstructFromLiteral));
287     DEFINE_STATIC_LOCAL(const AtomicString, lastModifiedHeader, ("last-modified", AtomicString::ConstructFromLiteral));
288     DEFINE_STATIC_LOCAL(const AtomicString, pragmaHeader, ("pragma", AtomicString::ConstructFromLiteral));
289     if (equalIgnoringCase(name, ageHeader))
290         m_haveParsedAgeHeader = false;
291     else if (equalIgnoringCase(name, cacheControlHeader) || equalIgnoringCase(name, pragmaHeader))
292         m_haveParsedCacheControlHeader = false;
293     else if (equalIgnoringCase(name, dateHeader))
294         m_haveParsedDateHeader = false;
295     else if (equalIgnoringCase(name, expiresHeader))
296         m_haveParsedExpiresHeader = false;
297     else if (equalIgnoringCase(name, lastModifiedHeader))
298         m_haveParsedLastModifiedHeader = false;
299
300     m_httpHeaderFields.set(name, value);
301 }
302
303 const HTTPHeaderMap& ResourceResponseBase::httpHeaderFields() const
304 {
305     lazyInit(CommonAndUncommonFields);
306
307     return m_httpHeaderFields;
308 }
309
310 void ResourceResponseBase::parseCacheControlDirectives() const
311 {
312     ASSERT(!m_haveParsedCacheControlHeader);
313
314     lazyInit(CommonFieldsOnly);
315
316     m_haveParsedCacheControlHeader = true;
317
318     m_cacheControlContainsMustRevalidate = false;
319     m_cacheControlContainsNoCache = false;
320     m_cacheControlMaxAge = numeric_limits<double>::quiet_NaN();
321
322     DEFINE_STATIC_LOCAL(const AtomicString, cacheControlString, ("cache-control", AtomicString::ConstructFromLiteral));
323     DEFINE_STATIC_LOCAL(const AtomicString, noCacheDirective, ("no-cache", AtomicString::ConstructFromLiteral));
324     DEFINE_STATIC_LOCAL(const AtomicString, noStoreDirective, ("no-store", AtomicString::ConstructFromLiteral));
325     DEFINE_STATIC_LOCAL(const AtomicString, mustRevalidateDirective, ("must-revalidate", AtomicString::ConstructFromLiteral));
326     DEFINE_STATIC_LOCAL(const AtomicString, maxAgeDirective, ("max-age", AtomicString::ConstructFromLiteral));
327
328     String cacheControlValue = m_httpHeaderFields.get(cacheControlString);
329     if (!cacheControlValue.isEmpty()) {
330         Vector<pair<String, String> > directives;
331         parseCacheHeader(cacheControlValue, directives);
332
333         size_t directivesSize = directives.size();
334         for (size_t i = 0; i < directivesSize; ++i) {
335             // RFC2616 14.9.1: A no-cache directive with a value is only meaningful for proxy caches.
336             // It should be ignored by a browser level cache.
337             if (equalIgnoringCase(directives[i].first, noCacheDirective) && directives[i].second.isEmpty())
338                 m_cacheControlContainsNoCache = true;
339             else if (equalIgnoringCase(directives[i].first, noStoreDirective))
340                 m_cacheControlContainsNoStore = true;
341             else if (equalIgnoringCase(directives[i].first, mustRevalidateDirective))
342                 m_cacheControlContainsMustRevalidate = true;
343             else if (equalIgnoringCase(directives[i].first, maxAgeDirective)) {
344                 if (!std::isnan(m_cacheControlMaxAge)) {
345                     // First max-age directive wins if there are multiple ones.
346                     continue;
347                 }
348                 bool ok;
349                 double maxAge = directives[i].second.toDouble(&ok);
350                 if (ok)
351                     m_cacheControlMaxAge = maxAge;
352             }
353         }
354     }
355
356     if (!m_cacheControlContainsNoCache) {
357         // Handle Pragma: no-cache
358         // This is deprecated and equivalent to Cache-control: no-cache
359         // Don't bother tokenizing the value, it is not important
360         DEFINE_STATIC_LOCAL(const AtomicString, pragmaHeader, ("pragma", AtomicString::ConstructFromLiteral));
361         String pragmaValue = m_httpHeaderFields.get(pragmaHeader);
362
363         m_cacheControlContainsNoCache = pragmaValue.lower().contains(noCacheDirective);
364     }
365 }
366     
367 bool ResourceResponseBase::cacheControlContainsNoCache() const
368 {
369     if (!m_haveParsedCacheControlHeader)
370         parseCacheControlDirectives();
371     return m_cacheControlContainsNoCache;
372 }
373
374 bool ResourceResponseBase::cacheControlContainsNoStore() const
375 {
376     if (!m_haveParsedCacheControlHeader)
377         parseCacheControlDirectives();
378     return m_cacheControlContainsNoStore;
379 }
380
381 bool ResourceResponseBase::cacheControlContainsMustRevalidate() const
382 {
383     if (!m_haveParsedCacheControlHeader)
384         parseCacheControlDirectives();
385     return m_cacheControlContainsMustRevalidate;
386 }
387
388 bool ResourceResponseBase::hasCacheValidatorFields() const
389 {
390     lazyInit(CommonFieldsOnly);
391
392     DEFINE_STATIC_LOCAL(const AtomicString, lastModifiedHeader, ("last-modified", AtomicString::ConstructFromLiteral));
393     DEFINE_STATIC_LOCAL(const AtomicString, eTagHeader, ("etag", AtomicString::ConstructFromLiteral));
394     return !m_httpHeaderFields.get(lastModifiedHeader).isEmpty() || !m_httpHeaderFields.get(eTagHeader).isEmpty();
395 }
396
397 double ResourceResponseBase::cacheControlMaxAge() const
398 {
399     if (!m_haveParsedCacheControlHeader)
400         parseCacheControlDirectives();
401     return m_cacheControlMaxAge;
402 }
403
404 static double parseDateValueInHeader(const HTTPHeaderMap& headers, const AtomicString& headerName)
405 {
406     String headerValue = headers.get(headerName);
407     if (headerValue.isEmpty())
408         return std::numeric_limits<double>::quiet_NaN(); 
409     // This handles all date formats required by RFC2616:
410     // Sun, 06 Nov 1994 08:49:37 GMT  ; RFC 822, updated by RFC 1123
411     // Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
412     // Sun Nov  6 08:49:37 1994       ; ANSI C's asctime() format
413     double dateInMilliseconds = parseDate(headerValue);
414     if (!isfinite(dateInMilliseconds))
415         return std::numeric_limits<double>::quiet_NaN();
416     return dateInMilliseconds / 1000;
417 }
418
419 double ResourceResponseBase::date() const
420 {
421     lazyInit(CommonFieldsOnly);
422
423     if (!m_haveParsedDateHeader) {
424         DEFINE_STATIC_LOCAL(const AtomicString, headerName, ("date", AtomicString::ConstructFromLiteral));
425         m_date = parseDateValueInHeader(m_httpHeaderFields, headerName);
426         m_haveParsedDateHeader = true;
427     }
428     return m_date;
429 }
430
431 double ResourceResponseBase::age() const
432 {
433     lazyInit(CommonFieldsOnly);
434
435     if (!m_haveParsedAgeHeader) {
436         DEFINE_STATIC_LOCAL(const AtomicString, headerName, ("age", AtomicString::ConstructFromLiteral));
437         String headerValue = m_httpHeaderFields.get(headerName);
438         bool ok;
439         m_age = headerValue.toDouble(&ok);
440         if (!ok)
441             m_age = std::numeric_limits<double>::quiet_NaN();
442         m_haveParsedAgeHeader = true;
443     }
444     return m_age;
445 }
446
447 double ResourceResponseBase::expires() const
448 {
449     lazyInit(CommonFieldsOnly);
450
451     if (!m_haveParsedExpiresHeader) {
452         DEFINE_STATIC_LOCAL(const AtomicString, headerName, ("expires", AtomicString::ConstructFromLiteral));
453         m_expires = parseDateValueInHeader(m_httpHeaderFields, headerName);
454         m_haveParsedExpiresHeader = true;
455     }
456     return m_expires;
457 }
458
459 double ResourceResponseBase::lastModified() const
460 {
461     lazyInit(CommonFieldsOnly);
462
463     if (!m_haveParsedLastModifiedHeader) {
464         DEFINE_STATIC_LOCAL(const AtomicString, headerName, ("last-modified", AtomicString::ConstructFromLiteral));
465         m_lastModified = parseDateValueInHeader(m_httpHeaderFields, headerName);
466         m_haveParsedLastModifiedHeader = true;
467     }
468     return m_lastModified;
469 }
470
471 bool ResourceResponseBase::isAttachment() const
472 {
473     lazyInit(CommonAndUncommonFields);
474
475     DEFINE_STATIC_LOCAL(const AtomicString, headerName, ("content-disposition", AtomicString::ConstructFromLiteral));
476     String value = m_httpHeaderFields.get(headerName);
477     size_t loc = value.find(';');
478     if (loc != notFound)
479         value = value.left(loc);
480     value = value.stripWhiteSpace();
481     DEFINE_STATIC_LOCAL(const AtomicString, attachmentString, ("attachment", AtomicString::ConstructFromLiteral));
482     return equalIgnoringCase(value, attachmentString);
483 }
484   
485 void ResourceResponseBase::setLastModifiedDate(time_t lastModifiedDate)
486 {
487     lazyInit(CommonAndUncommonFields);
488
489     m_lastModifiedDate = lastModifiedDate;
490 }
491
492 time_t ResourceResponseBase::lastModifiedDate() const
493 {
494     lazyInit(CommonAndUncommonFields);
495
496     return m_lastModifiedDate;
497 }
498
499 bool ResourceResponseBase::wasCached() const
500 {
501     lazyInit(CommonAndUncommonFields);
502
503     return m_wasCached;
504 }
505
506 void ResourceResponseBase::setWasCached(bool value)
507 {
508     m_wasCached = value;
509 }
510
511 bool ResourceResponseBase::connectionReused() const
512 {
513     lazyInit(CommonAndUncommonFields);
514
515     return m_connectionReused;
516 }
517
518 void ResourceResponseBase::setConnectionReused(bool connectionReused)
519 {
520     lazyInit(CommonAndUncommonFields);
521
522     m_connectionReused = connectionReused;
523 }
524
525 unsigned ResourceResponseBase::connectionID() const
526 {
527     lazyInit(CommonAndUncommonFields);
528
529     return m_connectionID;
530 }
531
532 void ResourceResponseBase::setConnectionID(unsigned connectionID)
533 {
534     lazyInit(CommonAndUncommonFields);
535
536     m_connectionID = connectionID;
537 }
538
539 ResourceLoadTiming* ResourceResponseBase::resourceLoadTiming() const
540 {
541     lazyInit(CommonAndUncommonFields);
542
543     return m_resourceLoadTiming.get();
544 }
545
546 void ResourceResponseBase::setResourceLoadTiming(PassRefPtr<ResourceLoadTiming> resourceLoadTiming)
547 {
548     lazyInit(CommonAndUncommonFields);
549
550     m_resourceLoadTiming = resourceLoadTiming;
551 }
552
553 PassRefPtr<ResourceLoadInfo> ResourceResponseBase::resourceLoadInfo() const
554 {
555     lazyInit(CommonAndUncommonFields);
556
557     return m_resourceLoadInfo.get();
558 }
559
560 void ResourceResponseBase::setResourceLoadInfo(PassRefPtr<ResourceLoadInfo> loadInfo)
561 {
562     lazyInit(CommonAndUncommonFields);
563
564     m_resourceLoadInfo = loadInfo;
565 }
566
567 void ResourceResponseBase::lazyInit(InitLevel initLevel) const
568 {
569     const_cast<ResourceResponse*>(static_cast<const ResourceResponse*>(this))->platformLazyInit(initLevel);
570 }
571
572 void ResourceResponseBase::reportMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const
573 {
574     MemoryClassInfo info(memoryObjectInfo, this, PlatformMemoryTypes::Loader);
575     info.addMember(m_url, "url");
576     info.addMember(m_mimeType, "mimeType");
577     info.addMember(m_textEncodingName, "textEncodingName");
578     info.addMember(m_suggestedFilename, "suggestedFilename");
579     info.addMember(m_httpStatusText, "httpStatusText");
580     info.addMember(m_httpHeaderFields, "httpHeaderFields");
581     info.addMember(m_resourceLoadTiming, "resourceLoadTiming");
582     info.addMember(m_resourceLoadInfo, "resourceLoadInfo");
583 }
584     
585 bool ResourceResponseBase::compare(const ResourceResponse& a, const ResourceResponse& b)
586 {
587     if (a.isNull() != b.isNull())
588         return false;  
589     if (a.url() != b.url())
590         return false;
591     if (a.mimeType() != b.mimeType())
592         return false;
593     if (a.expectedContentLength() != b.expectedContentLength())
594         return false;
595     if (a.textEncodingName() != b.textEncodingName())
596         return false;
597     if (a.suggestedFilename() != b.suggestedFilename())
598         return false;
599     if (a.httpStatusCode() != b.httpStatusCode())
600         return false;
601     if (a.httpStatusText() != b.httpStatusText())
602         return false;
603     if (a.httpHeaderFields() != b.httpHeaderFields())
604         return false;
605     if (a.resourceLoadTiming() && b.resourceLoadTiming() && *a.resourceLoadTiming() == *b.resourceLoadTiming())
606         return ResourceResponse::platformCompare(a, b);
607     if (a.resourceLoadTiming() != b.resourceLoadTiming())
608         return false;
609     return ResourceResponse::platformCompare(a, b);
610 }
611
612 static bool isCacheHeaderSeparator(UChar c)
613 {
614     // See RFC 2616, Section 2.2
615     switch (c) {
616         case '(':
617         case ')':
618         case '<':
619         case '>':
620         case '@':
621         case ',':
622         case ';':
623         case ':':
624         case '\\':
625         case '"':
626         case '/':
627         case '[':
628         case ']':
629         case '?':
630         case '=':
631         case '{':
632         case '}':
633         case ' ':
634         case '\t':
635             return true;
636         default:
637             return false;
638     }
639 }
640
641 static bool isControlCharacter(UChar c)
642 {
643     return c < ' ' || c == 127;
644 }
645
646 static inline String trimToNextSeparator(const String& str)
647 {
648     return str.substring(0, str.find(isCacheHeaderSeparator));
649 }
650
651 static void parseCacheHeader(const String& header, Vector<pair<String, String> >& result)
652 {
653     const String safeHeader = header.removeCharacters(isControlCharacter);
654     unsigned max = safeHeader.length();
655     for (unsigned pos = 0; pos < max; /* pos incremented in loop */) {
656         size_t nextCommaPosition = safeHeader.find(',', pos);
657         size_t nextEqualSignPosition = safeHeader.find('=', pos);
658         if (nextEqualSignPosition != notFound && (nextEqualSignPosition < nextCommaPosition || nextCommaPosition == notFound)) {
659             // Get directive name, parse right hand side of equal sign, then add to map
660             String directive = trimToNextSeparator(safeHeader.substring(pos, nextEqualSignPosition - pos).stripWhiteSpace());
661             pos += nextEqualSignPosition - pos + 1;
662
663             String value = safeHeader.substring(pos, max - pos).stripWhiteSpace();
664             if (value[0] == '"') {
665                 // The value is a quoted string
666                 size_t nextDoubleQuotePosition = value.find('"', 1);
667                 if (nextDoubleQuotePosition != notFound) {
668                     // Store the value as a quoted string without quotes
669                     result.append(pair<String, String>(directive, value.substring(1, nextDoubleQuotePosition - 1).stripWhiteSpace()));
670                     pos += (safeHeader.find('"', pos) - pos) + nextDoubleQuotePosition + 1;
671                     // Move past next comma, if there is one
672                     size_t nextCommaPosition2 = safeHeader.find(',', pos);
673                     if (nextCommaPosition2 != notFound)
674                         pos += nextCommaPosition2 - pos + 1;
675                     else
676                         return; // Parse error if there is anything left with no comma
677                 } else {
678                     // Parse error; just use the rest as the value
679                     result.append(pair<String, String>(directive, trimToNextSeparator(value.substring(1, value.length() - 1).stripWhiteSpace())));
680                     return;
681                 }
682             } else {
683                 // The value is a token until the next comma
684                 size_t nextCommaPosition2 = value.find(',');
685                 if (nextCommaPosition2 != notFound) {
686                     // The value is delimited by the next comma
687                     result.append(pair<String, String>(directive, trimToNextSeparator(value.substring(0, nextCommaPosition2).stripWhiteSpace())));
688                     pos += (safeHeader.find(',', pos) - pos) + 1;
689                 } else {
690                     // The rest is the value; no change to value needed
691                     result.append(pair<String, String>(directive, trimToNextSeparator(value)));
692                     return;
693                 }
694             }
695         } else if (nextCommaPosition != notFound && (nextCommaPosition < nextEqualSignPosition || nextEqualSignPosition == notFound)) {
696             // Add directive to map with empty string as value
697             result.append(pair<String, String>(trimToNextSeparator(safeHeader.substring(pos, nextCommaPosition - pos).stripWhiteSpace()), ""));
698             pos += nextCommaPosition - pos + 1;
699         } else {
700             // Add last directive to map with empty string as value
701             result.append(pair<String, String>(trimToNextSeparator(safeHeader.substring(pos, max - pos).stripWhiteSpace()), ""));
702             return;
703         }
704     }
705 }
706
707 }