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