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_win.c (10614B)


      1 #include "naett_internal.h"
      2 
      3 #ifdef __WINDOWS__
      4 
      5 #include <stdlib.h>
      6 #include <stdio.h>
      7 #include <string.h>
      8 #include <winhttp.h>
      9 #include <assert.h>
     10 #include <tchar.h>
     11 
     12 void naettPlatformInit(naettInitData initData) {
     13 }
     14 
     15 static char* winToUTF8(LPWSTR source) {
     16     int length = WideCharToMultiByte(CP_UTF8, 0, source, -1, NULL, 0, NULL, NULL);
     17     char* chars = (char*)malloc(length);
     18     int result = WideCharToMultiByte(CP_UTF8, 0, source, -1, chars, length, NULL, NULL);
     19     if (!result) {
     20         free(chars);
     21         return NULL;
     22     }
     23     return chars;
     24 }
     25 
     26 static LPWSTR winFromUTF8(const char* source) {
     27     int length = MultiByteToWideChar(CP_UTF8, 0, source, -1, NULL, 0);
     28     LPWSTR chars = (LPWSTR)malloc(length * sizeof(WCHAR));
     29     int result = MultiByteToWideChar(CP_UTF8, 0, source, -1, chars, length);
     30     if (!result) {
     31         free(chars);
     32         return NULL;
     33     }
     34     return chars;
     35 }
     36 
     37 #define ASPRINTF(result, fmt, ...)                        \
     38     {                                                     \
     39         size_t len = snprintf(NULL, 0, fmt, __VA_ARGS__); \
     40         *(result) = (char*)malloc(len + 1);               \
     41         snprintf(*(result), len + 1, fmt, __VA_ARGS__);   \
     42     }
     43 
     44 static LPWSTR wcsndup(LPCWSTR str, size_t len) {
     45     LPWSTR result = calloc(1, sizeof(WCHAR) * (len + 1));
     46     wcsncpy(result, str, len);
     47     return result;
     48 }
     49 
     50 static LPCWSTR packHeaders(InternalRequest* req) {
     51     char* packed = strdup("");
     52 
     53     KVLink* node = req->options.headers;
     54     while (node != NULL) {
     55         char* update;
     56         ASPRINTF(&update, "%s%s:%s%s", packed, node->key, node->value, node->next ? "\r\n" : "");
     57         free(packed);
     58         packed = update;
     59         node = node->next;
     60     }
     61 
     62     LPCWSTR winHeaders = winFromUTF8(packed);
     63     free(packed);
     64     return winHeaders;
     65 }
     66 
     67 static void unpackHeaders(InternalResponse* res, LPWSTR packed) {
     68     size_t len = 0;
     69     KVLink* firstHeader = NULL;
     70     while ((len = wcslen(packed)) != 0) {
     71         char* header = winToUTF8(packed);
     72         char* split = strchr(header, ':');
     73         if (split) {
     74             *split = 0;
     75             split++;
     76             while (*split == ' ') {
     77                 split++;
     78             }
     79             naettAlloc(KVLink, node);
     80             node->key = strdup(header);
     81             node->value = strdup(split);
     82             node->next = firstHeader;
     83             firstHeader = node;
     84         }
     85         free(header);
     86         packed += len + 1;
     87     }
     88     res->headers = firstHeader;
     89 }
     90 
     91 static void CALLBACK
     92 callback(HINTERNET request, DWORD_PTR context, DWORD status, LPVOID statusInformation, DWORD statusInfoLength) {
     93     InternalResponse* res = (InternalResponse*)context;
     94 
     95     switch (status) {
     96         case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE: {
     97             DWORD bufSize = 0;
     98             WinHttpQueryHeaders(request,
     99                 WINHTTP_QUERY_RAW_HEADERS,
    100                 WINHTTP_HEADER_NAME_BY_INDEX,
    101                 NULL,
    102                 &bufSize,
    103                 WINHTTP_NO_HEADER_INDEX);
    104             LPWSTR buffer = (LPWSTR)malloc(bufSize);
    105             WinHttpQueryHeaders(request,
    106                 WINHTTP_QUERY_RAW_HEADERS,
    107                 WINHTTP_HEADER_NAME_BY_INDEX,
    108                 buffer,
    109                 &bufSize,
    110                 WINHTTP_NO_HEADER_INDEX);
    111             unpackHeaders(res, buffer);
    112             free(buffer);
    113 
    114             const char* contentLength = naettGetHeader((naettRes*)res, "Content-Length");
    115             if (!contentLength || sscanf(contentLength, "%d", &res->contentLength) != 1) {
    116                 res->contentLength = -1;
    117             }
    118 
    119             DWORD statusCode = 0;
    120             DWORD statusCodeSize = sizeof(statusCode);
    121 
    122             WinHttpQueryHeaders(request,
    123                 WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
    124                 WINHTTP_HEADER_NAME_BY_INDEX,
    125                 &statusCode,
    126                 &statusCodeSize,
    127                 WINHTTP_NO_HEADER_INDEX);
    128             res->code = statusCode;
    129 
    130             if (!WinHttpQueryDataAvailable(request, NULL)) {
    131                 res->code = naettProtocolError;
    132                 res->complete = 1;
    133             }
    134         } break;
    135 
    136         case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE: {
    137             DWORD* available = (DWORD*)statusInformation;
    138             res->bytesLeft = *available;
    139             if (res->bytesLeft == 0) {
    140                 res->complete = 1;
    141                 break;
    142             }
    143 
    144             size_t bytesToRead = min(res->bytesLeft, sizeof(res->buffer));
    145             if (!WinHttpReadData(request, res->buffer, (DWORD)bytesToRead, NULL)) {
    146                 res->code = naettReadError;
    147                 res->complete = 1;
    148             }
    149         } break;
    150 
    151         case WINHTTP_CALLBACK_STATUS_READ_COMPLETE: {
    152             size_t bytesRead = statusInfoLength;
    153 
    154             InternalRequest* req = res->request;
    155             if (req->options.bodyWriter(res->buffer, (int)bytesRead, req->options.bodyWriterData) != bytesRead) {
    156                 res->code = naettReadError;
    157                 res->complete = 1;
    158             }
    159             res->totalBytesRead += (int)bytesRead;
    160             res->bytesLeft -= bytesRead;
    161             if (res->bytesLeft > 0) {
    162                 size_t bytesToRead = min(res->bytesLeft, sizeof(res->buffer));
    163                 if (!WinHttpReadData(request, res->buffer, (DWORD)bytesToRead, NULL)) {
    164                     res->code = naettReadError;
    165                     res->complete = 1;
    166                 }
    167             } else {
    168                 if (!WinHttpQueryDataAvailable(request, NULL)) {
    169                     res->code = naettProtocolError;
    170                     res->complete = 1;
    171                 }
    172             }
    173         } break;
    174 
    175         case WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE:
    176         case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE: {
    177             int bytesRead = res->request->options.bodyReader(
    178                 res->buffer, sizeof(res->buffer), res->request->options.bodyReaderData);
    179             if (bytesRead) {
    180                 WinHttpWriteData(request, res->buffer, bytesRead, NULL);
    181             } else {
    182                 if (!WinHttpReceiveResponse(request, NULL)) {
    183                     res->code = naettReadError;
    184                     res->complete = 1;
    185                 }
    186             }
    187         } break;
    188 
    189         //
    190         case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR: {
    191             WINHTTP_ASYNC_RESULT* result = (WINHTTP_ASYNC_RESULT*)statusInformation;
    192             switch (result->dwResult) {
    193                 case API_RECEIVE_RESPONSE:
    194                 case API_QUERY_DATA_AVAILABLE:
    195                 case API_READ_DATA:
    196                     res->code = naettReadError;
    197                     break;
    198                 case API_WRITE_DATA:
    199                     res->code = naettWriteError;
    200                     break;
    201                 case API_SEND_REQUEST:
    202                     res->code = naettConnectionError;
    203                     break;
    204                 default:
    205                     res->code = naettGenericError;
    206             }
    207 
    208             res->complete = 1;
    209         } break;
    210     }
    211 }
    212 
    213 int naettPlatformInitRequest(InternalRequest* req) {
    214     LPWSTR url = winFromUTF8(req->url);
    215 
    216     URL_COMPONENTS components;
    217     ZeroMemory(&components, sizeof(components));
    218     components.dwStructSize = sizeof(components);
    219     components.dwSchemeLength = (DWORD)-1;
    220     components.dwHostNameLength = (DWORD)-1;
    221     components.dwUrlPathLength = (DWORD)-1;
    222     components.dwExtraInfoLength = (DWORD)-1;
    223     BOOL cracked = WinHttpCrackUrl(url, 0, 0, &components);
    224 
    225     if (!cracked) {
    226         free(url);
    227         return 0;
    228     }
    229 
    230     req->host = wcsndup(components.lpszHostName, components.dwHostNameLength);
    231     req->resource = wcsndup(components.lpszUrlPath, components.dwUrlPathLength + components.dwExtraInfoLength);
    232     free(url);
    233 
    234     LPWSTR uaBuf = winFromUTF8(req->options.userAgent ? req->options.userAgent : NAETT_UA);
    235     req->session = WinHttpOpen(uaBuf,
    236         WINHTTP_ACCESS_TYPE_NO_PROXY,
    237         WINHTTP_NO_PROXY_NAME,
    238         WINHTTP_NO_PROXY_BYPASS,
    239         WINHTTP_FLAG_ASYNC);
    240     free(uaBuf);
    241 
    242     if (!req->session) {
    243         return 0;
    244     }
    245 
    246     WinHttpSetStatusCallback(req->session, callback, WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS, 0);
    247 
    248     // Set the connect timeout. Leave the other three timeouts at their default values.
    249     WinHttpSetTimeouts(req->session, 0, req->options.timeoutMS, 30000, 30000);
    250 
    251     req->connection = WinHttpConnect(req->session, req->host, components.nPort, 0);
    252     if (!req->connection) {
    253         naettPlatformFreeRequest(req);
    254         return 0;
    255     }
    256 
    257     LPWSTR verb = winFromUTF8(req->options.method);
    258     req->request = WinHttpOpenRequest(req->connection,
    259         verb,
    260         req->resource,
    261         NULL,
    262         WINHTTP_NO_REFERER,
    263         WINHTTP_DEFAULT_ACCEPT_TYPES,
    264         components.nScheme == INTERNET_SCHEME_HTTPS ? WINHTTP_FLAG_SECURE : 0);
    265     free(verb);
    266     if (!req->request) {
    267         naettPlatformFreeRequest(req);
    268         return 0;
    269     }
    270 
    271     LPCWSTR headers = packHeaders(req);
    272     if (headers[0] != 0) {
    273         if (!WinHttpAddRequestHeaders(
    274                 req->request, headers, -1, WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) {
    275             naettPlatformFreeRequest(req);
    276             free((LPWSTR)headers);
    277             return 0;
    278         }
    279     }
    280     free((LPWSTR)headers);
    281 
    282     return 1;
    283 }
    284 
    285 void naettPlatformMakeRequest(InternalResponse* res) {
    286     InternalRequest* req = res->request;
    287 
    288     LPCWSTR extraHeaders = WINHTTP_NO_ADDITIONAL_HEADERS;
    289     WCHAR contentLengthHeader[64];
    290 
    291     int contentLength = req->options.bodyReader(NULL, 0, req->options.bodyReaderData);
    292     if (contentLength > 0) {
    293         swprintf(contentLengthHeader, 64, L"Content-Length: %d", contentLength);
    294         extraHeaders = contentLengthHeader;
    295     }
    296 
    297     if (!WinHttpSendRequest(req->request, extraHeaders, -1, NULL, 0, 0, (DWORD_PTR)res)) {
    298         res->code = naettConnectionError;
    299         res->complete = 1;
    300     }
    301 }
    302 
    303 void naettPlatformFreeRequest(InternalRequest* req) {
    304     assert(req != NULL);
    305 
    306     if (req->request != NULL) {
    307         WinHttpCloseHandle(req->request);
    308         req->request = NULL;
    309     }
    310     if (req->connection != NULL) {
    311         WinHttpCloseHandle(req->connection);
    312         req->connection = NULL;
    313     }
    314     if (req->session != NULL) {
    315         WinHttpCloseHandle(req->session);
    316         req->session = NULL;
    317     }
    318     if (req->host != NULL) {
    319         free(req->host);
    320         req->host = NULL;
    321     }
    322     if (req->resource != NULL) {
    323         free(req->resource);
    324         req->resource = NULL;
    325     }
    326 }
    327 
    328 void naettPlatformCloseResponse(InternalResponse* res) {
    329 }
    330 
    331 #endif  // __WINDOWS__