2 * Copyright (C) 2014 Martin Willi
3 * Copyright (C) 2014 revosec AG
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>.
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
20 #include "winhttp_fetcher.h"
25 * Timeout for DNS resolution, in ms
27 #define RESOLVE_TIMEOUT 5000
30 * Timeout for TCP connect, in ms
32 #define CONNECT_TIMEOUT 10000
34 typedef struct private_winhttp_fetcher_t private_winhttp_fetcher_t
;
37 * Private data of a winhttp_fetcher_t.
39 struct private_winhttp_fetcher_t
{
44 winhttp_fetcher_t
public;
47 * WinHTTP session handle
57 * HTTP version string to use
62 * Optional HTTP headers, as allocated LPWSTR
64 linked_list_t
*headers
;
69 fetcher_callback_t cb
;
72 * Timeout for operations, in ms
77 * User pointer to store HTTP status code to
83 * Configure and send the HTTP request
85 static bool send_request(private_winhttp_fetcher_t
*this, HINTERNET request
)
87 WCHAR headers
[512] = L
"";
90 /* Set timeout. By default, send/receive does not time out */
91 if (!WinHttpSetTimeouts(request
, RESOLVE_TIMEOUT
, CONNECT_TIMEOUT
,
92 this->timeout
, this->timeout
))
94 DBG1(DBG_LIB
, "opening HTTP request failed: %u", GetLastError());
97 while (this->headers
->remove_first(this->headers
, (void**)&hdr
) == SUCCESS
)
99 wcsncat(headers
, hdr
, countof(headers
) - wcslen(headers
) - 1);
100 if (this->headers
->get_count(this->headers
))
102 wcsncat(headers
, L
"\r\n", countof(headers
) - wcslen(headers
) - 1);
106 if (!WinHttpSendRequest(request
, headers
, wcslen(headers
),
107 this->request
.ptr
, this->request
.len
, this->request
.len
, 0))
109 DBG1(DBG_LIB
, "sending HTTP request failed: %u", GetLastError());
116 * Read back result and invoke receive callback
118 static bool read_result(private_winhttp_fetcher_t
*this, HINTERNET request
,
124 DWORD codelen
= sizeof(code
);
126 if (!WinHttpReceiveResponse(request
, NULL
))
128 DBG1(DBG_LIB
, "reading HTTP response header failed: %u", GetLastError());
131 if (!WinHttpQueryHeaders(request
,
132 WINHTTP_QUERY_STATUS_CODE
| WINHTTP_QUERY_FLAG_NUMBER
,
133 NULL
, &code
, &codelen
, NULL
))
135 DBG1(DBG_LIB
, "reading HTTP status code failed: %u", GetLastError());
140 *this->result
= code
;
142 if (code
< 200 || code
>= 300)
143 { /* non-successful HTTP status code */
146 DBG1(DBG_LIB
, "HTTP request failed with status %u", code
);
150 if (this->cb
== fetcher_default_callback
)
152 *(chunk_t
*)user
= chunk_empty
;
156 if (!WinHttpReadData(request
, buf
, sizeof(buf
), &received
))
158 DBG1(DBG_LIB
, "reading HTTP response failed: %u", GetLastError());
163 /* end of response */
166 if (!this->cb(user
, chunk_create(buf
, received
)))
168 DBG1(DBG_LIB
, "processing response failed or cancelled");
176 * Parse an uri to wide string host and path, optionally set flags and port
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
)
183 WCHAR wuri
[512], extra
[256];
184 URL_COMPONENTS comps
= {
185 .dwStructSize
= sizeof(URL_COMPONENTS
),
186 .lpszHostName
= host
,
187 .dwHostNameLength
= hostlen
,
189 .dwUrlPathLength
= pathlen
,
190 .lpszUserName
= user
,
191 .dwUserNameLength
= userlen
,
192 .lpszPassword
= pass
,
193 .dwPasswordLength
= passlen
,
194 .lpszExtraInfo
= extra
,
195 .dwExtraInfoLength
= countof(extra
),
198 if (!MultiByteToWideChar(CP_THREAD_ACP
, 0, uri
, -1, wuri
, countof(wuri
)))
200 DBG1(DBG_LIB
, "converting URI failed: %u", GetLastError());
203 if (!WinHttpCrackUrl(wuri
, 0, ICU_ESCAPE
, &comps
))
205 DBG1(DBG_LIB
, "cracking URI failed: %u", GetLastError());
208 if (comps
.nScheme
== INTERNET_SCHEME_HTTPS
)
210 *flags
|= WINHTTP_FLAG_SECURE
;
212 if (comps
.dwExtraInfoLength
)
214 wcsncat(path
, extra
, countof(path
) - comps
.dwUrlPathLength
- 1);
224 * Set credentials for basic authentication, if given
226 static bool set_credentials(private_winhttp_fetcher_t
*this,
227 HINTERNET
*request
, LPWSTR user
, LPWSTR pass
)
229 if (!wcslen(user
) && !wcslen(pass
))
233 return WinHttpSetCredentials(request
, WINHTTP_AUTH_TARGET_SERVER
,
234 WINHTTP_AUTH_SCHEME_BASIC
, user
, pass
, NULL
);
237 METHOD(fetcher_t
, fetch
, status_t
,
238 private_winhttp_fetcher_t
*this, char *uri
, void *userdata
)
240 INTERNET_PORT port
= INTERNET_DEFAULT_PORT
;
241 status_t status
= FAILED
;
243 HINTERNET connection
, request
;
244 WCHAR host
[256], path
[512], user
[256], pass
[256], *method
;
246 if (this->request
.len
)
256 { /* zero-initialize for early failures */
260 if (parse_uri(this, uri
, host
, countof(host
), path
, countof(path
),
261 user
, countof(user
), pass
, countof(pass
), &flags
, &port
))
263 connection
= WinHttpConnect(this->session
, host
, port
, 0);
266 request
= WinHttpOpenRequest(connection
, method
, path
, this->version
,
268 WINHTTP_DEFAULT_ACCEPT_TYPES
, flags
);
271 if (set_credentials(this, request
, user
, pass
) &&
272 send_request(this, request
) &&
273 read_result(this, request
, userdata
))
277 WinHttpCloseHandle(request
);
281 DBG1(DBG_LIB
, "opening request failed: %u", GetLastError());
283 WinHttpCloseHandle(connection
);
287 DBG1(DBG_LIB
, "connection failed: %u", GetLastError());
294 * Append an header as wide string
296 static bool append_header(private_winhttp_fetcher_t
*this, char *name
)
301 len
= MultiByteToWideChar(CP_THREAD_ACP
, 0, name
, -1, NULL
, 0);
306 buf
= calloc(len
, sizeof(WCHAR
));
307 if (!MultiByteToWideChar(CP_THREAD_ACP
, 0, name
, -1, buf
, len
))
312 this->headers
->insert_last(this->headers
, buf
);
316 METHOD(fetcher_t
, set_option
, bool,
317 private_winhttp_fetcher_t
*this, fetcher_option_t option
, ...)
319 bool supported
= TRUE
;
323 va_start(args
, option
);
326 case FETCH_REQUEST_DATA
:
327 this->request
= va_arg(args
, chunk_t
);
329 case FETCH_REQUEST_TYPE
:
330 snprintf(buf
, sizeof(buf
), "Content-Type: %s", va_arg(args
, char*));
331 supported
= append_header(this, buf
);
333 case FETCH_REQUEST_HEADER
:
334 supported
= append_header(this, va_arg(args
, char*));
336 case FETCH_HTTP_VERSION_1_0
:
337 this->version
= L
"HTTP/1.0";
340 this->timeout
= va_arg(args
, u_int
) * 1000;
343 this->cb
= va_arg(args
, fetcher_callback_t
);
345 case FETCH_RESPONSE_CODE
:
346 this->result
= va_arg(args
, u_int
*);
349 /* not supported, FALL */
358 METHOD(fetcher_t
, destroy
, void,
359 private_winhttp_fetcher_t
*this)
361 WinHttpCloseHandle(this->session
);
362 this->headers
->destroy_function(this->headers
, free
);
366 * Described in header.
368 winhttp_fetcher_t
*winhttp_fetcher_create()
370 private_winhttp_fetcher_t
*this;
376 .set_option
= _set_option
,
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),
395 return &this->public;