winhttp: Fix a typo to properly release connection handle
[strongswan.git] / src / libstrongswan / plugins / winhttp / winhttp_fetcher.c
1 /*
2 * Copyright (C) 2014 Martin Willi
3 * Copyright (C) 2014 revosec AG
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * for more details.
14 */
15
16 #include <winsock2.h>
17 #include <windows.h>
18 #include <winhttp.h>
19
20 #include "winhttp_fetcher.h"
21
22 #include <library.h>
23
24 /**
25 * Timeout for DNS resolution, in ms
26 */
27 #define RESOLVE_TIMEOUT 5000
28
29 /**
30 * Timeout for TCP connect, in ms
31 */
32 #define CONNECT_TIMEOUT 10000
33
34 typedef struct private_winhttp_fetcher_t private_winhttp_fetcher_t;
35
36 /**
37 * Private data of a winhttp_fetcher_t.
38 */
39 struct private_winhttp_fetcher_t {
40
41 /**
42 * Public interface
43 */
44 winhttp_fetcher_t public;
45
46 /**
47 * WinHTTP session handle
48 */
49 HINTERNET session;
50
51 /**
52 * POST request data
53 */
54 chunk_t request;
55
56 /**
57 * HTTP version string to use
58 */
59 LPWSTR version;
60
61 /**
62 * Optional HTTP headers, as allocated LPWSTR
63 */
64 linked_list_t *headers;
65
66 /**
67 * Callback function
68 */
69 fetcher_callback_t cb;
70
71 /**
72 * Timeout for operations, in ms
73 */
74 u_long timeout;
75
76 /**
77 * User pointer to store HTTP status code to
78 */
79 u_int *result;
80 };
81
82 /**
83 * Configure and send the HTTP request
84 */
85 static bool send_request(private_winhttp_fetcher_t *this, HINTERNET request)
86 {
87 WCHAR headers[512] = L"";
88 LPWSTR hdr;
89
90 /* Set timeout. By default, send/receive does not time out */
91 if (!WinHttpSetTimeouts(request, RESOLVE_TIMEOUT, CONNECT_TIMEOUT,
92 this->timeout, this->timeout))
93 {
94 DBG1(DBG_LIB, "opening HTTP request failed: %u", GetLastError());
95 return FALSE;
96 }
97 while (this->headers->remove_first(this->headers, (void**)&hdr) == SUCCESS)
98 {
99 wcsncat(headers, hdr, countof(headers) - wcslen(headers) - 1);
100 if (this->headers->get_count(this->headers))
101 {
102 wcsncat(headers, L"\r\n", countof(headers) - wcslen(headers) - 1);
103 }
104 free(hdr);
105 }
106 if (!WinHttpSendRequest(request, headers, wcslen(headers),
107 this->request.ptr, this->request.len, this->request.len, 0))
108 {
109 DBG1(DBG_LIB, "sending HTTP request failed: %u", GetLastError());
110 return FALSE;
111 }
112 return TRUE;
113 }
114
115 /**
116 * Read back result and invoke receive callback
117 */
118 static bool read_result(private_winhttp_fetcher_t *this, HINTERNET request,
119 void *user)
120 {
121 DWORD received;
122 char buf[1024];
123 u_int32_t code;
124 DWORD codelen = sizeof(code);
125
126 if (!WinHttpReceiveResponse(request, NULL))
127 {
128 DBG1(DBG_LIB, "reading HTTP response header failed: %u", GetLastError());
129 return FALSE;
130 }
131 if (!WinHttpQueryHeaders(request,
132 WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
133 NULL, &code, &codelen, NULL))
134 {
135 DBG1(DBG_LIB, "reading HTTP status code failed: %u", GetLastError());
136 return FALSE;
137 }
138 if (this->result)
139 {
140 *this->result = code;
141 }
142 if (code < 200 || code >= 300)
143 { /* non-successful HTTP status code */
144 if (!this->result)
145 {
146 DBG1(DBG_LIB, "HTTP request failed with status %u", code);
147 }
148 return FALSE;
149 }
150 if (this->cb == fetcher_default_callback)
151 {
152 *(chunk_t*)user = chunk_empty;
153 }
154 while (TRUE)
155 {
156 if (!WinHttpReadData(request, buf, sizeof(buf), &received))
157 {
158 DBG1(DBG_LIB, "reading HTTP response failed: %u", GetLastError());
159 return FALSE;
160 }
161 if (received == 0)
162 {
163 /* end of response */
164 break;
165 }
166 if (!this->cb(user, chunk_create(buf, received)))
167 {
168 DBG1(DBG_LIB, "processing response failed or cancelled");
169 return FALSE;
170 }
171 }
172 return TRUE;
173 }
174
175 /**
176 * Parse an uri to wide string host and path, optionally set flags and port
177 */
178 static bool parse_uri(private_winhttp_fetcher_t *this, char *uri,
179 LPWSTR host, int hostlen, LPWSTR path, int pathlen,
180 LPWSTR user, int userlen, LPWSTR pass, int passlen,
181 DWORD *flags, INTERNET_PORT *port)
182 {
183 WCHAR wuri[512], extra[256];
184 URL_COMPONENTS comps = {
185 .dwStructSize = sizeof(URL_COMPONENTS),
186 .lpszHostName = host,
187 .dwHostNameLength = hostlen,
188 .lpszUrlPath = path,
189 .dwUrlPathLength = pathlen,
190 .lpszUserName = user,
191 .dwUserNameLength = userlen,
192 .lpszPassword = pass,
193 .dwPasswordLength = passlen,
194 .lpszExtraInfo = extra,
195 .dwExtraInfoLength = countof(extra),
196 };
197
198 if (!MultiByteToWideChar(CP_THREAD_ACP, 0, uri, -1, wuri, countof(wuri)))
199 {
200 DBG1(DBG_LIB, "converting URI failed: %u", GetLastError());
201 return FALSE;
202 }
203 if (!WinHttpCrackUrl(wuri, 0, ICU_ESCAPE, &comps))
204 {
205 DBG1(DBG_LIB, "cracking URI failed: %u", GetLastError());
206 return FALSE;
207 }
208 if (comps.nScheme == INTERNET_SCHEME_HTTPS)
209 {
210 *flags |= WINHTTP_FLAG_SECURE;
211 }
212 if (comps.dwExtraInfoLength)
213 {
214 wcsncat(path, extra, countof(path) - comps.dwUrlPathLength - 1);
215 }
216 if (comps.nPort)
217 {
218 *port = comps.nPort;
219 }
220 return TRUE;
221 }
222
223 /**
224 * Set credentials for basic authentication, if given
225 */
226 static bool set_credentials(private_winhttp_fetcher_t *this,
227 HINTERNET *request, LPWSTR user, LPWSTR pass)
228 {
229 if (!wcslen(user) && !wcslen(pass))
230 { /* skip */
231 return TRUE;
232 }
233 return WinHttpSetCredentials(request, WINHTTP_AUTH_TARGET_SERVER,
234 WINHTTP_AUTH_SCHEME_BASIC, user, pass, NULL);
235 }
236
237 METHOD(fetcher_t, fetch, status_t,
238 private_winhttp_fetcher_t *this, char *uri, void *userdata)
239 {
240 INTERNET_PORT port = INTERNET_DEFAULT_PORT;
241 status_t status = FAILED;
242 DWORD flags = 0;
243 HINTERNET connection, request;
244 WCHAR host[256], path[512], user[256], pass[256], *method;
245
246 if (this->request.len)
247 {
248 method = L"POST";
249 }
250 else
251 {
252 method = L"GET";
253 }
254
255 if (this->result)
256 { /* zero-initialize for early failures */
257 *this->result = 0;
258 }
259
260 if (parse_uri(this, uri, host, countof(host), path, countof(path),
261 user, countof(user), pass, countof(pass), &flags, &port))
262 {
263 connection = WinHttpConnect(this->session, host, port, 0);
264 if (connection)
265 {
266 request = WinHttpOpenRequest(connection, method, path, this->version,
267 WINHTTP_NO_REFERER,
268 WINHTTP_DEFAULT_ACCEPT_TYPES, flags);
269 if (request)
270 {
271 if (set_credentials(this, request, user, pass) &&
272 send_request(this, request) &&
273 read_result(this, request, userdata))
274 {
275 status = SUCCESS;
276 }
277 WinHttpCloseHandle(request);
278 }
279 else
280 {
281 DBG1(DBG_LIB, "opening request failed: %u", GetLastError());
282 }
283 WinHttpCloseHandle(connection);
284 }
285 else
286 {
287 DBG1(DBG_LIB, "connection failed: %u", GetLastError());
288 }
289 }
290 return status;
291 }
292
293 /**
294 * Append an header as wide string
295 */
296 static bool append_header(private_winhttp_fetcher_t *this, char *name)
297 {
298 int len;
299 LPWSTR buf;
300
301 len = MultiByteToWideChar(CP_THREAD_ACP, 0, name, -1, NULL, 0);
302 if (!len)
303 {
304 return FALSE;
305 }
306 buf = calloc(len, sizeof(WCHAR));
307 if (!MultiByteToWideChar(CP_THREAD_ACP, 0, name, -1, buf, len))
308 {
309 free(buf);
310 return FALSE;
311 }
312 this->headers->insert_last(this->headers, buf);
313 return TRUE;
314 }
315
316 METHOD(fetcher_t, set_option, bool,
317 private_winhttp_fetcher_t *this, fetcher_option_t option, ...)
318 {
319 bool supported = TRUE;
320 char buf[128];
321 va_list args;
322
323 va_start(args, option);
324 switch (option)
325 {
326 case FETCH_REQUEST_DATA:
327 this->request = va_arg(args, chunk_t);
328 break;
329 case FETCH_REQUEST_TYPE:
330 snprintf(buf, sizeof(buf), "Content-Type: %s", va_arg(args, char*));
331 supported = append_header(this, buf);
332 break;
333 case FETCH_REQUEST_HEADER:
334 supported = append_header(this, va_arg(args, char*));
335 break;
336 case FETCH_HTTP_VERSION_1_0:
337 this->version = L"HTTP/1.0";
338 break;
339 case FETCH_TIMEOUT:
340 this->timeout = va_arg(args, u_int) * 1000;
341 break;
342 case FETCH_CALLBACK:
343 this->cb = va_arg(args, fetcher_callback_t);
344 break;
345 case FETCH_RESPONSE_CODE:
346 this->result = va_arg(args, u_int*);
347 break;
348 case FETCH_SOURCEIP:
349 /* not supported, FALL */
350 default:
351 supported = FALSE;
352 break;
353 }
354 va_end(args);
355 return supported;
356 }
357
358 METHOD(fetcher_t, destroy, void,
359 private_winhttp_fetcher_t *this)
360 {
361 WinHttpCloseHandle(this->session);
362 this->headers->destroy_function(this->headers, free);
363 free(this);
364 }
365 /*
366 * Described in header.
367 */
368 winhttp_fetcher_t *winhttp_fetcher_create()
369 {
370 private_winhttp_fetcher_t *this;
371
372 INIT(this,
373 .public = {
374 .interface = {
375 .fetch = _fetch,
376 .set_option = _set_option,
377 .destroy = _destroy,
378 },
379 },
380 .version = L"HTTP/1.1",
381 .cb = fetcher_default_callback,
382 .headers = linked_list_create(),
383 .session = WinHttpOpen(L"strongSwan WinHTTP fetcher",
384 WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
385 WINHTTP_NO_PROXY_NAME,
386 WINHTTP_NO_PROXY_BYPASS, 0),
387 );
388
389 if (!this->session)
390 {
391 free(this);
392 return NULL;
393 }
394
395 return &this->public;
396 }