Home | History | Annotate | Download | only in WebCoreSupport
      1 /*
      2  * Copyright 2011, The Android Open Source Project
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  *  * Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  *  * Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
     14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 #include "config.h"
     27 #include "CacheResult.h"
     28 
     29 #include "WebResponse.h"
     30 #include "WebUrlLoaderClient.h"
     31 #include <platform/FileSystem.h>
     32 #include <wtf/text/CString.h>
     33 
     34 using namespace base;
     35 using namespace disk_cache;
     36 using namespace net;
     37 using namespace std;
     38 
     39 namespace android {
     40 
     41 // All public methods are called on a UI thread but we do work on the
     42 // Chromium thread. However, because we block the WebCore thread while this
     43 // work completes, we can never receive new public method calls while the
     44 // Chromium thread work is in progress.
     45 
     46 // Copied from HttpCache
     47 enum {
     48     kResponseInfoIndex = 0,
     49     kResponseContentIndex
     50 };
     51 
     52 CacheResult::CacheResult(disk_cache::Entry* entry, String url)
     53     : m_entry(entry)
     54     , m_onResponseHeadersDoneCallback(this, &CacheResult::onResponseHeadersDone)
     55     , m_onReadNextChunkDoneCallback(this, &CacheResult::onReadNextChunkDone)
     56     , m_url(url)
     57 {
     58     ASSERT(m_entry);
     59 }
     60 
     61 CacheResult::~CacheResult()
     62 {
     63     m_entry->Close();
     64     // TODO: Should we also call DoneReadingFromEntry() on the cache for our
     65     // entry?
     66 }
     67 
     68 int64 CacheResult::contentSize() const
     69 {
     70     // The android stack does not take the content length from the HTTP response
     71     // headers but calculates it when writing the content to disk. It can never
     72     // overflow a long because we limit the cache size.
     73     return m_entry->GetDataSize(kResponseContentIndex);
     74 }
     75 
     76 bool CacheResult::firstResponseHeader(const char* name, String* result, bool allowEmptyString) const
     77 {
     78     string value;
     79     if (responseHeaders() && responseHeaders()->EnumerateHeader(NULL, name, &value) && (!value.empty() || allowEmptyString)) {
     80         *result = String(value.c_str());
     81         return true;
     82     }
     83     return false;
     84 }
     85 
     86 String CacheResult::mimeType() const
     87 {
     88     string mimeType;
     89     if (responseHeaders())
     90         responseHeaders()->GetMimeType(&mimeType);
     91     if (!mimeType.length() && m_url.length())
     92         mimeType = WebResponse::resolveMimeType(std::string(m_url.utf8().data(), m_url.length()), "");
     93     return String(mimeType.c_str());
     94 }
     95 
     96 int64 CacheResult::expires() const
     97 {
     98      // We have to do this manually, rather than using HttpResponseHeaders::GetExpiresValue(),
     99      // to handle the "-1" and "0" special cases.
    100      string expiresString;
    101      if (responseHeaders() && responseHeaders()->EnumerateHeader(NULL, "expires", &expiresString)) {
    102          wstring expiresStringWide(expiresString.begin(), expiresString.end());  // inflate ascii
    103          // We require the time expressed as ms since the epoch.
    104          Time time;
    105          if (Time::FromString(expiresStringWide.c_str(), &time)) {
    106              // Will not overflow for a very long time!
    107              return static_cast<int64>(1000.0 * time.ToDoubleT());
    108          }
    109 
    110          if (expiresString == "-1" || expiresString == "0")
    111              return 0;
    112      }
    113 
    114      // TODO
    115      // The Android stack applies a heuristic to set an expiry date if the
    116      // expires header is not set or can't be parsed. I'm  not sure whether the Chromium cache
    117      // does this, and if so, it may not be possible for us to get hold of it
    118      // anyway to set it on the result.
    119      return -1;
    120 }
    121 
    122 int CacheResult::responseCode() const
    123 {
    124     return responseHeaders() ? responseHeaders()->response_code() : 0;
    125 }
    126 
    127 bool CacheResult::writeToFile(const String& filePath) const
    128 {
    129     // Getting the headers is potentially async, so post to the Chromium thread
    130     // and block here.
    131     MutexLocker lock(m_mutex);
    132 
    133     base::Thread* thread = WebUrlLoaderClient::ioThread();
    134     if (!thread)
    135         return false;
    136 
    137     m_filePath = filePath.threadsafeCopy();
    138     m_isAsyncOperationInProgress = true;
    139 
    140     thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(const_cast<CacheResult*>(this), &CacheResult::writeToFileImpl));
    141 
    142     while (m_isAsyncOperationInProgress)
    143         m_condition.wait(m_mutex);
    144 
    145     return m_wasWriteToFileSuccessful;
    146 }
    147 
    148 void CacheResult::writeToFileImpl()
    149 {
    150     m_bufferSize = m_entry->GetDataSize(kResponseContentIndex);
    151     m_readOffset = 0;
    152     m_wasWriteToFileSuccessful = false;
    153     readNextChunk();
    154 }
    155 
    156 void CacheResult::readNextChunk()
    157 {
    158     m_buffer = new IOBuffer(m_bufferSize);
    159     int rv = m_entry->ReadData(kResponseInfoIndex, m_readOffset, m_buffer, m_bufferSize, &m_onReadNextChunkDoneCallback);
    160     if (rv == ERR_IO_PENDING)
    161         return;
    162 
    163     onReadNextChunkDone(rv);
    164 };
    165 
    166 void CacheResult::onReadNextChunkDone(int size)
    167 {
    168     if (size > 0) {
    169         // Still more reading to be done.
    170         if (writeChunkToFile()) {
    171             // TODO: I assume that we need to clear and resize the buffer for the next read?
    172             m_readOffset += size;
    173             m_bufferSize -= size;
    174             readNextChunk();
    175         } else
    176             onWriteToFileDone();
    177         return;
    178     }
    179 
    180     if (!size) {
    181         // Reached end of file.
    182         if (writeChunkToFile())
    183             m_wasWriteToFileSuccessful = true;
    184     }
    185     onWriteToFileDone();
    186 }
    187 
    188 bool CacheResult::writeChunkToFile()
    189 {
    190     PlatformFileHandle file;
    191     file = openFile(m_filePath, OpenForWrite);
    192     if (!isHandleValid(file))
    193         return false;
    194     return WebCore::writeToFile(file, m_buffer->data(), m_bufferSize) == m_bufferSize;
    195 }
    196 
    197 void CacheResult::onWriteToFileDone()
    198 {
    199     MutexLocker lock(m_mutex);
    200     m_isAsyncOperationInProgress = false;
    201     m_condition.signal();
    202 }
    203 
    204 HttpResponseHeaders* CacheResult::responseHeaders() const
    205 {
    206     MutexLocker lock(m_mutex);
    207     if (m_responseHeaders)
    208         return m_responseHeaders;
    209 
    210     // Getting the headers is potentially async, so post to the Chromium thread
    211     // and block here.
    212     base::Thread* thread = WebUrlLoaderClient::ioThread();
    213     if (!thread)
    214         return 0;
    215 
    216     m_isAsyncOperationInProgress = true;
    217     thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(const_cast<CacheResult*>(this), &CacheResult::responseHeadersImpl));
    218 
    219     while (m_isAsyncOperationInProgress)
    220         m_condition.wait(m_mutex);
    221 
    222     return m_responseHeaders;
    223 }
    224 
    225 void CacheResult::responseHeadersImpl()
    226 {
    227     m_bufferSize = m_entry->GetDataSize(kResponseInfoIndex);
    228     m_buffer = new IOBuffer(m_bufferSize);
    229 
    230     int rv = m_entry->ReadData(kResponseInfoIndex, 0, m_buffer, m_bufferSize, &m_onResponseHeadersDoneCallback);
    231     if (rv == ERR_IO_PENDING)
    232         return;
    233 
    234     onResponseHeadersDone(rv);
    235 };
    236 
    237 void CacheResult::onResponseHeadersDone(int size)
    238 {
    239     MutexLocker lock(m_mutex);
    240     // It's OK to throw away the HttpResponseInfo object as we hold our own ref
    241     // to the headers.
    242     HttpResponseInfo response;
    243     bool truncated = false; // TODO: Waht is this param for?
    244     if (size == m_bufferSize && HttpCache::ParseResponseInfo(m_buffer->data(), m_bufferSize, &response, &truncated))
    245         m_responseHeaders = response.headers;
    246     m_isAsyncOperationInProgress = false;
    247     m_condition.signal();
    248 }
    249 
    250 } // namespace android
    251