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