AndroidClient.cpp 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  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 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 post data
  89. if (req.method == Request::POST)
  90. {
  91. jmethodID setPostData = env->GetMethodID(httpsClass, "setPostData", "([B)V");
  92. jbyteArray byteArray = env->NewByteArray((jsize) req.postdata.length());
  93. jbyte *byteArrayData = env->GetByteArrayElements(byteArray, nullptr);
  94. memcpy(byteArrayData, req.postdata.data(), req.postdata.length());
  95. env->ReleaseByteArrayElements(byteArray, byteArrayData, 0);
  96. env->CallVoidMethod(httpsObject, setPostData, byteArray);
  97. env->DeleteLocalRef(byteArray);
  98. }
  99. // Set headers
  100. if (!req.headers.empty())
  101. {
  102. jmethodID addHeader = env->GetMethodID(httpsClass, "addHeader", "(Ljava/lang/String;Ljava/lang/String;)V");
  103. for (auto &header : req.headers)
  104. {
  105. jstring headerKey = newStringUTF(env, header.first);
  106. jstring headerValue = newStringUTF(env, header.second);
  107. env->CallVoidMethod(httpsObject, addHeader, headerKey, headerValue);
  108. env->DeleteLocalRef(headerKey);
  109. env->DeleteLocalRef(headerValue);
  110. }
  111. }
  112. // Do request
  113. HTTPSClient::Reply response;
  114. jboolean status = env->CallBooleanMethod(httpsObject, request);
  115. // Get response
  116. response.responseCode = env->CallIntMethod(httpsObject, getResponseCode);
  117. if (status)
  118. {
  119. // Get headers
  120. jobjectArray interleavedHeaders = (jobjectArray) env->CallObjectMethod(httpsObject, getInterleavedHeaders);
  121. int len = env->GetArrayLength(interleavedHeaders);
  122. for (int i = 0; i < len; i += 2)
  123. {
  124. jstring key = (jstring) env->GetObjectArrayElement(interleavedHeaders, i);
  125. jstring value = (jstring) env->GetObjectArrayElement(interleavedHeaders, i + 1);
  126. response.headers[getStringUTF(env, key)] = getStringUTF(env, value);
  127. env->DeleteLocalRef(key);
  128. env->DeleteLocalRef(value);
  129. }
  130. env->DeleteLocalRef(interleavedHeaders);
  131. // Get response data
  132. jbyteArray responseData = (jbyteArray) env->CallObjectMethod(httpsObject, getResponse);
  133. if (responseData)
  134. {
  135. int len = env->GetArrayLength(responseData);
  136. jbyte *responseByte = env->GetByteArrayElements(responseData, nullptr);
  137. response.body = std::string((char *) responseByte, len);
  138. env->DeleteLocalRef(responseData);
  139. }
  140. }
  141. return response;
  142. }
  143. jclass AndroidClient::getHTTPSClass() const
  144. {
  145. JNIEnv *env = SDL_AndroidGetJNIEnv();
  146. jclass classLoaderClass = env->FindClass("java/lang/ClassLoader");
  147. jmethodID loadClass = env->GetMethodID(classLoaderClass, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
  148. jobject activity = SDL_AndroidGetActivity();
  149. if (activity == nullptr)
  150. return nullptr;
  151. jclass gameActivity = env->GetObjectClass(activity);
  152. jmethodID getLoader = env->GetMethodID(gameActivity, "getClassLoader", "()Ljava/lang/ClassLoader;");
  153. jobject classLoader = env->CallObjectMethod(activity, getLoader);
  154. jstring httpsClassName = env->NewStringUTF("org.love2d.luahttps.LuaHTTPS");
  155. jclass httpsClass = (jclass) env->CallObjectMethod(classLoader, loadClass, httpsClassName);
  156. env->DeleteLocalRef(gameActivity);
  157. env->DeleteLocalRef(httpsClassName);
  158. env->DeleteLocalRef(activity);
  159. env->DeleteLocalRef(classLoaderClass);
  160. return httpsClass;
  161. }
  162. #endif // HTTPS_BACKEND_ANDROID