WinHttpQueryHeaders fails with ERROR_INTERNET_INCORRECT_HANDLE_STATE

Questions about Wine on Linux
Post Reply
Sander Bouwhuis
Level 1
Level 1
Posts: 5
Joined: Tue Aug 12, 2014 3:59 am

WinHttpQueryHeaders fails with ERROR_INTERNET_INCORRECT_HANDLE_STATE

Post by Sander Bouwhuis »

I have my Windows C++ application mostly working under Wine now. The only thing that I'm struggling with is getting the web sockets to work correctly.
I have this function which on Windows has performed without problems for years already:

Code: Select all

// This function connects to the server
bool CWebSocketClient::Connect(std::wstring *pWstrDebugLog, const wchar_t *pwcServerIpAddress, uint16 u16ServerIpPort /* INTERNET_DEFAULT_PORT */, bool bEncryptedConnection /* true */, HANDLE hEvtNotifyNewMsg /* nullptr */)
{
  m_pWstrDebugLog = pWstrDebugLog;

  ENTER_FUNCTION_DEBUG_LOG_TO_MEM

  // Aren't websockets available?
  #ifdef BUILD_WEB_SOCKETS_UNAVAILABLE

    // Do nothing
    UNREFERENCED_PARAMETER(pwcServerIpAddress);
    UNREFERENCED_PARAMETER(u16ServerIpPort);
    UNREFERENCED_PARAMETER(bEncryptedConnection);
    UNREFERENCED_PARAMETER(hEvtNotifyNewMsg);

  #else

  try
  {
    CHECK_MEM_LEAKS

    int32 i32Pos;

    // Do we already have a session?
    if(m_hWebSocketSession)
      Disconnect();

    // We are going to use asynchronous mode
    m_bSynchronousMode = false;

    // Store the settings
    m_u16ServerIpPort        = u16ServerIpPort;
    m_hEvtNotifyParentNewMsg = hEvtNotifyNewMsg;

    // Get the address. Skip the protocol (e.g., wss://, https://, ...)
    if((i32Pos = FindInString(pwcServerIpAddress, L"://")) >= 0)
      i32Pos += 3;
    else
      i32Pos = 0;
    m_wstrServerIpAddress = &pwcServerIpAddress[i32Pos];

    // Split the address in a base URL and the path
    if((m_wstrServerIpAddress.GetLength() < gc_i32Max)  &&
       ((i32Pos = static_cast<int32>(m_wstrServerIpAddress.Find(L"/"))) > 0))
    {
      // Isn't this the last character?
      if(m_wstrServerIpAddress.GetLength() > static_cast<uint64>(i32Pos+1))
        m_wstrServerPath = &m_wstrServerIpAddress[i32Pos+1];
      m_wstrServerIpAddress.Erase(static_cast<uint32>(i32Pos), static_cast<uint32>(m_wstrServerIpAddress.GetLength()-i32Pos));
    }
    else
      m_wstrServerPath.Clear();

    // Create a session
    OutputDebugMessage(m_pWstrDebugLog, L"CWebSocketClient::Connect() : %s:%u\n", m_wstrServerIpAddress.GetString(), u16ServerIpPort);
    if((m_hWebSocketSession = WinHttpOpen(L"Dms", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, nullptr, nullptr, WINHTTP_FLAG_ASYNC | (bEncryptedConnection ? WINHTTP_FLAG_SECURE_DEFAULTS : 0))) != nullptr)
    {
      // Connect to the server
      if((m_hWebSocketConnection = WinHttpConnect(m_hWebSocketSession, m_wstrServerIpAddress.GetString(), m_u16ServerIpPort > 0 ? m_u16ServerIpPort : INTERNET_DEFAULT_PORT, 0)) != nullptr)
      {
        // Get a request handle
        if((m_hWebSocketRequest = WinHttpOpenRequest(m_hWebSocketConnection, L"GET", m_wstrServerPath.GetString(), nullptr, nullptr, nullptr, bEncryptedConnection ? WINHTTP_FLAG_SECURE : 0)) != nullptr)
        {
          // Request a protocol upgrade from http to web socket
          if(WinHttpSetOption(m_hWebSocketRequest, WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET, nullptr, 0))
          {
            bool bError{false};

            // Initiate a web socket handshake by sending a request
            while(!bError)
            {
              // Start the handshake
              if(WinHttpSendRequest(m_hWebSocketRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0))
              {
                // We successfully started the handshake
                break;
              }
              else
              {
                const DWORD dwResult = GetLastError();

                // What error occurred?
                if(dwResult == ERROR_WINHTTP_SECURE_FAILURE)
                {
                  DWORD dwFlags = SECURITY_FLAG_IGNORE_UNKNOWN_CA | SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE | SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID;

                  // We can try ignoring certificate errors, for instance because of self-signed certificates
                  if(WinHttpSetOption(m_hWebSocketRequest, WINHTTP_OPTION_SECURITY_FLAGS, &dwFlags, sizeof(dwFlags)))
                  {
                    // We should retry
                    OutputDebugMessageNoFormat(m_pWstrDebugLog, L"CWebSocketClient::Connect() -> WinHttpSendRequest() : dwError = ERROR_WINHTTP_SECURE_FAILURE -> trying non-secure fallback\n");
                  }
                  else
                  {
                    GetSystemErrorDesc(m_pWstrDebugLog, dwResult, L"CWebSocketClient::Connect() -> WinHttpSetOption(m_hWebSocketRequest, WINHTTP_OPTION_SECURITY_FLAGS, &dwFlags, sizeof(dwFlags))");
                    bError = true;
                  }
                }
                else if(dwResult == ERROR_WINHTTP_RESEND_REQUEST)
                {
                  // We should retry
                  OutputDebugMessageNoFormat(m_pWstrDebugLog, L"CWebSocketClient::Connect() -> WinHttpSendRequest() : dwError = ERROR_WINHTTP_RESEND_REQUEST -> retrying handshake\n");
                }
                else
                {
                  // An unknown error occurred
                  GetSystemErrorDesc(m_pWstrDebugLog, dwResult, L"CWebSocketClient::Connect() -> WinHttpSendRequest(m_hWebSocketRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0)");
                  bError = true;
                }
              }
            }

            // Did the handshake negotiation succeed?
            if(!bError)
            {
              // Get the response
              if(WinHttpReceiveResponse(m_hWebSocketRequest, nullptr))
              {
                uint32 u32StatusCode{0};
                DWORD  dwSize{sizeof(u32StatusCode)};

                // Get the headers
                if(WinHttpQueryHeaders(m_hWebSocketRequest, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &u32StatusCode, &dwSize, WINHTTP_NO_HEADER_INDEX))
                {
                  // Can we switch the protocol to web socket?
                  if(u32StatusCode == HTTP_STATUS_SWITCH_PROTOCOLS)
                  {
                    // Complete the handshake
                    if((m_hWebSocket = WinHttpWebSocketCompleteUpgrade(m_hWebSocketRequest, NULL)) != nullptr)
                    {
                      DWORD_PTR dwOption{DWORD_PTR(this)};

                      OutputDebugMessage(m_pWstrDebugLog, L"this = 0x%p   m_hWebSocket = 0x%p\n", this, m_hWebSocket);

                      // Free the request handle. From now on we can use the web socket handle
                      SAFE_CLOSE_WIN_HTTP_HANDLE(m_hWebSocketRequest);

                      // Set the context handle
                      if(WinHttpSetOption(m_hWebSocket, WINHTTP_OPTION_CONTEXT_VALUE, &dwOption, sizeof(dwOption)))
                      {
                        // Get notification messages
                        if(WinHttpSetStatusCallback(m_hWebSocket, OnEventCallback, WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS, NULL) != WINHTTP_INVALID_STATUS_CALLBACK)
                        {
                          DWORD                          dwBytesRecv{0}, dwError;
                          WINHTTP_WEB_SOCKET_BUFFER_TYPE eBufferType{WINHTTP_WEB_SOCKET_BUFFER_TYPE::WINHTTP_WEB_SOCKET_BINARY_FRAGMENT_BUFFER_TYPE};

                          // We are connected now
                          ResetEvent(m_hEvtWebSocketClosed);
                          m_bConnected = true;
                          OutputDebugMessage(m_pWstrDebugLog, L"CWebSocketClient::Connect() -> successfully connected web socket \"%s::%u\"\n", m_wstrServerIpAddress.GetString(), m_u16ServerIpPort > 0 ? m_u16ServerIpPort : INTERNET_DEFAULT_PORT);

                          // Allow the other side to start sending us data
                          if((dwError = WinHttpWebSocketReceive(m_hWebSocket, &m_pu8ReadBuffer[m_u32ReadBufferLen], m_u32MaxReadBufferLen - m_u32ReadBufferLen, &dwBytesRecv, &eBufferType)) != NO_ERROR)
                          {
                            // Which error occurred?
                            if(dwError == ERROR_INVALID_OPERATION)
                              OutputDebugMessage(m_pWstrDebugLog, L"CWebSocketClient::Connect() -> WinHttpWebSocketReceive() : dwError = %i (ERROR_INVALID_OPERATION : A close or receive is pending, or the receive channel has already been closed)\n", dwError);
                            else if(dwError == ERROR_INVALID_PARAMETER)
                              OutputDebugMessage(m_pWstrDebugLog, L"CWebSocketClient::Connect() -> WinHttpWebSocketReceive() : dwError = %i (ERROR_INVALID_PARAMETER)\n", dwError);

                            else if(dwError == ERROR_WINHTTP_INVALID_SERVER_RESPONSE)
                              OutputDebugMessage(m_pWstrDebugLog, L"CWebSocketClient::Connect() -> WinHttpWebSocketReceive() : dwError = %i (ERROR_WINHTTP_INVALID_SERVER_RESPONSE)\n", dwError);
                            else if(dwError == ERROR_WINHTTP_OPERATION_CANCELLED)
                              OutputDebugMessage(m_pWstrDebugLog, L"CWebSocketClient::Connect() -> WinHttpWebSocketReceive() : dwError = %i (ERROR_WINHTTP_OPERATION_CANCELLED : The operation was cancelled because WinHttpWebSocketClose was called to close the connection)\n", dwError);
                            else
                              OutputDebugMessage(m_pWstrDebugLog, L"CWebSocketClient::Connect() -> WinHttpWebSocketReceive() : dwError = %i (unknown error)\n", dwError);
                          }
                        }
                        else
                          GetSystemErrorDesc(m_pWstrDebugLog, GetLastError(), L"CWebSocketClient::Connect() -> WinHttpSetOption()");
                      }
                      else
                        GetSystemErrorDesc(m_pWstrDebugLog, GetLastError(), L"CWebSocketClient::Connect() -> WinHttpWebSocketCompleteUpgrade()");
                    }
                    else
                      GetSystemErrorDesc(m_pWstrDebugLog, GetLastError(), L"CWebSocketClient::Connect() -> WinHttpWebSocketCompleteUpgrade()");
                  }
                  else
                    OutputDebugMessage(m_pWstrDebugLog, CWString().Format(L"CWebSocketClient::Connect() -> WinHttpQueryHeaders() : expected u32StatusCode = %u   received u32StatusCode = %u\n", HTTP_STATUS_SWITCH_PROTOCOLS, u32StatusCode).GetString());
                }
                else
                  GetSystemErrorDesc(m_pWstrDebugLog, GetLastError(), CWString().Format(L"CWebSocketClient::Connect() -> WinHttpQueryHeaders() : u32StatusCode = %u   dwSize = %u", u32StatusCode, dwSize));
              }
              else
                GetSystemErrorDesc(m_pWstrDebugLog, GetLastError(), L"CWebSocketClient::Connect() -> WinHttpReceiveResponse()");
            }
            else
              GetSystemErrorDesc(m_pWstrDebugLog, GetLastError(), L"CWebSocketClient::Connect() -> WinHttpSendRequest()");
          }
          else
            GetSystemErrorDesc(m_pWstrDebugLog, GetLastError(), L"CWebSocketClient::Connect() -> WinHttpSetOption()");
        }
        else
          GetSystemErrorDesc(m_pWstrDebugLog, GetLastError(), L"CWebSocketClient::Connect() -> WinHttpOpenRequest()");
      }
      else
        GetSystemErrorDesc(m_pWstrDebugLog, GetLastError(), L"CWebSocketClient::Connect() -> WinHttpConnect()");
    }
    else
      GetSystemErrorDesc(m_pWstrDebugLog, GetLastError(), L"CWebSocketClient::Connect() -> WinHttpOpen()");

    // Didn't we successfully connect?
    if(!m_bConnected)
      Disconnect();
  }
  catch(...) { OnException(m_pWstrDebugLog, __FILE__, __LINE__, __FUNCSIG__, __TIMESTAMP__, nullptr); }

  #endif // #ifdef BUILD_WEB_SOCKETS_UNAVAILABLE

  return IsConnected();
}
On Linux in Wine though, I can connect but the function fails when it gets to this line:

Code: Select all

if(WinHttpQueryHeaders(m_hWebSocketRequest, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &u32StatusCode, &dwSize, WINHTTP_NO_HEADER_INDEX))
It always returns the error code ERROR_INTERNET_INCORRECT_HANDLE_STATE.

Is there something different I need to do in Wine compared to Windows for web sockets? Help would be greatly appreciated, because this problem is stopping us from switching from Windows to Linux.
qwertymnb
Level 5
Level 5
Posts: 278
Joined: Sun Jan 17, 2016 4:36 pm

Re: WinHttpQueryHeaders fails with ERROR_INTERNET_INCORRECT_HANDLE_STATE

Post by qwertymnb »

Best bet is to open a bugreport I guess, with the test you described. Maybe one of the devs might take it up
Post Reply