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
CacheResult(disk_cache::Entry * entry,String url)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
~CacheResult()61 CacheResult::~CacheResult()
62 {
63 m_entry->Close();
64 // TODO: Should we also call DoneReadingFromEntry() on the cache for our
65 // entry?
66 }
67
contentSize() const68 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
firstResponseHeader(const char * name,String * result,bool allowEmptyString) const76 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
mimeType() const86 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
expires() const96 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
responseCode() const122 int CacheResult::responseCode() const
123 {
124 return responseHeaders() ? responseHeaders()->response_code() : 0;
125 }
126
writeToFile(const String & filePath) const127 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
writeToFileImpl()148 void CacheResult::writeToFileImpl()
149 {
150 m_bufferSize = m_entry->GetDataSize(kResponseContentIndex);
151 m_readOffset = 0;
152 m_wasWriteToFileSuccessful = false;
153 readNextChunk();
154 }
155
readNextChunk()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
onReadNextChunkDone(int size)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
writeChunkToFile()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
onWriteToFileDone()197 void CacheResult::onWriteToFileDone()
198 {
199 MutexLocker lock(m_mutex);
200 m_isAsyncOperationInProgress = false;
201 m_condition.signal();
202 }
203
responseHeaders() const204 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
responseHeadersImpl()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
onResponseHeadersDone(int size)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