How to send HTTPS request using WinInet? - winapi

I want to send HTTPS GET request using WinInet. As far as i know, i should do it just like sending HTTP request except i have to use INTERNET_DEFAULT_HTTPS_PORT and INTERNET_FLAG_SECURE flag.
So here is what i tried:
#include "stdafx.h"
#include <string>
#include <windows.h>
#include <WinInet.h>
#pragma comment (lib, "Wininet.lib")
using namespace std;
// convert string
wstring CharPToWstring(const char* _charP)
{
return wstring(_charP, _charP + strlen(_charP));
}
// send https request
wstring SendHTTPSRequest_GET(const wstring& _server,
const wstring& _page,
const wstring& _params = L"")
{
char szData[1024];
// initialize WinInet
HINTERNET hInternet = ::InternetOpen(TEXT("WinInet Test"), INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
if (hInternet != NULL)
{
// open HTTP session
HINTERNET hConnect = ::InternetConnect(hInternet, _server.c_str(), INTERNET_DEFAULT_HTTPS_PORT, NULL,NULL, INTERNET_SERVICE_HTTP, INTERNET_FLAG_SECURE, 1);
if (hConnect != NULL)
{
wstring request = _page +
(_params.empty() ? L"" : (L"?" + _params));
// open request
HINTERNET hRequest = ::HttpOpenRequest(hConnect, L"GET", (LPCWSTR)request.c_str() ,NULL, NULL, 0, INTERNET_FLAG_KEEP_CONNECTION, 1);
if (hRequest != NULL)
{
// send request
BOOL isSend = ::HttpSendRequest(hRequest, NULL, 0, NULL, 0);
if (isSend)
{
for(;;)
{
// reading data
DWORD dwByteRead;
BOOL isRead = ::InternetReadFile(hRequest, szData, sizeof(szData) - 1, &dwByteRead);
// break cycle if error or end
if (isRead == FALSE || dwByteRead == 0)
break;
// saving result
szData[dwByteRead] = 0;
}
}
// close request
::InternetCloseHandle(hRequest);
}
// close session
::InternetCloseHandle(hConnect);
}
// close WinInet
::InternetCloseHandle(hInternet);
}
wstring answer = CharPToWstring(szData);
return answer;
}
int _tmain(int argc, _TCHAR* argv[])
{
wstring answer = SendHTTPSRequest_GET(L"www.site.com", L"page.php", L"param1=value1&param2=value2&param3=value3&param4=value4");
return 0;
}
And my function returned an answer:
<html>
<head><title>400 The plain HTTP request was sent to HTTPS port</title></head>
<body bgcolor="white">
<center><h1>400 Bad Request</h1></center>
<center>The plain HTTP request was sent to HTTPS port</center>
<hr><center>nginx/1.0.10</center>
</body>
</html>
What am i doing wrong?

INTERNET_FLAG_SECURE should be used in the flags for HttpOpenRequest, not InternetConnect.
This is explained in the MSDN documentation for WinINet API flags:
INTERNET_FLAG_SECURE
0x00800000
Uses secure transaction semantics. This translates to using Secure Sockets Layer/Private Communications Technology (SSL/PCT) and is only meaningful in HTTP requests. This flag is used by HttpOpenRequest and InternetOpenUrl, but this is redundant if https:// appears in the URL. The InternetConnect function uses this flag for HTTP connections; all the request handles created under this connection will inherit this flag.

Related

ESP8266 Returning code 400 on POST request in Django webapp

I deployed my django webapp to the internet but I can't POST request a data because it's returning
HTTP CODE 400,
const char* serverName = "https://example.com/api/v1/post_sample"; = returns 400 code.
but when I do: const char* serverName = "http://localhost:8000/api/v1/post_sample"; it works and return 201 code.
It didn't put any restrictions or auth for now on the web application. I even did a POST request on Postman to the web application I deployed, it also works.
Please help. Thanks!
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClient.h>
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
const char* serverName = "https://example.com/api/v1/post_sample";
unsigned long lastTime = 0;
unsigned long timerDelay = 5000;
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
Serial.println("Connecting");
while(WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("Connected to WiFi network with IP Address: ");
Serial.println(WiFi.localIP());
Serial.println("Timer set to 5 seconds (timerDelay variable), it will take 5 seconds before publishing the first reading.");
}
void loop() {
if ((millis() - lastTime) > timerDelay) {
//Check WiFi connection status
if(WiFi.status()== WL_CONNECTED){
HTTPClient http;
http.begin(serverName);
http.addHeader("Content-Type", "application/json");
int httpResponseCode = http.POST("{\"value\":\"1\"}");
Serial.print("HTTP Response code: ");
Serial.println(httpResponseCode);
http.end();
}
else {
Serial.println("WiFi Disconnected");
}
lastTime = millis();
}
}
You didn't establish a WiFiClient before trying to establish a http connection.
For POST request with ESP8266HTTPClient, you pass in the url when you establish http.begin(client, url) with an established client instance. See the example code. In your case, it should be:
if(WiFi.status()== WL_CONNECTED){
WiFiClient client;
HTTPClient http;
http.begin(client, serverName);
http.addHeader("Content-Type", "application/json");
int httpResponseCode = http.POST("{\"value\":\"1\"}");
Serial.print("HTTP Response code: ");
Serial.println(httpResponseCode);
http.end();
}
else {
Serial.println("WiFi Disconnected");
}
If you url is an secure connection with https as you shown in your example url, then you will need to establish a secure client with a library like <WiFiClientSecureBearSSL.h>. See example.

select with other objects than sockets on windows

I'm facing an issue doing a select() call waiting on a socket + pipe.
I know there are already some topics on that but I have read lots of things and their opposite and I can't figure out what is the best solution for my problem.
The best for me would be to use WaitForMultipleObjects() listening on these two objects but when I try to call it only on the WSAEvent object, it fails and last error catch is code 6 (Invalid Handle).
WSAEVENT sockEvent = WSACreateEvent();
sockEvent = WSAEventSelect(fd, sockEvent, FD_WRITE);
HANDLE *pHandles = &sockEvent;
DWORD dwEvent = WaitForMultipleObjects(1, pHandles, FALSE, amqp_time_ms_until(deadline));
switch (dwEvent)
{
// ghEvents[0] was signaled
case WAIT_OBJECT_0 + 0:
// TODO: Perform tasks required by this event
return AMQP_STATUS_OK;
// ghEvents[1] was signaled
case WAIT_OBJECT_0 + 1:
// TODO: Perform tasks required by this event
return AMQP_STATUS_POLL_EXTERNAL_WAKE;
case WAIT_TIMEOUT:
return AMQP_STATUS_TIMEOUT;
// Return value is invalid.
default:
return AMQP_STATUS_SOCKET_ERROR;
}
So WaitForMultipleObjects doesn't seems to Work with WinSocks events, however I have already seen some examples on the net working with it.
And the of WSACreateEvent documentation (https://msdn.microsoft.com/en-us/library/windows/desktop/ms741561%28v=vs.85%29.aspx) says this :
Windows Sockets 2 event objects are system objects in Windows
environments. Therefore, if a Windows application wants to use an
auto-reset event rather than a manual-reset event, the application can
call the CreateEvent function directly.
This doesn't mean that WSAEvent are based on regular windows events ? If it's the case why it doesn't work with WaitForMultipleObjects ? The doc says it can handle regular events.
Thanks for helping.
This is your problem:
sockEvent = WSAEventSelect(fd, sockEvent, FD_WRITE);
You're overwriting the event handle! (As documented, the return value for WSAEventSelect is either 0 or SOCKET_ERROR. It is not a new event handle.)
Try something like
if (WSAEventSelect(fd, sockEvent, FD_WRITE) != 0) return SOCKET_ERROR;
Looking at the declaration of WSAEVENT revealed that WSAEVENT is simply an alias for HANDLE. This explains the note of the WSACreateEvent documentation you added to your post. So WSACreateEvent simply creates a manual reset event by calling CreateEvent(..., TRUE, FALSE, ...);.
Therefore an event returned by WSACreateEvent has to work along with WaitForMultipleObjects(..).
According to the code you've posted I cannot see any reason why WaitForMultipleObjects(..) should return "invalid handle" when supplied with an event returned by WSACreateEvent...
It may be though that pipes do not work with WaitForMultipleObjects(..). I remember having problems with that a long time ago but I cannot remember the details right now. But maybe it is another place to start digging...
Here is the code of my little test application which creates two threads (one event thread signalling a normal event and a simple TCP/IP server sending data). In the main loop a connection to the server is established and signalled events are processed.
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#pragma comment(lib, "Ws2_32.lib");
#define SERVER_PORT 5000
HANDLE hSomeEvent;
HANDLE hSocketEvent;
DWORD WINAPI eventThread(LPVOID pData)
{
while (1)
{
SleepEx(2250, FALSE);
SetEvent(hSomeEvent);
}
return (0);
}
DWORD WINAPI serverThread(LPVOID pData)
{
SOCKET listener;
struct sockaddr_in sockaddr;
int size;
SOCKET client;
listener = socket(AF_INET, SOCK_STREAM, 0);
if (listener == INVALID_SOCKET)
{
printf("Could not create socket : %d" , WSAGetLastError());
}
sockaddr.sin_family = AF_INET;
sockaddr.sin_addr.s_addr = INADDR_ANY;
sockaddr.sin_port = htons(SERVER_PORT);
if (bind(listener, (struct sockaddr *)&sockaddr , sizeof(sockaddr)) == SOCKET_ERROR)
{
printf("Bind failed with error code : %d" , WSAGetLastError());
}
listen(listener, 1);
while (listener)
{
size = sizeof(struct sockaddr_in);
client = accept(listener, (struct sockaddr *)&sockaddr, &size);
printf("client connected\n");
while (client != INVALID_SOCKET)
{
SleepEx(5000, FALSE);
if (send(client, "hello\0", 6, 0) != 6)
{
closesocket(client);
shutdown(client, 2);
client = INVALID_SOCKET;
}
}
SetEvent(hSomeEvent);
}
return (0);
}
int main()
{
WSADATA wsaData;
HANDLE events[2];
DWORD result;
SOCKET s;
struct hostent *hp;
struct sockaddr_in sockaddr;
int len;
char buff[1024 * 16];
HANDLE *evtPtr;
WSAStartup(MAKEWORD(2, 2), &wsaData);
hSocketEvent = WSACreateEvent();
//hSocketEvent = CreateEvent(NULL, FALSE, FALSE, "socket_event");
hSomeEvent = CreateEvent(NULL, FALSE, FALSE, "some_event");
CreateThread(NULL, 0, eventThread, NULL, 0, &result);
CreateThread(NULL, 0, serverThread, NULL, 0, &result);
s = socket(AF_INET, SOCK_STREAM, 0);
if (s == INVALID_SOCKET)
{
printf("Could not create socket : %d" , WSAGetLastError());
}
hp = gethostbyname("127.0.0.1");
sockaddr.sin_addr.s_addr = *((unsigned long*)hp->h_addr);
sockaddr.sin_family = AF_INET;
sockaddr.sin_port = htons(SERVER_PORT);
if (connect(s, (struct sockaddr*)&sockaddr, sizeof(sockaddr)))
{
closesocket(s);
printf("Could not connect socket : %d" , WSAGetLastError());
}
WSAEventSelect(s, hSocketEvent, FD_READ);
do
{
//events[0] = hSocketEvent;
//events[1] = hSomeEvent;
//result = WaitForMultipleObjects(2, events, FALSE, 1000);
evtPtr = &hSocketEvent;
result = WaitForMultipleObjects(1, evtPtr, FALSE, 1000);
switch (result)
{
case WAIT_OBJECT_0 + 0:
printf("hSocketEvent is signalled!\n");
len = recv(s, buff, sizeof(buff), 0);
printf(" %d bytes received\n", len);
WSAResetEvent(hSocketEvent);
break;
case WAIT_OBJECT_0 + 1:
printf("hSomeEvent is signalled!\n");
break;
case WAIT_TIMEOUT:
printf("timeout\n");
break;
default:
printf("error = %d\n", GetLastError());
break;
}
}
while (1);
printf("\n\nend.");
getch();
return (0);
}
Note that if you use WSACreateEvent you have to manually reset the event after readinng the data (otherwise WaitForMultipleObjects(..) will go nuts).

Browse LDAP servers in the domain

I am following this guide.
I used the following code to display LDAP servers in the domain:
#include <dns_sd.h>
#include <stdio.h>
#include <pthread.h>
void ResolveCallBack(DNSServiceRef sdRef,
DNSServiceFlags flags,
uint32_t interfaceIndex,
DNSServiceErrorType errorCode,
const char *fullname,
const char *hosttarget,
uint16_t port, /* In network byte order */
uint16_t txtLen,
const unsigned char *txtRecord,
void *context) {
}
void BrowserCallBack(DNSServiceRef inServiceRef,
DNSServiceFlags inFlags,
uint32_t inIFI,
DNSServiceErrorType inError,
const char* inName,
const char* inType,
const char* inDomain,
void* inContext) {
DNSServiceErrorType err = DNSServiceResolve(&inServiceRef,
0, // Indicate it's a shared connection.
inIFI,
inName,
inType,
inDomain,
ResolveCallBack,
NULL);
printf("DNSServiceResolve err = %x, name = %s, type=%s, domain=%s\n",
err, inName, inType, inDomain);
}
int main() {
DNSServiceRef ServiceRef;
DNSServiceErrorType err = DNSServiceBrowse(&ServiceRef, // Receives reference to Bonjour browser object.
kDNSServiceFlagsDefault, // Indicate it's a shared connection.
kDNSServiceInterfaceIndexAny, // Browse on all network interfaces.
"_ldap._tcp", // Browse for service types.
NULL, // Browse on the default domain (e.g. local.).
BrowserCallBack, // Callback function when Bonjour events occur.
NULL); // Callback context.
printf("err = 0x%x\n", err);
int sockfd = DNSServiceRefSockFD(ServiceRef);
printf("sockfd = %d\n", sockfd);
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, 0);
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, 0);
struct timeval timeout;
timeout.tv_sec = 1;
timeout.tv_usec = 0;
fd_set descriptors;
FD_ZERO(&descriptors);
FD_SET(sockfd, &descriptors);
int r = select(sockfd + 1, &descriptors, NULL, NULL, &timeout);
printf("r = %d\n", r);
fflush(stdout);
if (r > 0) {
if (FD_ISSET(sockfd, &descriptors)) {
// This function will call the appropiate callback to process the
// event, in this case the BrowseReply static method.
err = DNSServiceProcessResult(ServiceRef);
if (err != kDNSServiceErr_NoError) {
printf("Error on process an event in event loop, e = 0x%x\n", err);
}
}
} else if (r == -1) {
printf("The select() call failed");
}
return 0;
}
However, this didn't give me any LDAP server.
Any Help on this?
Thanks in advance
N.B:
This command returns results:
$nslookup -type=any _ldap._tcp
So there is LDAP servers in the domain.
When I tried "_http._tcp" as the registration type this returns
results.
Operating system is Mac OS X 10.9.

libwebsockets write to all active connections after receive

I am toying around with a libwebsockets tutorial trying to make it such that, after it receives a message from a connection over a given protocol, it sends a response to all active connections implementing that protocol. I have used the function libwebsocket_callback_all_protocol but it is not doing what I think it should do from its name (I'm not quite sure what it does from the documentation).
The goal is to have two webpages open and, when info is sent from one, the result will be relayed to both. Below is my code - you'll see that libwebsocket_callback_all_protocol is called in main (which currently does nothing, I think....) :
#include <stdio.h>
#include <stdlib.h>
#include <libwebsockets.h>
#include <string.h>
static int callback_http(struct libwebsocket_context * this,
struct libwebsocket *wsi,
enum libwebsocket_callback_reasons reason, void *user,
void *in, size_t len)
{
return 0;
}
static int callback_dumb_increment(struct libwebsocket_context * this,
struct libwebsocket *wsi,
enum libwebsocket_callback_reasons reason,
void *user, void *in, size_t len)
{
switch (reason) {
case LWS_CALLBACK_ESTABLISHED: // just log message that someone is connecting
printf("connection established\n");
break;
case LWS_CALLBACK_RECEIVE: { // the funny part
// create a buffer to hold our response
// it has to have some pre and post padding. You don't need to care
// what comes there, libwebsockets will do everything for you. For more info see
// http://git.warmcat.com/cgi-bin/cgit/libwebsockets/tree/lib/libwebsockets.h#n597
unsigned char *buf = (unsigned char*) malloc(LWS_SEND_BUFFER_PRE_PADDING + len +
LWS_SEND_BUFFER_POST_PADDING);
int i;
// pointer to `void *in` holds the incomming request
// we're just going to put it in reverse order and put it in `buf` with
// correct offset. `len` holds length of the request.
for (i=0; i < len; i++) {
buf[LWS_SEND_BUFFER_PRE_PADDING + (len - 1) - i ] = ((char *) in)[i];
}
// log what we recieved and what we're going to send as a response.
// that disco syntax `%.*s` is used to print just a part of our buffer
// http://stackoverflow.com/questions/5189071/print-part-of-char-array
printf("received data: %s, replying: %.*s\n", (char *) in, (int) len,
buf + LWS_SEND_BUFFER_PRE_PADDING);
// send response
// just notice that we have to tell where exactly our response starts. That's
// why there's `buf[LWS_SEND_BUFFER_PRE_PADDING]` and how long it is.
// we know that our response has the same length as request because
// it's the same message in reverse order.
libwebsocket_write(wsi, &buf[LWS_SEND_BUFFER_PRE_PADDING], len, LWS_WRITE_TEXT);
// release memory back into the wild
free(buf);
break;
}
default:
break;
}
return 0;
}
static struct libwebsocket_protocols protocols[] = {
/* first protocol must always be HTTP handler */
{
"http-only", // name
callback_http, // callback
0, // per_session_data_size
0
},
{
"dumb-increment-protocol", // protocol name - very important!
callback_dumb_increment, // callback
0, // we don't use any per session data
0
},
{
NULL, NULL, 0, 0 /* End of list */
}
};
int main(void) {
// server url will be http://localhost:9000
int port = 9000;
const char *interface = NULL;
struct libwebsocket_context *context;
// we're not using ssl
const char *cert_path = NULL;
const char *key_path = NULL;
// no special options
int opts = 0;
// create libwebsocket context representing this server
struct lws_context_creation_info info;
memset(&info, 0, sizeof info);
info.port = port;
info.iface = interface;
info.protocols = protocols;
info.extensions = libwebsocket_get_internal_extensions();
info.ssl_cert_filepath = cert_path;
info.ssl_private_key_filepath = key_path;
info.gid = -1;
info.uid = -1;
info.options = opts;
info.user = NULL;
info.ka_time = 0;
info.ka_probes = 0;
info.ka_interval = 0;
/*context = libwebsocket_create_context(port, interface, protocols,
libwebsocket_get_internal_extensions,
cert_path, key_path, -1, -1, opts);
*/
context = libwebsocket_create_context(&info);
if (context == NULL) {
fprintf(stderr, "libwebsocket init failed\n");
return -1;
}
libwebsocket_callback_all_protocol(&protocols[1], LWS_CALLBACK_RECEIVE);
printf("starting server...\n");
// infinite loop, to end this server send SIGTERM. (CTRL+C)
while (1) {
libwebsocket_service(context, 50);
// libwebsocket_service will process all waiting events with their
// callback functions and then wait 50 ms.
// (this is a single threaded webserver and this will keep our server
// from generating load while there are not requests to process)
}
libwebsocket_context_destroy(context);
return 0;
}
I had the same problem, the libwebsocket_write on LWS_CALLBACK_ESTABLISHED generate some random segfault so using the mail list the libwebsockets developer Andy Green instructed me the correct way is to use libwebsocket_callback_on_writable_all_protocol, the file test-server/test-server.c in library source code shows sample of use.
libwebsocket_callback_on_writable_all_protocol(libwebsockets_get_protocol(wsi))
It worked very well to notify all instances, but it only call the write method in all connected instances, it do not define the data to send. You need to manage the data yourself. The sample source file test-server.c show a sample ring buffer to do it.
http://ml.libwebsockets.org/pipermail/libwebsockets/2015-January/001580.html
Hope it helps.
From what I can quickly grab from the documentation, in order to send a message to all clients, what you should do is store somewhere (in a vector, a hashmap, an array, whatever) the struct libwebsocket * wsi that you have access when your clients connect.
Then when you receive a message and want to broadcast it, simply call libwebsocket_write on all wsi * instances.
That's what I'd do, anyway.

WinInet - can't remove Content-Length from headers to manually chunk an upload

MSDN states that WinInet does not support chunked upload ("Client code must perform the chunking."). To me that meant I could manually chunk the transfer. My intention was to add "Transfer-Encoding: chunked" via HttpAddRequestHeaders, remove Content-Length via HttpAddRequestHeaders HTTP_ADDREQ_FLAG_REPLACE and then In my InternetWriteFile loop, write the data in chunked encoded blocks. The problem is, I cannot seem to convince WinInet to not send the Content-Length. Even after removal, it ends up sending "Content-Length: 0" to the server (in addition to "Transfer-Encoding: chunked") which is confusing the server.
I have also tried setting the HSR_CHUNKED flag in HttpSendRequestEx.
Does anyone have an example of getting WinInet to skip sending the Content-Length?
I know WinHTTP claims to support chunked upload, but we have other dependencies on WinInet which is why I am looking to solve the problem there if possible.
Here is a code sample of what I have tried:
#include <windows.h>
#include <wininet.h>
#include <tchar.h>
#include <stdio.h>
bool http_putfile(HINTERNET hConnection, TCHAR* resource);
#define HOST _T("www.website.com")
#define RESOURCE _T("/path/for/resource")
int _tmain(int argc, TCHAR* argv[])
{
LPCTSTR lpszHostName = HOST;
INTERNET_PORT nServerPort = INTERNET_DEFAULT_HTTP_PORT;
DWORD dwService = INTERNET_SERVICE_HTTP;
DWORD dwFlags = NULL;
DWORD dwContext = 0;
HINTERNET hSession = InternetOpen(
argv[0],
INTERNET_OPEN_TYPE_DIRECT,
NULL,
NULL,
NULL);
if(hSession != NULL)
{
HINTERNET hConnection = InternetConnect(
hSession,
lpszHostName,
nServerPort,
NULL,
NULL,
dwService,
dwFlags,
dwContext);
if(hConnection != NULL)
{
http_putfile(hConnection, RESOURCE);
InternetCloseHandle(hConnection);
}
else
{
printf("InternetConnect failed: %d\n", GetLastError());
}
InternetCloseHandle(hSession);
}
else
{
printf("InternetOpen failed: %d\n", GetLastError());
}
return 0;
}
bool http_putfile(HINTERNET hConnection, TCHAR* resource)
{
bool result = false;
HINTERNET hRequest = HttpOpenRequest(
hConnection,
_T("PUT"),
resource,
NULL,
NULL,
NULL,
INTERNET_FLAG_RELOAD | INTERNET_FLAG_EXISTING_CONNECT | INTERNET_FLAG_NO_CACHE_WRITE,
0);
if(hRequest != NULL)
{
HttpAddRequestHeaders(
hRequest,
_T("Transfer-Encoding: chunked\r\n"),
-1L,
HTTP_ADDREQ_FLAG_ADD | HTTP_ADDREQ_FLAG_REPLACE);
// have tried:
// Content-Length
// Content-Length:
// Content-Length\r\n
// Content-Length:\r\n
// all with/without HTTP_ADDREQ_FLAG_ADD. Have even tried adding in a Content-Length
// and then removing it. All results show "Content-Length: 0" in the header on the wire.
if(HttpAddRequestHeaders(
hRequest,
_T("Content-Length"),
-1L,
HTTP_ADDREQ_FLAG_ADD | HTTP_ADDREQ_FLAG_REPLACE) == FALSE)
{
DWORD err = GetLastError();
}
// have tried both 0 here for flags as documented on msdn
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa384318%28v=vs.85%29.aspx
// as well as other combinations of INITIATE/CHUNKED
if(HttpSendRequestEx(hRequest, NULL, NULL, HSR_INITIATE | HSR_CHUNKED /* 0 */, NULL))
{
DWORD wrote = 0;
char* chunks = "5\r\nCHUNK0\r\n";
if(InternetWriteFile(
hRequest,
chunks,
strlen(chunks),
&wrote) == FALSE)
{
printf("InternetWriteFile failed: %d\n", GetLastError());
}
HttpEndRequest(hRequest, NULL, 0, NULL);
}
else
{
printf("HttpSendRequestEx failed: %d\n", GetLastError);
}
InternetCloseHandle(hRequest);
}
else
{
printf("HttpOpenRequest failed: %d\n", GetLastError());
}
return result;
}
Well, it looks like you are right. In fact, I don't see anything to indicate that WinINet supports chunked uploading; only WinHTTP does. Looking at the Wine source code (which tends to be quite accurate), the "Content-Length" header is ALWAYS appended to the request for any method other than "GET".
According to the HTTP specs, if a non-identity "Transfer-Encoding" header is present then the message is considered to be chunked. If a "Content-Length" header is also present then it MUST be ignored. If the spurious "Content-Length: 0" header is causing you problems then it could be argued that it is an issue with the server. I would give WinHTTP a try and see if it fixes the problem.
Great question, I was frustrated by this myself for a good number of hours.
Your instincts about HTTP_ADDREQ_FLAG_REPLACE turned out to be right... However, you have to call it pretty late. Specifically you need to register a status callback, and when your callback gets called with INTERNET_STATUS_CONNECTED_TO_SERVER, that is when you remove the header:
// Inside InternetStatusCallback
//
if (dwInternetStatus == INTERNET_STATUS_CONNECTED_TO_SERVER)
{
HttpAddRequestHeaders(Handle, L"Content-Length", -1, HTTP_ADDREQ_FLAG_REPLACE);
}

Resources