Skip to content

Instantly share code, notes, and snippets.

@retorillo
Last active July 27, 2017 23:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save retorillo/b552bacc919d058830614180e680fa11 to your computer and use it in GitHub Desktop.
Save retorillo/b552bacc919d058830614180e680fa11 to your computer and use it in GitHub Desktop.

WinHTTP Development Tips

WinHTTP vs WinINet

According to this chart, WinHTTP has a lot weak points in comparison with WinINet. But WinHttp supports the following features:

  • Services Support (Can be run from a service or a service account)
  • Session Isolation(Separate sessions do not impact each other)
  • Impersonation(Supports being called while the thread is impersonating a different user)

WinHttpOpen

MSDN examples almost use WINHTTP_ACCESS_TYPE_DEFAULT_PROXY. But this is diprecated on Windows 8.1 and newer. Should use WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY alternatively.

Important Use of this option is deprecated on Windows 8.1 and newer. Use WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY instead. https://msdn.microsoft.com/ja-jp/library/windows/desktop/aa384098(v=vs.85).aspx)

HINTERNET session = WinHttpOpen(NULL, WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY,
  WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, NULL);

WinHttpCrackUrl

Usage of URL_COMPONENTS is a bit complex. Must set dw*Length of wanted parts to -1. Otherwise they never be cracked.

// Initialize the URL_COMPONENTS structure.
ZeroMemory(&urlComp, sizeof(urlComp));
urlComp.dwStructSize = sizeof(urlComp);

// Set required component lengths to non-zero 
// so that they are cracked.
urlComp.dwSchemeLength    = (DWORD)-1;
urlComp.dwHostNameLength  = (DWORD)-1;
urlComp.dwUrlPathLength   = (DWORD)-1;
urlComp.dwExtraInfoLength = (DWORD)-1;

Cracked parts lpsz* are addresses that were simply incremented without cutting trailing parts. For example, hostname of http://foo.bar/baz will be foo.bar/baz. To pick actual part of this, need to cut off by using its length. dw*Length

std::unique_ptr<WCHAR*> hostname(new WCHAR[urlComp.dwHostNameLength + 1]);
hostname.get()[urlComp.dwHostNameLength] = L'\0';
memcpy(hostname.get(), urlComp.lpszHostName, sizeof(WCHAR) * urlComp.dwHostNameLength);

WinHttpSendRequest

Note that this method may fail because of former method affection. For example, if call WinHttpOpenRequest along with WINHTTP_FLAG_SECURE by targeting with Non-SSL server, this method will fail although WinHttpOpenRequest is succeeded. (GetLastError returns ERROR_WINHTTP_CANNOT_CONNECT)

The following callback may be useful to trace processing. (To catch all statuses, call WinHttpSetStatusCallback along with WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS and WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS)

void CALLBACK onstatus(HINTERNET h, DWORD_PTR context, DWORD status, LPVOID info, DWORD infolen) {
  const WCHAR* statustext;
  switch (status) {
  case WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION:
    statustext = L"WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION";
    // Closing the connection to the server. The lpvStatusInformation
    // parameter is NULL.
    break;
  case WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER:
    statustext = L"WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER";
    // Successfully connected to the server. The lpvStatusInformation
    // parameter contains a pointer to an LPWSTR that indicates the IP address
    // of the server in dotted notation.
    break;
  case WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER:
    statustext = L"WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER";
    // Connecting to the server. The lpvStatusInformation parameter contains a
    // pointer to an LPWSTR that indicates the IP address of the server in
    // dotted notation.
    break;
  case WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED:
    statustext = L"WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED";
    // Successfully closed the connection to the server. The
    // lpvStatusInformation parameter is NULL.
    break;
  case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE:
    statustext = L"WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE";
    // Data is available to be retrieved with WinHttpReadData. The
    // lpvStatusInformation parameter points to a DWORD that contains the
    // number of bytes of data available. The dwStatusInformationLength
    // parameter itself is 4 (the size of a DWORD).
    break;
  case WINHTTP_CALLBACK_STATUS_HANDLE_CREATED:
    statustext = L"WINHTTP_CALLBACK_STATUS_HANDLE_CREATED";
    // An HINTERNET handle has been created. The lpvStatusInformation
    // parameter contains a pointer to the HINTERNET handle.
    break;
  case WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING:
    statustext = L"WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING";
    // This handle value has been terminated. The lpvStatusInformation
    // parameter contains a pointer to the HINTERNET handle. There will be no
    // more callbacks for this handle.
    break;
case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE:
    statustext = L"WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE";
    // The response header has been received and is available with
    // WinHttpQueryHeaders. The lpvStatusInformation parameter is NULL.
    break;
  case WINHTTP_CALLBACK_STATUS_INTERMEDIATE_RESPONSE:
    statustext = L"WINHTTP_CALLBACK_STATUS_INTERMEDIATE_RESPONSE";
    // Received an intermediate (100 level) status code message from the
    // server. The lpvStatusInformation parameter contains a pointer to a
    // DWORD that indicates the status code.
    break;
  case WINHTTP_CALLBACK_STATUS_NAME_RESOLVED:
    statustext = L"WINHTTP_CALLBACK_STATUS_NAME_RESOLVED";
    // Successfully found the IP address of the server. The
    // lpvStatusInformation parameter contains a pointer to a LPWSTR that
    // indicates the name that was resolved.
    break;
  case WINHTTP_CALLBACK_STATUS_READ_COMPLETE:
    statustext = L"WINHTTP_CALLBACK_STATUS_READ_COMPLETE";
    // Data was successfully read from the server. The lpvStatusInformation
    // parameter contains a pointer to the buffer specified in the call to
    // WinHttpReadData. The dwStatusInformationLength parameter contains the
    // number of bytes read.
    // When used by WinHttpWebSocketReceive, the lpvStatusInformation
    // parameter contains a pointer to a WINHTTP_WEB_SOCKET_STATUS structure,
    // and the dwStatusInformationLength parameter indicates the size of
    // lpvStatusInformation.
    break;
  case WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE:
    statustext = L"WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE";
    // Waiting for the server to respond to a request. The
    // lpvStatusInformation parameter is NULL.
    break;
  case WINHTTP_CALLBACK_STATUS_REDIRECT:
    statustext = L"WINHTTP_CALLBACK_STATUS_REDIRECT";
    // An HTTP request is about to automatically redirect the request. The
    // lpvStatusInformation parameter contains a pointer to an LPWSTR
    // indicating the new URL. At this point, the application can read any
    // data returned by the server with the redirect response and can query
    // the response headers. It can also cancel the operation by closing the
    // handle.
    break;
  case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR:
    statustext = L"WINHTTP_CALLBACK_STATUS_REQUEST_ERROR";
    // An error occurred while sending an HTTP request. The
    // lpvStatusInformation parameter contains a pointer to a
    // WINHTTP_ASYNC_RESULT structure. Its dwResult member indicates the ID of
    // the called function and dwError indicates the return value.
    break;
  case WINHTTP_CALLBACK_STATUS_REQUEST_SENT:
    statustext = L"WINHTTP_CALLBACK_STATUS_REQUEST_SENT";
    // Successfully sent the information request to the server. The
    // lpvStatusInformation parameter contains a pointer to a DWORD indicating
    // the number of bytes sent.
    break;
  case WINHTTP_CALLBACK_STATUS_RESOLVING_NAME:
    statustext = L"WINHTTP_CALLBACK_STATUS_RESOLVING_NAME";
    // Looking up the IP address of a server name. The lpvStatusInformation
    // parameter contains a pointer to the server name being resolved.
    break;
  case WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED:
    statustext = L"WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED";
    // Successfully received a response from the server. The
    // lpvStatusInformation parameter contains a pointer to a DWORD indicating
    // the number of bytes received.
    break;
  case WINHTTP_CALLBACK_STATUS_SECURE_FAILURE:
    statustext = L"WINHTTP_CALLBACK_STATUS_SECURE_FAILURE";
    // One or more errors were encountered while retrieving a Secure Sockets
    // Layer (SSL) certificate from the server. The lpvStatusInformation
    // parameter contains a flag. For more information, see the description
    // for lpvStatusInformation.
    break;
  case WINHTTP_CALLBACK_STATUS_SENDING_REQUEST:
    statustext = L"WINHTTP_CALLBACK_STATUS_SENDING_REQUEST";
    // Sending the information request to the server. The lpvStatusInformation
    // parameter is NULL.
    break;
  case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE:
    statustext = L"WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE";
    // The request completed successfully. The lpvStatusInformation parameter
    // is NULL.
    break;
  case WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE:
    statustext = L"WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE";
    // Data was successfully written to the server. The lpvStatusInformation
    // parameter contains a pointer to a DWORD that indicates the number of
    // bytes written.
    // When used by WinHttpWebSocketSend, the lpvStatusInformation parameter
    // contains a pointer to a WINHTTP_WEB_SOCKET_STATUS structure, and the
    // dwStatusInformationLength parameter indicates the size of
    // lpvStatusInformation.
    break;
  case WINHTTP_CALLBACK_STATUS_GETPROXYFORURL_COMPLETE:
    statustext = L"WINHTTP_CALLBACK_STATUS_GETPROXYFORURL_COMPLETE";
    // The operation initiated by a call to WinHttpGetProxyForUrlEx is
    // complete. Data is available to be retrieved with WinHttpReadData.
    break;
  case WINHTTP_CALLBACK_STATUS_CLOSE_COMPLETE:
    statustext = L"WINHTTP_CALLBACK_STATUS_CLOSE_COMPLETE";
    // The connection was successfully closed via a call to
    // WinHttpWebSocketClose.
    break;
  case WINHTTP_CALLBACK_STATUS_SHUTDOWN_COMPLETE:
    statustext = L"WINHTTP_CALLBACK_STATUS_SHUTDOWN_COMPLETE";
    // The connection was successfully shut down via a call to
    // WinHttpWebSocketShutdown.
    break;
  default:
    break;
  }
  #ifdef DEBUG
    WCHAR omsg[1024];
    wsprintf(omsg, L"%s %s %s", DIB_DBGSTR_PREFIX, statustext);
    OutputDebugString(omsg);
  #endif
}

WinHttpReadData

If want to write readed data into IStream, on modern Windows, SHCreateMemorySteam (https://msdn.microsoft.com/en-us/library/windows/desktop/bb773831(v=vs.85).aspx) seems to be recommended than CreateStreamOnHGlobal.

Consider using the SHCreateMemStream function, which produces better performance https://msdn.microsoft.com/en-us/library/windows/desktop/aa378980(v=vs.85).aspx

IStream* stream = SHCreateMemStream(NULL, NULL);
DWORD size, read;
do {
  BOOL successQuery = WinHttpQueryDataAvailable(request, &size);
  if (!successQuery)
    throw L"Failed to query data avaiable";
  std::unique_ptr<BYTE> buffer(new BYTE[size]);
  WinHttpReadData(request, reinterpret_cast<void*>(buffer.get()), size, &read);
  stream->Write(buffer.get(), read, NULL);
} while (size > 0);
memset(reinterpret_cast<BYTE*>(&zero), 0, sizeof(LARGE_INTEGER));
stream->Seek(zero, STREAM_SEEK_SET, reinterpret_cast<ULARGE_INTEGER*>(NULL));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment