naett.c

Tiny cross-platform HTTP / HTTPS client library in C.
git clone git://git.finwo.net/lib/naett.c
Log | Files | Refs | README | LICENSE

naett.c (49705B)


      1 // Inlined amalgam.h: //
      2 #include "naett.h"
      3 // Inlined naett_core.c: //
      4 // Inlined naett_internal.h: //
      5 #ifndef NAETT_INTERNAL_H
      6 #define NAETT_INTERNAL_H
      7 
      8 #ifdef _MSC_VER
      9     #define strcasecmp _stricmp
     10     #undef strdup
     11     #define strdup _strdup
     12 #endif
     13 
     14 #ifdef _WIN32
     15 #define WIN32_LEAN_AND_MEAN
     16 #define NOMINMAX
     17 #include <windows.h>
     18 #include <winhttp.h>
     19 #ifndef min
     20     #define min(x,y) ((x) < (y) ? (x) : (y))
     21 #endif
     22 #define __WINDOWS__ 1
     23 #endif
     24 
     25 #if __linux__ && !__ANDROID__
     26 #define __LINUX__ 1
     27 #include <curl/curl.h>
     28 #endif
     29 
     30 #if __ANDROID__
     31 #include <jni.h>
     32 #include <pthread.h>
     33 #endif
     34 
     35 #ifdef __APPLE__
     36 #include "TargetConditionals.h"
     37 #include <objc/objc.h>
     38 #if TARGET_OS_IPHONE
     39 #define __IOS__ 1
     40 #else
     41 #define __MACOS__ 1
     42 #endif
     43 #endif
     44 
     45 #define naettAlloc(TYPE, VAR) TYPE* VAR = (TYPE*)calloc(1, sizeof(TYPE))
     46 
     47 typedef struct KVLink {
     48     const char* key;
     49     const char* value;
     50     struct KVLink* next;
     51 } KVLink;
     52 
     53 typedef struct Buffer {
     54     void* data;
     55     int size;
     56     int capacity;
     57     int position;
     58 } Buffer;
     59 
     60 typedef struct {
     61     const char* method;
     62     const char* userAgent;
     63     int timeoutMS;
     64     naettReadFunc bodyReader;
     65     void* bodyReaderData;
     66     naettWriteFunc bodyWriter;
     67     void* bodyWriterData;
     68     KVLink* headers;
     69     Buffer body;
     70 } RequestOptions;
     71 
     72 typedef struct {
     73     RequestOptions options;
     74     const char* url;
     75 #if __APPLE__
     76     id urlRequest;
     77 #endif
     78 #if __ANDROID__
     79     jobject urlObject;
     80 #endif
     81 #if __WINDOWS__
     82     HINTERNET session;
     83     HINTERNET connection;
     84     HINTERNET request;
     85     LPWSTR host;
     86     LPWSTR resource;
     87 #endif
     88 } InternalRequest;
     89 
     90 typedef struct {
     91     InternalRequest* request;
     92     int code;
     93     int complete;
     94     KVLink* headers;
     95     Buffer body;
     96     int contentLength;  // 0 if headers not read, -1 if Content-Length missing.
     97     int totalBytesRead;
     98 #if __APPLE__
     99     id session;
    100 #endif
    101 #if __ANDROID__
    102     pthread_t workerThread;
    103     int closeRequested;
    104 #endif
    105 #if __LINUX__
    106     struct curl_slist* headerList;
    107 #endif
    108 #if __WINDOWS__
    109     char buffer[10240];
    110     size_t bytesLeft;
    111 #endif
    112 } InternalResponse;
    113 
    114 void naettPlatformInit(naettInitData initData);
    115 int naettPlatformInitRequest(InternalRequest* req);
    116 void naettPlatformMakeRequest(InternalResponse* res);
    117 void naettPlatformFreeRequest(InternalRequest* req);
    118 void naettPlatformCloseResponse(InternalResponse* res);
    119 
    120 #endif  // NAETT_INTERNAL_H
    121 // End of inlined naett_internal.h //
    122 
    123 #include <stdarg.h>
    124 #include <stdlib.h>
    125 #include <stddef.h>
    126 #include <string.h>
    127 #include <assert.h>
    128 
    129 typedef struct InternalParam* InternalParamPtr;
    130 typedef void (*ParamSetter)(InternalParamPtr param, InternalRequest* req);
    131 
    132 typedef struct InternalParam {
    133         ParamSetter setter;
    134         int offset;
    135         union {
    136             int integer;
    137             const char* string;
    138             struct {
    139                 const char* key;
    140                 const char* value;
    141             } kv;
    142             void* ptr;
    143             void (*func)(void);
    144         };
    145 } InternalParam;
    146 
    147 typedef struct InternalOption {
    148     #define maxParams 2
    149     int numParams;
    150     InternalParam params[maxParams];
    151 } InternalOption;
    152 
    153 static void stringSetter(InternalParamPtr param, InternalRequest* req) {
    154     char* stringCopy = strdup(param->string);
    155     char* opaque = (char*)&req->options;
    156     char** stringField = (char**)(opaque + param->offset);
    157     if (*stringField) {
    158         free(*stringField);
    159     }
    160     *stringField = stringCopy;
    161 }
    162 
    163 static void intSetter(InternalParamPtr param, InternalRequest* req) {
    164     char* opaque = (char*)&req->options;
    165     int* intField = (int*)(opaque + param->offset);
    166     *intField = param->integer;
    167 }
    168 
    169 static void ptrSetter(InternalParamPtr param, InternalRequest* req) {
    170     char* opaque = (char*)&req->options;
    171     void** ptrField = (void**)(opaque + param->offset);
    172     *ptrField = param->ptr;
    173 }
    174 
    175 static void kvSetter(InternalParamPtr param, InternalRequest* req) {
    176     char* opaque = (char*)&req->options;
    177     KVLink** kvField = (KVLink**)(opaque + param->offset);
    178 
    179     naettAlloc(KVLink, newNode);
    180     newNode->key = strdup(param->kv.key);
    181     newNode->value = strdup(param->kv.value);
    182     newNode->next = *kvField;
    183 
    184     *kvField = newNode;
    185 }
    186 
    187 static int defaultBodyReader(void* dest, int bufferSize, void* userData) {
    188     Buffer* buffer = (Buffer*) userData;
    189 
    190     if (dest == NULL) {
    191         return buffer->size;
    192     }
    193 
    194     int bytesToRead = buffer->size - buffer->position;
    195     if (bytesToRead > bufferSize) {
    196         bytesToRead = bufferSize;
    197     }
    198 
    199     const char* source = ((const char*)buffer->data) + buffer->position;
    200     memcpy(dest, source, bytesToRead);
    201     buffer->position += bytesToRead;
    202     return bytesToRead;
    203 }
    204 
    205 static int defaultBodyWriter(const void* source, int bytes, void* userData) {
    206     Buffer* buffer = (Buffer*) userData;
    207     int newCapacity = buffer->capacity;
    208     if (newCapacity == 0) {
    209         newCapacity = bytes;
    210     }
    211     while (newCapacity - buffer->size < bytes) {
    212         newCapacity *= 2;
    213     }
    214     if (newCapacity != buffer->capacity) {
    215         buffer->data = realloc(buffer->data, newCapacity);
    216         buffer->capacity = newCapacity;
    217     }
    218     char* dest = ((char*)buffer->data) + buffer->size;
    219     memcpy(dest, source, bytes);
    220     buffer->size += bytes;
    221     return bytes;
    222 }
    223 
    224 static int initialized = 0;
    225 
    226 static void initRequest(InternalRequest* req, const char* url) {
    227     assert(initialized);
    228     req->options.method = strdup("GET");
    229     req->options.timeoutMS = 5000;
    230     req->url = strdup(url);
    231 }
    232 
    233 static void applyOptionParams(InternalRequest* req, InternalOption* option) {
    234     for (int j = 0; j < option->numParams; j++) {
    235         InternalParam* param = option->params + j;
    236         param->setter(param, req);
    237     }
    238 }
    239 
    240 // Public API
    241 
    242 void naettInit(naettInitData initData) {
    243     assert(!initialized);
    244     naettPlatformInit(initData);
    245     initialized = 1;
    246 }
    247 
    248 naettOption* naettMethod(const char* method) {
    249     naettAlloc(InternalOption, option);
    250     option->numParams = 1;
    251     InternalParam* param = &option->params[0];
    252 
    253     param->string = method;
    254     param->offset = offsetof(RequestOptions, method);
    255     param->setter = stringSetter;
    256 
    257     return (naettOption*)option;
    258 }
    259 
    260 naettOption* naettHeader(const char* name, const char* value) {
    261     naettAlloc(InternalOption, option);
    262     option->numParams = 1;
    263     InternalParam* param = &option->params[0];
    264 
    265     param->kv.key = name;
    266     param->kv.value = value;
    267     param->offset = offsetof(RequestOptions, headers);
    268     param->setter = kvSetter;
    269 
    270     return (naettOption*)option;
    271 }
    272 
    273 naettOption* naettUserAgent(const char* userAgent) {
    274     naettAlloc(InternalOption, option);
    275     option->numParams = 1;
    276     InternalParam* param = &option->params[0];
    277 
    278     param->string = userAgent;
    279     param->offset = offsetof(RequestOptions, userAgent);
    280     param->setter = stringSetter;
    281 
    282     return (naettOption*)option;
    283 }
    284 
    285 naettOption* naettTimeout(int timeoutMS) {
    286     naettAlloc(InternalOption, option);
    287     option->numParams = 1;
    288     InternalParam* param = &option->params[0];
    289 
    290     param->integer = timeoutMS;
    291     param->offset = offsetof(RequestOptions, timeoutMS);
    292     param->setter = intSetter;
    293 
    294     return (naettOption*)option;
    295 }
    296 
    297 naettOption* naettBody(const char* body, int size) {
    298     naettAlloc(InternalOption, option);
    299     option->numParams = 2;
    300 
    301     InternalParam* bodyParam = &option->params[0];
    302     InternalParam* sizeParam = &option->params[1];
    303 
    304     bodyParam->ptr = (void*)body;
    305     bodyParam->offset = offsetof(RequestOptions, body) + offsetof(Buffer, data);
    306     bodyParam->setter = ptrSetter;
    307 
    308     sizeParam->integer = size;
    309     sizeParam->offset = offsetof(RequestOptions, body) + offsetof(Buffer, size);
    310     sizeParam->setter = intSetter;
    311 
    312     return (naettOption*)option;
    313 }
    314 
    315 naettOption* naettBodyReader(naettReadFunc reader, void* userData) {
    316     naettAlloc(InternalOption, option);
    317     option->numParams = 2;
    318 
    319     InternalParam* readerParam = &option->params[0];
    320     InternalParam* dataParam = &option->params[1];
    321 
    322     readerParam->func = (void (*)(void)) reader;
    323     readerParam->offset = offsetof(RequestOptions, bodyReader);
    324     readerParam->setter = ptrSetter;
    325 
    326     dataParam->ptr = userData;
    327     dataParam->offset = offsetof(RequestOptions, bodyReaderData);
    328     dataParam->setter = ptrSetter;
    329 
    330     return (naettOption*)option;
    331 }
    332 
    333 naettOption* naettBodyWriter(naettWriteFunc writer, void* userData) {
    334     naettAlloc(InternalOption, option);
    335     option->numParams = 2;
    336 
    337     InternalParam* writerParam = &option->params[0];
    338     InternalParam* dataParam = &option->params[1];
    339 
    340     writerParam->func = (void(*)(void)) writer;
    341     writerParam->offset = offsetof(RequestOptions, bodyWriter);
    342     writerParam->setter = ptrSetter;
    343 
    344     dataParam->ptr = userData;
    345     dataParam->offset = offsetof(RequestOptions, bodyWriterData);
    346     dataParam->setter = ptrSetter;
    347 
    348     return (naettOption*)option;
    349 }
    350 
    351 void setupDefaultRW(InternalRequest* req) {
    352     if (req->options.bodyReader == NULL) {
    353         req->options.bodyReader = defaultBodyReader;
    354         req->options.bodyReaderData = (void*) &req->options.body;
    355     }
    356     if (req->options.bodyReader == defaultBodyReader) {
    357         req->options.body.position = 0;
    358     }
    359     if (req->options.bodyWriter == NULL) {
    360         req->options.bodyWriter = defaultBodyWriter;
    361     }
    362 }
    363 
    364 naettReq* naettRequest_va(const char* url, int numArgs, ...) {
    365     assert(url != NULL);
    366 
    367     va_list args;
    368     InternalOption* option;
    369     naettAlloc(InternalRequest, req);
    370     initRequest(req, url);
    371 
    372     va_start(args, numArgs);
    373     for (int i = 0; i < numArgs; i++) {
    374         option = va_arg(args, InternalOption*);
    375         applyOptionParams(req, option);
    376         free(option);
    377     }
    378     va_end(args);
    379 
    380     setupDefaultRW(req);
    381 
    382     if (naettPlatformInitRequest(req)) {
    383         return (naettReq*)req;
    384     }
    385 
    386     naettFree((naettReq*) req);
    387     return NULL;
    388 }
    389 
    390 naettReq* naettRequestWithOptions(const char* url, int numOptions, const naettOption** options) {
    391     assert(url != NULL);
    392     assert(numOptions == 0 || options != NULL);
    393 
    394     naettAlloc(InternalRequest, req);
    395     initRequest(req, url);
    396 
    397     for (int i = 0; i < numOptions; i++) {
    398         InternalOption* option = (InternalOption*)options[i];
    399         applyOptionParams(req, option);
    400         free(option);
    401     }
    402 
    403     setupDefaultRW(req);
    404 
    405     if (naettPlatformInitRequest(req)) {
    406         return (naettReq*)req;
    407     }
    408 
    409     naettFree((naettReq*) req);
    410     return NULL;
    411 }
    412 
    413 naettRes* naettMake(naettReq* request) {
    414     assert(initialized);
    415     assert(request != NULL);
    416 
    417     InternalRequest* req = (InternalRequest*)request;
    418     naettAlloc(InternalResponse, res);
    419     res->request = req;
    420 
    421     if (req->options.bodyWriter == defaultBodyWriter) {
    422         req->options.bodyWriterData = (void*) &res->body;
    423     }
    424 
    425     naettPlatformMakeRequest(res);
    426     return (naettRes*) res;
    427 }
    428 
    429 const void* naettGetBody(naettRes* response, int* size) {
    430     assert(response != NULL);
    431     assert(size != NULL);
    432 
    433     InternalResponse* res = (InternalResponse*)response;
    434     *size = res->body.size;
    435     return res->body.data;
    436 }
    437 
    438 int naettGetTotalBytesRead(naettRes* response, int* totalSize) {
    439     assert(response != NULL);
    440     assert(totalSize != NULL);
    441 
    442     InternalResponse* res = (InternalResponse*)response;
    443     *totalSize = res->contentLength;
    444     return res->totalBytesRead;
    445 }
    446 
    447 const char* naettGetHeader(naettRes* response, const char* name) {
    448     assert(response != NULL);
    449     assert(name != NULL);
    450 
    451     InternalResponse* res = (InternalResponse*)response;
    452     KVLink* node = res->headers;
    453     while (node) {
    454         if (strcasecmp(name, node->key) == 0) {
    455             return node->value;
    456         }
    457         node = node->next;
    458     }
    459     return NULL;
    460 }
    461 
    462 void naettListHeaders(naettRes* response, naettHeaderLister lister, void* userData) {
    463     assert(response != NULL);
    464     assert(lister != NULL);
    465 
    466     InternalResponse* res = (InternalResponse*)response;
    467     KVLink* node = res->headers;
    468     while (node) {
    469         if (!lister(node->key, node->value, userData)) {
    470             return;
    471         }
    472         node = node->next;
    473     }
    474 }
    475 
    476 naettReq* naettGetRequest(naettRes* response) {
    477     assert(response != NULL);
    478     InternalResponse* res = (InternalResponse*)response;
    479     return (naettReq*) res->request;
    480 }
    481 
    482 int naettComplete(const naettRes* response) {
    483     assert(response != NULL);
    484     InternalResponse* res = (InternalResponse*)response;
    485     return res->complete;
    486 }
    487 
    488 int naettGetStatus(const naettRes* response) {
    489     assert(response != NULL);
    490     InternalResponse* res = (InternalResponse*)response;
    491     return res->code;
    492 }
    493 
    494 static void freeKVList(KVLink* node) {
    495     while (node != NULL) {
    496         free((void*) node->key);
    497         free((void*) node->value);
    498         KVLink* next = node->next;
    499         free(node);
    500         node = next;
    501     }
    502 }
    503 
    504 void naettFree(naettReq* request) {
    505     assert(request != NULL);
    506 
    507     InternalRequest* req = (InternalRequest*)request;
    508     naettPlatformFreeRequest(req);
    509     KVLink* node = req->options.headers;
    510     freeKVList(node);
    511     free((void*)req->options.method);
    512     free((void*)req->url);
    513     free(request);
    514 }
    515 
    516 void naettClose(naettRes* response) {
    517     assert(response != NULL);
    518 
    519     InternalResponse* res = (InternalResponse*)response;
    520     res->request = NULL;
    521     naettPlatformCloseResponse(res);
    522     KVLink* node = res->headers;
    523     freeKVList(node);
    524     free(res->body.data);
    525     free(response);
    526 }
    527 // End of inlined naett_core.c //
    528 
    529 // Inlined naett_osx.c: //
    530 //#include "naett_internal.h"
    531 
    532 #ifdef __APPLE__
    533 
    534 // Inlined naett_objc.h: //
    535 #ifndef NAETT_OBJC_H
    536 #define NAETT_OBJC_H
    537 
    538 #if defined(__IOS__) || defined (__MACOS__)
    539 #include <assert.h>
    540 #include <math.h>
    541 
    542 #include <objc/NSObjCRuntime.h>
    543 #include <objc/message.h>
    544 #include <objc/objc.h>
    545 #include <objc/runtime.h>
    546 
    547 #if defined(__OBJC__) && __has_feature(objc_arc)
    548 #error "ARC is not supported"
    549 #endif
    550 
    551 // ABI is a bit different between platforms
    552 #ifdef __arm64__
    553 #define abi_objc_msgSend_stret objc_msgSend
    554 #else
    555 #define abi_objc_msgSend_stret objc_msgSend_stret
    556 #endif
    557 #ifdef __i386__
    558 #define abi_objc_msgSend_fpret objc_msgSend_fpret
    559 #else
    560 #define abi_objc_msgSend_fpret objc_msgSend
    561 #endif
    562 
    563 #define objc_msgSendSuper_t(RET, ...) ((RET(*)(struct objc_super*, SEL, ##__VA_ARGS__))objc_msgSendSuper)
    564 #define objc_msgSend_t(RET, ...) ((RET(*)(id, SEL, ##__VA_ARGS__))objc_msgSend)
    565 #define objc_msgSend_stret_t(RET, ...) ((RET(*)(id, SEL, ##__VA_ARGS__))abi_objc_msgSend_stret)
    566 #define objc_msgSend_id objc_msgSend_t(id)
    567 #define objc_msgSend_void objc_msgSend_t(void)
    568 #define objc_msgSend_void_id objc_msgSend_t(void, id)
    569 #define objc_msgSend_void_bool objc_msgSend_t(void, bool)
    570 
    571 #define sel(NAME) sel_registerName(NAME)
    572 #define class(NAME) ((id)objc_getClass(NAME))
    573 #define makeClass(NAME, SUPER) \
    574     objc_allocateClassPair((Class)objc_getClass(SUPER), NAME, 0)
    575 
    576 // Check here to get the signature right:
    577 // https://nshipster.com/type-encodings/
    578 // https://ko9.org/posts/encode-types/
    579 #define addMethod(CLASS, NAME, IMPL, SIGNATURE) \
    580     if (!class_addMethod(CLASS, sel(NAME), (IMP) (IMPL), (SIGNATURE))) assert(false)
    581 
    582 #define addIvar(CLASS, NAME, SIZE, SIGNATURE) \
    583     if (!class_addIvar(CLASS, NAME, SIZE, rint(log2(SIZE)), SIGNATURE)) assert(false)
    584 
    585 #define objc_alloc(CLASS) objc_msgSend_id(class(CLASS), sel("alloc"))
    586 #define autorelease(OBJ) objc_msgSend_void(OBJ, sel("autorelease"))
    587 #define retain(OBJ) objc_msgSend_void(OBJ, sel("retain"))
    588 #define release(OBJ) objc_msgSend_void(OBJ, sel("release"))
    589 
    590 #if __LP64__ || NS_BUILD_32_LIKE_64
    591 #define NSIntegerEncoding "q"
    592 #define NSUIntegerEncoding "L"
    593 #else
    594 #define NSIntegerEncoding "i"
    595 #define NSUIntegerEncoding "I"
    596 #endif
    597 
    598 #endif // defined(__IOS__) || defined (__MACOS__)
    599 #endif // NAETT_OBJC_H
    600 // End of inlined naett_objc.h //
    601 
    602 #include <stdlib.h>
    603 #include <string.h>
    604 #include <stdio.h>
    605 
    606 #ifdef DEBUG
    607 static void _showPools(const char* context) {
    608     fprintf(stderr, "NSAutoreleasePool@%s:\n", context);
    609     objc_msgSend_void(class("NSAutoreleasePool"), sel("showPools"));
    610 }
    611 #define showPools(x) _showPools((x))
    612 #else
    613 #define showPools(x)
    614 #endif
    615 
    616 static id pool(void) {
    617     return objc_msgSend_id(objc_alloc("NSAutoreleasePool"), sel("init"));
    618 }
    619 
    620 static id sessionConfiguration = nil;
    621 
    622 void naettPlatformInit(naettInitData initData) {
    623     id NSThread = class("NSThread");
    624     SEL isMultiThreaded = sel("isMultiThreaded");
    625 
    626     if (!objc_msgSend_t(bool)(NSThread, isMultiThreaded)) {
    627         // Make a dummy call from a new thread to kick Cocoa into multi-threaded mode
    628         objc_msgSend_t(void, SEL, id, id)(
    629             NSThread, sel("detachNewThreadSelector:toTarget:withObject:"), isMultiThreaded, NSThread, nil);
    630     }
    631 
    632     sessionConfiguration = objc_msgSend_id(class("NSURLSessionConfiguration"), sel("ephemeralSessionConfiguration"));
    633     retain(sessionConfiguration);
    634 }
    635 
    636 id NSString(const char* string) {
    637     return objc_msgSend_t(id, const char*)(class("NSString"), sel("stringWithUTF8String:"), string);
    638 }
    639 
    640 int naettPlatformInitRequest(InternalRequest* req) {
    641     id p = pool();
    642 
    643     id urlString = NSString(req->url);
    644     id url = objc_msgSend_t(id, id)(class("NSURL"), sel("URLWithString:"), urlString);
    645 
    646     id request = objc_msgSend_t(id, id)(class("NSMutableURLRequest"), sel("requestWithURL:"), url);
    647 
    648     objc_msgSend_t(void, double)(request, sel("setTimeoutInterval:"), (double)(req->options.timeoutMS) / 1000.0);
    649     id methodString = NSString(req->options.method);
    650     objc_msgSend_t(void, id)(request, sel("setHTTPMethod:"), methodString);
    651 
    652     {
    653         id name = NSString("User-Agent");
    654         id value = NSString(req->options.userAgent ? req->options.userAgent : NAETT_UA);
    655         objc_msgSend_t(void, id, id)(request, sel("setValue:forHTTPHeaderField:"), value, name);
    656     }
    657 
    658     KVLink* header = req->options.headers;
    659     while (header != NULL) {
    660         id name = NSString(header->key);
    661         id value = NSString(header->value);
    662         objc_msgSend_t(void, id, id)(request, sel("setValue:forHTTPHeaderField:"), value, name);
    663         header = header->next;
    664     }
    665 
    666     char byteBuffer[10240];
    667     int bytesRead = 0;
    668 
    669     if (req->options.bodyReader != NULL) {
    670         id bodyData =
    671             objc_msgSend_t(id, NSUInteger)(class("NSMutableData"), sel("dataWithCapacity:"), sizeof(byteBuffer));
    672 
    673         int totalBytesRead = 0;
    674         do {
    675             bytesRead = req->options.bodyReader(byteBuffer, sizeof(byteBuffer), req->options.bodyReaderData);
    676             totalBytesRead += bytesRead;
    677             objc_msgSend_t(void, const void*, NSUInteger)(bodyData, sel("appendBytes:length:"), byteBuffer, bytesRead);
    678         } while (bytesRead > 0);
    679 
    680         if (totalBytesRead > 0) {
    681             objc_msgSend_t(void, id)(request, sel("setHTTPBody:"), bodyData);
    682         }
    683     }
    684 
    685     retain(request);
    686     req->urlRequest = request;
    687 
    688     release(p);
    689     return 1;
    690 }
    691 
    692 void didReceiveData(id self, SEL _sel, id session, id dataTask, id data) {
    693     InternalResponse* res = NULL;
    694     id p = pool();
    695 
    696     object_getInstanceVariable(self, "response", (void**)&res);
    697 
    698     if (res->headers == NULL) {
    699         id response = objc_msgSend_t(id)(dataTask, sel("response"));
    700         res->code = objc_msgSend_t(NSInteger)(response, sel("statusCode"));
    701         id allHeaders = objc_msgSend_t(id)(response, sel("allHeaderFields"));
    702 
    703         NSUInteger headerCount = objc_msgSend_t(NSUInteger)(allHeaders, sel("count"));
    704         id headerNames[headerCount];
    705         id headerValues[headerCount];
    706 
    707         objc_msgSend_t(NSInteger, id*, id*, NSUInteger)(
    708             allHeaders, sel("getObjects:andKeys:count:"), headerValues, headerNames, headerCount);
    709         KVLink* firstHeader = NULL;
    710         for (int i = 0; i < headerCount; i++) {
    711             naettAlloc(KVLink, node);
    712             node->key = strdup(objc_msgSend_t(const char*)(headerNames[i], sel("UTF8String")));
    713             node->value = strdup(objc_msgSend_t(const char*)(headerValues[i], sel("UTF8String")));
    714             node->next = firstHeader;
    715             firstHeader = node;
    716         }
    717         res->headers = firstHeader;
    718 
    719         const char* contentLength = naettGetHeader((naettRes*)res, "Content-Length");
    720         if (!contentLength || sscanf(contentLength, "%d", &res->contentLength) != 1) {
    721             res->contentLength = -1;
    722         }
    723     }
    724 
    725     const void* bytes = objc_msgSend_t(const void*)(data, sel("bytes"));
    726     NSUInteger length = objc_msgSend_t(NSUInteger)(data, sel("length"));
    727 
    728     res->request->options.bodyWriter(bytes, length, res->request->options.bodyWriterData);
    729     res->totalBytesRead += (int)length;
    730 
    731     release(p);
    732 }
    733 
    734 static void didComplete(id self, SEL _sel, id session, id dataTask, id error) {
    735     InternalResponse* res = NULL;
    736     object_getInstanceVariable(self, "response", (void**)&res);
    737     if (res != NULL) {
    738         if (error != nil) {
    739             res->code = naettConnectionError;
    740         }
    741         res->complete = 1;
    742     }
    743 }
    744 
    745 static id createDelegate(void) {
    746     static Class TaskDelegateClass = nil;
    747 
    748     if (!TaskDelegateClass) {
    749         TaskDelegateClass = objc_allocateClassPair((Class)objc_getClass("NSObject"), "naettTaskDelegate", 0);
    750         class_addProtocol(TaskDelegateClass, (Protocol* _Nonnull)objc_getProtocol("NSURLSessionDataDelegate"));
    751 
    752         addMethod(TaskDelegateClass, "URLSession:dataTask:didReceiveData:", didReceiveData, "v@:@@@");
    753         addMethod(TaskDelegateClass, "URLSession:task:didCompleteWithError:", didComplete, "v@:@@@");
    754         addIvar(TaskDelegateClass, "response", sizeof(void*), "^v");
    755     }
    756 
    757     id delegate = objc_msgSend_id((id)TaskDelegateClass, sel("alloc"));
    758     delegate = objc_msgSend_id(delegate, sel("init"));
    759 
    760     return delegate;
    761 }
    762 
    763 void naettPlatformMakeRequest(InternalResponse* res) {
    764     InternalRequest* req = res->request;
    765     id p = pool();
    766 
    767     id delegate = createDelegate();
    768     // delegate will be retained by session below
    769     autorelease(delegate);
    770     object_setInstanceVariable(delegate, "response", (void*)res);
    771 
    772     id session = objc_msgSend_t(id, id, id, id)(class("NSURLSession"),
    773         sel("sessionWithConfiguration:delegate:delegateQueue:"),
    774         sessionConfiguration,
    775         delegate,
    776         nil);
    777 
    778     res->session = session;
    779 
    780     id task = objc_msgSend_t(id, id)(session, sel("dataTaskWithRequest:"), req->urlRequest);
    781     objc_msgSend_void(task, sel("resume"));
    782 
    783     release(p);
    784 }
    785 
    786 void naettPlatformFreeRequest(InternalRequest* req) {
    787     release(req->urlRequest);
    788     req->urlRequest = nil;
    789 }
    790 
    791 void naettPlatformCloseResponse(InternalResponse* res) {
    792     objc_msgSend_void(res->session, sel("invalidateAndCancel"));
    793     res->session = nil;
    794 }
    795 
    796 #endif  // __APPLE__
    797 // End of inlined naett_osx.c //
    798 
    799 // Inlined naett_linux.c: //
    800 //#include "naett_internal.h"
    801 
    802 #if __linux__ && !__ANDROID__
    803 
    804 #include <curl/curl.h>
    805 #include <assert.h>
    806 #include <pthread.h>
    807 #include <unistd.h>
    808 #include <fcntl.h>
    809 #include <string.h>
    810 
    811 static pthread_t workerThread;
    812 static int handleReadFD = 0;
    813 static int handleWriteFD = 0;
    814 
    815 static void panic(const char* message) {
    816     fprintf(stderr, "%s\n", message);
    817     exit(1);
    818 }
    819 
    820 static void* curlWorker(void* data) {
    821     CURLM* mc = (CURLM*)data;
    822     int activeHandles = 0;
    823     int messagesLeft = 1;
    824 
    825     struct curl_waitfd readFd = { handleReadFD, CURL_WAIT_POLLIN };
    826 
    827     union {
    828         CURL* handle;
    829         char buf[sizeof(CURL*)];
    830     } newHandle;
    831 
    832     int newHandlePos = 0;
    833 
    834     while (1) {
    835         int status = curl_multi_perform(mc, &activeHandles);
    836         if (status != CURLM_OK) {
    837             panic("CURL processing failure");
    838         }
    839 
    840         struct CURLMsg* message = curl_multi_info_read(mc, &messagesLeft);
    841         if (message && message->msg == CURLMSG_DONE) {
    842             CURL* handle = message->easy_handle;
    843             InternalResponse* res = NULL;
    844             curl_easy_getinfo(handle, CURLINFO_PRIVATE, (char**)&res);
    845             curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &res->code);
    846             res->complete = 1;
    847             curl_easy_cleanup(handle);
    848         }
    849 
    850 
    851         int readyFDs = 0;
    852         curl_multi_wait(mc, &readFd, 1, 1, &readyFDs);
    853 
    854         if (readyFDs == 0 && activeHandles == 0 && messagesLeft == 0) {
    855             usleep(100 * 1000);
    856         }
    857 
    858         int bytesRead = read(handleReadFD, newHandle.buf, sizeof(newHandle.buf) - newHandlePos);
    859         if (bytesRead > 0) {
    860             newHandlePos += bytesRead;
    861         }
    862         if (newHandlePos == sizeof(newHandle.buf)) {
    863             curl_multi_add_handle(mc, newHandle.handle);
    864             newHandlePos = 0;
    865         }
    866     }
    867 
    868     return NULL;
    869 }
    870 
    871 void naettPlatformInit(naettInitData initData) {
    872     curl_global_init(CURL_GLOBAL_ALL);
    873     CURLM* mc = curl_multi_init();
    874     int fds[2];
    875     if (pipe(fds) != 0) {
    876         panic("Failed to open pipe");
    877     }
    878     handleReadFD = fds[0];
    879     handleWriteFD = fds[1];
    880 
    881     int flags = fcntl(handleReadFD, F_GETFL, 0);
    882     fcntl(handleReadFD, F_SETFL, flags | O_NONBLOCK);
    883 
    884     pthread_attr_t attr;
    885     pthread_attr_init(&attr);
    886     pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    887     pthread_create(&workerThread, &attr, curlWorker, mc);
    888 }
    889 
    890 int naettPlatformInitRequest(InternalRequest* req) {
    891     return 1;
    892 }
    893 
    894 static size_t readCallback(char* buffer, size_t size, size_t numItems, void* userData) {
    895     InternalResponse* res = (InternalResponse*)userData;
    896     InternalRequest* req = res->request;
    897     return req->options.bodyReader(buffer, size * numItems, req->options.bodyReaderData);
    898 }
    899 
    900 static size_t writeCallback(char* ptr, size_t size, size_t numItems, void* userData) {
    901     InternalResponse* res = (InternalResponse*)userData;
    902     InternalRequest* req = res->request;
    903     size_t bytesWritten = req->options.bodyWriter(ptr, size * numItems, req->options.bodyWriterData);
    904     res->totalBytesRead += bytesWritten;
    905     return bytesWritten;
    906 }
    907 
    908 #define METHOD(A, B, C) (((A) << 16) | ((B) << 8) | (C))
    909 
    910 static void setupMethod(CURL* curl, const char* method) {
    911     if (strlen(method) < 3) {
    912         return;
    913     }
    914 
    915     int methodCode = (method[0] << 16) | (method[1] << 8) | method[2];
    916 
    917     switch (methodCode) {
    918         case METHOD('G', 'E', 'T'):
    919         case METHOD('C', 'O', 'N'):
    920         case METHOD('O', 'P', 'T'):
    921             curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
    922             break;
    923         case METHOD('P', 'O', 'S'):
    924         case METHOD('P', 'A', 'T'):
    925         case METHOD('D', 'E', 'L'):
    926             curl_easy_setopt(curl, CURLOPT_POST, 1);
    927             break;
    928         case METHOD('P', 'U', 'T'):
    929             curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);
    930             break;
    931         case METHOD('H', 'E', 'A'):
    932         case METHOD('T', 'R', 'A'):
    933             curl_easy_setopt(curl, CURLOPT_NOBODY, 1);
    934             break;
    935     }
    936 
    937     curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, method);
    938 }
    939 
    940 static size_t headerCallback(char* buffer, size_t size, size_t nitems, void* userData) {
    941     InternalResponse* res = (InternalResponse*) userData;
    942     size_t headerSize = size * nitems;
    943 
    944     char* headerName = strndup(buffer, headerSize);
    945     char* split = strchr(headerName, ':');
    946     if (split) {
    947         *split = 0;
    948         split++;
    949         while (*split == ' ') {
    950             split++;
    951         }
    952         char* headerValue = strdup(split);
    953 
    954         char* cr = strchr(headerValue, 13);
    955         if (cr) {
    956             *cr = 0;
    957         }
    958 
    959         char* lf = strchr(headerValue, 10);
    960         if (lf) {
    961             *lf = 0;
    962         }
    963 
    964         naettAlloc(KVLink, node);
    965         node->next = res->headers;
    966         node->key = headerName;
    967         node->value = headerValue;
    968         res->headers = node;
    969     }
    970 
    971     return headerSize;
    972 }
    973 
    974 void naettPlatformMakeRequest(InternalResponse* res) {
    975     InternalRequest* req = res->request;
    976 
    977     CURL* c = curl_easy_init();
    978     curl_easy_setopt(c, CURLOPT_URL, req->url);
    979     curl_easy_setopt(c, CURLOPT_CONNECTTIMEOUT_MS, req->options.timeoutMS);
    980 
    981     curl_easy_setopt(c, CURLOPT_READFUNCTION, readCallback);
    982     curl_easy_setopt(c, CURLOPT_READDATA, res);
    983 
    984     curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, writeCallback);
    985     curl_easy_setopt(c, CURLOPT_WRITEDATA, res);
    986 
    987     curl_easy_setopt(c, CURLOPT_HEADERFUNCTION, headerCallback);
    988     curl_easy_setopt(c, CURLOPT_HEADERDATA, res);
    989 
    990     curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1);
    991 
    992     int bodySize = res->request->options.bodyReader(NULL, 0, res->request->options.bodyReaderData);
    993     curl_easy_setopt(c, CURLOPT_POSTFIELDSIZE, bodySize);
    994 
    995     setupMethod(c, req->options.method);
    996 
    997     struct curl_slist* headerList = NULL;
    998     char uaBuf[512];
    999     snprintf(uaBuf, sizeof(uaBuf), "User-Agent: %s", req->options.userAgent ? req->options.userAgent : NAETT_UA);
   1000     headerList = curl_slist_append(headerList, uaBuf);
   1001 
   1002     KVLink* header = req->options.headers;
   1003     size_t bufferSize = 0;
   1004     char* buffer = NULL;
   1005     while (header) {
   1006         size_t headerLength = strlen(header->key) + strlen(header->value) + 1 + 1;  // colon + null
   1007         if (headerLength > bufferSize) {
   1008             bufferSize = headerLength;
   1009             buffer = (char*)realloc(buffer, bufferSize);
   1010         }
   1011         snprintf(buffer, bufferSize, "%s:%s", header->key, header->value);
   1012         headerList = curl_slist_append(headerList, buffer);
   1013         header = header->next;
   1014     }
   1015     curl_easy_setopt(c, CURLOPT_HTTPHEADER, headerList);
   1016     free(buffer);
   1017     res->headerList = headerList;
   1018 
   1019     curl_easy_setopt(c, CURLOPT_PRIVATE, res);
   1020 
   1021     write(handleWriteFD, &c, sizeof(c));
   1022 }
   1023 
   1024 void naettPlatformFreeRequest(InternalRequest* req) {
   1025 }
   1026 
   1027 void naettPlatformCloseResponse(InternalResponse* res) {
   1028     curl_slist_free_all(res->headerList);
   1029 }
   1030 
   1031 #endif
   1032 // End of inlined naett_linux.c //
   1033 
   1034 // Inlined naett_win.c: //
   1035 //#include "naett_internal.h"
   1036 
   1037 #ifdef __WINDOWS__
   1038 
   1039 #include <stdlib.h>
   1040 #include <stdio.h>
   1041 #include <string.h>
   1042 #include <winhttp.h>
   1043 #include <assert.h>
   1044 #include <tchar.h>
   1045 
   1046 void naettPlatformInit(naettInitData initData) {
   1047 }
   1048 
   1049 static char* winToUTF8(LPWSTR source) {
   1050     int length = WideCharToMultiByte(CP_UTF8, 0, source, -1, NULL, 0, NULL, NULL);
   1051     char* chars = (char*)malloc(length);
   1052     int result = WideCharToMultiByte(CP_UTF8, 0, source, -1, chars, length, NULL, NULL);
   1053     if (!result) {
   1054         free(chars);
   1055         return NULL;
   1056     }
   1057     return chars;
   1058 }
   1059 
   1060 static LPWSTR winFromUTF8(const char* source) {
   1061     int length = MultiByteToWideChar(CP_UTF8, 0, source, -1, NULL, 0);
   1062     LPWSTR chars = (LPWSTR)malloc(length * sizeof(WCHAR));
   1063     int result = MultiByteToWideChar(CP_UTF8, 0, source, -1, chars, length);
   1064     if (!result) {
   1065         free(chars);
   1066         return NULL;
   1067     }
   1068     return chars;
   1069 }
   1070 
   1071 #define ASPRINTF(result, fmt, ...)                        \
   1072     {                                                     \
   1073         size_t len = snprintf(NULL, 0, fmt, __VA_ARGS__); \
   1074         *(result) = (char*)malloc(len + 1);               \
   1075         snprintf(*(result), len + 1, fmt, __VA_ARGS__);   \
   1076     }
   1077 
   1078 static LPWSTR wcsndup(LPCWSTR str, size_t len) {
   1079     LPWSTR result = calloc(1, sizeof(WCHAR) * (len + 1));
   1080     wcsncpy(result, str, len);
   1081     return result;
   1082 }
   1083 
   1084 static LPCWSTR packHeaders(InternalRequest* req) {
   1085     char* packed = strdup("");
   1086 
   1087     KVLink* node = req->options.headers;
   1088     while (node != NULL) {
   1089         char* update;
   1090         ASPRINTF(&update, "%s%s:%s%s", packed, node->key, node->value, node->next ? "\r\n" : "");
   1091         free(packed);
   1092         packed = update;
   1093         node = node->next;
   1094     }
   1095 
   1096     LPCWSTR winHeaders = winFromUTF8(packed);
   1097     free(packed);
   1098     return winHeaders;
   1099 }
   1100 
   1101 static void unpackHeaders(InternalResponse* res, LPWSTR packed) {
   1102     size_t len = 0;
   1103     KVLink* firstHeader = NULL;
   1104     while ((len = wcslen(packed)) != 0) {
   1105         char* header = winToUTF8(packed);
   1106         char* split = strchr(header, ':');
   1107         if (split) {
   1108             *split = 0;
   1109             split++;
   1110             while (*split == ' ') {
   1111                 split++;
   1112             }
   1113             naettAlloc(KVLink, node);
   1114             node->key = strdup(header);
   1115             node->value = strdup(split);
   1116             node->next = firstHeader;
   1117             firstHeader = node;
   1118         }
   1119         free(header);
   1120         packed += len + 1;
   1121     }
   1122     res->headers = firstHeader;
   1123 }
   1124 
   1125 static void CALLBACK
   1126 callback(HINTERNET request, DWORD_PTR context, DWORD status, LPVOID statusInformation, DWORD statusInfoLength) {
   1127     InternalResponse* res = (InternalResponse*)context;
   1128 
   1129     switch (status) {
   1130         case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE: {
   1131             DWORD bufSize = 0;
   1132             WinHttpQueryHeaders(request,
   1133                 WINHTTP_QUERY_RAW_HEADERS,
   1134                 WINHTTP_HEADER_NAME_BY_INDEX,
   1135                 NULL,
   1136                 &bufSize,
   1137                 WINHTTP_NO_HEADER_INDEX);
   1138             LPWSTR buffer = (LPWSTR)malloc(bufSize);
   1139             WinHttpQueryHeaders(request,
   1140                 WINHTTP_QUERY_RAW_HEADERS,
   1141                 WINHTTP_HEADER_NAME_BY_INDEX,
   1142                 buffer,
   1143                 &bufSize,
   1144                 WINHTTP_NO_HEADER_INDEX);
   1145             unpackHeaders(res, buffer);
   1146             free(buffer);
   1147 
   1148             const char* contentLength = naettGetHeader((naettRes*)res, "Content-Length");
   1149             if (!contentLength || sscanf(contentLength, "%d", &res->contentLength) != 1) {
   1150                 res->contentLength = -1;
   1151             }
   1152 
   1153             DWORD statusCode = 0;
   1154             DWORD statusCodeSize = sizeof(statusCode);
   1155 
   1156             WinHttpQueryHeaders(request,
   1157                 WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
   1158                 WINHTTP_HEADER_NAME_BY_INDEX,
   1159                 &statusCode,
   1160                 &statusCodeSize,
   1161                 WINHTTP_NO_HEADER_INDEX);
   1162             res->code = statusCode;
   1163 
   1164             if (!WinHttpQueryDataAvailable(request, NULL)) {
   1165                 res->code = naettProtocolError;
   1166                 res->complete = 1;
   1167             }
   1168         } break;
   1169 
   1170         case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE: {
   1171             DWORD* available = (DWORD*)statusInformation;
   1172             res->bytesLeft = *available;
   1173             if (res->bytesLeft == 0) {
   1174                 res->complete = 1;
   1175                 break;
   1176             }
   1177 
   1178             size_t bytesToRead = min(res->bytesLeft, sizeof(res->buffer));
   1179             if (!WinHttpReadData(request, res->buffer, (DWORD)bytesToRead, NULL)) {
   1180                 res->code = naettReadError;
   1181                 res->complete = 1;
   1182             }
   1183         } break;
   1184 
   1185         case WINHTTP_CALLBACK_STATUS_READ_COMPLETE: {
   1186             size_t bytesRead = statusInfoLength;
   1187 
   1188             InternalRequest* req = res->request;
   1189             if (req->options.bodyWriter(res->buffer, (int)bytesRead, req->options.bodyWriterData) != bytesRead) {
   1190                 res->code = naettReadError;
   1191                 res->complete = 1;
   1192             }
   1193             res->totalBytesRead += (int)bytesRead;
   1194             res->bytesLeft -= bytesRead;
   1195             if (res->bytesLeft > 0) {
   1196                 size_t bytesToRead = min(res->bytesLeft, sizeof(res->buffer));
   1197                 if (!WinHttpReadData(request, res->buffer, (DWORD)bytesToRead, NULL)) {
   1198                     res->code = naettReadError;
   1199                     res->complete = 1;
   1200                 }
   1201             } else {
   1202                 if (!WinHttpQueryDataAvailable(request, NULL)) {
   1203                     res->code = naettProtocolError;
   1204                     res->complete = 1;
   1205                 }
   1206             }
   1207         } break;
   1208 
   1209         case WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE:
   1210         case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE: {
   1211             int bytesRead = res->request->options.bodyReader(
   1212                 res->buffer, sizeof(res->buffer), res->request->options.bodyReaderData);
   1213             if (bytesRead) {
   1214                 WinHttpWriteData(request, res->buffer, bytesRead, NULL);
   1215             } else {
   1216                 if (!WinHttpReceiveResponse(request, NULL)) {
   1217                     res->code = naettReadError;
   1218                     res->complete = 1;
   1219                 }
   1220             }
   1221         } break;
   1222 
   1223         //
   1224         case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR: {
   1225             WINHTTP_ASYNC_RESULT* result = (WINHTTP_ASYNC_RESULT*)statusInformation;
   1226             switch (result->dwResult) {
   1227                 case API_RECEIVE_RESPONSE:
   1228                 case API_QUERY_DATA_AVAILABLE:
   1229                 case API_READ_DATA:
   1230                     res->code = naettReadError;
   1231                     break;
   1232                 case API_WRITE_DATA:
   1233                     res->code = naettWriteError;
   1234                     break;
   1235                 case API_SEND_REQUEST:
   1236                     res->code = naettConnectionError;
   1237                     break;
   1238                 default:
   1239                     res->code = naettGenericError;
   1240             }
   1241 
   1242             res->complete = 1;
   1243         } break;
   1244     }
   1245 }
   1246 
   1247 int naettPlatformInitRequest(InternalRequest* req) {
   1248     LPWSTR url = winFromUTF8(req->url);
   1249 
   1250     URL_COMPONENTS components;
   1251     ZeroMemory(&components, sizeof(components));
   1252     components.dwStructSize = sizeof(components);
   1253     components.dwSchemeLength = (DWORD)-1;
   1254     components.dwHostNameLength = (DWORD)-1;
   1255     components.dwUrlPathLength = (DWORD)-1;
   1256     components.dwExtraInfoLength = (DWORD)-1;
   1257     BOOL cracked = WinHttpCrackUrl(url, 0, 0, &components);
   1258 
   1259     if (!cracked) {
   1260         free(url);
   1261         return 0;
   1262     }
   1263 
   1264     req->host = wcsndup(components.lpszHostName, components.dwHostNameLength);
   1265     req->resource = wcsndup(components.lpszUrlPath, components.dwUrlPathLength + components.dwExtraInfoLength);
   1266     free(url);
   1267 
   1268     LPWSTR uaBuf = winFromUTF8(req->options.userAgent ? req->options.userAgent : NAETT_UA);
   1269     req->session = WinHttpOpen(uaBuf,
   1270         WINHTTP_ACCESS_TYPE_NO_PROXY,
   1271         WINHTTP_NO_PROXY_NAME,
   1272         WINHTTP_NO_PROXY_BYPASS,
   1273         WINHTTP_FLAG_ASYNC);
   1274     free(uaBuf);
   1275 
   1276     if (!req->session) {
   1277         return 0;
   1278     }
   1279 
   1280     WinHttpSetStatusCallback(req->session, callback, WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS, 0);
   1281 
   1282     // Set the connect timeout. Leave the other three timeouts at their default values.
   1283     WinHttpSetTimeouts(req->session, 0, req->options.timeoutMS, 30000, 30000);
   1284 
   1285     req->connection = WinHttpConnect(req->session, req->host, components.nPort, 0);
   1286     if (!req->connection) {
   1287         naettPlatformFreeRequest(req);
   1288         return 0;
   1289     }
   1290 
   1291     LPWSTR verb = winFromUTF8(req->options.method);
   1292     req->request = WinHttpOpenRequest(req->connection,
   1293         verb,
   1294         req->resource,
   1295         NULL,
   1296         WINHTTP_NO_REFERER,
   1297         WINHTTP_DEFAULT_ACCEPT_TYPES,
   1298         components.nScheme == INTERNET_SCHEME_HTTPS ? WINHTTP_FLAG_SECURE : 0);
   1299     free(verb);
   1300     if (!req->request) {
   1301         naettPlatformFreeRequest(req);
   1302         return 0;
   1303     }
   1304 
   1305     LPCWSTR headers = packHeaders(req);
   1306     if (headers[0] != 0) {
   1307         if (!WinHttpAddRequestHeaders(
   1308                 req->request, headers, -1, WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) {
   1309             naettPlatformFreeRequest(req);
   1310             free((LPWSTR)headers);
   1311             return 0;
   1312         }
   1313     }
   1314     free((LPWSTR)headers);
   1315 
   1316     return 1;
   1317 }
   1318 
   1319 void naettPlatformMakeRequest(InternalResponse* res) {
   1320     InternalRequest* req = res->request;
   1321 
   1322     LPCWSTR extraHeaders = WINHTTP_NO_ADDITIONAL_HEADERS;
   1323     WCHAR contentLengthHeader[64];
   1324 
   1325     int contentLength = req->options.bodyReader(NULL, 0, req->options.bodyReaderData);
   1326     if (contentLength > 0) {
   1327         swprintf(contentLengthHeader, 64, L"Content-Length: %d", contentLength);
   1328         extraHeaders = contentLengthHeader;
   1329     }
   1330 
   1331     if (!WinHttpSendRequest(req->request, extraHeaders, -1, NULL, 0, 0, (DWORD_PTR)res)) {
   1332         res->code = naettConnectionError;
   1333         res->complete = 1;
   1334     }
   1335 }
   1336 
   1337 void naettPlatformFreeRequest(InternalRequest* req) {
   1338     assert(req != NULL);
   1339 
   1340     if (req->request != NULL) {
   1341         WinHttpCloseHandle(req->request);
   1342         req->request = NULL;
   1343     }
   1344     if (req->connection != NULL) {
   1345         WinHttpCloseHandle(req->connection);
   1346         req->connection = NULL;
   1347     }
   1348     if (req->session != NULL) {
   1349         WinHttpCloseHandle(req->session);
   1350         req->session = NULL;
   1351     }
   1352     if (req->host != NULL) {
   1353         free(req->host);
   1354         req->host = NULL;
   1355     }
   1356     if (req->resource != NULL) {
   1357         free(req->resource);
   1358         req->resource = NULL;
   1359     }
   1360 }
   1361 
   1362 void naettPlatformCloseResponse(InternalResponse* res) {
   1363 }
   1364 
   1365 #endif  // __WINDOWS__
   1366 // End of inlined naett_win.c //
   1367 
   1368 // Inlined naett_android.c: //
   1369 //#include "naett_internal.h"
   1370 
   1371 #ifdef __ANDROID__
   1372 
   1373 #ifdef __cplusplus
   1374 #error "Cannot build in C++ mode"
   1375 #endif
   1376 
   1377 #include <stdlib.h>
   1378 #include <string.h>
   1379 #include <jni.h>
   1380 #include <android/log.h>
   1381 #include <pthread.h>
   1382 #include <stdarg.h>
   1383 
   1384 #ifndef NDEBUG
   1385 #define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, "naett", __VA_ARGS__))
   1386 #else
   1387 #define LOGD(...) ((void)0)
   1388 #endif
   1389 #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "naett", __VA_ARGS__))
   1390 #define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "naett", __VA_ARGS__))
   1391 
   1392 static JavaVM* globalVM = NULL;
   1393 
   1394 static JavaVM* getVM() {
   1395     if (globalVM == NULL) {
   1396         LOGE("Panic: No VM configured, exiting.");
   1397         exit(42);
   1398     }
   1399     return globalVM;
   1400 }
   1401 
   1402 static JNIEnv* getEnv() {
   1403     JavaVM* vm = getVM();
   1404     JNIEnv* env;
   1405     (*vm)->AttachCurrentThread(vm, &env, NULL);
   1406     return env;
   1407 }
   1408 
   1409 static int catch (JNIEnv* env) {
   1410     int thrown = (*env)->ExceptionCheck(env);
   1411     if (thrown) {
   1412         (*env)->ExceptionDescribe(env);
   1413     }
   1414     return thrown;
   1415 }
   1416 
   1417 static jmethodID getMethod(JNIEnv* env, jobject instance, const char* method, const char* sig) {
   1418     jclass clazz = (*env)->GetObjectClass(env, instance);
   1419     jmethodID id = (*env)->GetMethodID(env, clazz, method, sig);
   1420     (*env)->DeleteLocalRef(env, clazz);
   1421     return id;
   1422 }
   1423 
   1424 static jobject call(JNIEnv* env, jobject instance, const char* method, const char* sig, ...) {
   1425     jmethodID methodID = getMethod(env, instance, method, sig);
   1426     va_list args;
   1427     va_start(args, sig);
   1428     jobject result = (*env)->CallObjectMethodV(env, instance, methodID, args);
   1429     va_end(args);
   1430     return result;
   1431 }
   1432 
   1433 static void voidCall(JNIEnv* env, jobject instance, const char* method, const char* sig, ...) {
   1434     jmethodID methodID = getMethod(env, instance, method, sig);
   1435     va_list args;
   1436     va_start(args, sig);
   1437     (*env)->CallVoidMethodV(env, instance, methodID, args);
   1438     va_end(args);
   1439 }
   1440 
   1441 static jint intCall(JNIEnv* env, jobject instance, const char* method, const char* sig, ...) {
   1442     jmethodID methodID = getMethod(env, instance, method, sig);
   1443     va_list args;
   1444     va_start(args, sig);
   1445     jint result = (*env)->CallIntMethodV(env, instance, methodID, args);
   1446     va_end(args);
   1447     return result;
   1448 }
   1449 
   1450 void naettPlatformInit(naettInitData initData) {
   1451     globalVM = initData;
   1452 }
   1453 
   1454 int naettPlatformInitRequest(InternalRequest* req) {
   1455     JNIEnv* env = getEnv();
   1456     (*env)->PushLocalFrame(env, 10);
   1457     jclass URL = (*env)->FindClass(env, "java/net/URL");
   1458     jmethodID newURL = (*env)->GetMethodID(env, URL, "<init>", "(Ljava/lang/String;)V");
   1459     jstring urlString = (*env)->NewStringUTF(env, req->url);
   1460     jobject url = (*env)->NewObject(env, URL, newURL, urlString);
   1461     if (catch (env)) {
   1462         (*env)->PopLocalFrame(env, NULL);
   1463         return 0;
   1464     }
   1465     req->urlObject = (*env)->NewGlobalRef(env, url);
   1466     (*env)->PopLocalFrame(env, NULL);
   1467     return 1;
   1468 }
   1469 
   1470 static void* processRequest(void* data) {
   1471     const int bufSize = 10240;
   1472     char byteBuffer[bufSize];
   1473     InternalResponse* res = (InternalResponse*)data;
   1474     InternalRequest* req = res->request;
   1475 
   1476     JNIEnv* env = getEnv();
   1477     (*env)->PushLocalFrame(env, 100);
   1478 
   1479     jobject connection = call(env, req->urlObject, "openConnection", "()Ljava/net/URLConnection;");
   1480     if (catch (env)) {
   1481         res->code = naettConnectionError;
   1482         goto finally;
   1483     }
   1484 
   1485     {
   1486         jstring name = (*env)->NewStringUTF(env, "User-Agent");
   1487         jstring value = (*env)->NewStringUTF(env, req->options.userAgent ? req->options.userAgent : NAETT_UA);
   1488         voidCall(env, connection, "addRequestProperty", "(Ljava/lang/String;Ljava/lang/String;)V", name, value);
   1489         (*env)->DeleteLocalRef(env, name);
   1490         (*env)->DeleteLocalRef(env, value);
   1491     }
   1492 
   1493     KVLink* header = req->options.headers;
   1494     while (header != NULL) {
   1495         jstring name = (*env)->NewStringUTF(env, header->key);
   1496         jstring value = (*env)->NewStringUTF(env, header->value);
   1497         voidCall(env, connection, "addRequestProperty", "(Ljava/lang/String;Ljava/lang/String;)V", name, value);
   1498         (*env)->DeleteLocalRef(env, name);
   1499         (*env)->DeleteLocalRef(env, value);
   1500         header = header->next;
   1501     }
   1502 
   1503     jobject outputStream = NULL;
   1504     if (strcmp(req->options.method, "POST") == 0 || strcmp(req->options.method, "PUT") == 0 ||
   1505         strcmp(req->options.method, "PATCH") == 0 || strcmp(req->options.method, "DELETE") == 0) {
   1506         voidCall(env, connection, "setDoOutput", "(Z)V", 1);
   1507         outputStream = call(env, connection, "getOutputStream", "()Ljava/io/OutputStream;");
   1508     }
   1509     jobject methodString = (*env)->NewStringUTF(env, req->options.method);
   1510     voidCall(env, connection, "setRequestMethod", "(Ljava/lang/String;)V", methodString);
   1511     voidCall(env, connection, "setConnectTimeout", "(I)V", req->options.timeoutMS);
   1512     voidCall(env, connection, "setInstanceFollowRedirects", "(Z)V", 1);
   1513 
   1514     voidCall(env, connection, "connect", "()V");
   1515     if (catch (env)) {
   1516         res->code = naettConnectionError;
   1517         goto finally;
   1518     }
   1519 
   1520     jbyteArray buffer = (*env)->NewByteArray(env, bufSize);
   1521 
   1522     if (outputStream != NULL) {
   1523         int bytesRead = 0;
   1524         if (req->options.bodyReader != NULL)
   1525             do {
   1526                 bytesRead = req->options.bodyReader(byteBuffer, bufSize, req->options.bodyReaderData);
   1527                 if (bytesRead > 0) {
   1528                     (*env)->SetByteArrayRegion(env, buffer, 0, bytesRead, (const jbyte*) byteBuffer);
   1529                     voidCall(env, outputStream, "write", "([BII)V", buffer, 0, bytesRead);
   1530                 } else {
   1531                     break;
   1532                 }
   1533             } while (!res->closeRequested);
   1534         voidCall(env, outputStream, "close", "()V");
   1535     }
   1536 
   1537     jobject headerMap = call(env, connection, "getHeaderFields", "()Ljava/util/Map;");
   1538     if (catch (env)) {
   1539         res->code = naettProtocolError;
   1540         goto finally;
   1541     }
   1542 
   1543     jobject headerSet = call(env, headerMap, "keySet", "()Ljava/util/Set;");
   1544     jarray headers = call(env, headerSet, "toArray", "()[Ljava/lang/Object;");
   1545     jsize headerCount = (*env)->GetArrayLength(env, headers);
   1546 
   1547     KVLink *firstHeader = NULL;
   1548     for (int i = 0; i < headerCount; i++) {
   1549         jstring name = (*env)->GetObjectArrayElement(env, headers, i);
   1550         if (name == NULL) {
   1551             continue;
   1552         }
   1553         const char* nameString = (*env)->GetStringUTFChars(env, name, NULL);
   1554 
   1555         jobject values = call(env, headerMap, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", name);
   1556         jstring value = call(env, values, "get", "(I)Ljava/lang/Object;", 0);
   1557         const char* valueString = (*env)->GetStringUTFChars(env, value, NULL);
   1558 
   1559         naettAlloc(KVLink, node);
   1560         node->key = strdup(nameString);
   1561         node->value = strdup(valueString);
   1562         node->next = firstHeader;
   1563         firstHeader = node;
   1564 
   1565         (*env)->ReleaseStringUTFChars(env, name, nameString);
   1566         (*env)->ReleaseStringUTFChars(env, value, valueString);
   1567 
   1568         (*env)->DeleteLocalRef(env, name);
   1569         (*env)->DeleteLocalRef(env, value);
   1570         (*env)->DeleteLocalRef(env, values);
   1571     }
   1572     res->headers = firstHeader;
   1573 
   1574     const char *contentLength = naettGetHeader((naettRes *)res, "Content-Length");
   1575     if (!contentLength || sscanf(contentLength, "%d", &res->contentLength) != 1) {
   1576         res->contentLength = -1;
   1577     }
   1578 
   1579     int statusCode = intCall(env, connection, "getResponseCode", "()I");
   1580 
   1581     jobject inputStream = NULL;
   1582 
   1583     if (statusCode >= 400) {
   1584         inputStream = call(env, connection, "getErrorStream", "()Ljava/io/InputStream;");
   1585     } else {
   1586         inputStream = call(env, connection, "getInputStream", "()Ljava/io/InputStream;");
   1587     }
   1588     if (catch (env)) {
   1589         res->code = naettProtocolError;
   1590         goto finally;
   1591     }
   1592 
   1593     jint bytesRead = 0;
   1594     do {
   1595         bytesRead = intCall(env, inputStream, "read", "([B)I", buffer);
   1596         if (catch (env)) {
   1597             res->code = naettReadError;
   1598             goto finally;
   1599         }
   1600         if (bytesRead < 0) {
   1601             break;
   1602         } else if (bytesRead > 0) {
   1603             (*env)->GetByteArrayRegion(env, buffer, 0, bytesRead, (jbyte*) byteBuffer);
   1604             req->options.bodyWriter(byteBuffer, bytesRead, req->options.bodyWriterData);
   1605             res->totalBytesRead += bytesRead;
   1606         }
   1607     } while (!res->closeRequested);
   1608 
   1609     voidCall(env, inputStream, "close", "()V");
   1610 
   1611     res->code = statusCode;
   1612 
   1613 finally:
   1614     res->complete = 1;
   1615     (*env)->PopLocalFrame(env, NULL);
   1616     JavaVM* vm = getVM();
   1617     (*env)->ExceptionClear(env);
   1618     (*vm)->DetachCurrentThread(vm);
   1619     return NULL;
   1620 }
   1621 
   1622 static void startWorkerThread(InternalResponse* res) {
   1623     pthread_attr_t attr;
   1624     pthread_attr_init(&attr);
   1625     pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
   1626     pthread_create(&res->workerThread, &attr, processRequest, res);
   1627     pthread_setname_np(res->workerThread, "naett worker thread");
   1628 }
   1629 
   1630 void naettPlatformMakeRequest(InternalResponse* res) {
   1631     startWorkerThread(res);
   1632 }
   1633 
   1634 void naettPlatformFreeRequest(InternalRequest* req) {
   1635     JNIEnv* env = getEnv();
   1636     (*env)->DeleteGlobalRef(env, req->urlObject);
   1637 }
   1638 
   1639 void naettPlatformCloseResponse(InternalResponse* res) {
   1640     res->closeRequested = 1;
   1641     if (res->workerThread != 0) {
   1642         int joinResult = pthread_join(res->workerThread, NULL);
   1643         if (joinResult != 0) {
   1644             LOGE("Failed to join: %s", strerror(joinResult));
   1645         }
   1646     }
   1647 }
   1648 
   1649 #endif  // __ANDROID__
   1650 // End of inlined naett_android.c //
   1651 
   1652 // End of inlined amalgam.h //