AndroidClient.cpp 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. #include "AndroidClient.h"
  2. #ifdef HTTPS_BACKEND_ANDROID
  3. #include <sstream>
  4. #include <type_traits>
  5. #include <dlfcn.h>
  6. static std::string replace(const std::string &str, const std::string &from, const std::string &to)
  7. {
  8. std::stringstream ss;
  9. size_t oldpos = 0;
  10. while (true)
  11. {
  12. size_t pos = str.find(from, oldpos);
  13. if (pos == std::string::npos)
  14. {
  15. ss << str.substr(oldpos);
  16. break;
  17. }
  18. ss << str.substr(oldpos, pos - oldpos) << to;
  19. oldpos = pos + from.length();
  20. }
  21. return ss.str();
  22. }
  23. static jstring newStringUTF(JNIEnv *env, const std::string &str)
  24. {
  25. // We want std::string that contains null byte, hence length of 1.
  26. static std::string null("", 1);
  27. std::string newStr = replace(str, null, "\xC0\x80");
  28. jstring jstr = env->NewStringUTF(newStr.c_str());
  29. return jstr;
  30. }
  31. static std::string getStringUTF(JNIEnv *env, jstring str)
  32. {
  33. // We want std::string that contains null byte, hence length of 1.
  34. static std::string null("", 1);
  35. const char *c = env->GetStringUTFChars(str, nullptr);
  36. std::string result = replace(c, "\xC0\x80", null);
  37. env->ReleaseStringUTFChars(str, c);
  38. return result;
  39. }
  40. AndroidClient::AndroidClient()
  41. : HTTPSClient()
  42. , SDL_AndroidGetJNIEnv(nullptr)
  43. {
  44. // Look for SDL_AndroidGetJNIEnv
  45. SDL_AndroidGetJNIEnv = (decltype(SDL_AndroidGetJNIEnv)) dlsym(RTLD_DEFAULT, "SDL_AndroidGetJNIEnv");
  46. // Look for SDL_AndroidGetActivity
  47. SDL_AndroidGetActivity = (decltype(SDL_AndroidGetActivity)) dlsym(RTLD_DEFAULT, "SDL_AndroidGetActivity");
  48. }
  49. bool AndroidClient::valid() const
  50. {
  51. if (SDL_AndroidGetJNIEnv && SDL_AndroidGetActivity)
  52. {
  53. JNIEnv *env = SDL_AndroidGetJNIEnv();
  54. if (env)
  55. {
  56. jclass httpsClass = getHTTPSClass();
  57. if (env->ExceptionCheck())
  58. {
  59. env->ExceptionClear();
  60. return false;
  61. }
  62. env->DeleteLocalRef(httpsClass);
  63. return true;
  64. }
  65. }
  66. return false;
  67. }
  68. HTTPSClient::Reply AndroidClient::request(const HTTPSClient::Request &req)
  69. {
  70. JNIEnv *env = SDL_AndroidGetJNIEnv();
  71. jclass httpsClass = getHTTPSClass();
  72. if (httpsClass == nullptr)
  73. {
  74. env->ExceptionClear();
  75. throw std::runtime_error("Could not find class 'org.love2d.luahttps.LuaHTTPS'");
  76. }
  77. jmethodID constructor = env->GetMethodID(httpsClass, "<init>", "()V");
  78. jmethodID setURL = env->GetMethodID(httpsClass, "setUrl", "(Ljava/lang/String;)V");
  79. jmethodID setMethod = env->GetMethodID(httpsClass, "setMethod", "(Ljava/lang/String;)V");
  80. jmethodID request = env->GetMethodID(httpsClass, "request", "()Z");
  81. jmethodID getInterleavedHeaders = env->GetMethodID(httpsClass, "getInterleavedHeaders", "()[Ljava/lang/String;");
  82. jmethodID getResponse = env->GetMethodID(httpsClass, "getResponse", "()[B");
  83. jmethodID getResponseCode = env->GetMethodID(httpsClass, "getResponseCode", "()I");
  84. jobject httpsObject = env->NewObject(httpsClass, constructor);
  85. // Set URL
  86. jstring url = env->NewStringUTF(req.url.c_str());
  87. env->CallVoidMethod(httpsObject, setURL, url);
  88. env->DeleteLocalRef(url);
  89. // Set method
  90. jstring method = env->NewStringUTF(req.method.c_str());
  91. env->CallVoidMethod(httpsObject, setMethod, method);
  92. env->DeleteLocalRef(method);
  93. // Set post data
  94. if (req.postdata.size() > 0)
  95. {
  96. jmethodID setPostData = env->GetMethodID(httpsClass, "setPostData", "([B)V");
  97. jbyteArray byteArray = env->NewByteArray((jsize) req.postdata.length());
  98. jbyte *byteArrayData = env->GetByteArrayElements(byteArray, nullptr);
  99. memcpy(byteArrayData, req.postdata.data(), req.postdata.length());
  100. env->ReleaseByteArrayElements(byteArray, byteArrayData, 0);
  101. env->CallVoidMethod(httpsObject, setPostData, byteArray);
  102. env->DeleteLocalRef(byteArray);
  103. }
  104. // Set headers
  105. if (!req.headers.empty())
  106. {
  107. jmethodID addHeader = env->GetMethodID(httpsClass, "addHeader", "(Ljava/lang/String;Ljava/lang/String;)V");
  108. for (auto &header : req.headers)
  109. {
  110. jstring headerKey = newStringUTF(env, header.first);
  111. jstring headerValue = newStringUTF(env, header.second);
  112. env->CallVoidMethod(httpsObject, addHeader, headerKey, headerValue);
  113. env->DeleteLocalRef(headerKey);
  114. env->DeleteLocalRef(headerValue);
  115. }
  116. }
  117. // Do request
  118. HTTPSClient::Reply response;
  119. jboolean status = env->CallBooleanMethod(httpsObject, request);
  120. // Get response
  121. response.responseCode = env->CallIntMethod(httpsObject, getResponseCode);
  122. if (status)
  123. {
  124. // Get headers
  125. jobjectArray interleavedHeaders = (jobjectArray) env->CallObjectMethod(httpsObject, getInterleavedHeaders);
  126. int len = env->GetArrayLength(interleavedHeaders);
  127. for (int i = 0; i < len; i += 2)
  128. {
  129. jstring key = (jstring) env->GetObjectArrayElement(interleavedHeaders, i);
  130. jstring value = (jstring) env->GetObjectArrayElement(interleavedHeaders, i + 1);
  131. response.headers[getStringUTF(env, key)] = getStringUTF(env, value);
  132. env->DeleteLocalRef(key);
  133. env->DeleteLocalRef(value);
  134. }
  135. env->DeleteLocalRef(interleavedHeaders);
  136. // Get response data
  137. jbyteArray responseData = (jbyteArray) env->CallObjectMethod(httpsObject, getResponse);
  138. if (responseData)
  139. {
  140. int len = env->GetArrayLength(responseData);
  141. jbyte *responseByte = env->GetByteArrayElements(responseData, nullptr);
  142. response.body = std::string((char *) responseByte, len);
  143. env->DeleteLocalRef(responseData);
  144. }
  145. }
  146. return response;
  147. }
  148. jclass AndroidClient::getHTTPSClass() const
  149. {
  150. JNIEnv *env = SDL_AndroidGetJNIEnv();
  151. jclass classLoaderClass = env->FindClass("java/lang/ClassLoader");
  152. jmethodID loadClass = env->GetMethodID(classLoaderClass, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
  153. jobject activity = SDL_AndroidGetActivity();
  154. if (activity == nullptr)
  155. return nullptr;
  156. jclass gameActivity = env->GetObjectClass(activity);
  157. jmethodID getLoader = env->GetMethodID(gameActivity, "getClassLoader", "()Ljava/lang/ClassLoader;");
  158. jobject classLoader = env->CallObjectMethod(activity, getLoader);
  159. jstring httpsClassName = env->NewStringUTF("org.love2d.luahttps.LuaHTTPS");
  160. jclass httpsClass = (jclass) env->CallObjectMethod(classLoader, loadClass, httpsClassName);
  161. env->DeleteLocalRef(gameActivity);
  162. env->DeleteLocalRef(httpsClassName);
  163. env->DeleteLocalRef(activity);
  164. env->DeleteLocalRef(classLoaderClass);
  165. return httpsClass;
  166. }
  167. #endif // HTTPS_BACKEND_ANDROID