THRIFT-4882 Autodetect proxy settings with WinHTTP
Client: Delphi
Patch: Jens Geyer
diff --git a/lib/delphi/src/Thrift.WinHTTP.pas b/lib/delphi/src/Thrift.WinHTTP.pas
index 4b98f69..b26f6ba 100644
--- a/lib/delphi/src/Thrift.WinHTTP.pas
+++ b/lib/delphi/src/Thrift.WinHTTP.pas
@@ -63,6 +63,40 @@
LPURL_COMPONENTSW = LPURL_COMPONENTS;
+ // When retrieving proxy data, an application must free the lpszProxy and
+ // lpszProxyBypass strings contained in this structure (if they are non-NULL)
+ // using the GlobalFree function.
+ LPWINHTTP_PROXY_INFO = ^WINHTTP_PROXY_INFO;
+ WINHTTP_PROXY_INFO = record
+ dwAccessType : DWORD; // see WINHTTP_ACCESS_* types below
+ lpszProxy : LPWSTR; // proxy server list
+ lpszProxyBypass : LPWSTR; // proxy bypass list
+ end;
+
+ LPWINHTTP_PROXY_INFOW = ^WINHTTP_PROXY_INFOW;
+ WINHTTP_PROXY_INFOW = WINHTTP_PROXY_INFO;
+
+
+ WINHTTP_AUTOPROXY_OPTIONS = record
+ dwFlags : DWORD;
+ dwAutoDetectFlags : DWORD;
+ lpszAutoConfigUrl : LPCWSTR;
+ lpvReserved : LPVOID;
+ dwReserved : DWORD;
+ fAutoLogonIfChallenged : BOOL;
+ end;
+
+
+ WINHTTP_CURRENT_USER_IE_PROXY_CONFIG = record
+ fAutoDetect : BOOL;
+ lpszAutoConfigUrl : LPWSTR;
+ lpszProxy : LPWSTR;
+ lpszProxyBypass : LPWSTR;
+ end;
+
+
+
+
function WinHttpCloseHandle( aHandle : HINTERNET) : BOOL; stdcall;
function WinHttpOpen( const pszAgentW : LPCWSTR;
@@ -104,6 +138,16 @@
const dwModifiers : DWORD
) : BOOL; stdcall;
+function WinHttpGetProxyForUrl( const hSession : HINTERNET;
+ const lpcwszUrl : LPCWSTR;
+ const options : WINHTTP_AUTOPROXY_OPTIONS;
+ const info : WINHTTP_PROXY_INFO
+ ) : BOOL; stdcall;
+
+function WinHttpGetIEProxyConfigForCurrentUser( var config : WINHTTP_CURRENT_USER_IE_PROXY_CONFIG
+ ) : BOOL; stdcall;
+
+
function WinHttpSendRequest( const hRequest : HINTERNET;
const lpszHeaders : LPCWSTR;
const dwHeadersLength : DWORD;
@@ -353,6 +397,17 @@
or WINHTTP_FLAG_SECURE_PROTOCOL_SSL3
or WINHTTP_FLAG_SECURE_PROTOCOL_TLS1;
+ // AutoProxy
+ WINHTTP_AUTOPROXY_AUTO_DETECT = $00000001;
+ WINHTTP_AUTOPROXY_CONFIG_URL = $00000002;
+ WINHTTP_AUTOPROXY_HOST_KEEPCASE = $00000004;
+ WINHTTP_AUTOPROXY_HOST_LOWERCASE = $00000008;
+ WINHTTP_AUTOPROXY_RUN_INPROCESS = $00010000;
+ WINHTTP_AUTOPROXY_RUN_OUTPROCESS_ONLY = $00020000;
+
+ // Flags for dwAutoDetectFlags
+ WINHTTP_AUTO_DETECT_TYPE_DHCP = $00000001;
+ WINHTTP_AUTO_DETECT_TYPE_DNS_A = $00000002;
const
WINHTTP_ERROR_BASE = 12000;
@@ -417,11 +472,16 @@
type
+ IWinHTTPSession = interface;
+ IWinHTTPConnection = interface;
+
IWinHTTPRequest = interface
- ['{35C6D9D4-FDCE-42C6-B84C-9294E6FB904C}']
+ ['{0B7D095E-BB3D-4444-8686-5536E7D6437B}']
function Handle : HINTERNET;
+ function Connection : IWinHTTPConnection;
function AddRequestHeader( const aHeader : string; const addflag : DWORD = WINHTTP_ADDREQ_FLAG_ADD) : Boolean;
function SetTimeouts( const aResolveTimeout, aConnectTimeout, aSendTimeout, aReceiveTimeout : Int32) : Boolean;
+ procedure TryAutoProxy( const aUrl : string);
function SendRequest( const pBuf : Pointer; const dwBytes : DWORD; const dwExtra : DWORD = 0) : Boolean;
function WriteExtraData( const pBuf : Pointer; const dwBytes : DWORD) : DWORD;
function FlushAndReceiveResponse : Boolean;
@@ -430,8 +490,9 @@
end;
IWinHTTPConnection = interface
- ['{1C4F78B5-1525-4788-B638-A0E41BCF4D43}']
+ ['{ED5BCA49-84D6-4CFE-BF18-3238B1FF2AFB}']
function Handle : HINTERNET;
+ function Session : IWinHTTPSession;
function OpenRequest( const secure : Boolean; const aVerb, aObjName, aAcceptTypes : UnicodeString) : IWinHTTPRequest;
end;
@@ -519,6 +580,7 @@
// IWinHTTPConnection
function OpenRequest( const secure : Boolean; const aVerb, aObjName, aAcceptTypes : UnicodeString) : IWinHTTPRequest;
+ function Session : IWinHTTPSession;
public
constructor Create( const aSession : IWinHTTPSession; const aHostName : UnicodeString; const aPort : INTERNET_PORT);
@@ -533,8 +595,10 @@
FConnection : IWinHTTPConnection;
// IWinHTTPRequest
+ function Connection : IWinHTTPConnection;
function AddRequestHeader( const aHeader : string; const addflag : DWORD = WINHTTP_ADDREQ_FLAG_ADD) : Boolean;
function SetTimeouts( const aResolveTimeout, aConnectTimeout, aSendTimeout, aReceiveTimeout : Int32) : Boolean;
+ procedure TryAutoProxy( const aUrl : string);
function SendRequest( const pBuf : Pointer; const dwBytes : DWORD; const dwExtra : DWORD = 0) : Boolean;
function WriteExtraData( const pBuf : Pointer; const dwBytes : DWORD) : DWORD;
function FlushAndReceiveResponse : Boolean;
@@ -595,6 +659,18 @@
end;
+ WINHTTP_PROXY_INFO_Helper = record helper for WINHTTP_PROXY_INFO
+ procedure Initialize;
+ procedure FreeAllocatedResources;
+ end;
+
+
+ WINHTTP_CURRENT_USER_IE_PROXY_CONFIG_Helper = record helper for WINHTTP_CURRENT_USER_IE_PROXY_CONFIG
+ procedure Initialize;
+ procedure FreeAllocatedResources;
+ end;
+
+
EWinHTTPException = class(Exception);
implementation
@@ -610,6 +686,8 @@
function WinHttpQueryOption; stdcall; external WINHTTP_DLL;
function WinHttpSetOption; stdcall; external WINHTTP_DLL;
function WinHttpAddRequestHeaders; stdcall; external WINHTTP_DLL;
+function WinHttpGetProxyForUrl; stdcall; external WINHTTP_DLL;
+function WinHttpGetIEProxyConfigForCurrentUser; stdcall; external WINHTTP_DLL;
function WinHttpWriteData; stdcall; external WINHTTP_DLL;
function WinHttpReceiveResponse; stdcall; external WINHTTP_DLL;
function WinHttpQueryHeaders; stdcall; external WINHTTP_DLL;
@@ -619,6 +697,51 @@
function WinHttpCreateUrl; stdcall; external WINHTTP_DLL;
+{ misc. record helper }
+
+
+procedure GlobalFreeAndNil( var p : LPWSTR);
+begin
+ if p <> nil then begin
+ GlobalFree( HGLOBAL( p));
+ p := nil;
+ end;
+end;
+
+
+procedure WINHTTP_PROXY_INFO_Helper.Initialize;
+begin
+ FillChar( Self, SizeOf(Self), 0);
+end;
+
+
+procedure WINHTTP_PROXY_INFO_Helper.FreeAllocatedResources;
+// The caller must free the lpszProxy and lpszProxyBypass strings
+// if they are non-NULL. Use GlobalFree to free the strings.
+begin
+ GlobalFreeAndNil( lpszProxy);
+ GlobalFreeAndNil( lpszProxyBypass);
+ Initialize;
+end;
+
+
+procedure WINHTTP_CURRENT_USER_IE_PROXY_CONFIG_Helper.Initialize;
+begin
+ FillChar( Self, SizeOf(Self), 0);
+end;
+
+
+procedure WINHTTP_CURRENT_USER_IE_PROXY_CONFIG_Helper.FreeAllocatedResources;
+// The caller must free the lpszProxy, lpszProxyBypass and lpszAutoConfigUrl strings
+// if they are non-NULL. Use GlobalFree to free the strings.
+begin
+ GlobalFreeAndNil( lpszProxy);
+ GlobalFreeAndNil( lpszProxyBypass);
+ GlobalFreeAndNil( lpszAutoConfigUrl);
+ Initialize;
+end;
+
+
{ TWinHTTPHandleObjectImpl }
constructor TWinHTTPHandleObjectImpl.Create( const aHandle : HINTERNET);
@@ -713,6 +836,12 @@
end;
+function TWinHTTPConnectionImpl.Session : IWinHTTPSession;
+begin
+ result := FSession;
+end;
+
+
function TWinHTTPConnectionImpl.OpenRequest( const secure : Boolean; const aVerb, aObjName, aAcceptTypes : UnicodeString) : IWinHTTPRequest;
var dwFlags : DWORD;
begin
@@ -759,6 +888,12 @@
end;
+function TWinHTTPRequestImpl.Connection : IWinHTTPConnection;
+begin
+ result := FConnection;
+end;
+
+
function TWinHTTPRequestImpl.SetTimeouts( const aResolveTimeout, aConnectTimeout, aSendTimeout, aReceiveTimeout : Int32) : Boolean;
begin
result := WinHttpSetTimeouts( FHandle, aResolveTimeout, aConnectTimeout, aSendTimeout, aReceiveTimeout);
@@ -771,6 +906,85 @@
end;
+procedure TWinHTTPRequestImpl.TryAutoProxy( const aUrl : string);
+// From MSDN:
+// AutoProxy support is not fully integrated into the HTTP stack in WinHTTP.
+// Before sending a request, the application must call WinHttpGetProxyForUrl
+// to obtain the name of a proxy server and then call WinHttpSetOption using
+// WINHTTP_OPTION_PROXY to set the proxy configuration on the WinHTTP request
+// handle created by WinHttpOpenRequest.
+// See https://docs.microsoft.com/en-us/windows/desktop/winhttp/winhttp-autoproxy-api
+var
+ options : WINHTTP_AUTOPROXY_OPTIONS;
+ proxy : WINHTTP_PROXY_INFO;
+ ieProxy : WINHTTP_CURRENT_USER_IE_PROXY_CONFIG;
+ dwSize : DWORD;
+begin
+ // try AutoProxy via PAC first
+ proxy.Initialize;
+ try
+ FillChar( options, SizeOf(options), 0);
+ options.dwFlags := WINHTTP_AUTOPROXY_AUTO_DETECT;
+ options.dwAutoDetectFlags := WINHTTP_AUTO_DETECT_TYPE_DHCP or WINHTTP_AUTO_DETECT_TYPE_DNS_A;
+ options.fAutoLogonIfChallenged := TRUE;
+ if WinHttpGetProxyForUrl( FConnection.Session.Handle, PChar(aUrl), options, proxy) then begin
+ dwSize := SizeOf(proxy);
+ WinHttpSetOption( Handle, WINHTTP_OPTION_PROXY, @proxy, dwSize);
+ Exit;
+ end;
+
+ finally
+ proxy.FreeAllocatedResources;
+ end;
+
+ // Use IE settings as a fallback, useful in client (i.e. non-server) environments
+ ieProxy.Initialize;
+ try
+ if WinHttpGetIEProxyConfigForCurrentUser( ieProxy)
+ then begin
+
+ // lpszAutoConfigUrl = "Use automatic proxy configuration"
+ if ieProxy.lpszAutoConfigUrl <> nil then begin
+ options.lpszAutoConfigUrl := ieProxy.lpszAutoConfigUrl;
+ options.dwFlags := options.dwFlags or WINHTTP_AUTOPROXY_CONFIG_URL;
+
+ proxy.Initialize;
+ try
+ if WinHttpGetProxyForUrl( FConnection.Session.Handle, PChar(aUrl), options, proxy) then begin
+ dwSize := SizeOf(proxy);
+ WinHttpSetOption( Handle, WINHTTP_OPTION_PROXY, @proxy, dwSize);
+ Exit;
+ end;
+ finally
+ proxy.FreeAllocatedResources;
+ end;
+ end;
+
+ // lpszProxy = "use a proxy server"
+ if ieProxy.lpszProxy <> nil then begin
+ proxy.Initialize;
+ try
+ proxy.dwAccessType := WINHTTP_ACCESS_TYPE_NAMED_PROXY;
+ proxy.lpszProxy := ieProxy.lpszProxy;
+ proxy.lpszProxyBypass := ieProxy.lpszProxyBypass;
+ dwSize := SizeOf(proxy);
+ WinHttpSetOption( Handle, WINHTTP_OPTION_PROXY, @proxy, dwSize);
+ Exit;
+ finally
+ proxy.Initialize; // not FreeAllocatedResources, we only hold pointer copies!
+ end;
+ end;
+
+ end;
+
+ finally
+ ieProxy.FreeAllocatedResources;
+ end;
+end;
+
+
+
+
function TWinHTTPRequestImpl.SendRequest( const pBuf : Pointer; const dwBytes, dwExtra : DWORD) : Boolean;
begin
result := WinHttpSendRequest( FHandle,
@@ -979,3 +1193,4 @@
end.
+