| Index: jni/AndroidWebRequest.cpp |
| =================================================================== |
| new file mode 100644 |
| --- /dev/null |
| +++ b/jni/AndroidWebRequest.cpp |
| @@ -0,0 +1,292 @@ |
| +/* |
| + * This file is part of Adblock Plus <http://adblockplus.org/>, |
| + * Copyright (C) 2006-2013 Eyeo GmbH |
| + * |
| + * Adblock Plus is free software: you can redistribute it and/or modify |
| + * it under the terms of the GNU General Public License version 3 as |
| + * published by the Free Software Foundation. |
| + * |
| + * Adblock Plus is distributed in the hope that it will be useful, |
| + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| + * GNU General Public License for more details. |
| + * |
| + * You should have received a copy of the GNU General Public License |
| + * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. |
| + */ |
| + |
| +#include <AdblockPlus.h> |
| +#include "AndroidWebRequest.h" |
| +#include "Utils.h" |
| +#include "debug.h" |
| + |
| +struct JavaException : public std::exception |
| +{ |
| + jthrowable ex; |
| + |
| + JavaException(jthrowable e) : ex(e) |
| + { |
| + } |
| + |
| + const char* what() const throw() |
| + { |
| + return "Java Exception"; |
|
Wladimir Palant
2013/09/12 11:31:14
I think that this should return the actual excepti
|
| + } |
| +}; |
| + |
| +AndroidWebRequest::AndroidWebRequest(JavaVM*& gJvm) : globalJvm(gJvm) |
|
Felix Dahlke
2013/09/13 14:40:39
You can actually call the parameter globalJvm as w
|
| +{ |
| + JNIEnv* jniEnv = NULL; |
| + int stat = globalJvm->GetEnv((void **)&jniEnv, JNI_VERSION_1_6); |
| + if (stat == JNI_EDETACHED) |
| + { |
| + if (globalJvm->AttachCurrentThread(&jniEnv, NULL) != 0) |
| + throw std::runtime_error("Failed to get JNI environment"); |
| + } |
|
Wladimir Palant
2013/09/12 11:31:14
Seeing that this block of code is a recurring patt
|
| + |
| + jUrlClass = reinterpret_cast<jclass>(jniEnv->NewGlobalRef(jniEnv->FindClass("java/net/URL"))); |
| + jUrlConstructorID = jniEnv->GetMethodID(jUrlClass, "<init>", "(Ljava/lang/String;)V"); |
| + jUrlOpenConnectionID = jniEnv->GetMethodID(jUrlClass, "openConnection", "()Ljava/net/URLConnection;"); |
| + jConnectionClass = reinterpret_cast<jclass>(jniEnv->NewGlobalRef(jniEnv->FindClass("java/net/HttpURLConnection"))); |
| + jConnectionSetMethodID = jniEnv->GetMethodID(jConnectionClass, "setRequestMethod", "(Ljava/lang/String;)V"); |
| + jConnectionSetRequestPropertyID = jniEnv->GetMethodID(jConnectionClass, "setRequestProperty", "(Ljava/lang/String;Ljava/lang/String;)V"); |
| + jConnectionConnectID = jniEnv->GetMethodID(jConnectionClass, "connect", "()V"); |
| + jConnectionGetContentLengthID = jniEnv->GetMethodID(jConnectionClass, "getContentLength", "()I"); |
| + jConnectionGetResponseCodeID = jniEnv->GetMethodID(jConnectionClass, "getResponseCode", "()I"); |
| + jConnectionGetContentEncodingID = jniEnv->GetMethodID(jConnectionClass, "getContentEncoding", "()Ljava/lang/String;"); |
| + jConnectionGetInputStreamID = jniEnv->GetMethodID(jConnectionClass, "getInputStream", "()Ljava/io/InputStream;"); |
| + jInputStreamReaderClass = reinterpret_cast<jclass>(jniEnv->NewGlobalRef(jniEnv->FindClass("java/io/InputStreamReader"))); |
| + jInputStreamReaderConstructorID = jniEnv->GetMethodID(jInputStreamReaderClass, "<init>", "(Ljava/io/InputStream;Ljava/lang/String;)V"); |
| + jBufferedReaderClass = reinterpret_cast<jclass>(jniEnv->NewGlobalRef(jniEnv->FindClass("java/io/BufferedReader"))); |
| + jBufferedReaderConstructorID = jniEnv->GetMethodID(jBufferedReaderClass, "<init>", "(Ljava/io/Reader;)V"); |
| + jStringBuilderClass = reinterpret_cast<jclass>(jniEnv->NewGlobalRef(jniEnv->FindClass("java/lang/StringBuilder"))); |
| + jStringBuilderConstructorID = jniEnv->GetMethodID(jStringBuilderClass, "<init>", "()V"); |
| + jBufferedReaderReadID = jniEnv->GetMethodID(jBufferedReaderClass, "read", "([CII)I"); |
| + jStringBuilderAppendID = jniEnv->GetMethodID(jStringBuilderClass, "append", "([CII)Ljava/lang/StringBuilder;"); |
| + jStringBuilderToStringID = jniEnv->GetMethodID(jStringBuilderClass, "toString", "()Ljava/lang/String;"); |
| + jBufferedReaderCloseID = jniEnv->GetMethodID(jBufferedReaderClass, "close", "()V"); |
| + jConnectionGetHeaderFieldKeyID = jniEnv->GetMethodID(jConnectionClass, "getHeaderField", "(I)Ljava/lang/String;"); |
| + jConnectionGetHeaderFieldID = jniEnv->GetMethodID(jConnectionClass, "getHeaderFieldKey", "(I)Ljava/lang/String;"); |
| + |
| + if (stat == JNI_EDETACHED) |
| + globalJvm->DetachCurrentThread(); |
| +} |
| + |
| +AndroidWebRequest::~AndroidWebRequest() |
| +{ |
| + JNIEnv* jniEnv = NULL; |
| + int stat = globalJvm->GetEnv((void **)&jniEnv, JNI_VERSION_1_6); |
| + if (stat == JNI_EDETACHED) |
| + { |
| + if (globalJvm->AttachCurrentThread(&jniEnv, NULL) != 0) |
| + throw std::runtime_error("Failed to get JNI environment"); |
| + } |
| + |
| + jniEnv->DeleteGlobalRef(jUrlClass); |
| + jniEnv->DeleteGlobalRef(jConnectionClass); |
| + jniEnv->DeleteGlobalRef(jInputStreamReaderClass); |
| + jniEnv->DeleteGlobalRef(jBufferedReaderClass); |
| + jniEnv->DeleteGlobalRef(jStringBuilderClass); |
| + |
| + if (stat == JNI_EDETACHED) |
| + globalJvm->DetachCurrentThread(); |
| +} |
| + |
| +AdblockPlus::ServerResponse AndroidWebRequest::GET( |
| + const std::string& url, const AdblockPlus::HeaderList& requestHeaders) const |
| +{ |
| + JNIEnv* jniEnv = NULL; |
| + int stat = globalJvm->GetEnv((void **)&jniEnv, JNI_VERSION_1_6); |
| + if (stat == JNI_EDETACHED) |
| + { |
| + if (globalJvm->AttachCurrentThread(&jniEnv, NULL) != 0) |
| + throw std::runtime_error("Failed to get JNI environment"); |
| + } |
| + |
| + AdblockPlus::ServerResponse result; |
| + result.status = NS_ERROR_FAILURE; |
| + result.responseStatus = 0; |
| + |
| + try |
| + { |
| + // URL jUrl = new URL(url) |
|
Felix Dahlke
2013/09/13 14:40:39
Using JNI for all this is a bit cumbersome, I thin
|
| + jstring jUrlStr = jniEnv->NewStringUTF(url.c_str()); |
| + |
| + jobject jUrl = jniEnv->NewObject(jUrlClass, jUrlConstructorID, jUrlStr); |
| + if (jniEnv->ExceptionCheck()) |
| + throw JavaException(jniEnv->ExceptionOccurred()); |
|
Wladimir Palant
2013/09/12 11:31:14
Seeing this block of code repeated all over the pl
Felix Dahlke
2013/09/13 14:40:39
I'd vote for HandleExceptions();
I also think we
|
| + jniEnv->DeleteLocalRef(jUrlStr); |
|
Felix Dahlke
2013/09/13 14:40:39
DeleteLocalRef shouldn't be necessary here. It'll
|
| + |
| + // HttpURLConnection connection = (HttpURLConnection) jUrl.openConnection(); |
| + jobject jConnection = jniEnv->CallObjectMethod(jUrl, jUrlOpenConnectionID); |
| + if (jniEnv->ExceptionCheck()) |
| + throw JavaException(jniEnv->ExceptionOccurred()); |
| + |
| + // connection.setRequestMethod("GET"); |
| + jstring jMethod = jniEnv->NewStringUTF("GET"); |
| + jniEnv->CallVoidMethod(jConnection, jConnectionSetMethodID, jMethod); |
| + if (jniEnv->ExceptionCheck()) |
| + throw JavaException(jniEnv->ExceptionOccurred()); |
| + jniEnv->DeleteLocalRef(jMethod); |
| + |
| + for (int i = 0; i < requestHeaders.size(); i++) |
| + { |
| + // connection.setRequestProperty(requestHeaders[i].first, requestHeaders[i].second); |
| + jstring jHeader = jniEnv->NewStringUTF(requestHeaders[i].first.c_str()); |
| + jstring jValue = jniEnv->NewStringUTF(requestHeaders[i].second.c_str()); |
| + jniEnv->CallVoidMethod(jConnection, jConnectionSetRequestPropertyID, jHeader, jValue); |
| + if (jniEnv->ExceptionCheck()) |
| + throw JavaException(jniEnv->ExceptionOccurred()); |
| + jniEnv->DeleteLocalRef(jHeader); |
| + jniEnv->DeleteLocalRef(jValue); |
| + } |
| + |
| + // connection.connect(); |
| + jniEnv->CallVoidMethod(jConnection, jConnectionConnectID); |
| + if (jniEnv->ExceptionCheck()) |
| + throw JavaException(jniEnv->ExceptionOccurred()); |
| + |
| + // int lenghtOfFile = connection.getContentLength(); |
| + jint lenghtOfFile = jniEnv->CallIntMethod(jConnection, jConnectionGetContentLengthID); |
|
Wladimir Palant
2013/09/12 11:31:14
This variable's name is misspelled, and it isn't a
|
| + if (jniEnv->ExceptionCheck()) |
| + throw JavaException(jniEnv->ExceptionOccurred()); |
| + |
| + D(D_WARN, "Size: %d", lenghtOfFile); |
| + |
| + // result.responseStatus = connection.getResponseCode(); |
| + result.responseStatus = jniEnv->CallIntMethod(jConnection, jConnectionGetResponseCodeID); |
| + if (jniEnv->ExceptionCheck()) |
| + throw JavaException(jniEnv->ExceptionOccurred()); |
| + |
| + /* Read response data */ |
| + |
| + // String jEncoding = connection.getContentEncoding(); |
| + jstring jEncoding = (jstring) jniEnv->CallObjectMethod(jConnection, jConnectionGetContentEncodingID); |
| + if (jniEnv->ExceptionCheck()) |
| + throw JavaException(jniEnv->ExceptionOccurred()); |
| + |
| + if (jEncoding == NULL) |
| + jEncoding = jniEnv->NewStringUTF("utf-8"); |
| + |
| + // InputStream jis = connection.getInputStream(); |
| + jobject jis = jniEnv->CallObjectMethod(jConnection, jConnectionGetInputStreamID); |
| + if (jniEnv->ExceptionCheck()) |
| + throw JavaException(jniEnv->ExceptionOccurred()); |
| + |
| + // InputStreamReader jisr = new InputStreamReader(jis, jEncoding); |
| + jobject jisr = jniEnv->NewObject(jInputStreamReaderClass, jInputStreamReaderConstructorID, jis, jEncoding); |
| + if (jniEnv->ExceptionCheck()) |
| + throw JavaException(jniEnv->ExceptionOccurred()); |
| + |
| + jniEnv->DeleteLocalRef(jEncoding); |
| + |
| + // BufferedReader jin = new BufferedReader(jisr); |
| + jobject jin = jniEnv->NewObject(jBufferedReaderClass, jBufferedReaderConstructorID, jisr); |
| + if (jniEnv->ExceptionCheck()) |
| + throw JavaException(jniEnv->ExceptionOccurred()); |
| + |
| + // char[] jBuffer = new char[0x10000]; |
| + jcharArray jBuffer = jniEnv->NewCharArray(0x10000); |
| + |
| + // StringBuilder jout = new StringBuilder(); |
| + jobject jout = jniEnv->NewObject(jStringBuilderClass, jStringBuilderConstructorID); |
| + if (jniEnv->ExceptionCheck()) |
| + throw JavaException(jniEnv->ExceptionOccurred()); |
| + |
| + jlong total = 0; |
| + jint read; |
| + |
| + jint jBufferLength = (jint) jniEnv->GetArrayLength(jBuffer); |
| + |
| + do |
| + { |
| + // read = jin.read(buffer, 0, buffer.length); |
| + read = jniEnv->CallIntMethod(jin, jBufferedReaderReadID, jBuffer, 0, jBufferLength); |
| + if (jniEnv->ExceptionCheck()) |
| + throw JavaException(jniEnv->ExceptionOccurred()); |
| + |
| + if (read > 0) |
| + { |
| + // jout.append(buffer, 0, read); |
| + jniEnv->CallObjectMethod(jout, jStringBuilderAppendID, jBuffer, 0, jBufferLength); |
| + if (jniEnv->ExceptionCheck()) |
| + throw JavaException(jniEnv->ExceptionOccurred()); |
| + |
| + total += read; |
| + } |
| + } |
| + while (read >= 0); |
| + |
| + // String jData = out.toString(); |
| + jstring jData = (jstring) jniEnv->CallObjectMethod(jout, jStringBuilderToStringID); |
| + if (jniEnv->ExceptionCheck()) |
| + throw JavaException(jniEnv->ExceptionOccurred()); |
| + |
| + result.responseText = GetString(jniEnv, jData); |
| + |
| + // jin.close(); |
| + jniEnv->CallVoidMethod(jin, jBufferedReaderCloseID); |
| + if (jniEnv->ExceptionCheck()) |
| + throw JavaException(jniEnv->ExceptionOccurred()); |
| + |
| + jint i = 0; |
| + while (true) |
|
Felix Dahlke
2013/09/13 14:40:39
How about |for (jint i = 0; ; i++)| instead?
|
| + { |
| + // String jHeaderName = connection.getHeaderFieldKey(i) |
| + jstring jHeaderName = (jstring) jniEnv->CallObjectMethod(jConnection, jConnectionGetHeaderFieldKeyID, i); |
| + if (jniEnv->ExceptionCheck()) |
| + throw JavaException(jniEnv->ExceptionOccurred()); |
| + |
| + // String jHeaderValue = connection.getHeaderField(i) |
| + jstring jHeaderValue = (jstring) jniEnv->CallObjectMethod(jConnection, jConnectionGetHeaderFieldID, i); |
| + if (jniEnv->ExceptionCheck()) |
| + throw JavaException(jniEnv->ExceptionOccurred()); |
| + |
| + if (jHeaderValue == NULL) |
|
Wladimir Palant
2013/09/12 11:31:14
From all I know, NULL is a Windows-specific consta
Felix Dahlke
2013/09/13 14:40:39
NULL is not Windows-specific, it's in ANSI C. But
|
| + break; |
| + |
| + std::string headerName = GetString(jniEnv, jHeaderName); |
| + std::string headerValue = GetString(jniEnv, jHeaderValue); |
| + |
| + headerName = TrimString(headerName); |
| + headerValue = TrimString(headerValue); |
| + |
| + std::transform(headerName.begin(), headerName.end(), headerName.begin(), ::tolower); |
| + std::transform(headerValue.begin(), headerValue.end(), headerValue.begin(), ::tolower); |
|
Wladimir Palant
2013/09/12 11:31:14
Please do not lower-case the value, only the heade
|
| + |
| + result.responseHeaders.push_back(std::pair<std::string, std::string>(headerName, headerValue)); |
| + |
| + i++; |
| + } |
| + D(D_WARN, "Finished downloading"); |
| + |
| + result.status = AdblockPlus::DefaultWebRequest::NS_OK; |
| + } |
| + catch(JavaException& e) |
| + { |
| + jniEnv->ExceptionClear(); |
| + jclass jThrowableClass = jniEnv->FindClass("java/lang/Throwable"); |
| + jmethodID jThrowableToStringID = jniEnv->GetMethodID(jThrowableClass, "toString", "()Ljava/lang/String;"); |
| + jstring jMsg = (jstring) jniEnv->CallObjectMethod(e.ex, jThrowableToStringID); |
| + const char* msg = jniEnv->GetStringUTFChars(jMsg, 0); |
| + D(D_ERROR, "Java Exception: %s", msg); |
| + jniEnv->ReleaseStringUTFChars(jMsg, msg); |
| + jniEnv->DeleteLocalRef(jMsg); |
| + result.status = AdblockPlus::DefaultWebRequest::NS_ERROR_FAILURE; |
|
Wladimir Palant
2013/09/12 11:31:14
I think that we should try to do better than that.
|
| + } |
| + catch (const std::exception& e) |
| + { |
| + D(D_ERROR, "Exception: %s", e.what()); |
| + result.status = AdblockPlus::DefaultWebRequest::NS_ERROR_FAILURE; |
| + } |
| + catch (...) |
| + { |
| + D(D_ERROR, "Unknown exception"); |
| + result.status = AdblockPlus::DefaultWebRequest::NS_ERROR_FAILURE; |
| + } |
| + |
| + if (stat == JNI_EDETACHED) |
| + globalJvm->DetachCurrentThread(); |
| + |
| + return result; |
| +} |