blob: a02e1f9e2a0a55770e6644cf80d6c9faea9c71f0 [file] [log] [blame]
// Copyright 2007-2009 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========================================================================
// TODO(omaha): move EnclosePath and UnenclosePath functions from path.h to
// string.h
// TODO(omaha): Firefox detector does not handle proxy bypass.
#include "omaha/net/detector.h"
#include "base/scoped_ptr.h"
#include "omaha/common/atl_regexp.h"
#include "omaha/common/browser_utils.h"
#include "omaha/common/constants.h"
#include "omaha/common/debug.h"
#include "omaha/common/file_reader.h"
#include "omaha/common/path.h"
#include "omaha/common/reg_key.h"
#include "omaha/common/string.h"
#include "omaha/common/user_info.h"
#include "omaha/common/utils.h"
#include "omaha/common/time.h"
#include "omaha/net/http_client.h"
#include "omaha/net/network_config.h"
namespace omaha {
namespace {
// Returns true if the caller's impersonation or process access token user
// is LOCAL_SYSTEM.
bool IsRunningAsSystem() {
CString sid;
HRESULT hr = user_info::GetCurrentThreadUser(&sid);
if (SUCCEEDED(hr)) {
return IsLocalSystemSid(sid);
}
sid.Empty();
hr = user_info::GetCurrentUser(NULL, NULL, &sid);
if (SUCCEEDED(hr)) {
return IsLocalSystemSid(sid);
}
return false;
}
} // namespace
HRESULT GoogleProxyDetector::Detect(Config* config) {
ASSERT1(config);
RegKey reg_key;
HRESULT hr = reg_key.Open(reg_path_, KEY_READ);
if (FAILED(hr)) {
return hr;
}
CString proxy_host;
hr = reg_key.GetValue(kRegValueProxyHost, &proxy_host);
if (FAILED(hr)) {
return hr;
}
DWORD proxy_port(0);
hr = reg_key.GetValue(kRegValueProxyPort, &proxy_port);
if (FAILED(hr)) {
return hr;
}
*config = Config();
config->proxy.Format(_T("%s:%d"), proxy_host, proxy_port);
config->source = _T("Google");
return S_OK;
}
FirefoxProxyDetector::FirefoxProxyDetector()
: cached_prefs_last_modified_(0),
cached_config_(new Config) {}
FirefoxProxyDetector::~FirefoxProxyDetector() {}
HRESULT FirefoxProxyDetector::Detect(Config* config) {
ASSERT1(config);
// The Firefox profile is not available when running as a local system.
if (IsRunningAsSystem()) {
return E_FAIL;
}
const TCHAR* const kFirefoxPrefsJsFile = _T("\\prefs.js");
CString name, path;
HRESULT hr = GetFirefoxDefaultProfile(&name, &path);
if (FAILED(hr)) {
return hr;
}
path.Append(kFirefoxPrefsJsFile);
// Has the current profile been modified? Check the name, path, and
// last modified time of the profile are the same as their cached values.
FILETIME filetime_last_modified = {0};
int64 last_modified = 0;
if (SUCCEEDED(File::GetFileTime(path,
NULL,
NULL,
&filetime_last_modified))) {
last_modified = FileTimeToInt64(filetime_last_modified);
}
if (name.CompareNoCase(cached_prefs_name_) == 0 &&
path.CompareNoCase(cached_prefs_file_path_) == 0 &&
last_modified == cached_prefs_last_modified_ &&
last_modified) {
NET_LOG(L4, (_T("[using FF cached profile][%s]"), path));
*config = *cached_config_;
return S_OK;
}
hr = ParsePrefsFile(name, path, config);
if (SUCCEEDED(hr) && last_modified) {
NET_LOG(L4, (_T("[cache FF profile][%s]"), path));
cached_prefs_name_ = name;
cached_prefs_file_path_ = path;
cached_prefs_last_modified_ = last_modified;
*cached_config_ = *config;
}
return hr;
}
// This is what the proxy configuration in Firefox looks like:
// user_pref("network.proxy.autoconfig_url", "http://wpad/wpad.dat");
// user_pref("network.proxy.ftp", "127.0.0.1");
// user_pref("network.proxy.ftp_port", 8888);
// user_pref("network.proxy.gopher", "127.0.0.1");
// user_pref("network.proxy.gopher_port", 8888);
// user_pref("network.proxy.http", "127.0.0.1");
// user_pref("network.proxy.http_port", 8888);
// user_pref("network.proxy.share_proxy_settings", true);
// user_pref("network.proxy.socks", "127.0.0.1");
// user_pref("network.proxy.socks_port", 8888);
// user_pref("network.proxy.ssl", "127.0.0.1");
// user_pref("network.proxy.ssl_port", 8888);
// user_pref("network.proxy.type", 4);
HRESULT FirefoxProxyDetector::ParsePrefsFile(const TCHAR* name,
const TCHAR* file_path,
Config* config) {
ASSERT1(name);
ASSERT1(file_path);
ASSERT1(config);
*config = Config();
config->source = _T("FireFox");
// TODO(omaha): implement optimization not to parse the file again if it
// did not change.
UNREFERENCED_PARAMETER(name);
// There were issues in production where the code fails to allocate
// the 1MB memory buffer as it had been initially requested by a previous
// version of the code.
//
// The assert below is somehow flaky but useful to detect the unlikely cases
// when the prefs file can't be opened.
FileReader prefs_file;
const size_t kBufferSize = 0x10000; // 64KB buffer.
HRESULT hr = prefs_file.Init(file_path, kBufferSize);
ASSERT1(SUCCEEDED(hr));
if (FAILED(hr)) {
return hr;
}
CString proxy_type;
CString proxy_config_url;
CString proxy_http_host;
CString proxy_http_port;
CString proxy_ssl_host;
CString proxy_ssl_port;
// For each line in the prefs.js, try to parse the proxy information out.
char line[1024] = {0};
while (SUCCEEDED(prefs_file.ReadLineAnsi(arraysize(line), line))) {
ParsePrefsLine(line,
&proxy_type,
&proxy_config_url,
&proxy_http_host,
&proxy_http_port,
&proxy_ssl_host,
&proxy_ssl_port);
}
// The default in FireFox is direct connection so it may be that the
// network.proxy.type is missing.
int type = PROXY_TYPE_NO_PROXY;
if (!proxy_type.IsEmpty() &&
!String_StringToDecimalIntChecked(proxy_type, &type)) {
return E_UNEXPECTED;
}
// Direct connection.
if (type == PROXY_TYPE_NO_PROXY) {
return S_OK;
}
// We look for both proxy auto-detect and proxy config url, to emulate
// the IE behavior, where when the auto-detect fails it defaults to the
// auto config url. Firefox remembers the auto config url even if not used,
// so it might not hurt to try it out.
if (type & PROXY_TYPE_AUTO_DETECT) {
config->auto_detect = true;
}
if ((type & PROXY_TYPE_AUTO_CONFIG_URL) && !proxy_config_url.IsEmpty()) {
UnenclosePath(&proxy_config_url);
config->auto_config_url = proxy_config_url;
}
// Named proxy.
if (!(type & PROXY_TYPE_NAMED_PROXY)) {
return S_OK;
}
CString proxy;
hr = BuildProxyString(proxy_http_host,
proxy_http_port,
proxy_ssl_host,
proxy_ssl_port,
&proxy);
if (FAILED(hr)) {
return hr;
}
config->proxy = proxy;
return S_OK;
}
HRESULT FirefoxProxyDetector::BuildProxyString(const CString& proxy_http_host,
const CString& http_port,
const CString& proxy_ssl_host,
const CString& ssl_port,
CString* proxy) {
ASSERT1(proxy);
CString http_host = proxy_http_host;
CString ssl_host = proxy_ssl_host;
// The host names in the prefs file are strings literals.
UnenclosePath(&http_host);
UnenclosePath(&ssl_host);
// Validate the port values.
if (!http_port.IsEmpty()) {
int http_port_num = 0;
if (!String_StringToDecimalIntChecked(http_port, &http_port_num) ||
http_port_num <= 0 &&
http_port_num > INTERNET_MAX_PORT_NUMBER_VALUE) {
return E_INVALIDARG;
}
}
if (!ssl_port.IsEmpty()) {
int ssl_port_num = 0;
if (!String_StringToDecimalIntChecked(ssl_port, &ssl_port_num) ||
ssl_port_num <= 0 ||
ssl_port_num > INTERNET_MAX_PORT_NUMBER_VALUE) {
return E_INVALIDARG;
}
}
// Format the proxy string.
CString str;
if (!http_host.IsEmpty()) {
str.AppendFormat(_T("http=%s"), http_host);
if (!http_port.IsEmpty()) {
str.AppendFormat(_T(":%s"), http_port);
}
}
if (!ssl_host.IsEmpty()) {
// Append a separator if needed.
if (!str.IsEmpty()) {
str += _T(';');
}
str.AppendFormat(_T("https=%s"), ssl_host);
if (!ssl_port.IsEmpty()) {
str.AppendFormat(_T(":%s"), ssl_port);
}
}
*proxy = str;
return S_OK;
}
// Parses a line from the prefs.js. An example of line to parse is:
// user_pref("network.proxy.http", "foo");
void FirefoxProxyDetector::ParsePrefsLine(const char* ansi_line,
CString* proxy_type,
CString* proxy_config_url,
CString* proxy_http_host,
CString* proxy_http_port,
CString* proxy_ssl_host,
CString* proxy_ssl_port) {
// Skip the lines that do not contain "network.proxy" to speed up the
// parsing. This is important for large prefs files.
if (strstr(ansi_line, "network.proxy.") == NULL) {
return;
}
AtlRE proxy_type_regex(_T("^\\b*user_pref\\b*\\(\\b*\\\"network\\.proxy\\.type\\\"\\b*,\\b*{\\d+}\\)"), false); // NOLINT
AtlRE proxy_config_url_regex(_T("^\\b*user_pref\\b*\\(\\b*\\\"network\\.proxy\\.autoconfig_url\\\"\\b*,\\b*{\\q}\\)"), false); // NOLINT
AtlRE proxy_http_host_regex(_T("^\\b*user_pref\\b*\\(\\b*\\\"network\\.proxy\\.http\\\"\\b*,\\b*{\\q}\\)"), false); // NOLINT
AtlRE proxy_http_port_regex(_T("^\\b*user_pref\\b*\\(\\b*\\\"network\\.proxy\\.http_port\\\"\\b*,\\b*{\\d+}\\)"), false); // NOLINT
AtlRE proxy_ssl_host_regex(_T("^\\b*user_pref\\b*\\(\\b*\\\"network\\.proxy\\.ssl\\\"\\b*,\\b*{\\q}\\)"), false); // NOLINT
AtlRE proxy_ssl_port_regex(_T("^\\b*user_pref\\b*\\(\\b*\\\"network\\.proxy\\.ssl_port\\\"\\b*,\\b*{\\d+}\\)"), false); // NOLINT
CString line(ansi_line);
if (AtlRE::PartialMatch(line, proxy_type_regex, proxy_type)) {
return;
}
if (AtlRE::PartialMatch(line, proxy_config_url_regex, proxy_config_url)) {
return;
}
if (AtlRE::PartialMatch(line, proxy_http_host_regex, proxy_http_host)) {
return;
}
if (AtlRE::PartialMatch(line, proxy_http_port_regex, proxy_http_port)) {
return;
}
if (AtlRE::PartialMatch(line, proxy_ssl_host_regex, proxy_ssl_host)) {
return;
}
if (AtlRE::PartialMatch(line, proxy_ssl_port_regex, proxy_ssl_port)) {
return;
}
}
HRESULT DefaultProxyDetector::Detect(Config* config) {
ASSERT1(config);
scoped_ptr<HttpClient> http_client(CreateHttpClient());
// We expect to be able to instantiate either of the http clients.
ASSERT1(http_client.get());
if (!http_client.get()) {
return E_UNEXPECTED;
}
HRESULT hr = http_client->Initialize();
if (FAILED(hr)) {
return hr;
}
HttpClient::ProxyInfo proxy_info = {0};
hr = http_client->GetDefaultProxyConfiguration(&proxy_info);
if (FAILED(hr)) {
return hr;
}
if (proxy_info.access_type == WINHTTP_ACCESS_TYPE_NAMED_PROXY) {
Config proxy_config;
proxy_config.source = _T("winhttp");
proxy_config.proxy = proxy_info.proxy;
proxy_config.proxy_bypass = proxy_info.proxy_bypass;
*config = proxy_config;
return S_OK;
} else {
return E_FAIL;
}
}
HRESULT IEProxyDetector::Detect(Config* config) {
ASSERT1(config);
// Internet Explorer proxy configuration is not available when running as
// local system.
if (IsRunningAsSystem()) {
return E_FAIL;
}
scoped_ptr<HttpClient> http_client(CreateHttpClient());
// We expect to be able to instantiate either of the http clients.
ASSERT1(http_client.get());
if (!http_client.get()) {
return E_UNEXPECTED;
}
HRESULT hr = http_client->Initialize();
if (FAILED(hr)) {
return hr;
}
HttpClient::CurrentUserIEProxyConfig ie_proxy_config = {0};
hr = http_client->GetIEProxyConfiguration(&ie_proxy_config);
if (FAILED(hr)) {
return hr;
}
config->source = _T("IE");
config->auto_detect = ie_proxy_config.auto_detect;
config->auto_config_url = ie_proxy_config.auto_config_url;
config->proxy = ie_proxy_config.proxy;
config->proxy_bypass = ie_proxy_config.proxy_bypass;
::GlobalFree(const_cast<TCHAR*>(ie_proxy_config.auto_config_url));
::GlobalFree(const_cast<TCHAR*>(ie_proxy_config.proxy));
::GlobalFree(const_cast<TCHAR*>(ie_proxy_config.proxy_bypass));
return S_OK;
};
} // namespace omaha