How to programmatically upload files using WebDav on Internet Information Services 7.0 using the WinInet API


Consider the following scenario:

Your application is programmatically uploading files to a WebDav directory hosted on IIS 7, by using the WinInet API to send a HTTP PUT verb. You may experience that your application, which was working correctly on IIS6 and Windows Server 2003, may fail to upload files to the IIS 7 WebDav directory, even though no code changes have been made to your WinInet application.


The cause of this issue is due to a design change for WebDav running on IIS 7.  WebDav running on IIS 7 now requires authentication and will not work if only Anonymous Authentication is used. As a result of this, your application which has been using the WinInet API sequence of HttpSendRequestEx, InternetWriteFile, HttpEndRequest will experience that the call to HttpEndRequest returns FALSE and GetLastError() will indicate an error code of 12032 - ERROR_INTERNET_FORCE_RETRY.


The resolution to this issue is to retry the same sequence of operations, namely:

A.) HttpSendRequestEx
B.) InternetWriteFile
C.) HttpEndRequest

until HttpEndRequest does not return FALSE and GetLastError() does not report back 12032 (or there is some other error). If IIS is presented with wrong authentication information, then IIS will keep returning an HTTP 401 error for each retry; therefore, you will need to keep track of how many times the HttpEndRequest function returns back error 12032 and hence prevent running into an infinite loop.

In the case of Windows NTLM Authentication, HttpEndRequest will return back error 12032 for a maximum of two times to satisfy the 3 way NTLM handshake. The first 12032 error will indicate a HTTP 401 error response from the server and the second 12032 error will indicate the Type-2 NTLM handshake message from the server following which, if valid authentication info is passed to IIS, the user will be authenticated correctly and the upload will be successful.

When you use a retry logic to call the above functions in a loop, notice that the call to InternetWriteFile is being made multiple times. What this means is that the call to InternetWriteFile will end up writing the data over the network and this will cause a waste of bandwidth. To prevent this from happening, you can send a dummy HTTP HEAD request to the server which will "pre-authenticate" the request and cause the subsequent call of HttpSendRequest to not send the HTTP Payload when InternetWriteFile is called. If you are familiar with Network Monitor or WinInet logging, you will see that the first PUT request sent to the server will have a Content-Length of zero, which prevents the payload transfer and the payload will not be transferred until the NTLM handshake is complete.

The WebDav feature does not require you to use Windows Authentication; you can configure your WebDav server to use Basic Authentication over SSL which will ensure the safety of the data upload. When Basic authentication is configured, you can directly inject a valid base-64 encoded username password string in the outgoing request, which will prevent IIS returning back a HTTP 401 error and therefore HttpEndRequest will not return back error 12032. You can add the Basic authentication information to the outgoing request by calling the WinInet API:

HttpAddRequestHeaders(hRequest, "Authorization: Basic <<valid base-64 encoded username:password string>>\r\n", -1, HTTP_ADDREQ_FLAG_ADD);

Do this before calling HttpSendRequestEx to directly inject the Authorization header in the outgoing HTTP request.

The following code sample shows how you can use the retry logic to handle the error 12032 return response from HttpEndRequest. Please note that this sample does not cover the "pre authenticate" request discussed above. In order to Pre-Authenticate, all you will need to do is to call HttpOpenRequest, HttpSendRequest with the HTTP HEAD verb to the target server before you call the HttpSendRequestEx code below.

Code Sample:

BOOL UseHttpSendReqEx(HINTERNET hRequest, DWORD dwPostSize)
    DWORD dwBytesWritten;
    int iChunkCtr;
    BYTE pBuffer[1024];
    BOOL bRet;

    BufferIn.dwStructSize = sizeof( INTERNET_BUFFERS ); // Must be set or you will get an error
    BufferIn.Next = NULL;
    BufferIn.lpcszHeader = NULL;
    BufferIn.dwHeadersLength = 0;
    BufferIn.dwHeadersTotal = 0;

    BufferIn.lpvBuffer = NULL;               
    BufferIn.dwBufferLength = 0;
    BufferIn.dwBufferTotal = dwPostSize; // This is the only member used other than dwStructSize
    BufferIn.dwOffsetLow = 0;
    BufferIn.dwOffsetHigh = 0;

    //  The following variable will keep track of the number of times HttpSendRequestEx is called
    int iNumTrials = 0;
    bool bRetVal = FALSE;

    //  The retry goto is to re-try the operation when HttpEndRequest returns error 12032.
        if(!HttpSendRequestEx( hRequest, &BufferIn, NULL, 0, 0))
            printf( "Error on HttpSendRequestEx %d\n",GetLastError());
            return FALSE;

        FillMemory(pBuffer, 1024, 'D'); // Fill buffer with data

        for(iChunkCtr=1; iChunkCtr<=(int)dwPostSize/1024 && bRet; iChunkCtr++)
            dwBytesWritten = 0;
            if(bRet=InternetWriteFile( hRequest, pBuffer, 1024, &dwBytesWritten))
                printf( "\r%d bytes sent.", iChunkCtr*1024);
            printf( "\nError on InternetWriteFile %lu\n",GetLastError() );
            return FALSE;

        if(!HttpEndRequest(hRequest, NULL, 0, 0))
            int iLastError = GetLastError();
            printf( "Error on HttpEndRequest %lu \n", iLastError);

            //  Use the following logic to "retry" after receiving error 12032 from HttpEndRequest
            //  Error 12032 = ERROR_INTERNET_FORCE_RETRY means that you just need to send the request again
            //  Sending request again means that you simply need to call:
            //  HttpSendRequest, InternetWriteFile, HttpEndRequest until HttpEndRequest does not return
            //  back error 12032.
            //  Since NTLM is a 3-way handshake protocol, it will happen that HttpEndRequest will return
            //  error 12032 two times and hence the following check.
            //  If error 12032 is returned 3 or more number of times, then there is some Other error.
            if(iLastError == 12032 && iNumTrials < 3) {
                continue;   // This will retry HttpSendRequestEx...
            return FALSE;
        return TRUE;