• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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