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