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