Proxy mit HTTPS und Authentifizierung



  • Hallo,

    ich habe ein Problem mit den WinINet Funktionen. Ich komme nicht durch einen HTTPS Proxy, der auch noch Benutzername und Passwort verlangt (Base64-Kodierung).

    Bei HTTP funktioniert es einwandfrei, nur bei HTTPS nicht.
    Ich überwache das Netzwerk mit WireShark und mir fällt dabei auf, dass im Falle HTTP der Header "Proxy-Authorization: Basic dGVzdHVzZXI6UGFzc3dvcmQ=", der an den Proxy-Server geht, vorhanden ist, aber bei HTTPS nicht. Deswegen antwortet der Proxy im Falle HTTPS auch immer wieder, dass eine Authentifizierung nötig sei.

    Ich hatte eigentlich gehofft, ziemlich einfach zwischen HTTP und HTTPS umschalten zu könnnen, aber irgendwie klappt das nicht.

    Habe ich vielleicht ein Flag vergessen, einen falschen Parameter gesetzt oder einen Funktionsaufruf vergessen?

    Hier ist der Code:

    #include <Windows.h>
    #include "Wininet.h"
    #include <sstream>
    #include <exception>
    
    #define HEADERBUFFERSIZE (1024)
    #define DOWNLOADBUFFERSIZE (4096)
    
    struct DownloadStatus
    {
    	bool finished;
    	bool running;
    };
    enum Protocoll
    {
    	HTTP,
    	HTTPS
    };
    
    DownloadStatus downloadStatus;
    Protocoll protocoll = Protocoll::HTTP;
    std::string proxyIP = "192.168.1.1";
    std::string proxyPort = "800";
    std::string userAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64; x64; rv:22.0) Gecko/20100101 Firefox/22.0";
    
    bool useProxy = true;
    bool doProxyAuthentication = true;
    
    std::string proxyUsername = "testuser";
    std::string proxyPassword = "Password";
    
    std::string downloadString(std::string url)
    {
    	url = "https://example.com/LatestVersion.php";
    
    	HINTERNET internetOpen;
    	HINTERNET internetOpenUrl;
    	DWORD bufferSize = HEADERBUFFERSIZE;
    	char* headerBuffer = new char[bufferSize+1];
    	DWORD headerIndex;
    	unsigned int headerCode;
    	char* downloadBuffer = new char[DOWNLOADBUFFERSIZE];
    	DWORD read;
    
    	downloadStatus.finished = false;
    	downloadStatus.running = true;
    
    	if(useProxy)
    	{
    		std::string proxyName;
    		if(protocoll ==Protocoll::HTTP)
    		{
    			proxyName = "http=http://" + proxyIP + ":" + proxyPort;
    		}
    		else
    		{
    			proxyName = "https=https://" + proxyIP + ":" + proxyPort;
    		}
    
    		internetOpen  = InternetOpenA(userAgent.c_str(),INTERNET_OPEN_TYPE_PROXY,proxyName.c_str(),NULL,0);
    	}
    	else
    	{
    		internetOpen  = InternetOpenA(userAgent.c_str(),INTERNET_OPEN_TYPE_DIRECT,NULL,NULL,0);
    	}
    	if(internetOpen==NULL)
    	{
    		DWORD err = GetLastError();
    		downloadStatus.running = false;
    		downloadStatus.finished = true;
    		delete downloadBuffer;
    		delete headerBuffer;
    		std::stringstream reason("");
    		reason << "InternetOpen failed\nGetLastError(): " << err;
    		throw std::exception(reason.str().c_str());
    	}
    	if(protocoll ==Protocoll::HTTP)
    	{
    		if(url.find("https:") == 0)
    		{
    			url = url.substr(strlen("https:"));
    			url = "http:" + url;
    		}
    	}
    	else
    	{
    		if(url.find("http:") == 0)
    		{
    			url = url.substr(strlen("http:"));
    			url = "https:" + url;
    		}
    	}
    
    	if(doProxyAuthentication)
    	{
    		std::string authStr = "Proxy-Authorization: Basic dGVzdHVzZXI6UGFzc3dvcmQ=";
    		internetOpenUrl = InternetOpenUrlA(internetOpen,url.c_str(),authStr.c_str(),authStr.length(),INTERNET_FLAG_RELOAD,1);
    	}
    	else
    	{
    		internetOpenUrl = InternetOpenUrlA(internetOpen,url.c_str(),NULL,-1,INTERNET_FLAG_RELOAD,1);
    	}
    
    	if(internetOpenUrl==NULL)
    	{
    		DWORD err1 = GetLastError();
    		DWORD err2;
    		char tempStr2[1024];
    		DWORD tempStr2Length = 1024;
    
    		InternetGetLastResponseInfoA(&err2,tempStr2,&tempStr2Length);
    
    		InternetCloseHandle(internetOpen);
    		downloadStatus.running = false;
    		downloadStatus.finished = true;
    		delete downloadBuffer;
    		delete headerBuffer;
    
    		std::stringstream reason("");
    		reason << "InternetOpenUrl failed\nGetLastError(): " << err1 << "\nInternetGetLastResponseInfo(): " << err2 << "\n" << tempStr2;
    		throw std::exception(reason.str().c_str());
    	}
    
      //Ab hier geht es eigentlich nur noch darum, die Antwort auszulesen und zu interpretieren und im Erfolgsfall einen Wert zurückzugeben oder eine Exception
      //zu werfen, woraufhin die Funktion eventuell nochmal mit anderen Parametern aufgerufen wird.
    	while(true)
    	{
    		memset(headerBuffer,0,bufferSize+1);
    		headerIndex = 0;
    		if(!HttpQueryInfoA(internetOpenUrl,HTTP_QUERY_STATUS_CODE,headerBuffer,&bufferSize,&headerIndex))
    		{
    			DWORD err = GetLastError();
    			if(err == ERROR_INSUFFICIENT_BUFFER)
    			{
    				delete headerBuffer;
    				headerBuffer = new char[bufferSize+1];
    				continue;
    			}
    			else
    			{
    				InternetCloseHandle(internetOpenUrl);
    				InternetCloseHandle(internetOpen);
    				downloadStatus.running = false;
    				downloadStatus.finished = true;
    				delete downloadBuffer;
    				delete headerBuffer;
    
    				std::stringstream reason("");
    				reason << "HttpQueryInfo failed\nGetLastError(): " << err;
    				throw std::exception(reason.str().c_str());
    			}
    		}
    		else
    		{
    			break;
    		}
    	}
    	headerCode = atoi(headerBuffer);
    	if(headerCode!=200)
    	{
    		InternetCloseHandle(internetOpenUrl);
    		InternetCloseHandle(internetOpen);
    		downloadStatus.running = false;
    		downloadStatus.finished = true;
    		delete downloadBuffer;
    		delete headerBuffer;
    
    		std::stringstream reason("");
    		reason << "Unexpected HTTP response code: " << headerCode;
    		throw std::exception(reason.str().c_str());
    	}
    
    	while(true)
    	{
    		memset(headerBuffer,0,bufferSize+1);
    		headerIndex = 0;
    		if(!HttpQueryInfoA(internetOpenUrl,HTTP_QUERY_CONTENT_LENGTH,headerBuffer,&bufferSize,&headerIndex))
    		{
    			DWORD err = GetLastError();
    			if(err == ERROR_INSUFFICIENT_BUFFER)
    			{
    				delete headerBuffer;
    				headerBuffer = new char[bufferSize+1];
    				continue;
    			}
    			else
    			{
    				InternetCloseHandle(internetOpenUrl);
    				InternetCloseHandle(internetOpen);
    				downloadStatus.running = false;
    				downloadStatus.finished = true;
    				delete downloadBuffer;
    				delete headerBuffer;
    
    				std::stringstream reason("");
    				reason << "HttpQueryInfo failed\nGetLastError(): " << err;
    				throw std::exception(reason.str().c_str());
    			}
    		}
    		else
    		{
    			break;
    		}
    	}
    
    	std::stringstream dataStream("");
    	while(downloadStatus.running)
    	{
    		memset(downloadBuffer,0,DOWNLOADBUFFERSIZE);
    		bool erg = InternetReadFile(internetOpenUrl,downloadBuffer,DOWNLOADBUFFERSIZE,&read);
    		if(erg && read==0) //Download finished
    		{
    			break;
    		}
    		if(read >0)
    		{
    			dataStream << downloadBuffer;
    		}
    	}
    
    	InternetCloseHandle(internetOpenUrl);
    	InternetCloseHandle(internetOpen);
    	downloadStatus.running = false;
    	downloadStatus.finished = true;
    	delete downloadBuffer;
    	delete headerBuffer;
    
    	return dataStream.str();
    }
    

    Kurze Erklärungen:
    Die ganzen Parameter, die vor der Funktionsdefinition hier im Beispiel konstante Werte haben, werden vor dem Aufruf natürlich entsprechend gesetzt.

    Also der Code funktioniert wirklich wenn

    Protocoll protocoll = Protocoll::HTTP;
    

    gesetzt wird. Nicht aber bei

    Protocoll protocoll = Protocoll::HTTPS;
    
    if(headerCode!=200)
    {
    ...
    }
    

    Die Exception, die in diesem Block geworfen wird bewirkt, dass im Falle von HTTP-Response-Code "407" in der aufrufenden Funktion Benutzername und Passwort vom Anwender abgefragt werden und dann die Funktion nochmal aufgerufen wird. (Zu Beginn ist proxyUsername="" und proxyPassword=""

    std::string authStr = "Proxy-Authorization: Basic dGVzdHVzZXI6UGFzc3dvcmQ=";
    

    wird natürlich auch dynamisch erzeugt, je nachdem was als Benutzername und Passwort eingegeben wurde. Aber damit der Code hier direkt ausführbar ist, habe ich mal nicht zu weit ausholen wollen mit externen Bibliotheken usw.

    Ich weiß das ist alles nicht gerade höchst effizient und auch für manche nicht besonders elegant programmiert, aber ich möchte zunächst einfach nur mal, dass es funktioniert.

    Das hier liefert Wireshark bei HTTP:

    Request:
    
    GET http://example.com/LatestVersion.php HTTP/1.0
    Proxy-Authorization: Basic dGVzdHVzZXI6UGFzc3dvcmQ=
    User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; x64; rv:22.0) Gecko/20100101 Firefox/22.0
    Host: example.com
    Pragma: no-cache
    
    Antwort:
    
    HTTP/1.0 200 OK
    Server: Apache-Coyote/1.1
    Accept-Ranges: bytes
    ETag: W/"8-1374758882685"
    Last-Modified: Thu, 25 Jul 2013 13:28:02 GMT
    Content-Length: 8
    Date: Thu, 25 Jul 2013 15:59:38 GMT
    X-Cache: MISS from ipfire.localdomain
    X-Cache-Lookup: HIT from ipfire.localdomain:800
    Via: 1.0 ipfire.localdomain (squid/3.1.23)
    Connection: close
    
    1.13.7.x
    

    Und das hier liefert Wireshark bei HTTPS, und zwar immer wieder obwohl auch hier bei InternetOpenUrlA(...) der Header "Proxy-Authorization" gsetzt wird.

    Request:
    
    CONNECT example.com:443 HTTP/1.0
    User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; x64; rv:22.0) Gecko/20100101 Firefox/22.0
    Host: example.com:443
    Content-Length: 0
    Proxy-Connection: Keep-Alive
    Pragma: no-cache
    
    Antwort:
    
    HTTP/1.0 407 Proxy Authentication Required
    Server: squid/3.1.23
    Mime-Version: 1.0
    Date: Thu, 25 Jul 2013 16:02:36 GMT
    Content-Type: text/html
    Content-Length: 3342
    X-Squid-Error: ERR_CACHE_ACCESS_DENIED 0
    Proxy-Authenticate: Basic realm="IPFire Advanced Proxy Server"
    X-Cache: MISS from ipfire.localdomain
    X-Cache-Lookup: NONE from ipfire.localdomain:800
    Via: 1.0 ipfire.localdomain (squid/3.1.23)
    Connection: keep-alive
    
    ...
    

    Hat jemand ne Idee wie ich dieses WinINet-Zeugs dazu bekomme auch bei HTTPS die Authentifizierungs-Daten an den Proxy zu schicken? Ich möchte den Aufwand gering halten, sprich: ich will nicht unbedingt eine ganz andere Bibliothek für meine Downloads anbinden müssen und das was ich bisher schon implementiert habe wegschmeißen...

    P.S.: ich habe auch schon das hier versucht, klappt aber auch nur bei HTTP und nicht bei HTTPS, aber vielleicht hab ich auch hier was falsch gemacht:
    http://msdn.microsoft.com/en-us/library/windows/desktop/aa384220%28v=vs.85%29.aspx

    Danke schon im Voraus!

    MfG





  • Ich würde hier im Forum diese Frage nicht stellen wenn ich nicht bereits tagelang nach einer Lösung gegoogelt hätte.

    Vielen Dank für deinen Beitrag.

    Nochmal: der Code funktioniert doch auch bei HTTP. Nur wenn ich am Anfang der URL "https://" stehen habe, wird der Header

    Proxy-Authorization: Basic dGVzdHVzZXI6UGFzc3dvcmQ=
    

    nicht mitgeschickt.

    Alle Lösungen die ich bisher gefunden habe, funktionieren nur mit HTTP aber nicht mit HTTPS. Es ist immer das selbe Problem.

    Aber wieso wird dieser Header bei HTTPS nicht geschickt? Es ist doch sonst der selbe Code!

    Der Proxy benötigt jedenfalls die Header

    CONNECT example.com:443 HTTP/1.0
    Proxy-Authorization: Basic dGVzdHVzZXI6UGFzc3dvcmQ=
    

    unverschlüsselt. Dann soll er die Verbindung zum Ziel-Server aufbauen und dann wird erst verschlüsselt.



  • Jetzt sehe ich das Problem...
    Hmm... warum verwendest Du nicht WinHTTP? WinInet ist eigentlich deprecated...

    Hier ist ein Beispiel wie dies mit https geht:
    http://msdn.microsoft.com/en-us/library/windows/desktop/aa383144

    Und gleich noch als Hinweis:

    Porting WinINet Applications to WinHTTP
    http://msdn.microsoft.com/en-us/library/windows/desktop/aa384068



  • Vielen Dank für deine Antwort!

    Ursprünglich habe ich WinINet benutzt weil es das erste war was ich gefunden hatte und weil es auch auf Anhieb funktioniert hat. Daher hab ich gar nicht nach weiteren Lösungen geschaut. Bisher hat das auch wunderbar funktioniert da ich bisher keine Proxy-Einstellungen unterstützen musste. Aber nun scheitere ich wohl an WinINet, wie es aussieht. Aber die Portierung auf WinHttp sollte kein allzu großer Aufwand sein.

    Ich habe den Code aus deinem ersten Link mal probiert. Auch das funktioniert sehr gut mit HTTP aber unter HTTPS auch nicht. Diesmal fehlt allerdings ein anderer Header und zwar der User-Agent, wobei der Header Proxy-Authorization diesmal mitgeschickt wird.

    Ich habe den Beispiel-Code fast komplett übernommen und nur kleine Modifikationen gemacht (Erklärungen siehe unten):

    #include <windows.h>
    #include <winhttp.h>
    #include <stdio.h>
    #include <string>
    
    #pragma comment(lib, "winhttp.lib")
    
    DWORD ChooseAuthScheme( DWORD dwSupportedSchemes )
    {
      //  It is the server's responsibility only to accept 
      //  authentication schemes that provide a sufficient
      //  level of security to protect the servers resources.
      //
      //  The client is also obligated only to use an authentication
      //  scheme that adequately protects its username and password.
      //
      //  Thus, this sample code does not use Basic authentication  
      //  becaus Basic authentication exposes the client's username
      //  and password to anyone monitoring the connection.
    
      if( dwSupportedSchemes & WINHTTP_AUTH_SCHEME_NEGOTIATE )
        return WINHTTP_AUTH_SCHEME_NEGOTIATE;
      else if( dwSupportedSchemes & WINHTTP_AUTH_SCHEME_NTLM )
        return WINHTTP_AUTH_SCHEME_NTLM;
      else if( dwSupportedSchemes & WINHTTP_AUTH_SCHEME_PASSPORT )
        return WINHTTP_AUTH_SCHEME_PASSPORT;
      else if( dwSupportedSchemes & WINHTTP_AUTH_SCHEME_DIGEST )
        return WINHTTP_AUTH_SCHEME_DIGEST;
      else if (dwSupportedSchemes & WINHTTP_AUTH_SCHEME_BASIC)
    	return WINHTTP_AUTH_SCHEME_BASIC;
      else
        return 0;
    }
    
    struct SWinHttpSampleGet
    {
      LPCWSTR szServer;
      LPCWSTR szPath;
      BOOL fUseSSL;
      LPCWSTR szServerUsername;
      LPCWSTR szServerPassword;
      LPCWSTR szProxyUsername;
      LPCWSTR szProxyPassword;
    };
    //void WinHttpAuthSample( IN SWinHttpSampleGet *pGetRequest )
    std::string downloadString(std::string url)
    {
      SWinHttpSampleGet *pGetRequest = new SWinHttpSampleGet;
      pGetRequest->szServer = L"example.com";
      pGetRequest->szPath = L"/LatestVersion.php";
      pGetRequest->fUseSSL = true;
      pGetRequest->szServerUsername = L"";
      pGetRequest->szServerPassword = L"";
      pGetRequest->szProxyUsername = L"testuser";
      pGetRequest->szProxyPassword = L"Password";
    
      DWORD dwStatusCode = 0;
      DWORD dwSupportedSchemes;
      DWORD dwFirstScheme;
      DWORD dwSelectedScheme;
      DWORD dwTarget;
      DWORD dwLastStatus = 0;
      DWORD dwSize = sizeof(DWORD);
      BOOL  bResults = FALSE;
      BOOL  bDone = FALSE;
    
      DWORD dwProxyAuthScheme = 0;
      HINTERNET  hSession = NULL, 
                 hConnect = NULL,
                 hRequest = NULL;
    
      std::wstring proxyName;
      if(pGetRequest->fUseSSL)
      {
    	  proxyName = L"https=https://192.168.1.1:800";
      }
      else
      {
    	  proxyName = L"http=http://192.168.1.1:800";
      }
    
      // Use WinHttpOpen to obtain a session handle.
      hSession = WinHttpOpen( L"Mozilla/5.0 (Windows NT 6.1; WOW64; x64; rv:22.0) Gecko/20100101 Firefox/22.0",
                              WINHTTP_ACCESS_TYPE_NAMED_PROXY,
    						  proxyName.c_str(), 
                              WINHTTP_NO_PROXY_BYPASS, 0 );
    
      INTERNET_PORT nPort = ( pGetRequest->fUseSSL ) ? 
                            INTERNET_DEFAULT_HTTPS_PORT  :
                            INTERNET_DEFAULT_HTTP_PORT;
    
      // Specify an HTTP server.
      if( hSession )
        hConnect = WinHttpConnect( hSession, 
                                   pGetRequest->szServer, 
                                   nPort, 0 );
    
      // Create an HTTP request handle.
      if( hConnect )
        hRequest = WinHttpOpenRequest( hConnect, 
                                       L"GET", 
                                       pGetRequest->szPath,
                                       NULL, 
                                       WINHTTP_NO_REFERER, 
                                       WINHTTP_DEFAULT_ACCEPT_TYPES,
                                       ( pGetRequest->fUseSSL ) ? 
                                           WINHTTP_FLAG_SECURE : 0 );
    
      // Continue to send a request until status code 
      // is not 401 or 407.
      if( hRequest == NULL )
        bDone = TRUE;
    
      while( !bDone )
      {
        //  If a proxy authentication challenge was responded to, reset
        //  those credentials before each SendRequest, because the proxy  
        //  may require re-authentication after responding to a 401 or  
        //  to a redirect. If you don't, you can get into a 
        //  407-401-407-401- loop.
        if( dwProxyAuthScheme != 0 )
          bResults = WinHttpSetCredentials( hRequest, 
                                            WINHTTP_AUTH_TARGET_PROXY, 
                                            dwProxyAuthScheme, 
                                            pGetRequest->szProxyUsername,
                                            pGetRequest->szProxyPassword,
                                            NULL );
        // Send a request.
        bResults = WinHttpSendRequest( hRequest,
                                       WINHTTP_NO_ADDITIONAL_HEADERS,
                                       0,
                                       WINHTTP_NO_REQUEST_DATA,
                                       0, 
                                       0, 
                                       0 );
    
        // End the request.
        if( bResults )
          bResults = WinHttpReceiveResponse( hRequest, NULL );
    
        // Resend the request in case of 
        // ERROR_WINHTTP_RESEND_REQUEST error.
        if( !bResults && GetLastError( ) == ERROR_WINHTTP_RESEND_REQUEST)
            continue;
    
        // Check the status code.
        if( bResults ) 
          bResults = WinHttpQueryHeaders( hRequest, 
                                          WINHTTP_QUERY_STATUS_CODE |
                                          WINHTTP_QUERY_FLAG_NUMBER,
                                          NULL, 
                                          &dwStatusCode, 
                                          &dwSize, 
                                          NULL );
    
        if( bResults )
        {
          switch( dwStatusCode )
          {
            case 200: 
              // The resource was successfully retrieved.
              // You can use WinHttpReadData to read the 
              // contents of the server's response.
              printf( "The resource was successfully retrieved.\n" );
              bDone = TRUE;
              break;
    
            case 401:
              // The server requires authentication.
              printf(" The server requires authentication. Sending credentials...\n" );
    
              // Obtain the supported and preferred schemes.
              bResults = WinHttpQueryAuthSchemes( hRequest, 
                                                  &dwSupportedSchemes, 
                                                  &dwFirstScheme, 
                                                  &dwTarget );
    
              // Set the credentials before resending the request.
              if( bResults )
              {
                dwSelectedScheme = ChooseAuthScheme( dwSupportedSchemes);
    
                if( dwSelectedScheme == 0 )
                  bDone = TRUE;
                else
                  bResults = WinHttpSetCredentials( hRequest, 
                                            dwTarget, 
                                            dwSelectedScheme,
                                            pGetRequest->szServerUsername,
                                            pGetRequest->szServerPassword,
                                            NULL );
              }
    
              // If the same credentials are requested twice, abort the
              // request.  For simplicity, this sample does not check
              // for a repeated sequence of status codes.
              if( dwLastStatus == 401 )
                bDone = TRUE;
    
              break;
    
            case 407:
              // The proxy requires authentication.
              printf( "The proxy requires authentication.  Sending credentials...\n" );
    
              // Obtain the supported and preferred schemes.
              bResults = WinHttpQueryAuthSchemes( hRequest, 
                                                  &dwSupportedSchemes, 
                                                  &dwFirstScheme, 
                                                  &dwTarget );
    
              // Set the credentials before resending the request.
              if( bResults )
                dwProxyAuthScheme = ChooseAuthScheme(dwSupportedSchemes);
    
              // If the same credentials are requested twice, abort the
              // request.  For simplicity, this sample does not check 
              // for a repeated sequence of status codes.
              if( dwLastStatus == 407 )
                bDone = TRUE;
              break;
    
            default:
              // The status code does not indicate success.
              printf("Error. Status code %d returned.\n", dwStatusCode);
              bDone = TRUE;
          }
        }
    
        // Keep track of the last status code.
        dwLastStatus = dwStatusCode;
    
        // If there are any errors, break out of the loop.
        if( !bResults ) 
            bDone = TRUE;
      }
    
      // Report any errors.
      if( !bResults )
      {
        DWORD dwLastError = GetLastError( );
        printf( "Error %d has occurred.\n", dwLastError );
      }
    
      char buffer[1024];
      DWORD dwNumberOfBytesRead = 1;
      bool readOn = true;
      std::string ret = "";
      while(readOn && dwNumberOfBytesRead>0)
      {
    	  memset(buffer,0,sizeof(buffer));
    	  readOn = WinHttpReadData(hRequest,buffer,sizeof(buffer)-1,&dwNumberOfBytesRead);
    	  ret.append(buffer);
      }
      FILE* answerFile = fopen("D:\\Answer.html","wb");
      fprintf(answerFile,"%s",ret.c_str());
      fclose(answerFile);
    
      // Close any open handles.
      if( hRequest ) WinHttpCloseHandle( hRequest );
      if( hConnect ) WinHttpCloseHandle( hConnect );
      if( hSession ) WinHttpCloseHandle( hSession );
    
      return ret;
    }
    

    Modifikationen:
    1. #include <string> eingefügt
    2. In der Funktion DWORD ChooseAuthScheme( DWORD dwSupportedSchemes ) folgende Zeilen eingefügt, damit auch die Basic-Authentifizierung klappt:

    else if (dwSupportedSchemes & WINHTTP_AUTH_SCHEME_BASIC)
    	return WINHTTP_AUTH_SCHEME_BASIC;
    

    3.Die Funktionssignatur geändert, damit es sich in mein restliches Programm einfügt. Daher habe ich den ursrpünglichen Parameter *SWinHttpSampleGet pGetRequest künstlich selber angelegt und mit Werten befüllt. Der urprüngliche Parameter wird von der Funktion verwendet, der neue Parameter std::string url nicht.

    //void WinHttpAuthSample( IN SWinHttpSampleGet *pGetRequest )
    std::string downloadString(std::string url)
    {
      SWinHttpSampleGet *pGetRequest = new SWinHttpSampleGet;
      pGetRequest->szServer = L"example.com";
      pGetRequest->szPath = L"/LatestVersion.php";
      pGetRequest->fUseSSL = true;
      pGetRequest->szServerUsername = L"";
      pGetRequest->szServerPassword = L"";
      pGetRequest->szProxyUsername = L"testuser";
      pGetRequest->szProxyPassword = L"Password";
    
      ...
    

    4. Die Aufrufparameter von WinHttpOpen(...) angepasst, da unser Test-Proxy den User-Agent filtert und da ich den Proxy selber angeben möchte anstatt automatisch die Systemeinstellungen zu benutzen (wie im Original-Code):

    std::wstring proxyName;
      if(pGetRequest->fUseSSL)
      {
    	  proxyName = L"https=https://192.168.1.1:800";
      }
      else
      {
    	  proxyName = L"http=http://192.168.1.1:800";
      }
    
      // Use WinHttpOpen to obtain a session handle.
      hSession = WinHttpOpen( L"Mozilla/5.0 (Windows NT 6.1; WOW64; x64; rv:22.0) Gecko/20100101 Firefox/22.0",
                              WINHTTP_ACCESS_TYPE_NAMED_PROXY,
    						  proxyName.c_str(), 
                              WINHTTP_NO_PROXY_BYPASS, 0 );
    

    5. Am Ende der Funktion wird nur noch zusätzlich die Antwort ausgelesen und in eine Datei geschrieben:

    char buffer[1024];
      DWORD dwNumberOfBytesRead = 1;
      bool readOn = true;
      std::string ret = "";
      while(readOn && dwNumberOfBytesRead>0)
      {
    	  memset(buffer,0,sizeof(buffer));
    	  readOn = WinHttpReadData(hRequest,buffer,sizeof(buffer)-1,&dwNumberOfBytesRead);
    	  ret.append(buffer);
      }
      FILE* answerFile = fopen("D:\\Answer.html","wb");
      fprintf(answerFile,"%s",ret.c_str());
      fclose(answerFile);
    

    Der Rest ist gleich geblieben, ich hoffe ich habe keine Modifikation vergessen zu erwähnen.

    Also wie gesagt, jetzt scheint es nicht mehr an der Authentifizierung zu scheitern sondern am User-Agent. Wenn ich die User-Agent-Prüfung im Proxy deaktiviere dann funktioniert es. Allerdings sollte mein Programm das schon auch noch können.

    Hier WireShark bei HTTP, d.h. pGetRequest->fUseSSL = false;:
    Erste Verbindung:

    GET http://example.com/LatestVersion.php HTTP/1.1
    User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; x64; rv:22.0) Gecko/20100101 Firefox/22.0
    Proxy-Connection: Keep-Alive
    Host: example.com
    
    HTTP/1.0 407 Proxy Authentication Required
    Server: squid/3.1.23
    Mime-Version: 1.0
    Date: Fri, 26 Jul 2013 13:31:44 GMT
    Content-Type: text/html
    Content-Length: 3407
    X-Squid-Error: ERR_CACHE_ACCESS_DENIED 0
    Proxy-Authenticate: Basic realm="IPFire Advanced Proxy Server"
    X-Cache: MISS from ipfire.localdomain
    X-Cache-Lookup: NONE from ipfire.localdomain:800
    Via: 1.0 ipfire.localdomain (squid/3.1.23)
    Connection: keep-alive
    
    ...Automatisch generierte Fehlermeldung-HTML...
    

    Zweite Verbindung:

    GET http://example.com/LatestVersion.php HTTP/1.1
    User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; x64; rv:22.0) Gecko/20100101 Firefox/22.0
    Proxy-Connection: Keep-Alive
    Host: example.com
    Proxy-Authorization: Basic dGVzdHVzZXI6UGFzc3dvcmQ=
    
    HTTP/1.0 200 OK
    Server: Apache-Coyote/1.1
    Accept-Ranges: bytes
    ETag: W/"8-1374758882685"
    Last-Modified: Thu, 25 Jul 2013 13:28:02 GMT
    Content-Length: 8
    Date: Fri, 26 Jul 2013 11:59:11 GMT
    Age: 5553
    X-Cache: HIT from ipfire.localdomain
    X-Cache-Lookup: HIT from ipfire.localdomain:800
    Via: 1.0 ipfire.localdomain (squid/3.1.23)
    Connection: keep-alive
    
    ...Korrekte Antwort...
    

    Und hier bei HTTPS, also pGetRequest->fUseSSL = true;:
    Erste Verbindung:

    CONNECT example.com:443 HTTP/1.1
    Host: example.com:443
    
    HTTP/1.0 407 Proxy Authentication Required
    Server: squid/3.1.23
    Mime-Version: 1.0
    Date: Fri, 26 Jul 2013 13:35:21 GMT
    Content-Type: text/html
    Content-Length: 3122
    X-Squid-Error: ERR_CACHE_ACCESS_DENIED 0
    Proxy-Authenticate: Basic realm="IPFire Advanced Proxy Server"
    X-Cache: MISS from ipfire.localdomain
    X-Cache-Lookup: NONE from ipfire.localdomain:800
    Via: 1.0 ipfire.localdomain (squid/3.1.23)
    Connection: keep-alive
    
    ...Automatisch generierte Fehlermeldung-HTML...
    

    Zweite Verbindung:

    CONNECT example.com:443 HTTP/1.1
    Host: example.com:443
    Proxy-Authorization: Basic dGVzdHVzZXI6UGFzc3dvcmQ=
    
    HTTP/1.0 403 Forbidden
    Server: squid/3.1.23
    Mime-Version: 1.0
    Date: Fri, 26 Jul 2013 13:35:21 GMT
    Content-Type: text/html
    Content-Length: 3166
    X-Squid-Error: ERR_ACCESS_DENIED 0
    X-Cache: MISS from ipfire.localdomain
    X-Cache-Lookup: NONE from ipfire.localdomain:800
    Via: 1.0 ipfire.localdomain (squid/3.1.23)
    Connection: keep-alive
    
    ...Automatisch generierte Fehlermeldung-HTML...
    

    Der User-Agent wird von mir ja eigentlich schon angegeben beim Aufruf von WinHttpOpen(...). Allerdings geht das nur (verschlüsselt, vermute ich) an den eigentlichen Ziel-Server. Der Proxy muss den User-Agent aber auch bekommen und zwar unverschlüsselt.

    Ich habe auch versucht die Funktion WinHttpSendRequest(...) mit dem User-Agent als zusätzlichen Header aufzurufen, aber das geht auch nicht an den Proxy, sondern wohl nur an den eigentliche Ziel-Server (man sieht das naturlich in WireShark nicht weil verschlüsselt)

    Wie bringe ich dem Programm also bei, den User-Agent auch an den Proxy zu schicken?

    Vielen Dank schon mal!

    MfG

    EDIT:
    So sieht das ganze im FireFox aus:

    CONNECT example.com:443 HTTP/1.1
    User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:22.0) Gecko/20100101 Firefox/22.0
    Proxy-Connection: keep-alive
    Connection: keep-alive
    Host: example.com
    Proxy-Authorization: Basic dGVzdHVzZXI6UGFzc3dvcmQ=
    
    HTTP/1.0 200 Connection established
    
    ...Verschlüsseltes Kauderwelsch...
    


  • Hat keiner ne Idee?

    In C# (Unity3D) hatten wir das selbe Problem. Da wurde auch der User-Agent-Header nicht mitgeschickt und wir hatten dann die Klasse, die das Netzwerk-Zeugs erledigen sollte, selber neu implementiert... (ob es da was fertiges gegeben hätte weiß ich gerade nicht, war nicht meine Aufgabe)

    Das kann es doch nicht sein oder?

    Weiß vielleicht jemand eine Library, die sicher meine Anforderungen unterstützt?
    -Also Proxy-Fähigkeit mit HTTPS und Proxy-Authentication und User-Agent
    -Status-Abfrage des aktuellen Downloads versteht sich von selbst
    -Andere Protokolle dürfen natürlich auch unterstützt werden (zusätzlich zu HTTP und HTTPS)



  • Also, ich habe jetzt nicht jede Zeile deines Codes im Detail gelesen, aber ich kann nur generell empfehlen alle "organisatorischen" Daten der Verbindung über die Options zu verwalten. Das wären dann "WinHttpSetOption" bzw. "InternetSetOption". Da existieren jeweils Flags für das Setzen des Proxies und anderer Daten wie auch der User-Agent. Diese Einträge selber in die Header einzufügen bringt sonst e.v. die Proxy-Logik durcheinander.
    Zu Bedenken ist auch, dass man Options auf allen Handles bearbeiten kann.
    Es ist bei mir schon einige Jahre her, dass ich sowas umgesetzt habe, aber ich glaube mich zu erinnern, dass das Verhalten besonders bei Proxies leicht anders war, wenn bestimmte Flags auf der Session, der Connection oder dem Request gesetzt waren.
    Da bleibt wohl nur herumprobieren, denn die Dokus lassen nicht auf ein eindeutiges Verhaltensmuster schließen.

    Eine "klassische" Bibliothek für HTTP ist sicher libCURL mit openSSL.

    PS: Ich finde es allerdings schon etwas extrem, dass der Proxy auf einen User-Agent prüft. Besonders bei CONNECT soll der doch nur Bytes weiterleiten. Keine meiner bisher selbst implementierten HTTP und Proxy Klassen könnte das umsetzen und das war auch noch nie ein Thema ... aber gut zu wissen, dass sowas auftreten kann 😃

    mfG 😉



  • Danke für deine Antwort, xor!

    InternetSetOption hab ich schon versucht einzusetzen gemäß den Beispiel-Codes von MSDN. WinHttpSetOption hab ich noch nicht versucht. Ich werds versuchen, glaube aber nicht wirklich an einen Erfolg.

    Den Proxy den wir zum Testen verwenden ist IPFire. Da kann man das eben alles einstellen und kombinieren und darum gehen wir davon aus dass andere Proxies das auch können und dass es "da draußen" auch wirklich benutzt wird.

    Auf libCurl umzustellen ist halt ein größerer Aufwand. Daher die Frage ob es nicht mit den WinAPI-Funktionen auch geht.



  • Die Sache mit dem User-Agent geht schon ziemlich ins Detail. Da bist du vermutlich im MSDN Forum besser aufgehoben.

    Den Proxy den wir zum Testen verwenden ist IPFire. Da kann man das eben alles einstellen und kombinieren und darum gehen wir davon aus dass andere Proxies das auch können und dass es "da draußen" auch wirklich benutzt wird.

    Auf libCurl umzustellen ist halt ein größerer Aufwand. Daher die Frage ob es nicht mit den WinAPI-Funktionen auch geht.

    Wisst ihr denn wirklich dass ihr das auch braucht?

    Ich hab' zwar nicht SO viel Erfahrung mit "CONNECT", aber mir wäre noch nie ein Proxy untergekommen der den User-Agent überprüft.

    Klar, es wäre vermutlich "nice to have", aber wenn WinHTTP es nicht unterstützt ist davon auszugehen dass sehr sehr viele Programme es nicht unterstützen werden. Und daher eben die Überlegung: wenn diese Programme es nicht brauchen, seid ihr dann sicher dass ihr es wirklich braucht?

    Ich würde das wohl eher weglassen und erstmal warten ob Bug-Reports/Feature-Requests diesbezüglich reinkommen.



  • Ob wir das sicher brauchen werden wohl nur die Bug-Reports/Feature-Requests zeigen. Ich dachte nur es gäbe einen einfachen Weg das hinzubekommen, dass es eigentlich schnell zu implementieren sein müsste und dass ich nur irgendwo etwas übersehen habe.

    Aber das hat jetzt bei uns gerade keine hohe Priorität mehr.

    Danke an alle!


Log in to reply