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