Отправка файлов в каталог IIS WebDav с помощью API WinINet

В этой статье описывается, как программными средствами передавать файлы в каталог WebDav, размещенный в Microsoft IIS (IIS), с помощью API WinINet.

Оригинальная версия продукта: службы IIS
Исходный номер базы знаний: 2001156

Симптомы

Приложение программно отправляет файлы в каталог WebDav, размещенный в IIS, с помощью API WinINet для отправки команды HTTP PUT . Вы заметили, что приложение правильно работает в СЛУЖБАх IIS и Windows Server, но может не передавать файлы в каталог IIS WebDav, даже если в приложение WinINet не было внесено никаких изменений в код.

Причина

Причина этой проблемы связана с изменением структуры WebDav, работающей в IIS. WebDav, работающий в СЛУЖБАх IIS, теперь требует проверки подлинности и не будет работать, если используется только анонимная проверка подлинности. Из-за этого приложение, использующее последовательность HttpSendRequestExAPI WinINet , InternetWriteFileHttpEndRequest будет испытывать, что вызов HttpEndRequest возвращает значение FALSE и GetLastError() будет указывать код ошибки 12032 — ERROR_INTERNET_FORCE_RETRY.

Разрешение

Решение этой проблемы заключается в том, чтобы повторить одну и ту же последовательность операций, а именно:

  1. HttpSendRequestEx
  2. InternetWriteFile
  3. HttpEndRequest

Пока HttpEndRequest не возвращает значение FALSE и GetLastError() не сообщает обратно 12032 (или возникает другая ошибка). Если службам IIS представлены неправильные сведения о проверке подлинности, службы IIS будут продолжать возвращать ошибку HTTP 401 для каждой попытки. Таким образом, необходимо отслеживать, сколько раз HttpEndRequest функция возвращает ошибку 12032 и предотвратить запуск в бесконечный цикл.

При наличии проверки подлинности HttpEndRequest Windows NTLM возвращает ошибку 12032 максимум два раза для удовлетворения трехстороннего подтверждения NTLM. Первая ошибка 12032 будет указывать на ответ 401 http error 401 от сервера, а вторая ошибка 12032 будет указывать на сообщение подтверждения NTLM Type-2 от сервера, которое, если допустимые сведения о проверке подлинности передаются в IIS, пользователь будет проходить проверку подлинности правильно и отправка будет успешно выполнена.

При использовании логики повторных попыток для вызова указанных выше функций в цикле обратите внимание, что вызов InternetWriteFile выполняется несколько раз. Это означает, что вызов в InternetWriteFile конечном итоге будет записывать данные по сети, и это приведет к трате пропускной способности. Чтобы избежать этого, вы можете отправить на сервер фиктивный HTTP-запрос HEAD , который предварительно проверяет подлинность запроса и вызывает последующий HttpSendRequest вызов не отправлять полезные данные HTTP при InternetWriteFile вызове. Если вы знакомы с сетевым монитором или ведением журнала WinINet, вы увидите, что первый PUT запрос, отправляемый на сервер, будет иметь нулевую длину содержимого, что предотвращает передачу полезных данных, а полезные данные не будут передаваться до завершения подтверждения NTLM.

Для функции WebDav не требуется использовать проверку подлинности Windows. Вы можете настроить сервер WebDav для использования обычной проверки подлинности по протоколу SSL, что обеспечит безопасность отправки данных. Если настроена обычная проверка подлинности, вы можете напрямую внедрить допустимую строку пароля пользователя в кодировке Base-64 в исходящий запрос, что предотвратит возврат iis обратно ошибки HTTP 401 и поэтому HttpEndRequest не вернет ошибку 12032. Вы можете добавить сведения об обычной проверке подлинности в исходящий запрос, вызвав API WinINet:

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

Сделайте это перед вызовом HttpSendRequestEx , чтобы напрямую внедрить заголовок Authorization в исходящий HTTP-запрос.

В следующем примере кода показано, как можно использовать логику повторных попыток для обработки ответа на ошибку 12032, возвращаемого от HttpEndRequest. Этот пример не охватывает описанный выше запрос на предварительную проверку подлинности. Чтобы выполнить предварительную проверку подлинности, все, что вам нужно сделать, — это вызвать HttpOpenRequestс HttpSendRequest командой HTTP HEAD на целевой сервер перед вызовом приведенного HttpSendRequestEx ниже кода.

Пример кода

BOOL UseHttpSendReqEx(HINTERNET hRequest, DWORD dwPostSize)
{
    INTERNET_BUFFERS BufferIn;
    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.
    while(1)
    {
        if(!HttpSendRequestEx( hRequest, &BufferIn, NULL, 0, 0))
        {
            printf( "Error on HttpSendRequestEx %d\n",GetLastError());
            return FALSE;
        }
        FillMemory(pBuffer, 1024, 'D'); // Fill buffer with data
        bRet=TRUE;
        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);
        }
        if(!bRet)
        {
            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) {
                iNumTrials++;
                continue;   // This will retry HttpSendRequestEx...
            }
            return FALSE;
        }
        return TRUE;
    }
}

Дополнительная информация