1 : // Copyright 2014 Google Inc. All Rights Reserved.
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // http://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 :
15 : #include "syzygy/kasko/http_agent_impl.h"
16 :
17 : #include <windows.h>
18 : #include <winhttp.h>
19 :
20 : #include <memory>
21 : #include <string>
22 :
23 : #include "base/file_version_info.h"
24 : #include "base/logging.h"
25 : #include "base/macros.h"
26 : #include "base/sys_info.h"
27 : #include "base/strings/string16.h"
28 : #include "base/win/scoped_handle.h"
29 : #include "base/win/windows_version.h"
30 : #include "syzygy/common/com_utils.h"
31 : #include "syzygy/kasko/http_response.h"
32 : #include "syzygy/kasko/internet_helpers.h"
33 : #include "syzygy/kasko/user_agent.h"
34 :
35 m : namespace kasko {
36 :
37 m : namespace {
38 :
39 m : class WinHttpHandleTraits {
40 m : public:
41 m : typedef HINTERNET Handle;
42 m : static bool CloseHandle(HINTERNET handle) {
43 m : return ::WinHttpCloseHandle(handle) == TRUE;
44 m : }
45 m : static bool IsHandleValid(HINTERNET handle) { return handle != nullptr; }
46 m : static HINTERNET NullHandle() { return nullptr; }
47 :
48 m : private:
49 m : DISALLOW_IMPLICIT_CONSTRUCTORS(WinHttpHandleTraits);
50 m : };
51 :
52 m : typedef base::win::GenericScopedHandle<WinHttpHandleTraits,
53 m : base::win::DummyVerifierTraits>
54 m : ScopedWinHttpHandle;
55 :
56 : // A helper class that retrieves and frees user proxy settings.
57 m : class AutoWinHttpProxyConfig {
58 m : public:
59 m : AutoWinHttpProxyConfig() : proxy_config_() {}
60 m : ~AutoWinHttpProxyConfig() {
61 m : if (proxy_config_.lpszAutoConfigUrl)
62 m : ::GlobalFree(proxy_config_.lpszAutoConfigUrl);
63 m : if (proxy_config_.lpszProxy)
64 m : ::GlobalFree(proxy_config_.lpszProxy);
65 m : if (proxy_config_.lpszProxyBypass)
66 m : ::GlobalFree(proxy_config_.lpszProxyBypass);
67 m : }
68 :
69 : // Attempts to load the user proxy settings. If successful, returns true.
70 m : bool Load() {
71 m : if (::WinHttpGetIEProxyConfigForCurrentUser(&proxy_config_))
72 m : return true;
73 :
74 m : LOG(ERROR) << "WinHttpGetIEProxyConfigForCurrentUser() failed: "
75 m : << ::common::LogWe();
76 m : return false;
77 m : }
78 :
79 m : DWORD access_type() const {
80 m : return (proxy() == WINHTTP_NO_PROXY_NAME) ? WINHTTP_ACCESS_TYPE_NO_PROXY
81 m : : WINHTTP_ACCESS_TYPE_NAMED_PROXY;
82 m : }
83 :
84 : // Indicates whether proxy auto-detection is enabled.
85 m : const bool auto_detect() const { return proxy_config_.fAutoDetect == TRUE; }
86 :
87 : // Returns the proxy auto-configuration URL, or an empty string if automatic
88 : // proxy configuration is disabled. Only valid after a successful call to
89 : // Load().
90 m : const base::char16* auto_config_url() const {
91 m : return proxy_config_.lpszAutoConfigUrl ? proxy_config_.lpszAutoConfigUrl
92 m : : L"";
93 m : }
94 :
95 : // Returns the proxy configuration string that should be passed to
96 : // WinHttpOpen.
97 m : const base::char16* proxy() const {
98 m : return (proxy_config_.lpszProxy && proxy_config_.lpszProxy[0] != 0)
99 m : ? proxy_config_.lpszProxy
100 m : : WINHTTP_NO_PROXY_NAME;
101 m : }
102 :
103 : // Returns the proxy bypass configuration string that should be passed to
104 : // WinHttpOpen. Only valid after a successful call to Load().
105 m : const base::char16* proxy_bypass() const {
106 m : return access_type() == WINHTTP_ACCESS_TYPE_NO_PROXY
107 m : ? WINHTTP_NO_PROXY_BYPASS
108 m : : proxy_config_.lpszProxyBypass;
109 m : }
110 :
111 m : private:
112 m : WINHTTP_CURRENT_USER_IE_PROXY_CONFIG proxy_config_;
113 :
114 m : DISALLOW_COPY_AND_ASSIGN(AutoWinHttpProxyConfig);
115 m : };
116 :
117 : // A helper class that retrieves and frees URL-specific proxy settings.
118 m : class AutoWinHttpUrlProxyConfig {
119 m : public:
120 : // Constructs an instance that will use the auto-configuration URL (if any)
121 : // from |proxy_config| to retrieve URL-specific proxy settings.
122 m : explicit AutoWinHttpUrlProxyConfig(const AutoWinHttpProxyConfig& proxy_config)
123 m : : auto_detect_(proxy_config.auto_detect()),
124 m : auto_config_url_(proxy_config.auto_config_url()),
125 m : is_valid_(false),
126 m : url_proxy_config_() {}
127 :
128 m : ~AutoWinHttpUrlProxyConfig() {
129 m : if (url_proxy_config_.lpszProxy)
130 m : ::GlobalFree(url_proxy_config_.lpszProxy);
131 m : if (url_proxy_config_.lpszProxyBypass)
132 m : ::GlobalFree(url_proxy_config_.lpszProxyBypass);
133 m : }
134 :
135 : // Loads URL-specific proxy settings for |url| using |session|. Returns true
136 : // if auto-configuration is disabled or if the settings are successfully
137 : // loaded.
138 m : bool Load(HINTERNET session, const base::string16& url) {
139 : // http://msdn.microsoft.com/en-us/library/fze2ytx2(v=vs.110).aspx implies
140 : // that auto-detection is to be used before a specified configuration file.
141 :
142 : // TODO(erikwright): It's not clear if an error from WinHttpGetProxyForUrl
143 : // means that no proxy is detected and we should proceed with a direct
144 : // connection or that something unexpected happened. In the latter case we
145 : // should presumably log an error and possibly not attempt a direct
146 : // connection. Manual testing will be required to verify the behaviour of
147 : // this code in different proxy scenarios.
148 m : if (auto_detect_) {
149 m : WINHTTP_AUTOPROXY_OPTIONS options = {0};
150 m : options.dwFlags =
151 m : WINHTTP_AUTOPROXY_AUTO_DETECT | WINHTTP_AUTOPROXY_RUN_OUTPROCESS_ONLY;
152 m : options.dwAutoDetectFlags =
153 m : WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A;
154 m : if (::WinHttpGetProxyForUrl(session, url.c_str(), &options,
155 m : &url_proxy_config_)) {
156 m : is_valid_ = true;
157 m : return true;
158 m : }
159 :
160 m : switch (::GetLastError()) {
161 m : case ERROR_WINHTTP_AUTODETECTION_FAILED:
162 m : case ERROR_WINHTTP_AUTO_PROXY_SERVICE_ERROR:
163 m : break;
164 m : default:
165 m : LOG(ERROR) << "Unexpected error durring "
166 m : "WinHttpGetProxyForUrl(WINHTTP_AUTOPROXY_AUTO_DETECT)"
167 m : << ::common::LogWe();
168 m : return false;
169 m : }
170 m : }
171 :
172 : // Auto-detection is disabled or did not detect a configuration.
173 m : if (!auto_config_url_.empty()) {
174 m : WINHTTP_AUTOPROXY_OPTIONS options = {0};
175 m : options.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL;
176 m : options.lpszAutoConfigUrl = auto_config_url_.c_str();
177 :
178 m : if (::WinHttpGetProxyForUrl(session, url.c_str(), &options,
179 m : &url_proxy_config_)) {
180 m : is_valid_ = true;
181 m : return true;
182 m : }
183 :
184 m : LOG(ERROR)
185 m : << "WinHttpGetProxyForUrl(WINHTTP_AUTOPROXY_CONFIG_URL) failed: "
186 m : << ::common::LogWe();
187 m : return false;
188 m : }
189 m : return true;
190 m : }
191 :
192 : // Returns the loaded settings, or NULL if auto-configuration is disabled.
193 : // Only valid after a successful call to Load().
194 m : WINHTTP_PROXY_INFO* get() {
195 m : return is_valid_ ? &url_proxy_config_ : nullptr;
196 m : }
197 :
198 m : private:
199 m : bool auto_detect_;
200 m : base::string16 auto_config_url_;
201 m : bool is_valid_;
202 m : WINHTTP_PROXY_INFO url_proxy_config_;
203 :
204 m : DISALLOW_COPY_AND_ASSIGN(AutoWinHttpUrlProxyConfig);
205 m : };
206 :
207 : // Implements HttpResponse using the WinHTTP API.
208 m : class HttpResponseImpl : public HttpResponse {
209 m : public:
210 m : ~HttpResponseImpl() override;
211 :
212 : // Issues the request defined by its parameters and, if successful, returns an
213 : // HttpResponse that may be used to access the response. See HttpAgent::Post
214 : // for a description of the parameters.
215 m : static std::unique_ptr<HttpResponse> Create(
216 m : const base::string16& user_agent,
217 m : const base::string16& host,
218 m : uint16_t port,
219 m : const base::string16& path,
220 m : bool secure,
221 m : const base::string16& extra_headers,
222 m : const std::string& body);
223 :
224 : // HttpResponse implementation.
225 m : bool GetStatusCode(uint16_t* status_code) override;
226 m : bool GetContentLength(bool* has_content_length,
227 m : size_t* content_length) override;
228 m : bool GetContentType(bool* has_content_type,
229 m : base::string16* content_type) override;
230 m : bool HasData(bool* has_data) override;
231 m : bool ReadData(char* buffer, size_t* count) override;
232 :
233 m : private:
234 m : HttpResponseImpl();
235 :
236 : // Invokes WinHttpQueryHeaders. If the header indicated by |info_level| is
237 : // present its value will be read into |buffer| (having size |buffer_length|).
238 : // |header_present| will indicate whether the header was found. The result is
239 : // true if the header is successfully read or determined to be absent.
240 m : bool QueryHeader(DWORD info_level,
241 m : bool* header_present,
242 m : void* buffer,
243 m : DWORD buffer_length);
244 :
245 : // WinHttp handles used for the request.
246 m : ScopedWinHttpHandle session_;
247 m : ScopedWinHttpHandle connection_;
248 m : ScopedWinHttpHandle request_;
249 :
250 m : DISALLOW_COPY_AND_ASSIGN(HttpResponseImpl);
251 m : };
252 :
253 m : HttpResponseImpl::~HttpResponseImpl() {}
254 :
255 : // static
256 m : std::unique_ptr<HttpResponse> HttpResponseImpl::Create(
257 m : const base::string16& user_agent,
258 m : const base::string16& host,
259 m : uint16_t port,
260 m : const base::string16& path,
261 m : bool secure,
262 m : const base::string16& extra_headers,
263 m : const std::string& body) {
264 : // Retrieve the user's proxy configuration.
265 m : AutoWinHttpProxyConfig proxy_config;
266 m : if (!proxy_config.Load())
267 m : return std::unique_ptr<HttpResponse>();
268 :
269 : // Tentatively create an instance. We will return it if we are able to
270 : // successfully initialize it.
271 m : std::unique_ptr<HttpResponseImpl> instance(new HttpResponseImpl);
272 :
273 : // Open a WinHTTP session.
274 m : instance->session_.Set(
275 m : ::WinHttpOpen(user_agent.c_str(), proxy_config.access_type(),
276 m : proxy_config.proxy(), proxy_config.proxy_bypass(), 0));
277 m : if (!instance->session_.IsValid()) {
278 m : LOG(ERROR) << "WinHttpOpen() failed: " << ::common::LogWe();
279 m : return std::unique_ptr<HttpResponse>();
280 m : }
281 :
282 : // Look up URL-specific proxy settings. If this fails, we will fall back to
283 : // working without a proxy.
284 m : AutoWinHttpUrlProxyConfig url_proxy_config(proxy_config);
285 m : url_proxy_config.Load(instance->session_.Get(),
286 m : ComposeUrl(host, port, path, secure));
287 :
288 : // Connect to a host/port.
289 m : instance->connection_.Set(
290 m : ::WinHttpConnect(instance->session_.Get(), host.c_str(), port, 0));
291 m : if (!instance->connection_.IsValid()) {
292 m : LOG(ERROR) << "WinHttpConnect() failed with host " << host << " and port "
293 m : << port << ": " << ::common::LogWe();
294 m : return std::unique_ptr<HttpResponse>();
295 m : }
296 :
297 : // Initiate a request. This doesn't actually send the request yet.
298 m : instance->request_.Set(
299 m : ::WinHttpOpenRequest(instance->connection_.Get(), L"POST", path.c_str(),
300 m : NULL, // version
301 m : NULL, // referer
302 m : NULL, // accept types
303 m : secure ? WINHTTP_FLAG_SECURE : 0));
304 m : if (!instance->connection_.IsValid()) {
305 m : LOG(ERROR) << "WinHttpConnect() failed with host " << host << " and port "
306 m : << port << ": " << ::common::LogWe();
307 m : return std::unique_ptr<HttpResponse>();
308 m : }
309 :
310 : // Disable cookies and authentication. This request should be completely
311 : // stateless and untied to any identity of any sort.
312 m : DWORD option_value = WINHTTP_DISABLE_COOKIES | WINHTTP_DISABLE_AUTHENTICATION;
313 m : if (!::WinHttpSetOption(instance->request_.Get(),
314 m : WINHTTP_OPTION_DISABLE_FEATURE, &option_value,
315 m : sizeof(option_value))) {
316 m : LOG(ERROR) << "WinHttpSetOption(WINHTTP_DISABLE_COOKIES | "
317 m : "WINHTTP_DISABLE_AUTHENTICATION) failed: "
318 m : << ::common::LogWe();
319 m : return std::unique_ptr<HttpResponse>();
320 m : }
321 :
322 : // If this URL is configured to use a proxy, set that up now.
323 m : if (url_proxy_config.get()) {
324 m : if (!::WinHttpSetOption(instance->request_.Get(), WINHTTP_OPTION_PROXY,
325 m : url_proxy_config.get(),
326 m : sizeof(*url_proxy_config.get()))) {
327 m : LOG(ERROR) << "WinHttpSetOption(WINHTTP_OPTION_PROXY) failed: "
328 m : << ::common::LogWe();
329 m : return std::unique_ptr<HttpResponse>();
330 m : }
331 m : }
332 :
333 : // Send the request.
334 m : if (!::WinHttpSendRequest(instance->request_.Get(), extra_headers.c_str(),
335 m : static_cast<DWORD>(-1),
336 m : const_cast<char*>(body.data()),
337 m : static_cast<DWORD>(body.size()),
338 m : static_cast<DWORD>(body.size()), NULL)) {
339 m : LOG(ERROR) << "Failed to send HTTP request to host " << host << " and port "
340 m : << port << ": " << ::common::LogWe();
341 m : return std::unique_ptr<HttpResponse>();
342 m : }
343 :
344 : // This seems to read at least all headers from the response. The remainder of
345 : // the body, if any, may be read during subsequent calls to WinHttpReadData().
346 m : if (!::WinHttpReceiveResponse(instance->request_.Get(), 0)) {
347 m : LOG(ERROR) << "Failed to complete HTTP request to host " << host
348 m : << " and port " << port << ": " << ::common::LogWe();
349 m : return std::unique_ptr<HttpResponse>();
350 m : }
351 :
352 m : return std::move(instance);
353 m : }
354 :
355 m : bool HttpResponseImpl::GetStatusCode(uint16_t* status_code) {
356 m : DCHECK(status_code);
357 :
358 m : bool has_status_code = false;
359 m : DWORD status_code_buffer = 0;
360 :
361 m : if (QueryHeader(WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
362 m : &has_status_code, &status_code_buffer,
363 m : sizeof(status_code_buffer))) {
364 m : DCHECK(has_status_code);
365 m : *status_code = status_code_buffer;
366 m : return true;
367 m : }
368 :
369 m : return false;
370 m : }
371 :
372 m : bool HttpResponseImpl::GetContentLength(bool* has_content_length,
373 m : size_t* content_length) {
374 m : DCHECK(has_content_length);
375 m : DCHECK(content_length);
376 :
377 m : DWORD content_length_header_value = 0;
378 :
379 m : if (QueryHeader(WINHTTP_QUERY_CONTENT_LENGTH | WINHTTP_QUERY_FLAG_NUMBER,
380 m : has_content_length, &content_length_header_value,
381 m : sizeof(content_length_header_value))) {
382 m : if (has_content_length)
383 m : *content_length = content_length_header_value;
384 m : return true;
385 m : }
386 :
387 m : return false;
388 m : }
389 :
390 m : bool HttpResponseImpl::GetContentType(bool* has_content_type,
391 m : base::string16* content_type) {
392 m : DCHECK(has_content_type);
393 m : DCHECK(content_type);
394 :
395 m : base::char16 content_type_buffer[256] = {0};
396 :
397 m : if (QueryHeader(WINHTTP_QUERY_CONTENT_TYPE, has_content_type,
398 m : &content_type_buffer, sizeof(content_type_buffer))) {
399 m : if (has_content_type)
400 m : *content_type = content_type_buffer;
401 m : return true;
402 m : }
403 :
404 m : return false;
405 m : }
406 :
407 m : bool HttpResponseImpl::HasData(bool* has_data) {
408 m : DCHECK(has_data);
409 :
410 m : DWORD leftover_data = 0;
411 m : if (!::WinHttpQueryDataAvailable(request_.Get(), &leftover_data)) {
412 m : LOG(ERROR) << "WinHttpQueryDataAvailable failed: " << ::common::LogWe();
413 m : return false;
414 m : }
415 m : if (leftover_data != 0)
416 m : *has_data = true;
417 m : else
418 m : *has_data = false;
419 m : return true;
420 m : }
421 :
422 m : bool HttpResponseImpl::ReadData(char* buffer, size_t* count) {
423 m : DCHECK(buffer);
424 m : DCHECK(count);
425 :
426 m : DWORD size_read = 0;
427 m : if (!::WinHttpReadData(request_.Get(), buffer, *count, &size_read)) {
428 m : LOG(ERROR) << "Failed to read response body: " << ::common::LogWe();
429 m : return false;
430 m : }
431 m : *count = size_read;
432 m : return true;
433 m : }
434 :
435 m : HttpResponseImpl::HttpResponseImpl() {}
436 :
437 m : bool HttpResponseImpl::QueryHeader(DWORD info_level,
438 m : bool* header_present,
439 m : void* buffer,
440 m : DWORD buffer_length) {
441 m : if (::WinHttpQueryHeaders(request_.Get(), info_level,
442 m : WINHTTP_HEADER_NAME_BY_INDEX, buffer,
443 m : &buffer_length, 0)) {
444 m : *header_present = true;
445 m : return true;
446 m : }
447 :
448 m : if (::GetLastError() == ERROR_WINHTTP_HEADER_NOT_FOUND) {
449 m : *header_present = false;
450 m : return true;
451 m : }
452 :
453 m : LOG(ERROR) << "WinHttpQueryHeaders failed:" << ::common::LogWe();
454 m : return false;
455 m : }
456 :
457 m : base::string16 GetWinHttpVersion() {
458 m : HMODULE win_http_module = nullptr;
459 m : if (::GetModuleHandleEx(0, L"winhttp.dll", &win_http_module)) {
460 m : std::unique_ptr<FileVersionInfo> win_http_module_version_info(
461 m : FileVersionInfo::CreateFileVersionInfoForModule(win_http_module));
462 m : ::FreeLibrary(win_http_module);
463 m : if (win_http_module_version_info)
464 m : return win_http_module_version_info->product_version();
465 m : }
466 m : return L"?";
467 m : }
468 :
469 : // Adapted from Chromium content/common/user_agent.cc
470 m : void GetOSAndCPU(UserAgent* user_agent) {
471 m : int32_t os_major_version = 0;
472 m : int32_t os_minor_version = 0;
473 m : int32_t os_bugfix_version = 0;
474 m : base::SysInfo::OperatingSystemVersionNumbers(&os_major_version,
475 m : &os_minor_version,
476 m : &os_bugfix_version);
477 m : user_agent->set_os_version(os_major_version, os_minor_version);
478 :
479 m : base::win::OSInfo* os_info = base::win::OSInfo::GetInstance();
480 m : if (os_info->wow64_status() == base::win::OSInfo::WOW64_ENABLED) {
481 m : user_agent->set_architecture(UserAgent::WOW64);
482 m : } else {
483 m : base::win::OSInfo::WindowsArchitecture windows_architecture =
484 m : os_info->architecture();
485 m : if (windows_architecture == base::win::OSInfo::X64_ARCHITECTURE)
486 m : user_agent->set_architecture(UserAgent::X64);
487 m : else if (windows_architecture == base::win::OSInfo::IA64_ARCHITECTURE)
488 m : user_agent->set_architecture(UserAgent::IA64);
489 m : else
490 m : user_agent->set_architecture(UserAgent::X86);
491 m : }
492 m : }
493 :
494 m : } // namespace
495 :
496 m : HttpAgentImpl::HttpAgentImpl(const base::string16& product_name,
497 m : const base::string16& product_version) {
498 m : UserAgent user_agent(product_name, product_version);
499 m : user_agent.set_winhttp_version(GetWinHttpVersion());
500 m : GetOSAndCPU(&user_agent);
501 m : user_agent_ = user_agent.AsString();
502 m : }
503 :
504 m : HttpAgentImpl::~HttpAgentImpl() {}
505 :
506 m : std::unique_ptr<HttpResponse> HttpAgentImpl::Post(
507 m : const base::string16& host,
508 m : uint16_t port,
509 m : const base::string16& path,
510 m : bool secure,
511 m : const base::string16& extra_headers,
512 m : const std::string& body) {
513 m : return HttpResponseImpl::Create(user_agent_, host, port, path, secure,
514 m : extra_headers, body);
515 m : }
516 :
517 m : } // namespace kasko
|