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 34using namespace base; 35using namespace disk_cache; 36using namespace net; 37using namespace std; 38 39namespace 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 47enum { 48 kResponseInfoIndex = 0, 49 kResponseContentIndex 50}; 51 52CacheResult::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 61CacheResult::~CacheResult() 62{ 63 m_entry->Close(); 64 // TODO: Should we also call DoneReadingFromEntry() on the cache for our 65 // entry? 66} 67 68int64 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 76bool 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 86String 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 96int64 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 122int CacheResult::responseCode() const 123{ 124 return responseHeaders() ? responseHeaders()->response_code() : 0; 125} 126 127bool 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 148void CacheResult::writeToFileImpl() 149{ 150 m_bufferSize = m_entry->GetDataSize(kResponseContentIndex); 151 m_readOffset = 0; 152 m_wasWriteToFileSuccessful = false; 153 readNextChunk(); 154} 155 156void 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 166void 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 188bool 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 197void CacheResult::onWriteToFileDone() 198{ 199 MutexLocker lock(m_mutex); 200 m_isAsyncOperationInProgress = false; 201 m_condition.signal(); 202} 203 204HttpResponseHeaders* 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 225void 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 237void 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