http.c 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907
  1. #include <stdlib.h>
  2. #include <stddef.h>
  3. #include <stdint.h>
  4. #include <string.h>
  5. #include <stdbool.h>
  6. #include <stdio.h>
  7. typedef void fn_header(void* userdata, const char* name, size_t nameLength, const char* value, size_t valueLength);
  8. typedef struct {
  9. const char* url;
  10. const char* method;
  11. const char** headers;
  12. uint32_t headerCount;
  13. const char* data;
  14. size_t size;
  15. } http_request_t;
  16. typedef struct {
  17. const char* error;
  18. uint32_t status;
  19. char* data;
  20. size_t size;
  21. fn_header* header_cb;
  22. void* userdata;
  23. } http_response_t;
  24. #if defined(_WIN32)
  25. #define WIN32_LEAN_AND_MEAN
  26. #include <windows.h>
  27. #include <wininet.h>
  28. static HINTERNET internet;
  29. static void http_init(void) {
  30. internet = InternetOpen("LOVR", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
  31. }
  32. static void http_destroy(void) {
  33. InternetCloseHandle(internet);
  34. }
  35. static bool http_request(http_request_t* req, http_response_t* res) {
  36. if (req->size > UINT32_MAX) {
  37. res->error = "request data too large";
  38. return false;
  39. }
  40. if (!internet) {
  41. res->error = "unknown error";
  42. return false;
  43. }
  44. // parse URL
  45. size_t length = strlen(req->url);
  46. const char* url = req->url;
  47. bool https = false;
  48. if (length > 8 && !memcmp(url, "https://", 8)) {
  49. https = true;
  50. length -= 8;
  51. url += 8;
  52. } else if (length > 7 && !memcmp(url, "http://", 7)) {
  53. length -= 7;
  54. url += 7;
  55. } else {
  56. res->error = "invalid url scheme";
  57. return false;
  58. }
  59. if (strchr(url, '@')) {
  60. res->error = "invalid url";
  61. return false;
  62. }
  63. char* path = strchr(url, '/');
  64. size_t hostLength = path ? path - url : length;
  65. INTERNET_PORT port = https ? 443 : 80;
  66. char* colon = memchr(url, ':', hostLength);
  67. if (colon) {
  68. hostLength = colon - url;
  69. port = 0;
  70. for (char* p = colon + 1; *p && *p != '/'; p++) {
  71. if (*p < '0' || *p > '9') {
  72. res->error = "invalid url port";
  73. return false;
  74. } else {
  75. port *= 10;
  76. port += *p - '0';
  77. }
  78. }
  79. if (port <= 0 || port >= 65536) {
  80. res->error = "invalid url port";
  81. return false;
  82. }
  83. }
  84. char host[256];
  85. if (sizeof(host) > hostLength) {
  86. memcpy(host, url, hostLength);
  87. host[hostLength] = '\0';
  88. } else {
  89. res->error = "invalid url host";
  90. return false;
  91. }
  92. // connection
  93. HINTERNET connection = InternetConnectA(internet, host, port, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0);
  94. if (!connection) {
  95. res->error = "system error while setting up request";
  96. return false;
  97. }
  98. // setup request
  99. const char* method = req->method ? req->method : (req->data ? "POST" : "GET");
  100. DWORD flags = 0;
  101. flags |= INTERNET_FLAG_NO_AUTH;
  102. flags |= INTERNET_FLAG_NO_CACHE_WRITE;
  103. flags |= INTERNET_FLAG_NO_COOKIES;
  104. flags |= INTERNET_FLAG_NO_UI;
  105. flags |= https ? INTERNET_FLAG_SECURE : 0;
  106. HINTERNET request = HttpOpenRequestA(connection, method, path, NULL, NULL, NULL, flags, 0);
  107. if (!request) {
  108. InternetCloseHandle(connection);
  109. res->error = "system error while setting up request";
  110. return false;
  111. }
  112. // Need default Content-Type for POSTs
  113. if (req->data) {
  114. const char* contentType = "Content-Type: application/x-www-form-urlencoded\r\n";
  115. HttpAddRequestHeadersA(request, contentType, -1L, HTTP_ADDREQ_FLAG_ADD);
  116. }
  117. // request headers
  118. if (req->headerCount >= 0) {
  119. char* header = NULL;
  120. size_t capacity = 0;
  121. for (uint32_t i = 0; i < req->headerCount; i++) {
  122. const char* name = req->headers[2 * i + 0];
  123. const char* value = req->headers[2 * i + 1];
  124. const char* format = "%s: %s\r\n";
  125. int length = snprintf(NULL, 0, format, name, value);
  126. if (length > UINT32_MAX) continue;
  127. if (length + 1 > capacity) {
  128. capacity = length + 1;
  129. header = realloc(header, capacity);
  130. if (!header) {
  131. InternetCloseHandle(connection);
  132. res->error = "out of memory";
  133. return false;
  134. }
  135. }
  136. sprintf(header, format, name, value);
  137. HttpAddRequestHeadersA(request, header, (DWORD) length, HTTP_ADDREQ_FLAG_ADD | HTTP_ADDREQ_FLAG_REPLACE);
  138. }
  139. free(header);
  140. }
  141. // do the thing
  142. bool success = HttpSendRequestA(request, NULL, 0, (void*) req->data, (DWORD) req->size);
  143. if (!success) {
  144. InternetCloseHandle(connection);
  145. res->error = "system error while sending request";
  146. return false;
  147. }
  148. // status
  149. DWORD status;
  150. DWORD bufferSize = sizeof(status);
  151. DWORD index = 0;
  152. HttpQueryInfoA(request, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &status, &bufferSize, &index);
  153. res->status = status;
  154. index = 0;
  155. // response headers
  156. char stack[1024];
  157. char* buffer = stack;
  158. bufferSize = sizeof(stack);
  159. success = HttpQueryInfoA(request, HTTP_QUERY_RAW_HEADERS, buffer, &bufferSize, &index);
  160. if (!success) {
  161. if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
  162. buffer = malloc(bufferSize);
  163. if (!buffer) {
  164. InternetCloseHandle(request);
  165. InternetCloseHandle(connection);
  166. res->error = "out of memory";
  167. return false;
  168. }
  169. success = HttpQueryInfoA(request, HTTP_QUERY_RAW_HEADERS, buffer, &bufferSize, &index);
  170. }
  171. if (!success) {
  172. if (buffer != stack) free(buffer);
  173. InternetCloseHandle(request);
  174. InternetCloseHandle(connection);
  175. res->error = "system error while parsing headers";
  176. return false;
  177. }
  178. }
  179. char* header = buffer;
  180. while (*header) {
  181. size_t length = strlen(header);
  182. char* colon = strchr(header, ':');
  183. if (colon && colon != header && length >= (size_t) (colon - header + 2)) {
  184. char* name = header;
  185. char* value = colon + 2;
  186. size_t nameLength = colon - header;
  187. size_t valueLength = length - (colon - header + 2);
  188. res->header_cb(res->userdata, name, nameLength, value, valueLength);
  189. }
  190. header += length + 1;
  191. }
  192. if (buffer != stack) {
  193. free(buffer);
  194. }
  195. // body
  196. res->data = NULL;
  197. res->size = 0;
  198. for (;;) {
  199. DWORD bytes = 0;
  200. if (!InternetQueryDataAvailable(request, &bytes, 0, 0)) {
  201. free(res->data);
  202. InternetCloseHandle(request);
  203. InternetCloseHandle(connection);
  204. res->error = "system error while reading response";
  205. return false;
  206. }
  207. if (bytes == 0) {
  208. break;
  209. }
  210. res->data = realloc(res->data, res->size + bytes);
  211. if (!res->data) {
  212. InternetCloseHandle(request);
  213. InternetCloseHandle(connection);
  214. res->error = "out of memory";
  215. return false;
  216. }
  217. if (InternetReadFile(request, res->data + res->size, bytes, &bytes)) {
  218. res->size += bytes;
  219. } else {
  220. free(res->data);
  221. InternetCloseHandle(request);
  222. InternetCloseHandle(connection);
  223. res->error = "system error while reading response";
  224. return false;
  225. }
  226. }
  227. InternetCloseHandle(request);
  228. InternetCloseHandle(connection);
  229. return true;
  230. }
  231. #elif defined(__ANDROID__)
  232. #include <jni.h>
  233. static JavaVM* jvm;
  234. // LÖVR calls this before loading the plugin
  235. jint JNI_OnLoad(JavaVM* vm, void* reserved) {
  236. jvm = vm;
  237. return 0;
  238. }
  239. static void http_init(void) {
  240. //
  241. }
  242. static void http_destroy(void) {
  243. //
  244. }
  245. static bool handleException(JNIEnv* jni, http_response_t* response, const char* message) {
  246. if ((*jni)->ExceptionCheck(jni)) {
  247. (*jni)->ExceptionClear(jni);
  248. response->error = message;
  249. return true;
  250. }
  251. return false;
  252. }
  253. static bool http_request(http_request_t* request, http_response_t* response) {
  254. if (!jvm) {
  255. response->error = "Java VM not available";
  256. return false;
  257. }
  258. JNIEnv* jni;
  259. if ((*jvm)->GetEnv(jvm, (void**) &jni, JNI_VERSION_1_6) == JNI_EDETACHED) {
  260. response->error = "Java VM not attached to this thread ;_;";
  261. return false;
  262. }
  263. // URL jurl = new URL(request->url);
  264. jclass jURL = (*jni)->FindClass(jni, "java/net/URL");
  265. jmethodID jURL_init = (*jni)->GetMethodID(jni, jURL, "<init>", "(Ljava/lang/String;)V");
  266. jstring jurlstring = (*jni)->NewStringUTF(jni, request->url);
  267. jobject jurl = (*jni)->NewObject(jni, jURL, jURL_init, jurlstring);
  268. if (handleException(jni, response, "invalid url")) return false;
  269. (*jni)->DeleteLocalRef(jni, jurlstring);
  270. // HttpURLConnection jconnection = (HttpURLConnection) jurl.openConnection();
  271. jmethodID jURL_openConnection = (*jni)->GetMethodID(jni, jURL, "openConnection", "()Ljava/net/URLConnection;");
  272. jobject jconnection = (*jni)->CallObjectMethod(jni, jurl, jURL_openConnection);
  273. if (handleException(jni, response, "connection failure")) return false;
  274. (*jni)->DeleteLocalRef(jni, jurl);
  275. (*jni)->DeleteLocalRef(jni, jURL);
  276. // jconnection.setRequestMethod(method);
  277. jclass jHttpURLConnection = (*jni)->FindClass(jni, "java/net/HttpURLConnection");
  278. jmethodID jHttpURLConnection_setRequestMethod = (*jni)->GetMethodID(jni, jHttpURLConnection, "setRequestMethod", "(Ljava/lang/String;)V");
  279. const char* method = request->method ? request->method : (request->data ? "POST" : "GET");
  280. jstring jmethod = (*jni)->NewStringUTF(jni, method);
  281. (*jni)->CallVoidMethod(jni, jconnection, jHttpURLConnection_setRequestMethod, jmethod);
  282. if (handleException(jni, response, "invalid request method")) return false;
  283. (*jni)->DeleteLocalRef(jni, jmethod);
  284. // jconnection.setRequestProperty(headerName, headerValue);
  285. jmethodID jURLConnection_setRequestProperty = (*jni)->GetMethodID(jni, jHttpURLConnection, "setRequestProperty", "(Ljava/lang/String;Ljava/lang/String;)V");
  286. for (uint32_t i = 0; i < request->headerCount; i++) {
  287. jstring jname = (*jni)->NewStringUTF(jni, request->headers[2 * i + 0]);
  288. jstring jvalue = (*jni)->NewStringUTF(jni, request->headers[2 * i + 1]);
  289. (*jni)->CallVoidMethod(jni, jconnection, jURLConnection_setRequestProperty, jname, jvalue);
  290. (*jni)->DeleteLocalRef(jni, jname);
  291. (*jni)->DeleteLocalRef(jni, jvalue);
  292. }
  293. if (request->data) {
  294. // jconnection.setDoOutput(true);
  295. jmethodID jURLConnection_setDoOutput = (*jni)->GetMethodID(jni, jHttpURLConnection, "setDoOutput", "(Z)V");
  296. (*jni)->CallVoidMethod(jni, jconnection, jURLConnection_setDoOutput, true);
  297. // OutputStream joutput = jconnection.getOutputStream();
  298. jmethodID jURLConnection_getOutputStream = (*jni)->GetMethodID(jni, jHttpURLConnection, "getOutputStream", "()Ljava/io/OutputStream;");
  299. jobject joutput = (*jni)->CallObjectMethod(jni, jconnection, jURLConnection_getOutputStream);
  300. if (handleException(jni, response, "failed to write request data")) return false;
  301. jbyteArray jarray = (*jni)->NewByteArray(jni, request->size);
  302. if (handleException(jni, response, "out of memory")) return false;
  303. // joutput.write(request->data);
  304. jbyte* bytes = (*jni)->GetByteArrayElements(jni, jarray, NULL);
  305. memcpy(bytes, request->data, request->size);
  306. (*jni)->ReleaseByteArrayElements(jni, jarray, bytes, 0);
  307. jclass jOutputStream = (*jni)->FindClass(jni, "java/io/OutputStream");
  308. jmethodID jOutputStream_write = (*jni)->GetMethodID(jni, jOutputStream, "write", "([B)V");
  309. (*jni)->CallVoidMethod(jni, joutput, jOutputStream_write, jarray);
  310. if (handleException(jni, response, "failed to write request data")) return false;
  311. (*jni)->DeleteLocalRef(jni, jarray);
  312. (*jni)->DeleteLocalRef(jni, joutput);
  313. (*jni)->DeleteLocalRef(jni, jOutputStream);
  314. }
  315. // jconnection.connect();
  316. jmethodID jURLConnection_connect = (*jni)->GetMethodID(jni, jHttpURLConnection, "connect", "()V");
  317. (*jni)->CallVoidMethod(jni, jconnection, jURLConnection_connect);
  318. if (handleException(jni, response, "connection failure")) return false;
  319. // response->status = jconnection.getResponseCode();
  320. jmethodID jHttpURLConnection_getResponseCode = (*jni)->GetMethodID(jni, jHttpURLConnection, "getResponseCode", "()I");
  321. response->status = (*jni)->CallIntMethod(jni, jconnection, jHttpURLConnection_getResponseCode);
  322. if (handleException(jni, response, "connection failure")) return false;
  323. jmethodID jHttpURLConnection_getHeaderFieldKey = (*jni)->GetMethodID(jni, jHttpURLConnection, "getHeaderFieldKey", "(I)Ljava/lang/String;");
  324. jmethodID jHttpURLConnection_getHeaderField = (*jni)->GetMethodID(jni, jHttpURLConnection, "getHeaderField", "(I)Ljava/lang/String;");
  325. jint headerIndex = 0;
  326. for (;;) {
  327. jstring jname = (*jni)->CallObjectMethod(jni, jconnection, jHttpURLConnection_getHeaderFieldKey, headerIndex);
  328. jstring jvalue = (*jni)->CallObjectMethod(jni, jconnection, jHttpURLConnection_getHeaderField, headerIndex);
  329. if (!jvalue) {
  330. break;
  331. }
  332. if (!jname) {
  333. headerIndex++;
  334. continue;
  335. }
  336. size_t nameLength = (*jni)->GetStringUTFLength(jni, jname);
  337. const char* name = (*jni)->GetStringUTFChars(jni, jname, NULL);
  338. size_t valueLength = (*jni)->GetStringUTFLength(jni, jvalue);
  339. const char* value = (*jni)->GetStringUTFChars(jni, jvalue, NULL);
  340. // TODO name/value use Java's weird "modified UTF" encoding. It's close to utf8 but not quite.
  341. response->header_cb(response->userdata, name, nameLength, value, valueLength);
  342. (*jni)->ReleaseStringUTFChars(jni, jname, name);
  343. (*jni)->ReleaseStringUTFChars(jni, jvalue, value);
  344. (*jni)->DeleteLocalRef(jni, jname);
  345. (*jni)->DeleteLocalRef(jni, jvalue);
  346. headerIndex++;
  347. }
  348. // InputStream jinput = jconnection.getInputStream(); (or getErrorStream)
  349. jmethodID jURLConnection_getInputStream = (*jni)->GetMethodID(jni, jHttpURLConnection, "getInputStream", "()Ljava/io/InputStream;");
  350. jmethodID jURLConnection_getErrorStream = (*jni)->GetMethodID(jni, jHttpURLConnection, "getErrorStream", "()Ljava/io/InputStream;");
  351. jobject jinput;
  352. if (response->status >= 400) {
  353. jinput = (*jni)->CallObjectMethod(jni, jconnection, jURLConnection_getErrorStream);
  354. } else {
  355. jinput = (*jni)->CallObjectMethod(jni, jconnection, jURLConnection_getInputStream);
  356. }
  357. if (handleException(jni, response, "failed to read response data")) return false;
  358. jclass jInputStream = (*jni)->FindClass(jni, "java/io/InputStream");
  359. jmethodID jInputStream_read = (*jni)->GetMethodID(jni, jInputStream, "read", "([B)I");
  360. response->data = NULL;
  361. response->size = 0;
  362. jbyteArray jbuffer = (*jni)->NewByteArray(jni, 16384);
  363. if (handleException(jni, response, "out of memory")) return false;
  364. for (;;) {
  365. // int bytesRead = jinput.read(buffer);
  366. jint bytesRead = (*jni)->CallIntMethod(jni, jinput, jInputStream_read, jbuffer);
  367. if (handleException(jni, response, "failed to read response data")) return false;
  368. if (bytesRead == -1) {
  369. break;
  370. }
  371. response->data = realloc(response->data, response->size + bytesRead);
  372. if (!response->data) {
  373. response->error = "out of memory";
  374. return false;
  375. }
  376. (*jni)->GetByteArrayRegion(jni, jbuffer, 0, bytesRead, (jbyte*) response->data + response->size);
  377. response->size += bytesRead;
  378. }
  379. (*jni)->DeleteLocalRef(jni, jbuffer);
  380. (*jni)->DeleteLocalRef(jni, jinput);
  381. (*jni)->DeleteLocalRef(jni, jInputStream);
  382. // jconnection.disconnect();
  383. jmethodID jURLConnection_disconnect = (*jni)->GetMethodID(jni, jHttpURLConnection, "disconnect", "()V");
  384. (*jni)->CallVoidMethod(jni, jconnection, jURLConnection_disconnect);
  385. (*jni)->DeleteLocalRef(jni, jHttpURLConnection);
  386. (*jni)->DeleteLocalRef(jni, jconnection);
  387. return true;
  388. }
  389. #elif defined(__linux__)
  390. #include <curl/curl.h>
  391. #include <dlfcn.h>
  392. typedef CURLcode fn_global_init(long flags);
  393. typedef void fn_global_cleanup(void);
  394. typedef CURL* fn_easy_init(void);
  395. typedef CURLcode fn_easy_setopt(CURL *curl, CURLoption option, ...);
  396. typedef CURLcode fn_easy_perform(CURL *curl);
  397. typedef void fn_easy_cleanup(CURL* curl);
  398. typedef CURLcode fn_easy_getinfo(CURL* curl, CURLINFO info, ...);
  399. typedef const char* fn_easy_strerror(CURLcode error);
  400. typedef struct curl_slist *fn_slist_append(struct curl_slist *list, const char *string);
  401. typedef void fn_slist_free_all(struct curl_slist *list);
  402. #define FN_DECLARE(f) fn_##f* f;
  403. #define FN_LOAD(f) curl.f = (fn_##f*) dlsym(library, "curl_"#f);
  404. #define FN_FOREACH(X)\
  405. X(global_init)\
  406. X(global_cleanup)\
  407. X(easy_init)\
  408. X(easy_setopt)\
  409. X(easy_perform)\
  410. X(easy_cleanup)\
  411. X(easy_getinfo)\
  412. X(easy_strerror)\
  413. X(slist_append)\
  414. X(slist_free_all)
  415. static struct {
  416. FN_FOREACH(FN_DECLARE)
  417. } curl;
  418. static void* library;
  419. static void http_init(void) {
  420. library = dlopen("libcurl.so.4", RTLD_LAZY);
  421. if (!library) library = dlopen("libcurl.so", RTLD_LAZY);
  422. if (library) {
  423. FN_FOREACH(FN_LOAD)
  424. if (curl.global_init(CURL_GLOBAL_DEFAULT)) {
  425. dlclose(library);
  426. library = NULL;
  427. }
  428. }
  429. }
  430. static void http_destroy(void) {
  431. if (library) {
  432. curl.global_cleanup();
  433. dlclose(library);
  434. }
  435. }
  436. static size_t writer(void* buffer, size_t size, size_t count, void* userdata) {
  437. http_response_t* response = userdata;
  438. response->data = realloc(response->data, response->size + size * count);
  439. if (!response->data) {
  440. response->error = "out of memory";
  441. return 0;
  442. }
  443. memcpy(response->data + response->size, buffer, size * count);
  444. response->size += size * count;
  445. return size * count;
  446. }
  447. // would rather use curl_easy_nextheader, but it's too new right now
  448. static size_t onHeader(char* buffer, size_t size, size_t count, void* userdata) {
  449. http_response_t* response = userdata;
  450. char* colon = memchr(buffer, ':', size * count);
  451. if (colon) {
  452. char* name = buffer;
  453. char* value = colon + 1;
  454. size_t nameLength = colon - buffer;
  455. size_t valueLength = size * count - (nameLength + 1);
  456. while (valueLength > 0 && (*value == ' ' || *value == '\t')) value++, valueLength--;
  457. while (valueLength > 0 && (value[valueLength - 1] == '\n' || value[valueLength - 1] == '\r')) valueLength--;
  458. response->header_cb(response->userdata, name, nameLength, value, valueLength);
  459. }
  460. return size * count;
  461. }
  462. static bool http_request(http_request_t* request, http_response_t* response) {
  463. if (!library) return response->error = "curl unavailable", false;
  464. CURL* handle = curl.easy_init();
  465. if (!handle) return response->error = "curl unavailable", false;
  466. #if LIBCURL_VERSION_NUM >= 0x075500
  467. curl.easy_setopt(handle, CURLOPT_PROTOCOLS_STR, "http,https");
  468. #else
  469. curl.easy_setopt(handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
  470. #endif
  471. curl.easy_setopt(handle, CURLOPT_URL, request->url);
  472. if (request->method) {
  473. curl.easy_setopt(handle, CURLOPT_CUSTOMREQUEST, request->method);
  474. if (!strcmp(request->method, "HEAD")) curl.easy_setopt(handle, CURLOPT_NOBODY, 1);
  475. }
  476. if (request->data && (!request->method || (strcmp(request->method, "GET") && strcmp(request->method, "HEAD")))) {
  477. curl.easy_setopt(handle, CURLOPT_POSTFIELDS, request->data);
  478. curl.easy_setopt(handle, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t) request->size);
  479. }
  480. struct curl_slist* headers = NULL;
  481. if (request->headerCount > 0) {
  482. char* header = NULL;
  483. size_t capacity = 0;
  484. for (uint32_t i = 0; i < request->headerCount; i++) {
  485. const char* name = request->headers[2 * i + 0];
  486. const char* value = request->headers[2 * i + 1];
  487. const char* format = "%s: %s";
  488. int length = snprintf(NULL, 0, format, name, value);
  489. if (length + 1 > capacity) {
  490. capacity = length + 1;
  491. header = realloc(header, capacity);
  492. if (!header) {
  493. response->error = "out of memory";
  494. return false;
  495. }
  496. }
  497. sprintf(header, format, name, value);
  498. headers = curl.slist_append(headers, header);
  499. }
  500. curl.easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
  501. free(header);
  502. }
  503. curl.easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1);
  504. response->size = 0;
  505. response->data = NULL;
  506. curl.easy_setopt(handle, CURLOPT_WRITEDATA, response);
  507. curl.easy_setopt(handle, CURLOPT_WRITEFUNCTION, writer);
  508. curl.easy_setopt(handle, CURLOPT_HEADERDATA, response);
  509. curl.easy_setopt(handle, CURLOPT_HEADERFUNCTION, onHeader);
  510. CURLcode error = curl.easy_perform(handle);
  511. if (error != CURLE_OK) {
  512. response->error = curl.easy_strerror(error);
  513. return false;
  514. }
  515. long status;
  516. curl.easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &status);
  517. response->status = status;
  518. curl.easy_cleanup(handle);
  519. curl.slist_free_all(headers);
  520. return true;
  521. }
  522. #elif defined(__APPLE__)
  523. #include <objc/objc-runtime.h>
  524. #include <dispatch/dispatch.h>
  525. #define cls(T) ((id) objc_getClass(#T))
  526. #define msg(ret, obj, fn) ((ret(*)(id, SEL)) objc_msgSend)(obj, sel_getUid(fn))
  527. #define msg1(ret, obj, fn, T1, A1) ((ret(*)(id, SEL, T1)) objc_msgSend)(obj, sel_getUid(fn), A1)
  528. #define msg2(ret, obj, fn, T1, A1, T2, A2) ((ret(*)(id, SEL, T1, T2)) objc_msgSend)(obj, sel_getUid(fn), A1, A2)
  529. #define msg3(ret, obj, fn, T1, A1, T2, A2, T3, A3) ((ret(*)(id, SEL, T1, T2, T3)) objc_msgSend)(obj, sel_getUid(fn), A1, A2, A3)
  530. typedef void (^CompletionHandler)(id data, id response, id error);
  531. static void http_init(void) {
  532. //
  533. }
  534. static void http_destroy(void) {
  535. //
  536. }
  537. static bool http_request(http_request_t* request, http_response_t* response) {
  538. id NSString = cls(NSString);
  539. id urlNS = msg1(id, NSString, "stringWithUTF8String:", const char*, request->url);
  540. id url = msg1(id, cls(NSURL), "URLWithString:", id, urlNS);
  541. id req = msg1(id, cls(NSMutableURLRequest), "requestWithURL:", id, url);
  542. // Method
  543. const char* method = request->method ? request->method : (request->data ? "POST" : "GET");
  544. id methodNS = msg1(id, NSString, "stringWithUTF8String:", const char*, method);
  545. msg1(void, req, "setHTTPMethod:", id, methodNS);
  546. // Body
  547. if (request->data && strcmp(method, "GET") && strcmp(method, "HEAD")) {
  548. id body = msg3(id, cls(NSData), "dataWithBytesNoCopy:length:freeWhenDone:", void*, (void*) request->data, unsigned long, (unsigned long) request->size, BOOL, NO);
  549. msg1(void, req, "setHTTPBody:", id, body);
  550. }
  551. // Headers
  552. for (uint32_t i = 0; i < request->headerCount; i++) {
  553. id key = msg1(id, NSString, "stringWithUTF8String:", const char*, request->headers[2 * i + 0]);
  554. id val = msg1(id, NSString, "stringWithUTF8String:", const char*, request->headers[2 * i + 1]);
  555. msg2(void, req, "setValue:forHTTPHeaderField:", id, val, id, key);
  556. }
  557. __block id data = nil;
  558. __block id res = nil;
  559. __block id error = nil;
  560. dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
  561. CompletionHandler onComplete = ^(id d, id r, id e) {
  562. data = d;
  563. res = r;
  564. error = e;
  565. dispatch_semaphore_signal(semaphore);
  566. };
  567. // Task
  568. id session = msg(id, cls(NSURLSession), "sharedSession");
  569. id task = msg2(id, session, "dataTaskWithRequest:completionHandler:", id, req, CompletionHandler, onComplete);
  570. msg(void, task, "resume");
  571. dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
  572. if (data) {
  573. response->size = msg(unsigned long, data, "length");
  574. response->data = malloc(response->size);
  575. if (!response->data) {
  576. response->error = "out of memory";
  577. return false;
  578. }
  579. msg2(void, data, "getBytes:length:", void*, response->data, unsigned long, response->size);
  580. }
  581. if (res) {
  582. response->status = msg(long, res, "statusCode");
  583. id headers = msg(id, res, "allHeaderFields");
  584. id enumerator = msg(id, headers, "keyEnumerator");
  585. for (;;) {
  586. id keyNS = msg(id, enumerator, "nextObject");
  587. if (!keyNS) break;
  588. id valNS = msg1(id, headers, "valueForKey:", id, keyNS);
  589. const char* key = msg(const char*, keyNS, "UTF8String");
  590. const char* val = msg(const char*, valNS, "UTF8String");
  591. unsigned long keyLength = msg(unsigned long, keyNS, "length");
  592. unsigned long valLength = msg(unsigned long, valNS, "length");
  593. response->header_cb(response->userdata, key, keyLength, val, valLength);
  594. }
  595. }
  596. response->error = "unknown error"; // TODO
  597. return !error;
  598. }
  599. #else
  600. #error "Unsupported HTTP platform"
  601. #endif
  602. // Lua API
  603. #include <lua.h>
  604. #include <lauxlib.h>
  605. static bool isunreserved(char c) {
  606. switch (c) {
  607. case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i':
  608. case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':
  609. case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z':
  610. case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I':
  611. case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
  612. case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z':
  613. case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '0':
  614. case '-': case '_': case '.': case '~':
  615. return true;
  616. default:
  617. return false;
  618. }
  619. }
  620. static size_t urlencode(char* dst, const char* str, size_t len) {
  621. size_t res = 0;
  622. for (size_t i = 0; i < len; i++, str++) {
  623. if (isunreserved(*str)) {
  624. dst[res++] = *str;
  625. } else {
  626. dst[res++] = '%';
  627. dst[res++] = '0' + *str / 16;
  628. dst[res++] = '0' + *str % 16;
  629. }
  630. }
  631. return res;
  632. }
  633. static void addheader(void* userdata, const char* name, size_t nameLength, const char* value, size_t valueLength) {
  634. lua_State* L = userdata;
  635. lua_pushlstring(L, name, nameLength);
  636. lua_pushlstring(L, value, valueLength);
  637. lua_settable(L, 3);
  638. }
  639. static int l_http_request(lua_State* L) {
  640. http_request_t request = { 0 };
  641. request.url = luaL_checkstring(L, 1);
  642. if (!strstr(request.url, "://")) {
  643. lua_pushliteral(L, "http://");
  644. lua_pushvalue(L, 1);
  645. lua_concat(L, 2);
  646. lua_replace(L, 1);
  647. request.url = lua_tostring(L, 1);
  648. }
  649. char* data = NULL;
  650. size_t size = 0;
  651. size_t capacity = 0;
  652. if (lua_istable(L, 2)) {
  653. lua_getfield(L, 2, "data");
  654. switch (lua_type(L, -1)) {
  655. case LUA_TNIL:
  656. break;
  657. case LUA_TSTRING:
  658. request.data = lua_tolstring(L, -1, &request.size);
  659. break;
  660. case LUA_TTABLE:
  661. lua_pushnil(L);
  662. while (lua_next(L, -2) != 0) {
  663. if (lua_type(L, -2) == LUA_TSTRING && lua_isstring(L, -1)) {
  664. size_t keyLength, valLength;
  665. const char* key = lua_tolstring(L, -2, &keyLength);
  666. const char* val = lua_tolstring(L, -1, &valLength);
  667. size_t maxLength = 3 * keyLength + 1 + 3 * valLength + 1;
  668. if (size + maxLength > capacity) {
  669. capacity = size + maxLength;
  670. data = realloc(data, capacity);
  671. if (!data) return luaL_error(L, "Out of memory");
  672. }
  673. size += urlencode(data + size, key, keyLength);
  674. data[size++] = '=';
  675. size += urlencode(data + size, val, valLength);
  676. data[size++] = '&';
  677. }
  678. lua_pop(L, 1);
  679. }
  680. request.data = data;
  681. request.size = size - 1;
  682. break;
  683. case LUA_TLIGHTUSERDATA:
  684. lua_getfield(L, 2, "datasize");
  685. if (lua_type(L, -1) != LUA_TNUMBER) return luaL_error(L, "Expected numeric 'datasize' key");
  686. request.data = lua_touserdata(L, -2);
  687. request.size = lua_tointeger(L, -1);
  688. lua_pop(L, 1);
  689. break;
  690. default:
  691. return luaL_error(L, "Expected string, table, or lightuserdata for request data");
  692. }
  693. lua_pop(L, 1);
  694. lua_getglobal(L, "string");
  695. lua_getfield(L, -1, "upper");
  696. lua_getfield(L, 2, "method");
  697. if (lua_isstring(L, -1)) {
  698. lua_call(L, 1, 1);
  699. request.method = lua_tostring(L, -1);
  700. lua_pop(L, 2);
  701. } else {
  702. lua_pop(L, 3);
  703. }
  704. lua_getfield(L, 2, "headers");
  705. if (lua_istable(L, -1)) {
  706. lua_pushnil(L);
  707. while (lua_next(L, -2) != 0) {
  708. if (lua_type(L, -2) == LUA_TSTRING && lua_isstring(L, -1)) {
  709. request.headers = realloc(request.headers, (request.headerCount + 1) * 2 * sizeof(char*));
  710. if (!request.headers) return luaL_error(L, "Out of memory");
  711. request.headers[request.headerCount * 2 + 0] = lua_tostring(L, -2);
  712. request.headers[request.headerCount * 2 + 1] = lua_tostring(L, -1);
  713. request.headerCount++;
  714. }
  715. lua_pop(L, 1);
  716. }
  717. }
  718. lua_pop(L, 1);
  719. }
  720. http_response_t response = {
  721. .header_cb = addheader,
  722. .userdata = L
  723. };
  724. lua_settop(L, 2);
  725. lua_newtable(L);
  726. bool success = http_request(&request, &response);
  727. free(request.headers);
  728. free(data);
  729. if (!success) {
  730. lua_pushnil(L);
  731. lua_pushstring(L, response.error);
  732. return 2;
  733. }
  734. lua_pushinteger(L, response.status);
  735. lua_pushlstring(L, response.data, response.size);
  736. lua_pushvalue(L, -3);
  737. free(response.data);
  738. return 3;
  739. }
  740. int l_http_destroy(lua_State* L) {
  741. http_destroy();
  742. return 0;
  743. }
  744. #ifdef _WIN32
  745. #define HTTP_EXPORT __declspec(dllexport)
  746. #else
  747. #define HTTP_EXPORT __attribute__((visibility("default")))
  748. #endif
  749. HTTP_EXPORT int luaopen_http(lua_State* L) {
  750. http_init();
  751. lua_newtable(L);
  752. lua_pushcfunction(L, l_http_request);
  753. lua_setfield(L, -2, "request");
  754. lua_newuserdata(L, sizeof(void*));
  755. lua_createtable(L, 0, 1);
  756. lua_pushcfunction(L, l_http_destroy);
  757. lua_setfield(L, -2, "__gc");
  758. lua_setmetatable(L, -2);
  759. lua_setfield(L, -2, "");
  760. return 1;
  761. }