AndroidClient.cpp 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  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_AndroidGetJNIEnv
  44. LibraryLoader::LoadSymbol(SDL_AndroidGetJNIEnv, library, "SDL_AndroidGetJNIEnv");
  45. // Look for SDL_AndroidGetActivity
  46. LibraryLoader::LoadSymbol(SDL_AndroidGetActivity, library, "SDL_AndroidGetActivity");
  47. }
  48. bool AndroidClient::valid() const
  49. {
  50. if (SDL_AndroidGetJNIEnv && SDL_AndroidGetActivity)
  51. {
  52. JNIEnv *env = SDL_AndroidGetJNIEnv();
  53. if (env)
  54. {
  55. jclass httpsClass = getHTTPSClass();
  56. if (env->ExceptionCheck())
  57. {
  58. env->ExceptionClear();
  59. return false;
  60. }
  61. env->DeleteLocalRef(httpsClass);
  62. return true;
  63. }
  64. }
  65. return false;
  66. }
  67. HTTPSClient::Reply AndroidClient::request(const HTTPSClient::Request &req)
  68. {
  69. JNIEnv *env = SDL_AndroidGetJNIEnv();
  70. jclass httpsClass = getHTTPSClass();
  71. if (httpsClass == nullptr)
  72. {
  73. env->ExceptionClear();
  74. throw std::runtime_error("Could not find class 'org.love2d.luahttps.LuaHTTPS'");
  75. }
  76. jmethodID constructor = env->GetMethodID(httpsClass, "<init>", "()V");
  77. jmethodID setURL = env->GetMethodID(httpsClass, "setUrl", "(Ljava/lang/String;)V");
  78. jmethodID setMethod = env->GetMethodID(httpsClass, "setMethod", "(Ljava/lang/String;)V");
  79. jmethodID request = env->GetMethodID(httpsClass, "request", "()Z");
  80. jmethodID getInterleavedHeaders = env->GetMethodID(httpsClass, "getInterleavedHeaders", "()[Ljava/lang/String;");
  81. jmethodID getResponse = env->GetMethodID(httpsClass, "getResponse", "()[B");
  82. jmethodID getResponseCode = env->GetMethodID(httpsClass, "getResponseCode", "()I");
  83. jobject httpsObject = env->NewObject(httpsClass, constructor);
  84. // Set URL
  85. jstring url = env->NewStringUTF(req.url.c_str());
  86. env->CallVoidMethod(httpsObject, setURL, url);
  87. env->DeleteLocalRef(url);
  88. // Set method
  89. jstring method = env->NewStringUTF(req.method.c_str());
  90. env->CallVoidMethod(httpsObject, setMethod, method);
  91. env->DeleteLocalRef(method);
  92. // Set post data
  93. if (!req.postdata.empty())
  94. {
  95. jmethodID setPostData = env->GetMethodID(httpsClass, "setPostData", "([B)V");
  96. jbyteArray byteArray = env->NewByteArray((jsize) req.postdata.length());
  97. jbyte *byteArrayData = env->GetByteArrayElements(byteArray, nullptr);
  98. // The usage of memcpy is intentional.
  99. // NOLINTNEXTLINE
  100. memcpy(byteArrayData, req.postdata.data(), req.postdata.length());
  101. env->ReleaseByteArrayElements(byteArray, byteArrayData, 0);
  102. env->CallVoidMethod(httpsObject, setPostData, byteArray);
  103. env->DeleteLocalRef(byteArray);
  104. }
  105. // Set headers
  106. if (!req.headers.empty())
  107. {
  108. jmethodID addHeader = env->GetMethodID(httpsClass, "addHeader", "(Ljava/lang/String;Ljava/lang/String;)V");
  109. for (auto &header : req.headers)
  110. {
  111. jstring headerKey = newStringUTF(env, header.first);
  112. jstring headerValue = newStringUTF(env, header.second);
  113. env->CallVoidMethod(httpsObject, addHeader, headerKey, headerValue);
  114. env->DeleteLocalRef(headerKey);
  115. env->DeleteLocalRef(headerValue);
  116. }
  117. }
  118. // Do request
  119. HTTPSClient::Reply response;
  120. jboolean status = env->CallBooleanMethod(httpsObject, request);
  121. // Get response
  122. response.responseCode = env->CallIntMethod(httpsObject, getResponseCode);
  123. if (status)
  124. {
  125. // Get headers
  126. jobjectArray interleavedHeaders = (jobjectArray) env->CallObjectMethod(httpsObject, getInterleavedHeaders);
  127. int headerLen = env->GetArrayLength(interleavedHeaders);
  128. for (int i = 0; i < headerLen; i += 2)
  129. {
  130. jstring key = (jstring) env->GetObjectArrayElement(interleavedHeaders, i);
  131. jstring value = (jstring) env->GetObjectArrayElement(interleavedHeaders, i + 1);
  132. response.headers[getStringUTF(env, key)] = getStringUTF(env, value);
  133. env->DeleteLocalRef(key);
  134. env->DeleteLocalRef(value);
  135. }
  136. env->DeleteLocalRef(interleavedHeaders);
  137. // Get response data
  138. jbyteArray responseData = (jbyteArray) env->CallObjectMethod(httpsObject, getResponse);
  139. if (responseData)
  140. {
  141. int responseLen = env->GetArrayLength(responseData);
  142. jbyte *responseByte = env->GetByteArrayElements(responseData, nullptr);
  143. response.body = std::string((char *) responseByte, responseLen);
  144. env->DeleteLocalRef(responseData);
  145. }
  146. }
  147. env->DeleteLocalRef(httpsObject);
  148. return response;
  149. }
  150. jclass AndroidClient::getHTTPSClass() const
  151. {
  152. JNIEnv *env = SDL_AndroidGetJNIEnv();
  153. jclass classLoaderClass = env->FindClass("java/lang/ClassLoader");
  154. jmethodID loadClass = env->GetMethodID(classLoaderClass, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
  155. jobject activity = SDL_AndroidGetActivity();
  156. if (activity == nullptr)
  157. return nullptr;
  158. jclass gameActivity = env->GetObjectClass(activity);
  159. jmethodID getLoader = env->GetMethodID(gameActivity, "getClassLoader", "()Ljava/lang/ClassLoader;");
  160. jobject classLoader = env->CallObjectMethod(activity, getLoader);
  161. jstring httpsClassName = env->NewStringUTF("org.love2d.luahttps.LuaHTTPS");
  162. jclass httpsClass = (jclass) env->CallObjectMethod(classLoader, loadClass, httpsClassName);
  163. env->DeleteLocalRef(gameActivity);
  164. env->DeleteLocalRef(httpsClassName);
  165. env->DeleteLocalRef(activity);
  166. env->DeleteLocalRef(classLoaderClass);
  167. return httpsClass;
  168. }
  169. #endif // HTTPS_BACKEND_ANDROID