1/*
2 * Copyright 2006, 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#define LOG_TAG "webcoreglue"
27
28#include "config.h"
29#include "WebCoreResourceLoader.h"
30
31#include "CString.h"
32#include "ResourceError.h"
33#include "ResourceHandle.h"
34#include "ResourceHandleClient.h"
35#include "ResourceHandleInternal.h"
36#include "ResourceResponse.h"
37#include "SkUtils.h"
38#ifdef ANDROID_INSTRUMENT
39#include "TimeCounter.h"
40#endif
41#include "WebCoreJni.h"
42
43#include <JNIHelp.h>
44#include <JNIUtility.h>
45#include <SkTypes.h>
46#include <stdlib.h>
47#include <utils/misc.h>
48#include <wtf/Platform.h>
49
50namespace android {
51
52// ----------------------------------------------------------------------------
53
54static struct resourceloader_t {
55    jfieldID    mObject;
56    jmethodID   mCancelMethodID;
57    jmethodID   mDownloadFileMethodID;
58    jmethodID   mWillLoadFromCacheMethodID;
59    jmethodID   mPauseLoadMethodID;
60} gResourceLoader;
61
62// ----------------------------------------------------------------------------
63
64#define GET_NATIVE_HANDLE(env, obj) ((WebCore::ResourceHandle*)env->GetIntField(obj, gResourceLoader.mObject))
65#define SET_NATIVE_HANDLE(env, obj, handle) (env->SetIntField(obj, gResourceLoader.mObject, handle))
66
67//-----------------------------------------------------------------------------
68// ResourceLoadHandler
69
70PassRefPtr<WebCore::ResourceLoaderAndroid> WebCoreResourceLoader::create(JNIEnv *env, jobject jLoadListener)
71{
72    return adoptRef<WebCore::ResourceLoaderAndroid>(new WebCoreResourceLoader(env, jLoadListener));
73}
74
75WebCoreResourceLoader::WebCoreResourceLoader(JNIEnv *env, jobject jLoadListener)
76    : mPausedLoad(false)
77{
78    mJLoader = env->NewGlobalRef(jLoadListener);
79}
80
81WebCoreResourceLoader::~WebCoreResourceLoader()
82{
83    JNIEnv* env = JSC::Bindings::getJNIEnv();
84    SET_NATIVE_HANDLE(env, mJLoader, 0);
85    env->DeleteGlobalRef(mJLoader);
86    mJLoader = 0;
87}
88
89void WebCoreResourceLoader::cancel()
90{
91    JNIEnv* env = JSC::Bindings::getJNIEnv();
92    env->CallVoidMethod(mJLoader, gResourceLoader.mCancelMethodID);
93    checkException(env);
94}
95
96void WebCoreResourceLoader::downloadFile()
97{
98    JNIEnv* env = JSC::Bindings::getJNIEnv();
99    env->CallVoidMethod(mJLoader, gResourceLoader.mDownloadFileMethodID);
100    checkException(env);
101}
102
103void WebCoreResourceLoader::pauseLoad(bool pause)
104{
105    if (mPausedLoad == pause)
106        return;
107
108    mPausedLoad = pause;
109    JNIEnv* env = JSC::Bindings::getJNIEnv();
110    env->CallVoidMethod(mJLoader, gResourceLoader.mPauseLoadMethodID, pause);
111    checkException(env);
112}
113
114/*
115* This static method is called to check to see if a POST response is in
116* the cache. This may be slow, but is only used during a navigation to
117* a POST response.
118*/
119bool WebCoreResourceLoader::willLoadFromCache(const WebCore::KURL& url, int64_t identifier)
120{
121    JNIEnv* env = JSC::Bindings::getJNIEnv();
122    WebCore::String urlStr = url.string();
123    jstring jUrlStr = env->NewString(urlStr.characters(), urlStr.length());
124    jclass resourceLoader = env->FindClass("android/webkit/LoadListener");
125    bool val = env->CallStaticBooleanMethod(resourceLoader,
126            gResourceLoader.mWillLoadFromCacheMethodID, jUrlStr, identifier);
127    checkException(env);
128    env->DeleteLocalRef(jUrlStr);
129
130    return val;
131}
132
133// ----------------------------------------------------------------------------
134void WebCoreResourceLoader::SetResponseHeader(JNIEnv* env, jobject obj, jint nativeResponse, jstring key, jstring val)
135{
136#ifdef ANDROID_INSTRUMENT
137    TimeCounterAuto counter(TimeCounter::ResourceTimeCounter);
138#endif
139
140    WebCore::ResourceResponse* response = (WebCore::ResourceResponse*)nativeResponse;
141    LOG_ASSERT(response, "nativeSetResponseHeader must take a valid response pointer!");
142
143    LOG_ASSERT(key, "How did a null value become a key?");
144    if (val) {
145        WebCore::String valStr = to_string(env, val);
146        if (!valStr.isEmpty())
147            response->setHTTPHeaderField(to_string(env, key), valStr);
148    }
149}
150
151jint WebCoreResourceLoader::CreateResponse(JNIEnv* env, jobject obj, jstring url, jint statusCode,
152                                                    jstring statusText, jstring mimeType, jlong expectedLength,
153                                                    jstring encoding)
154{
155#ifdef ANDROID_INSTRUMENT
156    TimeCounterAuto counter(TimeCounter::ResourceTimeCounter);
157#endif
158    LOG_ASSERT(url, "Must have a url in the response!");
159    WebCore::KURL kurl(WebCore::ParsedURLString, to_string(env, url));
160    WebCore::String encodingStr;
161    WebCore::String mimeTypeStr;
162    if (mimeType) {
163        mimeTypeStr = to_string(env, mimeType);
164        LOGV("Response setMIMEType: %s", mimeTypeStr.latin1().data());
165    }
166    if (encoding) {
167        encodingStr = to_string(env, encoding);
168        LOGV("Response setTextEncodingName: %s", encodingStr.latin1().data());
169    }
170    WebCore::ResourceResponse* response = new WebCore::ResourceResponse(
171            kurl, mimeTypeStr, (long long)expectedLength,
172            encodingStr, WebCore::String());
173    response->setHTTPStatusCode(statusCode);
174    if (statusText) {
175        WebCore::String status = to_string(env, statusText);
176        response->setHTTPStatusText(status);
177        LOGV("Response setStatusText: %s", status.latin1().data());
178    }
179    return (int)response;
180}
181
182void WebCoreResourceLoader::ReceivedResponse(JNIEnv* env, jobject obj, jint nativeResponse)
183{
184#ifdef ANDROID_INSTRUMENT
185    TimeCounterAuto counter(TimeCounter::ResourceTimeCounter);
186#endif
187    WebCore::ResourceHandle* handle = GET_NATIVE_HANDLE(env, obj);
188    LOG_ASSERT(handle, "nativeReceivedResponse must take a valid handle!");
189    // ResourceLoader::didFail() can set handle to be NULL, we need to check
190    if (!handle)
191        return;
192
193    WebCore::ResourceResponse* response = (WebCore::ResourceResponse*)nativeResponse;
194    LOG_ASSERT(response, "nativeReceivedResponse must take a valid resource pointer!");
195    handle->client()->didReceiveResponse(handle, *response);
196    // As the client makes a copy of the response, delete it here.
197    delete response;
198}
199
200void WebCoreResourceLoader::AddData(JNIEnv* env, jobject obj, jbyteArray dataArray, jint length)
201{
202#ifdef ANDROID_INSTRUMENT
203    TimeCounterAuto counter(TimeCounter::ResourceTimeCounter);
204#endif
205    LOGV("webcore_resourceloader data(%d)", length);
206
207    WebCore::ResourceHandle* handle = GET_NATIVE_HANDLE(env, obj);
208    LOG_ASSERT(handle, "nativeAddData must take a valid handle!");
209    // ResourceLoader::didFail() can set handle to be NULL, we need to check
210    if (!handle)
211        return;
212
213    SkAutoMemoryUsageProbe  mup("android_webcore_resourceloader_nativeAddData");
214
215    bool result = false;
216    jbyte * data =  env->GetByteArrayElements(dataArray, NULL);
217
218    LOG_ASSERT(handle->client(), "Why do we not have a client?");
219    handle->client()->didReceiveData(handle, (const char *)data, length, length);
220    env->ReleaseByteArrayElements(dataArray, data, JNI_ABORT);
221}
222
223void WebCoreResourceLoader::Finished(JNIEnv* env, jobject obj)
224{
225#ifdef ANDROID_INSTRUMENT
226    TimeCounterAuto counter(TimeCounter::ResourceTimeCounter);
227#endif
228    LOGV("webcore_resourceloader finished");
229    WebCore::ResourceHandle* handle = GET_NATIVE_HANDLE(env, obj);
230    LOG_ASSERT(handle, "nativeFinished must take a valid handle!");
231    // ResourceLoader::didFail() can set handle to be NULL, we need to check
232    if (!handle)
233        return;
234
235    LOG_ASSERT(handle->client(), "Why do we not have a client?");
236    handle->client()->didFinishLoading(handle);
237}
238
239jstring WebCoreResourceLoader::RedirectedToUrl(JNIEnv* env, jobject obj,
240        jstring baseUrl, jstring redirectTo, jint nativeResponse)
241{
242#ifdef ANDROID_INSTRUMENT
243    TimeCounterAuto counter(TimeCounter::ResourceTimeCounter);
244#endif
245    LOGV("webcore_resourceloader redirectedToUrl");
246    WebCore::ResourceHandle* handle = GET_NATIVE_HANDLE(env, obj);
247    LOG_ASSERT(handle, "nativeRedirectedToUrl must take a valid handle!");
248    // ResourceLoader::didFail() can set handle to be NULL, we need to check
249    if (!handle)
250        return NULL;
251
252    LOG_ASSERT(handle->client(), "Why do we not have a client?");
253    WebCore::ResourceRequest r = handle->request();
254    WebCore::KURL url(WebCore::KURL(WebCore::ParsedURLString, to_string(env, baseUrl)),
255            to_string(env, redirectTo));
256    WebCore::ResourceResponse* response = (WebCore::ResourceResponse*)nativeResponse;
257    // If the url fails to resolve the relative path, return null.
258    if (url.protocol().isEmpty()) {
259        delete response;
260        return NULL;
261    } else {
262        // Ensure the protocol is lowercase.
263        url.setProtocol(url.protocol().lower());
264    }
265    // Set the url after updating the protocol.
266    r.setURL(url);
267    if (r.httpMethod() == "POST") {
268        r.setHTTPMethod("GET");
269        r.clearHTTPReferrer();
270        r.setHTTPBody(0);
271        r.setHTTPContentType("");
272    }
273    handle->client()->willSendRequest(handle, r, *response);
274    delete response;
275    WebCore::String s = url.string();
276    return env->NewString((unsigned short*)s.characters(), s.length());
277}
278
279void WebCoreResourceLoader::Error(JNIEnv* env, jobject obj, jint id, jstring description,
280        jstring failingUrl)
281{
282#ifdef ANDROID_INSTRUMENT
283    TimeCounterAuto counter(TimeCounter::ResourceTimeCounter);
284#endif
285    LOGV("webcore_resourceloader error");
286    WebCore::ResourceHandle* handle = GET_NATIVE_HANDLE(env, obj);
287    LOG_ASSERT(handle, "nativeError must take a valid handle!");
288    // ResourceLoader::didFail() can set handle to be NULL, we need to check
289    if (!handle)
290        return;
291
292    handle->client()->didFail(handle, WebCore::ResourceError("", id,
293                to_string(env, failingUrl), to_string(env, description)));
294}
295
296// ----------------------------------------------------------------------------
297
298/*
299 * JNI registration.
300 */
301static JNINativeMethod gResourceloaderMethods[] = {
302    /* name, signature, funcPtr */
303    { "nativeSetResponseHeader", "(ILjava/lang/String;Ljava/lang/String;)V",
304        (void*) WebCoreResourceLoader::SetResponseHeader },
305    { "nativeCreateResponse", "(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;JLjava/lang/String;)I",
306        (void*) WebCoreResourceLoader::CreateResponse },
307    { "nativeReceivedResponse", "(I)V",
308        (void*) WebCoreResourceLoader::ReceivedResponse },
309    { "nativeAddData", "([BI)V",
310        (void*) WebCoreResourceLoader::AddData },
311    { "nativeFinished", "()V",
312        (void*) WebCoreResourceLoader::Finished },
313    { "nativeRedirectedToUrl", "(Ljava/lang/String;Ljava/lang/String;I)Ljava/lang/String;",
314        (void*) WebCoreResourceLoader::RedirectedToUrl },
315    { "nativeError", "(ILjava/lang/String;Ljava/lang/String;)V",
316        (void*) WebCoreResourceLoader::Error }
317};
318
319int register_resource_loader(JNIEnv* env)
320{
321    jclass resourceLoader = env->FindClass("android/webkit/LoadListener");
322    LOG_FATAL_IF(resourceLoader == NULL,
323        "Unable to find class android/webkit/LoadListener");
324
325    gResourceLoader.mObject =
326        env->GetFieldID(resourceLoader, "mNativeLoader", "I");
327    LOG_FATAL_IF(gResourceLoader.mObject == NULL,
328        "Unable to find android/webkit/LoadListener.mNativeLoader");
329
330    gResourceLoader.mCancelMethodID =
331        env->GetMethodID(resourceLoader, "cancel", "()V");
332    LOG_FATAL_IF(gResourceLoader.mCancelMethodID == NULL,
333        "Could not find method cancel on LoadListener");
334
335    gResourceLoader.mDownloadFileMethodID =
336        env->GetMethodID(resourceLoader, "downloadFile", "()V");
337    LOG_FATAL_IF(gResourceLoader.mDownloadFileMethodID == NULL,
338        "Could not find method downloadFile on LoadListener");
339
340    gResourceLoader.mPauseLoadMethodID =
341        env->GetMethodID(resourceLoader, "pauseLoad", "(Z)V");
342    LOG_FATAL_IF(gResourceLoader.mPauseLoadMethodID == NULL,
343        "Could not find method pauseLoad on LoadListener");
344
345    gResourceLoader.mWillLoadFromCacheMethodID =
346        env->GetStaticMethodID(resourceLoader, "willLoadFromCache", "(Ljava/lang/String;J)Z");
347    LOG_FATAL_IF(gResourceLoader.mWillLoadFromCacheMethodID == NULL,
348        "Could not find static method willLoadFromCache on LoadListener");
349
350    return jniRegisterNativeMethods(env, "android/webkit/LoadListener",
351                     gResourceloaderMethods, NELEM(gResourceloaderMethods));
352}
353
354} /* namespace android */
355