123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907 |
- #include <stdlib.h>
- #include <stddef.h>
- #include <stdint.h>
- #include <string.h>
- #include <stdbool.h>
- #include <stdio.h>
- typedef void fn_header(void* userdata, const char* name, size_t nameLength, const char* value, size_t valueLength);
- typedef struct {
- const char* url;
- const char* method;
- const char** headers;
- uint32_t headerCount;
- const char* data;
- size_t size;
- } http_request_t;
- typedef struct {
- const char* error;
- uint32_t status;
- char* data;
- size_t size;
- fn_header* header_cb;
- void* userdata;
- } http_response_t;
- #if defined(_WIN32)
- #define WIN32_LEAN_AND_MEAN
- #include <windows.h>
- #include <wininet.h>
- static HINTERNET internet;
- static void http_init(void) {
- internet = InternetOpen("LOVR", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
- }
- static void http_destroy(void) {
- InternetCloseHandle(internet);
- }
- static bool http_request(http_request_t* req, http_response_t* res) {
- if (req->size > UINT32_MAX) {
- res->error = "request data too large";
- return false;
- }
- if (!internet) {
- res->error = "unknown error";
- return false;
- }
- // parse URL
- size_t length = strlen(req->url);
- const char* url = req->url;
- bool https = false;
- if (length > 8 && !memcmp(url, "https://", 8)) {
- https = true;
- length -= 8;
- url += 8;
- } else if (length > 7 && !memcmp(url, "http://", 7)) {
- length -= 7;
- url += 7;
- } else {
- res->error = "invalid url scheme";
- return false;
- }
- if (strchr(url, '@')) {
- res->error = "invalid url";
- return false;
- }
- char* path = strchr(url, '/');
- size_t hostLength = path ? path - url : length;
- INTERNET_PORT port = https ? 443 : 80;
- char* colon = memchr(url, ':', hostLength);
- if (colon) {
- hostLength = colon - url;
- port = 0;
- for (char* p = colon + 1; *p && *p != '/'; p++) {
- if (*p < '0' || *p > '9') {
- res->error = "invalid url port";
- return false;
- } else {
- port *= 10;
- port += *p - '0';
- }
- }
- if (port <= 0 || port >= 65536) {
- res->error = "invalid url port";
- return false;
- }
- }
- char host[256];
- if (sizeof(host) > hostLength) {
- memcpy(host, url, hostLength);
- host[hostLength] = '\0';
- } else {
- res->error = "invalid url host";
- return false;
- }
- // connection
- HINTERNET connection = InternetConnectA(internet, host, port, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0);
- if (!connection) {
- res->error = "system error while setting up request";
- return false;
- }
- // setup request
- const char* method = req->method ? req->method : (req->data ? "POST" : "GET");
- DWORD flags = 0;
- flags |= INTERNET_FLAG_NO_AUTH;
- flags |= INTERNET_FLAG_NO_CACHE_WRITE;
- flags |= INTERNET_FLAG_NO_COOKIES;
- flags |= INTERNET_FLAG_NO_UI;
- flags |= https ? INTERNET_FLAG_SECURE : 0;
- HINTERNET request = HttpOpenRequestA(connection, method, path, NULL, NULL, NULL, flags, 0);
- if (!request) {
- InternetCloseHandle(connection);
- res->error = "system error while setting up request";
- return false;
- }
- // Need default Content-Type for POSTs
- if (req->data) {
- const char* contentType = "Content-Type: application/x-www-form-urlencoded\r\n";
- HttpAddRequestHeadersA(request, contentType, -1L, HTTP_ADDREQ_FLAG_ADD);
- }
- // request headers
- if (req->headerCount >= 0) {
- char* header = NULL;
- size_t capacity = 0;
- for (uint32_t i = 0; i < req->headerCount; i++) {
- const char* name = req->headers[2 * i + 0];
- const char* value = req->headers[2 * i + 1];
- const char* format = "%s: %s\r\n";
- int length = snprintf(NULL, 0, format, name, value);
- if (length > UINT32_MAX) continue;
- if (length + 1 > capacity) {
- capacity = length + 1;
- header = realloc(header, capacity);
- if (!header) {
- InternetCloseHandle(connection);
- res->error = "out of memory";
- return false;
- }
- }
- sprintf(header, format, name, value);
- HttpAddRequestHeadersA(request, header, (DWORD) length, HTTP_ADDREQ_FLAG_ADD | HTTP_ADDREQ_FLAG_REPLACE);
- }
- free(header);
- }
- // do the thing
- bool success = HttpSendRequestA(request, NULL, 0, (void*) req->data, (DWORD) req->size);
- if (!success) {
- InternetCloseHandle(connection);
- res->error = "system error while sending request";
- return false;
- }
- // status
- DWORD status;
- DWORD bufferSize = sizeof(status);
- DWORD index = 0;
- HttpQueryInfoA(request, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &status, &bufferSize, &index);
- res->status = status;
- index = 0;
- // response headers
- char stack[1024];
- char* buffer = stack;
- bufferSize = sizeof(stack);
- success = HttpQueryInfoA(request, HTTP_QUERY_RAW_HEADERS, buffer, &bufferSize, &index);
- if (!success) {
- if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
- buffer = malloc(bufferSize);
- if (!buffer) {
- InternetCloseHandle(request);
- InternetCloseHandle(connection);
- res->error = "out of memory";
- return false;
- }
- success = HttpQueryInfoA(request, HTTP_QUERY_RAW_HEADERS, buffer, &bufferSize, &index);
- }
- if (!success) {
- if (buffer != stack) free(buffer);
- InternetCloseHandle(request);
- InternetCloseHandle(connection);
- res->error = "system error while parsing headers";
- return false;
- }
- }
- char* header = buffer;
- while (*header) {
- size_t length = strlen(header);
- char* colon = strchr(header, ':');
- if (colon && colon != header && length >= (size_t) (colon - header + 2)) {
- char* name = header;
- char* value = colon + 2;
- size_t nameLength = colon - header;
- size_t valueLength = length - (colon - header + 2);
- res->header_cb(res->userdata, name, nameLength, value, valueLength);
- }
- header += length + 1;
- }
- if (buffer != stack) {
- free(buffer);
- }
- // body
- res->data = NULL;
- res->size = 0;
- for (;;) {
- DWORD bytes = 0;
- if (!InternetQueryDataAvailable(request, &bytes, 0, 0)) {
- free(res->data);
- InternetCloseHandle(request);
- InternetCloseHandle(connection);
- res->error = "system error while reading response";
- return false;
- }
- if (bytes == 0) {
- break;
- }
- res->data = realloc(res->data, res->size + bytes);
- if (!res->data) {
- InternetCloseHandle(request);
- InternetCloseHandle(connection);
- res->error = "out of memory";
- return false;
- }
- if (InternetReadFile(request, res->data + res->size, bytes, &bytes)) {
- res->size += bytes;
- } else {
- free(res->data);
- InternetCloseHandle(request);
- InternetCloseHandle(connection);
- res->error = "system error while reading response";
- return false;
- }
- }
- InternetCloseHandle(request);
- InternetCloseHandle(connection);
- return true;
- }
- #elif defined(__ANDROID__)
- #include <jni.h>
- static JavaVM* jvm;
- // LÖVR calls this before loading the plugin
- jint JNI_OnLoad(JavaVM* vm, void* reserved) {
- jvm = vm;
- return 0;
- }
- static void http_init(void) {
- //
- }
- static void http_destroy(void) {
- //
- }
- static bool handleException(JNIEnv* jni, http_response_t* response, const char* message) {
- if ((*jni)->ExceptionCheck(jni)) {
- (*jni)->ExceptionClear(jni);
- response->error = message;
- return true;
- }
- return false;
- }
- static bool http_request(http_request_t* request, http_response_t* response) {
- if (!jvm) {
- response->error = "Java VM not available";
- return false;
- }
- JNIEnv* jni;
- if ((*jvm)->GetEnv(jvm, (void**) &jni, JNI_VERSION_1_6) == JNI_EDETACHED) {
- response->error = "Java VM not attached to this thread ;_;";
- return false;
- }
- // URL jurl = new URL(request->url);
- jclass jURL = (*jni)->FindClass(jni, "java/net/URL");
- jmethodID jURL_init = (*jni)->GetMethodID(jni, jURL, "<init>", "(Ljava/lang/String;)V");
- jstring jurlstring = (*jni)->NewStringUTF(jni, request->url);
- jobject jurl = (*jni)->NewObject(jni, jURL, jURL_init, jurlstring);
- if (handleException(jni, response, "invalid url")) return false;
- (*jni)->DeleteLocalRef(jni, jurlstring);
- // HttpURLConnection jconnection = (HttpURLConnection) jurl.openConnection();
- jmethodID jURL_openConnection = (*jni)->GetMethodID(jni, jURL, "openConnection", "()Ljava/net/URLConnection;");
- jobject jconnection = (*jni)->CallObjectMethod(jni, jurl, jURL_openConnection);
- if (handleException(jni, response, "connection failure")) return false;
- (*jni)->DeleteLocalRef(jni, jurl);
- (*jni)->DeleteLocalRef(jni, jURL);
- // jconnection.setRequestMethod(method);
- jclass jHttpURLConnection = (*jni)->FindClass(jni, "java/net/HttpURLConnection");
- jmethodID jHttpURLConnection_setRequestMethod = (*jni)->GetMethodID(jni, jHttpURLConnection, "setRequestMethod", "(Ljava/lang/String;)V");
- const char* method = request->method ? request->method : (request->data ? "POST" : "GET");
- jstring jmethod = (*jni)->NewStringUTF(jni, method);
- (*jni)->CallVoidMethod(jni, jconnection, jHttpURLConnection_setRequestMethod, jmethod);
- if (handleException(jni, response, "invalid request method")) return false;
- (*jni)->DeleteLocalRef(jni, jmethod);
- // jconnection.setRequestProperty(headerName, headerValue);
- jmethodID jURLConnection_setRequestProperty = (*jni)->GetMethodID(jni, jHttpURLConnection, "setRequestProperty", "(Ljava/lang/String;Ljava/lang/String;)V");
- for (uint32_t i = 0; i < request->headerCount; i++) {
- jstring jname = (*jni)->NewStringUTF(jni, request->headers[2 * i + 0]);
- jstring jvalue = (*jni)->NewStringUTF(jni, request->headers[2 * i + 1]);
- (*jni)->CallVoidMethod(jni, jconnection, jURLConnection_setRequestProperty, jname, jvalue);
- (*jni)->DeleteLocalRef(jni, jname);
- (*jni)->DeleteLocalRef(jni, jvalue);
- }
- if (request->data) {
- // jconnection.setDoOutput(true);
- jmethodID jURLConnection_setDoOutput = (*jni)->GetMethodID(jni, jHttpURLConnection, "setDoOutput", "(Z)V");
- (*jni)->CallVoidMethod(jni, jconnection, jURLConnection_setDoOutput, true);
- // OutputStream joutput = jconnection.getOutputStream();
- jmethodID jURLConnection_getOutputStream = (*jni)->GetMethodID(jni, jHttpURLConnection, "getOutputStream", "()Ljava/io/OutputStream;");
- jobject joutput = (*jni)->CallObjectMethod(jni, jconnection, jURLConnection_getOutputStream);
- if (handleException(jni, response, "failed to write request data")) return false;
- jbyteArray jarray = (*jni)->NewByteArray(jni, request->size);
- if (handleException(jni, response, "out of memory")) return false;
- // joutput.write(request->data);
- jbyte* bytes = (*jni)->GetByteArrayElements(jni, jarray, NULL);
- memcpy(bytes, request->data, request->size);
- (*jni)->ReleaseByteArrayElements(jni, jarray, bytes, 0);
- jclass jOutputStream = (*jni)->FindClass(jni, "java/io/OutputStream");
- jmethodID jOutputStream_write = (*jni)->GetMethodID(jni, jOutputStream, "write", "([B)V");
- (*jni)->CallVoidMethod(jni, joutput, jOutputStream_write, jarray);
- if (handleException(jni, response, "failed to write request data")) return false;
- (*jni)->DeleteLocalRef(jni, jarray);
- (*jni)->DeleteLocalRef(jni, joutput);
- (*jni)->DeleteLocalRef(jni, jOutputStream);
- }
- // jconnection.connect();
- jmethodID jURLConnection_connect = (*jni)->GetMethodID(jni, jHttpURLConnection, "connect", "()V");
- (*jni)->CallVoidMethod(jni, jconnection, jURLConnection_connect);
- if (handleException(jni, response, "connection failure")) return false;
- // response->status = jconnection.getResponseCode();
- jmethodID jHttpURLConnection_getResponseCode = (*jni)->GetMethodID(jni, jHttpURLConnection, "getResponseCode", "()I");
- response->status = (*jni)->CallIntMethod(jni, jconnection, jHttpURLConnection_getResponseCode);
- if (handleException(jni, response, "connection failure")) return false;
- jmethodID jHttpURLConnection_getHeaderFieldKey = (*jni)->GetMethodID(jni, jHttpURLConnection, "getHeaderFieldKey", "(I)Ljava/lang/String;");
- jmethodID jHttpURLConnection_getHeaderField = (*jni)->GetMethodID(jni, jHttpURLConnection, "getHeaderField", "(I)Ljava/lang/String;");
- jint headerIndex = 0;
- for (;;) {
- jstring jname = (*jni)->CallObjectMethod(jni, jconnection, jHttpURLConnection_getHeaderFieldKey, headerIndex);
- jstring jvalue = (*jni)->CallObjectMethod(jni, jconnection, jHttpURLConnection_getHeaderField, headerIndex);
- if (!jvalue) {
- break;
- }
- if (!jname) {
- headerIndex++;
- continue;
- }
- size_t nameLength = (*jni)->GetStringUTFLength(jni, jname);
- const char* name = (*jni)->GetStringUTFChars(jni, jname, NULL);
- size_t valueLength = (*jni)->GetStringUTFLength(jni, jvalue);
- const char* value = (*jni)->GetStringUTFChars(jni, jvalue, NULL);
- // TODO name/value use Java's weird "modified UTF" encoding. It's close to utf8 but not quite.
- response->header_cb(response->userdata, name, nameLength, value, valueLength);
- (*jni)->ReleaseStringUTFChars(jni, jname, name);
- (*jni)->ReleaseStringUTFChars(jni, jvalue, value);
- (*jni)->DeleteLocalRef(jni, jname);
- (*jni)->DeleteLocalRef(jni, jvalue);
- headerIndex++;
- }
- // InputStream jinput = jconnection.getInputStream(); (or getErrorStream)
- jmethodID jURLConnection_getInputStream = (*jni)->GetMethodID(jni, jHttpURLConnection, "getInputStream", "()Ljava/io/InputStream;");
- jmethodID jURLConnection_getErrorStream = (*jni)->GetMethodID(jni, jHttpURLConnection, "getErrorStream", "()Ljava/io/InputStream;");
- jobject jinput;
- if (response->status >= 400) {
- jinput = (*jni)->CallObjectMethod(jni, jconnection, jURLConnection_getErrorStream);
- } else {
- jinput = (*jni)->CallObjectMethod(jni, jconnection, jURLConnection_getInputStream);
- }
- if (handleException(jni, response, "failed to read response data")) return false;
- jclass jInputStream = (*jni)->FindClass(jni, "java/io/InputStream");
- jmethodID jInputStream_read = (*jni)->GetMethodID(jni, jInputStream, "read", "([B)I");
- response->data = NULL;
- response->size = 0;
- jbyteArray jbuffer = (*jni)->NewByteArray(jni, 16384);
- if (handleException(jni, response, "out of memory")) return false;
- for (;;) {
- // int bytesRead = jinput.read(buffer);
- jint bytesRead = (*jni)->CallIntMethod(jni, jinput, jInputStream_read, jbuffer);
- if (handleException(jni, response, "failed to read response data")) return false;
- if (bytesRead == -1) {
- break;
- }
- response->data = realloc(response->data, response->size + bytesRead);
- if (!response->data) {
- response->error = "out of memory";
- return false;
- }
- (*jni)->GetByteArrayRegion(jni, jbuffer, 0, bytesRead, (jbyte*) response->data + response->size);
- response->size += bytesRead;
- }
- (*jni)->DeleteLocalRef(jni, jbuffer);
- (*jni)->DeleteLocalRef(jni, jinput);
- (*jni)->DeleteLocalRef(jni, jInputStream);
- // jconnection.disconnect();
- jmethodID jURLConnection_disconnect = (*jni)->GetMethodID(jni, jHttpURLConnection, "disconnect", "()V");
- (*jni)->CallVoidMethod(jni, jconnection, jURLConnection_disconnect);
- (*jni)->DeleteLocalRef(jni, jHttpURLConnection);
- (*jni)->DeleteLocalRef(jni, jconnection);
- return true;
- }
- #elif defined(__linux__)
- #include <curl/curl.h>
- #include <dlfcn.h>
- typedef CURLcode fn_global_init(long flags);
- typedef void fn_global_cleanup(void);
- typedef CURL* fn_easy_init(void);
- typedef CURLcode fn_easy_setopt(CURL *curl, CURLoption option, ...);
- typedef CURLcode fn_easy_perform(CURL *curl);
- typedef void fn_easy_cleanup(CURL* curl);
- typedef CURLcode fn_easy_getinfo(CURL* curl, CURLINFO info, ...);
- typedef const char* fn_easy_strerror(CURLcode error);
- typedef struct curl_slist *fn_slist_append(struct curl_slist *list, const char *string);
- typedef void fn_slist_free_all(struct curl_slist *list);
- #define FN_DECLARE(f) fn_##f* f;
- #define FN_LOAD(f) curl.f = (fn_##f*) dlsym(library, "curl_"#f);
- #define FN_FOREACH(X)\
- X(global_init)\
- X(global_cleanup)\
- X(easy_init)\
- X(easy_setopt)\
- X(easy_perform)\
- X(easy_cleanup)\
- X(easy_getinfo)\
- X(easy_strerror)\
- X(slist_append)\
- X(slist_free_all)
- static struct {
- FN_FOREACH(FN_DECLARE)
- } curl;
- static void* library;
- static void http_init(void) {
- library = dlopen("libcurl.so.4", RTLD_LAZY);
- if (!library) library = dlopen("libcurl.so", RTLD_LAZY);
- if (library) {
- FN_FOREACH(FN_LOAD)
- if (curl.global_init(CURL_GLOBAL_DEFAULT)) {
- dlclose(library);
- library = NULL;
- }
- }
- }
- static void http_destroy(void) {
- if (library) {
- curl.global_cleanup();
- dlclose(library);
- }
- }
- static size_t writer(void* buffer, size_t size, size_t count, void* userdata) {
- http_response_t* response = userdata;
- response->data = realloc(response->data, response->size + size * count);
- if (!response->data) {
- response->error = "out of memory";
- return 0;
- }
- memcpy(response->data + response->size, buffer, size * count);
- response->size += size * count;
- return size * count;
- }
- // would rather use curl_easy_nextheader, but it's too new right now
- static size_t onHeader(char* buffer, size_t size, size_t count, void* userdata) {
- http_response_t* response = userdata;
- char* colon = memchr(buffer, ':', size * count);
- if (colon) {
- char* name = buffer;
- char* value = colon + 1;
- size_t nameLength = colon - buffer;
- size_t valueLength = size * count - (nameLength + 1);
- while (valueLength > 0 && (*value == ' ' || *value == '\t')) value++, valueLength--;
- while (valueLength > 0 && (value[valueLength - 1] == '\n' || value[valueLength - 1] == '\r')) valueLength--;
- response->header_cb(response->userdata, name, nameLength, value, valueLength);
- }
- return size * count;
- }
- static bool http_request(http_request_t* request, http_response_t* response) {
- if (!library) return response->error = "curl unavailable", false;
- CURL* handle = curl.easy_init();
- if (!handle) return response->error = "curl unavailable", false;
- #if LIBCURL_VERSION_NUM >= 0x075500
- curl.easy_setopt(handle, CURLOPT_PROTOCOLS_STR, "http,https");
- #else
- curl.easy_setopt(handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
- #endif
- curl.easy_setopt(handle, CURLOPT_URL, request->url);
- if (request->method) {
- curl.easy_setopt(handle, CURLOPT_CUSTOMREQUEST, request->method);
- if (!strcmp(request->method, "HEAD")) curl.easy_setopt(handle, CURLOPT_NOBODY, 1);
- }
- if (request->data && (!request->method || (strcmp(request->method, "GET") && strcmp(request->method, "HEAD")))) {
- curl.easy_setopt(handle, CURLOPT_POSTFIELDS, request->data);
- curl.easy_setopt(handle, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t) request->size);
- }
- struct curl_slist* headers = NULL;
- if (request->headerCount > 0) {
- char* header = NULL;
- size_t capacity = 0;
- for (uint32_t i = 0; i < request->headerCount; i++) {
- const char* name = request->headers[2 * i + 0];
- const char* value = request->headers[2 * i + 1];
- const char* format = "%s: %s";
- int length = snprintf(NULL, 0, format, name, value);
- if (length + 1 > capacity) {
- capacity = length + 1;
- header = realloc(header, capacity);
- if (!header) {
- response->error = "out of memory";
- return false;
- }
- }
- sprintf(header, format, name, value);
- headers = curl.slist_append(headers, header);
- }
- curl.easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
- free(header);
- }
- curl.easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1);
- response->size = 0;
- response->data = NULL;
- curl.easy_setopt(handle, CURLOPT_WRITEDATA, response);
- curl.easy_setopt(handle, CURLOPT_WRITEFUNCTION, writer);
- curl.easy_setopt(handle, CURLOPT_HEADERDATA, response);
- curl.easy_setopt(handle, CURLOPT_HEADERFUNCTION, onHeader);
- CURLcode error = curl.easy_perform(handle);
- if (error != CURLE_OK) {
- response->error = curl.easy_strerror(error);
- return false;
- }
- long status;
- curl.easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &status);
- response->status = status;
- curl.easy_cleanup(handle);
- curl.slist_free_all(headers);
- return true;
- }
- #elif defined(__APPLE__)
- #include <objc/objc-runtime.h>
- #include <dispatch/dispatch.h>
- #define cls(T) ((id) objc_getClass(#T))
- #define msg(ret, obj, fn) ((ret(*)(id, SEL)) objc_msgSend)(obj, sel_getUid(fn))
- #define msg1(ret, obj, fn, T1, A1) ((ret(*)(id, SEL, T1)) objc_msgSend)(obj, sel_getUid(fn), A1)
- #define msg2(ret, obj, fn, T1, A1, T2, A2) ((ret(*)(id, SEL, T1, T2)) objc_msgSend)(obj, sel_getUid(fn), A1, A2)
- #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)
- typedef void (^CompletionHandler)(id data, id response, id error);
- static void http_init(void) {
- //
- }
- static void http_destroy(void) {
- //
- }
- static bool http_request(http_request_t* request, http_response_t* response) {
- id NSString = cls(NSString);
- id urlNS = msg1(id, NSString, "stringWithUTF8String:", const char*, request->url);
- id url = msg1(id, cls(NSURL), "URLWithString:", id, urlNS);
- id req = msg1(id, cls(NSMutableURLRequest), "requestWithURL:", id, url);
- // Method
- const char* method = request->method ? request->method : (request->data ? "POST" : "GET");
- id methodNS = msg1(id, NSString, "stringWithUTF8String:", const char*, method);
- msg1(void, req, "setHTTPMethod:", id, methodNS);
- // Body
- if (request->data && strcmp(method, "GET") && strcmp(method, "HEAD")) {
- id body = msg3(id, cls(NSData), "dataWithBytesNoCopy:length:freeWhenDone:", void*, (void*) request->data, unsigned long, (unsigned long) request->size, BOOL, NO);
- msg1(void, req, "setHTTPBody:", id, body);
- }
- // Headers
- for (uint32_t i = 0; i < request->headerCount; i++) {
- id key = msg1(id, NSString, "stringWithUTF8String:", const char*, request->headers[2 * i + 0]);
- id val = msg1(id, NSString, "stringWithUTF8String:", const char*, request->headers[2 * i + 1]);
- msg2(void, req, "setValue:forHTTPHeaderField:", id, val, id, key);
- }
- __block id data = nil;
- __block id res = nil;
- __block id error = nil;
- dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
- CompletionHandler onComplete = ^(id d, id r, id e) {
- data = d;
- res = r;
- error = e;
- dispatch_semaphore_signal(semaphore);
- };
- // Task
- id session = msg(id, cls(NSURLSession), "sharedSession");
- id task = msg2(id, session, "dataTaskWithRequest:completionHandler:", id, req, CompletionHandler, onComplete);
- msg(void, task, "resume");
- dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
- if (data) {
- response->size = msg(unsigned long, data, "length");
- response->data = malloc(response->size);
- if (!response->data) {
- response->error = "out of memory";
- return false;
- }
- msg2(void, data, "getBytes:length:", void*, response->data, unsigned long, response->size);
- }
- if (res) {
- response->status = msg(long, res, "statusCode");
- id headers = msg(id, res, "allHeaderFields");
- id enumerator = msg(id, headers, "keyEnumerator");
- for (;;) {
- id keyNS = msg(id, enumerator, "nextObject");
- if (!keyNS) break;
- id valNS = msg1(id, headers, "valueForKey:", id, keyNS);
- const char* key = msg(const char*, keyNS, "UTF8String");
- const char* val = msg(const char*, valNS, "UTF8String");
- unsigned long keyLength = msg(unsigned long, keyNS, "length");
- unsigned long valLength = msg(unsigned long, valNS, "length");
- response->header_cb(response->userdata, key, keyLength, val, valLength);
- }
- }
- response->error = "unknown error"; // TODO
- return !error;
- }
- #else
- #error "Unsupported HTTP platform"
- #endif
- // Lua API
- #include <lua.h>
- #include <lauxlib.h>
- static bool isunreserved(char c) {
- switch (c) {
- case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i':
- case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':
- case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z':
- case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I':
- case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
- case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z':
- case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '0':
- case '-': case '_': case '.': case '~':
- return true;
- default:
- return false;
- }
- }
- static size_t urlencode(char* dst, const char* str, size_t len) {
- size_t res = 0;
- for (size_t i = 0; i < len; i++, str++) {
- if (isunreserved(*str)) {
- dst[res++] = *str;
- } else {
- dst[res++] = '%';
- dst[res++] = '0' + *str / 16;
- dst[res++] = '0' + *str % 16;
- }
- }
- return res;
- }
- static void addheader(void* userdata, const char* name, size_t nameLength, const char* value, size_t valueLength) {
- lua_State* L = userdata;
- lua_pushlstring(L, name, nameLength);
- lua_pushlstring(L, value, valueLength);
- lua_settable(L, 3);
- }
- static int l_http_request(lua_State* L) {
- http_request_t request = { 0 };
- request.url = luaL_checkstring(L, 1);
- if (!strstr(request.url, "://")) {
- lua_pushliteral(L, "http://");
- lua_pushvalue(L, 1);
- lua_concat(L, 2);
- lua_replace(L, 1);
- request.url = lua_tostring(L, 1);
- }
- char* data = NULL;
- size_t size = 0;
- size_t capacity = 0;
- if (lua_istable(L, 2)) {
- lua_getfield(L, 2, "data");
- switch (lua_type(L, -1)) {
- case LUA_TNIL:
- break;
- case LUA_TSTRING:
- request.data = lua_tolstring(L, -1, &request.size);
- break;
- case LUA_TTABLE:
- lua_pushnil(L);
- while (lua_next(L, -2) != 0) {
- if (lua_type(L, -2) == LUA_TSTRING && lua_isstring(L, -1)) {
- size_t keyLength, valLength;
- const char* key = lua_tolstring(L, -2, &keyLength);
- const char* val = lua_tolstring(L, -1, &valLength);
- size_t maxLength = 3 * keyLength + 1 + 3 * valLength + 1;
- if (size + maxLength > capacity) {
- capacity = size + maxLength;
- data = realloc(data, capacity);
- if (!data) return luaL_error(L, "Out of memory");
- }
- size += urlencode(data + size, key, keyLength);
- data[size++] = '=';
- size += urlencode(data + size, val, valLength);
- data[size++] = '&';
- }
- lua_pop(L, 1);
- }
- request.data = data;
- request.size = size - 1;
- break;
- case LUA_TLIGHTUSERDATA:
- lua_getfield(L, 2, "datasize");
- if (lua_type(L, -1) != LUA_TNUMBER) return luaL_error(L, "Expected numeric 'datasize' key");
- request.data = lua_touserdata(L, -2);
- request.size = lua_tointeger(L, -1);
- lua_pop(L, 1);
- break;
- default:
- return luaL_error(L, "Expected string, table, or lightuserdata for request data");
- }
- lua_pop(L, 1);
- lua_getglobal(L, "string");
- lua_getfield(L, -1, "upper");
- lua_getfield(L, 2, "method");
- if (lua_isstring(L, -1)) {
- lua_call(L, 1, 1);
- request.method = lua_tostring(L, -1);
- lua_pop(L, 2);
- } else {
- lua_pop(L, 3);
- }
- lua_getfield(L, 2, "headers");
- if (lua_istable(L, -1)) {
- lua_pushnil(L);
- while (lua_next(L, -2) != 0) {
- if (lua_type(L, -2) == LUA_TSTRING && lua_isstring(L, -1)) {
- request.headers = realloc(request.headers, (request.headerCount + 1) * 2 * sizeof(char*));
- if (!request.headers) return luaL_error(L, "Out of memory");
- request.headers[request.headerCount * 2 + 0] = lua_tostring(L, -2);
- request.headers[request.headerCount * 2 + 1] = lua_tostring(L, -1);
- request.headerCount++;
- }
- lua_pop(L, 1);
- }
- }
- lua_pop(L, 1);
- }
- http_response_t response = {
- .header_cb = addheader,
- .userdata = L
- };
- lua_settop(L, 2);
- lua_newtable(L);
- bool success = http_request(&request, &response);
- free(request.headers);
- free(data);
- if (!success) {
- lua_pushnil(L);
- lua_pushstring(L, response.error);
- return 2;
- }
- lua_pushinteger(L, response.status);
- lua_pushlstring(L, response.data, response.size);
- lua_pushvalue(L, -3);
- free(response.data);
- return 3;
- }
- int l_http_destroy(lua_State* L) {
- http_destroy();
- return 0;
- }
- #ifdef _WIN32
- #define HTTP_EXPORT __declspec(dllexport)
- #else
- #define HTTP_EXPORT __attribute__((visibility("default")))
- #endif
- HTTP_EXPORT int luaopen_http(lua_State* L) {
- http_init();
- lua_newtable(L);
- lua_pushcfunction(L, l_http_request);
- lua_setfield(L, -2, "request");
- lua_newuserdata(L, sizeof(void*));
- lua_createtable(L, 0, 1);
- lua_pushcfunction(L, l_http_destroy);
- lua_setfield(L, -2, "__gc");
- lua_setmetatable(L, -2);
- lua_setfield(L, -2, "");
- return 1;
- }
|