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__