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_.IsValid()) {
277 i : LOG(ERROR) << "WinHttpOpen() failed: " << ::common::LogWe();
278 i : return scoped_ptr<HttpResponse>();
279 : }
280 :
281 : // Look up URL-specific proxy settings. If this fails, we will fall back to
282 : // working without a proxy.
283 E : AutoWinHttpUrlProxyConfig url_proxy_config(proxy_config);
284 : url_proxy_config.Load(instance->session_.Get(),
285 E : ComposeUrl(host, port, path, secure));
286 :
287 : // Connect to a host/port.
288 : instance->connection_.Set(
289 E : ::WinHttpConnect(instance->session_.Get(), host.c_str(), port, 0));
290 E : if (!instance->connection_.IsValid()) {
291 i : LOG(ERROR) << "WinHttpConnect() failed with host " << host << " and port "
292 : << port << ": " << ::common::LogWe();
293 i : return scoped_ptr<HttpResponse>();
294 : }
295 :
296 : // Initiate a request. This doesn't actually send the request yet.
297 : instance->request_.Set(
298 : ::WinHttpOpenRequest(instance->connection_.Get(), L"POST", path.c_str(),
299 : NULL, // version
300 : NULL, // referer
301 : NULL, // accept types
302 E : secure ? WINHTTP_FLAG_SECURE : 0));
303 E : if (!instance->connection_.IsValid()) {
304 i : LOG(ERROR) << "WinHttpConnect() failed with host " << host << " and port "
305 : << port << ": " << ::common::LogWe();
306 i : return scoped_ptr<HttpResponse>();
307 : }
308 :
309 : // Disable cookies and authentication. This request should be completely
310 : // stateless and untied to any identity of any sort.
311 E : DWORD option_value = WINHTTP_DISABLE_COOKIES | WINHTTP_DISABLE_AUTHENTICATION;
312 : if (!::WinHttpSetOption(instance->request_.Get(),
313 : WINHTTP_OPTION_DISABLE_FEATURE, &option_value,
314 E : sizeof(option_value))) {
315 i : LOG(ERROR) << "WinHttpSetOption(WINHTTP_DISABLE_COOKIES | "
316 : "WINHTTP_DISABLE_AUTHENTICATION) failed: "
317 : << ::common::LogWe();
318 i : return scoped_ptr<HttpResponse>();
319 : }
320 :
321 : // If this URL is configured to use a proxy, set that up now.
322 E : if (url_proxy_config.get()) {
323 : if (!::WinHttpSetOption(instance->request_.Get(), WINHTTP_OPTION_PROXY,
324 : url_proxy_config.get(),
325 i : sizeof(*url_proxy_config.get()))) {
326 i : LOG(ERROR) << "WinHttpSetOption(WINHTTP_OPTION_PROXY) failed: "
327 : << ::common::LogWe();
328 i : return scoped_ptr<HttpResponse>();
329 : }
330 : }
331 :
332 : // Send the request.
333 : if (!::WinHttpSendRequest(instance->request_.Get(), extra_headers.c_str(),
334 : static_cast<DWORD>(-1),
335 : const_cast<char*>(body.data()),
336 : static_cast<DWORD>(body.size()),
337 E : static_cast<DWORD>(body.size()), NULL)) {
338 i : LOG(ERROR) << "Failed to send HTTP request to host " << host << " and port "
339 : << port << ": " << ::common::LogWe();
340 i : return scoped_ptr<HttpResponse>();
341 : }
342 :
343 : // This seems to read at least all headers from the response. The remainder of
344 : // the body, if any, may be read during subsequent calls to WinHttpReadData().
345 E : if (!::WinHttpReceiveResponse(instance->request_.Get(), 0)) {
346 i : LOG(ERROR) << "Failed to complete HTTP request to host " << host
347 : << " and port " << port << ": " << ::common::LogWe();
348 i : return scoped_ptr<HttpResponse>();
349 : }
350 :
351 E : return instance.Pass();
352 E : }
353 :
354 E : bool HttpResponseImpl::GetStatusCode(uint16_t* status_code) {
355 E : DCHECK(status_code);
356 :
357 E : bool has_status_code = false;
358 E : DWORD status_code_buffer = 0;
359 :
360 : if (QueryHeader(WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
361 : &has_status_code, &status_code_buffer,
362 E : sizeof(status_code_buffer))) {
363 E : DCHECK(has_status_code);
364 E : *status_code = status_code_buffer;
365 E : return true;
366 : }
367 :
368 i : return false;
369 E : }
370 :
371 : bool HttpResponseImpl::GetContentLength(bool* has_content_length,
372 E : size_t* content_length) {
373 E : DCHECK(has_content_length);
374 E : DCHECK(content_length);
375 :
376 E : DWORD content_length_header_value = 0;
377 :
378 : if (QueryHeader(WINHTTP_QUERY_CONTENT_LENGTH | WINHTTP_QUERY_FLAG_NUMBER,
379 : has_content_length, &content_length_header_value,
380 E : sizeof(content_length_header_value))) {
381 E : if (has_content_length)
382 E : *content_length = content_length_header_value;
383 E : return true;
384 : }
385 :
386 i : return false;
387 E : }
388 :
389 : bool HttpResponseImpl::GetContentType(bool* has_content_type,
390 E : base::string16* content_type) {
391 E : DCHECK(has_content_type);
392 E : DCHECK(content_type);
393 :
394 E : base::char16 content_type_buffer[256] = {0};
395 :
396 : if (QueryHeader(WINHTTP_QUERY_CONTENT_TYPE, has_content_type,
397 E : &content_type_buffer, sizeof(content_type_buffer))) {
398 E : if (has_content_type)
399 E : *content_type = content_type_buffer;
400 E : return true;
401 : }
402 :
403 i : return false;
404 E : }
405 :
406 E : bool HttpResponseImpl::HasData(bool* has_data) {
407 E : DCHECK(has_data);
408 :
409 E : DWORD leftover_data = 0;
410 E : if (!::WinHttpQueryDataAvailable(request_.Get(), &leftover_data)) {
411 i : LOG(ERROR) << "WinHttpQueryDataAvailable failed: " << ::common::LogWe();
412 i : return false;
413 : }
414 E : if (leftover_data != 0)
415 i : *has_data = true;
416 i : else
417 E : *has_data = false;
418 E : return true;
419 E : }
420 :
421 E : bool HttpResponseImpl::ReadData(char* buffer, size_t* count) {
422 E : DCHECK(buffer);
423 E : DCHECK(count);
424 :
425 E : DWORD size_read = 0;
426 E : if (!::WinHttpReadData(request_.Get(), buffer, *count, &size_read)) {
427 i : LOG(ERROR) << "Failed to read response body: " << ::common::LogWe();
428 i : return false;
429 : }
430 E : *count = size_read;
431 E : return true;
432 E : }
433 :
434 E : HttpResponseImpl::HttpResponseImpl() {}
435 :
436 : bool HttpResponseImpl::QueryHeader(DWORD info_level,
437 : bool* header_present,
438 : void* buffer,
439 E : DWORD buffer_length) {
440 : if (::WinHttpQueryHeaders(request_.Get(), info_level,
441 : WINHTTP_HEADER_NAME_BY_INDEX, buffer,
442 E : &buffer_length, 0)) {
443 E : *header_present = true;
444 E : return true;
445 : }
446 :
447 E : if (::GetLastError() == ERROR_WINHTTP_HEADER_NOT_FOUND) {
448 E : *header_present = false;
449 E : return true;
450 : }
451 :
452 i : LOG(ERROR) << "WinHttpQueryHeaders failed:" << ::common::LogWe();
453 i : return false;
454 E : }
455 :
456 E : base::string16 GetWinHttpVersion() {
457 E : HMODULE win_http_module = nullptr;
458 E : if (::GetModuleHandleEx(0, L"winhttp.dll", &win_http_module)) {
459 : scoped_ptr<FileVersionInfo> win_http_module_version_info(
460 E : FileVersionInfo::CreateFileVersionInfoForModule(win_http_module));
461 E : ::FreeLibrary(win_http_module);
462 E : if (win_http_module_version_info)
463 E : return win_http_module_version_info->product_version();
464 i : }
465 i : return L"?";
466 E : }
467 :
468 : // Adapted from Chromium content/common/user_agent.cc
469 E : void GetOSAndCPU(UserAgent* user_agent) {
470 E : int32 os_major_version = 0;
471 E : int32 os_minor_version = 0;
472 E : int32 os_bugfix_version = 0;
473 : base::SysInfo::OperatingSystemVersionNumbers(&os_major_version,
474 : &os_minor_version,
475 E : &os_bugfix_version);
476 E : user_agent->set_os_version(os_major_version, os_minor_version);
477 :
478 E : base::win::OSInfo* os_info = base::win::OSInfo::GetInstance();
479 E : if (os_info->wow64_status() == base::win::OSInfo::WOW64_ENABLED) {
480 E : user_agent->set_architecture(UserAgent::WOW64);
481 E : } else {
482 : base::win::OSInfo::WindowsArchitecture windows_architecture =
483 i : os_info->architecture();
484 i : if (windows_architecture == base::win::OSInfo::X64_ARCHITECTURE)
485 i : user_agent->set_architecture(UserAgent::X64);
486 i : else if (windows_architecture == base::win::OSInfo::IA64_ARCHITECTURE)
487 i : user_agent->set_architecture(UserAgent::IA64);
488 i : else
489 i : user_agent->set_architecture(UserAgent::X86);
490 : }
491 E : }
492 :
493 : } // namespace
494 :
495 : HttpAgentImpl::HttpAgentImpl(const base::string16& product_name,
496 E : const base::string16& product_version) {
497 E : UserAgent user_agent(product_name, product_version);
498 E : user_agent.set_winhttp_version(GetWinHttpVersion());
499 E : GetOSAndCPU(&user_agent);
500 E : user_agent_ = user_agent.AsString();
501 E : }
502 :
503 E : HttpAgentImpl::~HttpAgentImpl() {}
504 :
505 : scoped_ptr<HttpResponse> HttpAgentImpl::Post(
506 : const base::string16& host,
507 : uint16_t port,
508 : const base::string16& path,
509 : bool secure,
510 : const base::string16& extra_headers,
511 E : const std::string& body) {
512 : return HttpResponseImpl::Create(user_agent_, host, port, path, secure,
513 E : extra_headers, body);
514 E : }
515 :
516 : } // namespace kasko
|