Web Inspector: NMI: now when we can detect instrumented classes we can
[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 "PlatformMemoryInstrumentation.h"
32 #include "ResourceResponse.h"
33 #include <wtf/CurrentTime.h>
34 #include <wtf/MathExtras.h>
35 #include <wtf/StdLibExtras.h>
36
37 using namespace std;
38
39 namespace WebCore {
40
41 static void parseCacheHeader(const String& header, Vector<pair<String, String> >& result);
42
43 inline const ResourceResponse& ResourceResponseBase::asResourceResponse() const
44 {
45     return *static_cast<const ResourceResponse*>(this);
46 }
47
48 ResourceResponseBase::ResourceResponseBase()  
49     : m_expectedContentLength(0)
50     , m_httpStatusCode(0)
51     , m_lastModifiedDate(0)
52     , m_wasCached(false)
53     , m_connectionID(0)
54     , m_connectionReused(false)
55     , m_isNull(true)
56     , m_haveParsedCacheControlHeader(false)
57     , m_haveParsedAgeHeader(false)
58     , m_haveParsedDateHeader(false)
59     , m_haveParsedExpiresHeader(false)
60     , m_haveParsedLastModifiedHeader(false)
61     , m_cacheControlContainsNoCache(false)
62     , m_cacheControlContainsNoStore(false)
63     , m_cacheControlContainsMustRevalidate(false)
64     , m_cacheControlMaxAge(0.0)
65     , m_age(0.0)
66     , m_date(0.0)
67     , m_expires(0.0)
68     , m_lastModified(0.0)
69 {
70 }
71
72 ResourceResponseBase::ResourceResponseBase(const KURL& url, const String& mimeType, long long expectedLength, const String& textEncodingName, const String& filename)
73     : m_url(url)
74     , m_mimeType(mimeType)
75     , m_expectedContentLength(expectedLength)
76     , m_textEncodingName(textEncodingName)
77     , m_suggestedFilename(filename)
78     , m_httpStatusCode(0)
79     , m_lastModifiedDate(0)
80     , m_wasCached(false)
81     , m_connectionID(0)
82     , m_connectionReused(false)
83     , m_isNull(false)
84     , m_haveParsedCacheControlHeader(false)
85     , m_haveParsedAgeHeader(false)
86     , m_haveParsedDateHeader(false)
87     , m_haveParsedExpiresHeader(false)
88     , m_haveParsedLastModifiedHeader(false)
89     , m_cacheControlContainsNoCache(false)
90     , m_cacheControlContainsNoStore(false)
91     , m_cacheControlContainsMustRevalidate(false)
92     , m_cacheControlMaxAge(0.0)
93     , m_age(0.0)
94     , m_date(0.0)
95     , m_expires(0.0)
96     , m_lastModified(0.0)
97 {
98 }
99
100 PassOwnPtr<ResourceResponse> ResourceResponseBase::adopt(PassOwnPtr<CrossThreadResourceResponseData> data)
101 {
102     OwnPtr<ResourceResponse> response = adoptPtr(new ResourceResponse);
103     response->setURL(data->m_url);
104     response->setMimeType(data->m_mimeType);
105     response->setExpectedContentLength(data->m_expectedContentLength);
106     response->setTextEncodingName(data->m_textEncodingName);
107     response->setSuggestedFilename(data->m_suggestedFilename);
108
109     response->setHTTPStatusCode(data->m_httpStatusCode);
110     response->setHTTPStatusText(data->m_httpStatusText);
111
112     response->lazyInit(CommonAndUncommonFields);
113     response->m_httpHeaderFields.adopt(data->m_httpHeaders.release());
114     response->setLastModifiedDate(data->m_lastModifiedDate);
115     response->setResourceLoadTiming(data->m_resourceLoadTiming.release());
116     response->doPlatformAdopt(data);
117     return response.release();
118 }
119
120 PassOwnPtr<CrossThreadResourceResponseData> ResourceResponseBase::copyData() const
121 {
122     OwnPtr<CrossThreadResourceResponseData> data = adoptPtr(new CrossThreadResourceResponseData);
123     data->m_url = url().copy();
124     data->m_mimeType = mimeType().isolatedCopy();
125     data->m_expectedContentLength = expectedContentLength();
126     data->m_textEncodingName = textEncodingName().isolatedCopy();
127     data->m_suggestedFilename = suggestedFilename().isolatedCopy();
128     data->m_httpStatusCode = httpStatusCode();
129     data->m_httpStatusText = httpStatusText().isolatedCopy();
130     data->m_httpHeaders = httpHeaderFields().copyData();
131     data->m_lastModifiedDate = lastModifiedDate();
132     if (m_resourceLoadTiming)
133         data->m_resourceLoadTiming = m_resourceLoadTiming->deepCopy();
134     return asResourceResponse().doPlatformCopyData(data.release());
135 }
136
137 bool ResourceResponseBase::isHTTP() const
138 {
139     lazyInit(CommonFieldsOnly);
140
141     String protocol = m_url.protocol();
142
143     return equalIgnoringCase(protocol, "http")  || equalIgnoringCase(protocol, "https");
144 }
145
146 const KURL& ResourceResponseBase::url() const
147 {
148     lazyInit(CommonFieldsOnly);
149
150     return m_url; 
151 }
152
153 void ResourceResponseBase::setURL(const KURL& url)
154 {
155     lazyInit(CommonFieldsOnly);
156     m_isNull = false;
157
158     m_url = url; 
159 }
160
161 const String& ResourceResponseBase::mimeType() const
162 {
163     lazyInit(CommonFieldsOnly);
164
165     return m_mimeType; 
166 }
167
168 void ResourceResponseBase::setMimeType(const String& mimeType)
169 {
170     lazyInit(CommonFieldsOnly);
171     m_isNull = false;
172
173     m_mimeType = mimeType; 
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     m_expectedContentLength = expectedContentLength; 
189 }
190
191 const String& ResourceResponseBase::textEncodingName() const
192 {
193     lazyInit(CommonFieldsOnly);
194
195     return m_textEncodingName;
196 }
197
198 void ResourceResponseBase::setTextEncodingName(const String& encodingName)
199 {
200     lazyInit(CommonFieldsOnly);
201     m_isNull = false;
202
203     m_textEncodingName = encodingName; 
204 }
205
206 // FIXME should compute this on the fly
207 const String& ResourceResponseBase::suggestedFilename() const
208 {
209     lazyInit(AllFields);
210
211     return m_suggestedFilename;
212 }
213
214 void ResourceResponseBase::setSuggestedFilename(const String& suggestedName)
215 {
216     lazyInit(AllFields);
217     m_isNull = false;
218
219     m_suggestedFilename = suggestedName; 
220 }
221
222 int ResourceResponseBase::httpStatusCode() const
223 {
224     lazyInit(CommonFieldsOnly);
225
226     return m_httpStatusCode;
227 }
228
229 void ResourceResponseBase::setHTTPStatusCode(int statusCode)
230 {
231     lazyInit(CommonFieldsOnly);
232
233     m_httpStatusCode = statusCode;
234 }
235
236 const String& ResourceResponseBase::httpStatusText() const 
237 {
238     lazyInit(CommonAndUncommonFields);
239
240     return m_httpStatusText; 
241 }
242
243 void ResourceResponseBase::setHTTPStatusText(const String& statusText) 
244 {
245     lazyInit(CommonAndUncommonFields);
246
247     m_httpStatusText = statusText; 
248 }
249
250 String ResourceResponseBase::httpHeaderField(const AtomicString& name) const
251 {
252     lazyInit(CommonFieldsOnly);
253
254     // If we already have the header, just return it instead of consuming memory by grabing all headers.
255     String value = m_httpHeaderFields.get(name);
256     if (!value.isEmpty())        
257         return value;
258
259     lazyInit(CommonAndUncommonFields);
260
261     return m_httpHeaderFields.get(name); 
262 }
263
264 String ResourceResponseBase::httpHeaderField(const char* name) const
265 {
266     lazyInit(CommonFieldsOnly);
267
268     // If we already have the header, just return it instead of consuming memory by grabing all headers.
269     String value = m_httpHeaderFields.get(name);
270     if (!value.isEmpty())
271         return value;
272
273     lazyInit(CommonAndUncommonFields);
274
275     return m_httpHeaderFields.get(name); 
276 }
277
278 void ResourceResponseBase::setHTTPHeaderField(const AtomicString& name, const String& value)
279 {
280     lazyInit(CommonAndUncommonFields);
281
282     DEFINE_STATIC_LOCAL(const AtomicString, ageHeader, ("age"));
283     DEFINE_STATIC_LOCAL(const AtomicString, cacheControlHeader, ("cache-control"));
284     DEFINE_STATIC_LOCAL(const AtomicString, dateHeader, ("date"));
285     DEFINE_STATIC_LOCAL(const AtomicString, expiresHeader, ("expires"));
286     DEFINE_STATIC_LOCAL(const AtomicString, lastModifiedHeader, ("last-modified"));
287     DEFINE_STATIC_LOCAL(const AtomicString, pragmaHeader, ("pragma"));
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     m_httpHeaderFields.set(name, value);
300 }
301
302 const HTTPHeaderMap& ResourceResponseBase::httpHeaderFields() const
303 {
304     lazyInit(CommonAndUncommonFields);
305
306     return m_httpHeaderFields;
307 }
308
309 void ResourceResponseBase::parseCacheControlDirectives() const
310 {
311     ASSERT(!m_haveParsedCacheControlHeader);
312
313     lazyInit(CommonFieldsOnly);
314
315     m_haveParsedCacheControlHeader = true;
316
317     m_cacheControlContainsMustRevalidate = false;
318     m_cacheControlContainsNoCache = false;
319     m_cacheControlMaxAge = numeric_limits<double>::quiet_NaN();
320
321     DEFINE_STATIC_LOCAL(const AtomicString, cacheControlString, ("cache-control"));
322     DEFINE_STATIC_LOCAL(const AtomicString, noCacheDirective, ("no-cache"));
323     DEFINE_STATIC_LOCAL(const AtomicString, noStoreDirective, ("no-store"));
324     DEFINE_STATIC_LOCAL(const AtomicString, mustRevalidateDirective, ("must-revalidate"));
325     DEFINE_STATIC_LOCAL(const AtomicString, maxAgeDirective, ("max-age"));
326
327     String cacheControlValue = m_httpHeaderFields.get(cacheControlString);
328     if (!cacheControlValue.isEmpty()) {
329         Vector<pair<String, String> > directives;
330         parseCacheHeader(cacheControlValue, directives);
331
332         size_t directivesSize = directives.size();
333         for (size_t i = 0; i < directivesSize; ++i) {
334             // RFC2616 14.9.1: A no-cache directive with a value is only meaningful for proxy caches.
335             // It should be ignored by a browser level cache.
336             if (equalIgnoringCase(directives[i].first, noCacheDirective) && directives[i].second.isEmpty())
337                 m_cacheControlContainsNoCache = true;
338             else if (equalIgnoringCase(directives[i].first, noStoreDirective))
339                 m_cacheControlContainsNoStore = true;
340             else if (equalIgnoringCase(directives[i].first, mustRevalidateDirective))
341                 m_cacheControlContainsMustRevalidate = true;
342             else if (equalIgnoringCase(directives[i].first, maxAgeDirective)) {
343                 if (!isnan(m_cacheControlMaxAge)) {
344                     // First max-age directive wins if there are multiple ones.
345                     continue;
346                 }
347                 bool ok;
348                 double maxAge = directives[i].second.toDouble(&ok);
349                 if (ok)
350                     m_cacheControlMaxAge = maxAge;
351             }
352         }
353     }
354
355     if (!m_cacheControlContainsNoCache) {
356         // Handle Pragma: no-cache
357         // This is deprecated and equivalent to Cache-control: no-cache
358         // Don't bother tokenizing the value, it is not important
359         DEFINE_STATIC_LOCAL(const AtomicString, pragmaHeader, ("pragma"));
360         String pragmaValue = m_httpHeaderFields.get(pragmaHeader);
361
362         m_cacheControlContainsNoCache = pragmaValue.lower().contains(noCacheDirective);
363     }
364 }
365     
366 bool ResourceResponseBase::cacheControlContainsNoCache() const
367 {
368     if (!m_haveParsedCacheControlHeader)
369         parseCacheControlDirectives();
370     return m_cacheControlContainsNoCache;
371 }
372
373 bool ResourceResponseBase::cacheControlContainsNoStore() const
374 {
375     if (!m_haveParsedCacheControlHeader)
376         parseCacheControlDirectives();
377     return m_cacheControlContainsNoStore;
378 }
379
380 bool ResourceResponseBase::cacheControlContainsMustRevalidate() const
381 {
382     if (!m_haveParsedCacheControlHeader)
383         parseCacheControlDirectives();
384     return m_cacheControlContainsMustRevalidate;
385 }
386
387 bool ResourceResponseBase::hasCacheValidatorFields() const
388 {
389     lazyInit(CommonFieldsOnly);
390
391     DEFINE_STATIC_LOCAL(const AtomicString, lastModifiedHeader, ("last-modified"));
392     DEFINE_STATIC_LOCAL(const AtomicString, eTagHeader, ("etag"));
393     return !m_httpHeaderFields.get(lastModifiedHeader).isEmpty() || !m_httpHeaderFields.get(eTagHeader).isEmpty();
394 }
395
396 double ResourceResponseBase::cacheControlMaxAge() const
397 {
398     if (!m_haveParsedCacheControlHeader)
399         parseCacheControlDirectives();
400     return m_cacheControlMaxAge;
401 }
402
403 static double parseDateValueInHeader(const HTTPHeaderMap& headers, const AtomicString& headerName)
404 {
405     String headerValue = headers.get(headerName);
406     if (headerValue.isEmpty())
407         return std::numeric_limits<double>::quiet_NaN(); 
408     // This handles all date formats required by RFC2616:
409     // Sun, 06 Nov 1994 08:49:37 GMT  ; RFC 822, updated by RFC 1123
410     // Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
411     // Sun Nov  6 08:49:37 1994       ; ANSI C's asctime() format
412     double dateInMilliseconds = parseDate(headerValue);
413     if (!isfinite(dateInMilliseconds))
414         return std::numeric_limits<double>::quiet_NaN();
415     return dateInMilliseconds / 1000;
416 }
417
418 double ResourceResponseBase::date() const
419 {
420     lazyInit(CommonFieldsOnly);
421
422     if (!m_haveParsedDateHeader) {
423         DEFINE_STATIC_LOCAL(const AtomicString, headerName, ("date"));
424         m_date = parseDateValueInHeader(m_httpHeaderFields, headerName);
425         m_haveParsedDateHeader = true;
426     }
427     return m_date;
428 }
429
430 double ResourceResponseBase::age() const
431 {
432     lazyInit(CommonFieldsOnly);
433
434     if (!m_haveParsedAgeHeader) {
435         DEFINE_STATIC_LOCAL(const AtomicString, headerName, ("age"));
436         String headerValue = m_httpHeaderFields.get(headerName);
437         bool ok;
438         m_age = headerValue.toDouble(&ok);
439         if (!ok)
440             m_age = std::numeric_limits<double>::quiet_NaN();
441         m_haveParsedAgeHeader = true;
442     }
443     return m_age;
444 }
445
446 double ResourceResponseBase::expires() const
447 {
448     lazyInit(CommonFieldsOnly);
449
450     if (!m_haveParsedExpiresHeader) {
451         DEFINE_STATIC_LOCAL(const AtomicString, headerName, ("expires"));
452         m_expires = parseDateValueInHeader(m_httpHeaderFields, headerName);
453         m_haveParsedExpiresHeader = true;
454     }
455     return m_expires;
456 }
457
458 double ResourceResponseBase::lastModified() const
459 {
460     lazyInit(CommonFieldsOnly);
461
462     if (!m_haveParsedLastModifiedHeader) {
463         DEFINE_STATIC_LOCAL(const AtomicString, headerName, ("last-modified"));
464         m_lastModified = parseDateValueInHeader(m_httpHeaderFields, headerName);
465         m_haveParsedLastModifiedHeader = true;
466     }
467     return m_lastModified;
468 }
469
470 bool ResourceResponseBase::isAttachment() const
471 {
472     lazyInit(CommonAndUncommonFields);
473
474     DEFINE_STATIC_LOCAL(const AtomicString, headerName, ("content-disposition"));
475     String value = m_httpHeaderFields.get(headerName);
476     size_t loc = value.find(';');
477     if (loc != notFound)
478         value = value.left(loc);
479     value = value.stripWhiteSpace();
480     DEFINE_STATIC_LOCAL(const AtomicString, attachmentString, ("attachment"));
481     return equalIgnoringCase(value, attachmentString);
482 }
483   
484 void ResourceResponseBase::setLastModifiedDate(time_t lastModifiedDate)
485 {
486     lazyInit(CommonAndUncommonFields);
487
488     m_lastModifiedDate = lastModifiedDate;
489 }
490
491 time_t ResourceResponseBase::lastModifiedDate() const
492 {
493     lazyInit(CommonAndUncommonFields);
494
495     return m_lastModifiedDate;
496 }
497
498 bool ResourceResponseBase::wasCached() const
499 {
500     lazyInit(CommonAndUncommonFields);
501
502     return m_wasCached;
503 }
504
505 void ResourceResponseBase::setWasCached(bool value)
506 {
507     m_wasCached = value;
508 }
509
510 bool ResourceResponseBase::connectionReused() const
511 {
512     lazyInit(CommonAndUncommonFields);
513
514     return m_connectionReused;
515 }
516
517 void ResourceResponseBase::setConnectionReused(bool connectionReused)
518 {
519     lazyInit(CommonAndUncommonFields);
520
521     m_connectionReused = connectionReused;
522 }
523
524 unsigned ResourceResponseBase::connectionID() const
525 {
526     lazyInit(CommonAndUncommonFields);
527
528     return m_connectionID;
529 }
530
531 void ResourceResponseBase::setConnectionID(unsigned connectionID)
532 {
533     lazyInit(CommonAndUncommonFields);
534
535     m_connectionID = connectionID;
536 }
537
538 ResourceLoadTiming* ResourceResponseBase::resourceLoadTiming() const
539 {
540     lazyInit(CommonAndUncommonFields);
541
542     return m_resourceLoadTiming.get();
543 }
544
545 void ResourceResponseBase::setResourceLoadTiming(PassRefPtr<ResourceLoadTiming> resourceLoadTiming)
546 {
547     lazyInit(CommonAndUncommonFields);
548
549     m_resourceLoadTiming = resourceLoadTiming;
550 }
551
552 PassRefPtr<ResourceLoadInfo> ResourceResponseBase::resourceLoadInfo() const
553 {
554     lazyInit(CommonAndUncommonFields);
555
556     return m_resourceLoadInfo.get();
557 }
558
559 void ResourceResponseBase::setResourceLoadInfo(PassRefPtr<ResourceLoadInfo> loadInfo)
560 {
561     lazyInit(CommonAndUncommonFields);
562
563     m_resourceLoadInfo = loadInfo;
564 }
565
566 void ResourceResponseBase::lazyInit(InitLevel initLevel) const
567 {
568     const_cast<ResourceResponse*>(static_cast<const ResourceResponse*>(this))->platformLazyInit(initLevel);
569 }
570
571 void ResourceResponseBase::reportMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const
572 {
573     MemoryClassInfo info(memoryObjectInfo, this, PlatformMemoryTypes::Loader);
574     info.addMember(m_url);
575     info.addMember(m_mimeType);
576     info.addMember(m_textEncodingName);
577     info.addMember(m_suggestedFilename);
578     info.addMember(m_httpStatusText);
579     info.addHashMap(m_httpHeaderFields);
580     info.addInstrumentedMapEntries(m_httpHeaderFields);
581     info.addMember(m_resourceLoadTiming);
582     info.addMember(m_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 }