AndroidClient.cpp 6.3 KB

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