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