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