/***************************************************************
 * Purpose:   Proxy settings detection
 *
 *  Copyright (C) 2015 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
 *  Copyright (C) 2010 Sourcefire, Inc.
 *
 *  Authors: Török Edwin
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program. If not, see <http://www.gnu.org/licenses/>.
 **************************************************************/
#include "wx_pch.h"
#include "SigUIMain.h"

#ifdef _WIN32
#include <cstring>
#include <wx/uri.h>
#include <wx/dynlib.h>
#include <wx/tokenzr.h>

typedef struct
{
    BOOL   fAutoDetect;
    LPWSTR lpszAutoConfigUrl;
    LPWSTR lpszProxy;
    LPWSTR lpszProxyBypass;
} WINHTTP_CURRENT_USER_IE_PROXY_CONFIG;

typedef struct {
  DWORD   dwFlags;
  DWORD   dwAutoDetectFlags;
  LPCWSTR lpszAutoConfigUrl;
  LPVOID  lpvReserved;
  DWORD   dwReserved;
  BOOL    fAutoLogonIfChallenged;
} WINHTTP_AUTOPROXY_OPTIONS;

typedef struct {
  DWORD  dwAccessType;
  LPWSTR lpszProxy;
  LPWSTR lpszProxyBypass;
} WINHTTP_PROXY_INFO;

typedef void* HINTERNET;

#define wxDLW_VOIDMETHOD_DEFINE( name, args, argnames ) \
    typedef void (WINAPI * wxDL_METHOD_TYPE(name)) args ; \
    wxDL_METHOD_TYPE(name) wxDL_METHOD_NAME(name); \
    void name args \
        { if ( m_ok ) wxDL_METHOD_NAME(name) argnames ; }

#define wxDLW_METHOD_DEFINE( rettype, name, args, argnames, defret ) \
    typedef rettype (WINAPI * wxDL_METHOD_TYPE(name)) args ; \
    wxDL_METHOD_TYPE(name) wxDL_METHOD_NAME(name); \
    rettype name args \
        { return m_ok ? wxDL_METHOD_NAME(name) argnames : defret; }


class WHttp {
    public:
	WHttp() {
	    m_whttp.Load("winhttp.dll", wxDL_DEFAULT | wxDL_VERBATIM | wxDL_QUIET);
	    m_ok = m_whttp.IsLoaded();
	    if (!m_ok)
		return;
	    m_ok = InitializeMethods();
	}
	bool IsOK() const { return m_ok; }

	wxDLW_METHOD_DEFINE(BOOL, WinHttpGetIEProxyConfigForCurrentUser,
			   (WINHTTP_CURRENT_USER_IE_PROXY_CONFIG *pProxyConfig),
			   (pProxyConfig), FALSE)
    wxDLW_METHOD_DEFINE(BOOL, WinHttpGetProxyForUrl,
			   (HINTERNET hSession, LPCWSTR lpcwszUrl,
			    WINHTTP_AUTOPROXY_OPTIONS *pAutoProxyOptions,
			    WINHTTP_PROXY_INFO *pProxyInfo),
			   (hSession, lpcwszUrl, pAutoProxyOptions, pProxyInfo), FALSE)
	wxDLW_METHOD_DEFINE(HINTERNET, WinHttpOpen,
			   (LPCWSTR pwszUserAgent,
			    DWORD dwAccessType,
			    LPCWSTR pwszProxyName,
			    LPCWSTR pwszProxyByPass,
			    DWORD dwFlags),
			   (pwszUserAgent, dwAccessType, pwszProxyName,
			    pwszProxyByPass, dwFlags), 0)
	wxDLW_METHOD_DEFINE(BOOL, WinHttpCloseHandle,
			   (HINTERNET hInternet),
			   (hInternet), FALSE)
    private:
	wxDynamicLibrary m_whttp;
	bool m_ok;

	bool InitializeMethods() {
	    wxDL_METHOD_LOAD(m_whttp, WinHttpGetIEProxyConfigForCurrentUser);
	    wxDL_METHOD_LOAD(m_whttp, WinHttpGetProxyForUrl);
	    wxDL_METHOD_LOAD(m_whttp, WinHttpOpen);
	    wxDL_METHOD_LOAD(m_whttp, WinHttpCloseHandle);
	    return true;
	}
};

void SigUIFrame::m_proxy_autodetOnButtonClick( wxCommandEvent& WXUNUSED(event) )
{
    // there should be a simpler API to do this

    WINHTTP_CURRENT_USER_IE_PROXY_CONFIG config;
    WINHTTP_AUTOPROXY_OPTIONS autoproxy_opts;
    WINHTTP_PROXY_INFO autoproxy_info;

    memset(&config, 0, sizeof(config));

    wxLogVerbose("Starting proxy autodetection");
    WHttp whttp;
    if (!whttp.IsOK()) {
	wxLogError(_("WinHTTP could not be loaded: %s"), wxSysErrorMsg(wxSysErrorCode()));
	return;
    }

    bool need_autoproxy = false;
    memset(&config, 0, sizeof(config));
    memset(&autoproxy_opts, 0, sizeof(autoproxy_opts));
    if (whttp.WinHttpGetIEProxyConfigForCurrentUser(&config) == TRUE) {
	wxLogVerbose("IE proxy: %s", config.lpszProxy);
	if (config.fAutoDetect) {
	    wxLogVerbose("Autodetect is set");
	    need_autoproxy = true;
	    autoproxy_opts.dwFlags |= 1;
	}
	if (config.lpszAutoConfigUrl) {
	    need_autoproxy = true;
	    wxLogVerbose("Autoconfig URL: %s", config.lpszAutoConfigUrl);
	    autoproxy_opts.dwFlags |= 2;
	    autoproxy_opts.lpszAutoConfigUrl = config.lpszAutoConfigUrl;
	}
    } else {
	wxLogVerbose("Failed to get IE proxy settings: %s", wxSysErrorMsg(wxSysErrorCode()));
	need_autoproxy = true;
	autoproxy_opts.dwFlags |= 1;
    }

    wxString proxy = wxEmptyString;

    if (need_autoproxy) {
	autoproxy_opts.dwAutoDetectFlags = 3;
	autoproxy_opts.fAutoLogonIfChallenged = TRUE;

	HINTERNET h = whttp.WinHttpOpen(L"SigUI",1,NULL,NULL,0);

	if (h) {
	    wxLogVerbose("Retrieving proxy settings for URL");
	    if (whttp.WinHttpGetProxyForUrl(h,
					    L"http://db.local.win.clamav.net",
					    &autoproxy_opts,
					    &autoproxy_info)) {
		wxLogVerbose("proxy: %s, accesstype: %d",
			     autoproxy_info.lpszProxy,
			     autoproxy_info.dwAccessType);
		if (autoproxy_info.dwAccessType == 3)
		    proxy = wxString(autoproxy_info.lpszProxy);
	    } else {
		wxLogVerbose("Autoconfig failed, falling back to manual IE settings");
		proxy = config.lpszProxy;
	    }
	    whttp.WinHttpCloseHandle(h);
	} else {
	    wxLogVerbose("WinHttpOpen failed", wxSysErrorMsg(wxSysErrorCode()));
	}
    } else {
	proxy = config.lpszProxy;
    }
    wxLogVerbose("Final proxy: %s", proxy);

    if (proxy.empty()) {
        m_proxy->SetValue(false);
	wxCommandEvent ev;
	m_proxyOnCheckBox(ev);
        return;
    }

    wxStringTokenizer tokenizer(proxy, ";");
    while (tokenizer.HasMoreTokens()) {
	wxString token = tokenizer.GetNextToken();
	if (token.Find('=') == wxNOT_FOUND) {
	    token = token.Prepend("http=");
	}
	if (!token.StartsWith("http="))
	    continue;
	token = token.Mid(5);
	if (token.StartsWith("http://"))
	    token = token.Mid(7);
	token = "http://" + token;
	wxLogVerbose("token: %s", token);
	wxURI uri(token);
	m_proxy->SetValue(true);
        m_proxy_server->SetValue(uri.GetServer());
        if (uri.HasPort())
            m_proxy_port->SetValue(uri.GetPort());
        else
            m_proxy_port->SetValue(80);
        m_proxyauth->SetValue(false);
	wxCommandEvent ev;
	m_proxyOnCheckBox(ev);
	return;
    }
}
#else
void SigUIFrame::m_proxy_autodetOnButtonClick( wxCommandEvent& event )
{ }
#endif