Приложение FILEUPL
Доводилось ли вам пользоваться новой популярной услугой известной антивирусной фирмы АО “ДиалогНаука” - поиск вирусов в файлах через Internet?
Если нет, то мы расскажем в чем здесь дело, так как эта услуга имеет самое непосредственное отношение к расширениям ISAPI.
Обсуждая проблемы антивирусной защиты сети Internet с генеральным директором АО “ДиалогНаука” Сергеем Григорьевичем Антимоновым, мы предложили идею организовать бесплатную антивирусную проверку файлов пользователей сети Internet самыми новыми версиями антивирусных программ, созданных в этой фирме. Эта идея была реализована сотрудником фирмы Spektrum Максимом Синевым в виде расширения ISAPI.
Каждый пользователь сети Internet, загрузив с помощью навигатора Netscape Navigator версии 2.0 или более поздней версии соответствующую страницу с сервера АО “ДиалогНаука” (адрес этого сервера http://www.dials.ccas.ru) может проверить любой свой файл или архив файлов на предмет зараженности вирусами. Страница антивирусной проверки показана на рис. 8.3.
Рис. 8.3. Страница сервера АО “ДиалогНаука”, предназначенная для поиска вирусов в локальных файлах пользователей
В нижней части этого рисунка расположена форма, состоящая из списка, органа управления, предназначенного для выбора файла, и кнопки с надписью “Go!”. Список позволяет вам выбрать антивирусную программу, с помощью которой будет выполняться проверка (Aidstest или Doctor Web).
Нажав кнопку “Browse”, с помощью обычной диалоговой панели с названием File Upload вы можете выбрать файл программы или архивный файл для проверки. Этот файл может быть расположен не только на жестком диске, но и на дискете.
Выбрав файл, нажмите кнопку “Go!”. Файл будет передан на сервер WWW АО “ДиалогНаука”, где его обработает соответствующее расширение ISAPI. После приема файла расширение запустит антивирусную программу, выбранную пользователем из списка, и проверит с ее помощью присланный файл.
Результаты проверки будут оформлены в виде документа HTML, который пользователь увидит в окне навигатора (рис. 8.4).
Рис. 8.4. Результаты удаленной антивирусной проверки
В данном случае был проверен файл A:\C-639.COM, и в нем был найден вирус Khizhnjak.639.
Такая удаленная антивирусная проверка быстро стала популярной, так как она бесплатная и очень удобная: не выходя из дома вы можете проверить любые свои файлы. Заметим, кстати, что проблема антивирусной защиты достаточно сложна. Даже после того как вы проверили через Internet все свои файлы и ни в одном из них не был обнаружен вирус, это еще не дает гарантии, что ваш компьютер не заражен - вирусы живут не только в файлах.
Если вас заинтересовали вопросы антивирусной защиты, прочитайте нашу книгу “Осторожно: компьютерные вирусы”, которая вышла в серии “Персональный компьютер. Шаг за шагом”. Эта книга содержит наиболее полную информацию по антивирусной защите и написана на базе опыта, накопленного в АО “ДиалогНаука”. Ну а сейчас мы вернемся к расширениям ISAPI.
Мы подготовили для вас исходные тексты расширения ISAPI fileupl.dll, способного передавать файлы описанным выше образом. Вы можете использовать их как прототип для разработки собственных систем удаленного приема и обработки файлов, аналогичных только что описанной системы удаленной антивирусной проверки файлов.
На чем основана система удаленного приема файлов сервером WWW?
Она основана на экспериментальном протоколе, описанном в документе RFC1867. Если вы не знакомы с описаниями RFC, сходите на сервер http://www.cis.ohio-state.edu/htbin/rfc. Здесь вы найдете бесчисленное множество различного рода спецификаций и протоколов, на которых, собственно, и базируется работа Internet. Каждый документ имеет свой номер, по которому его можно легко найти на указанном сервере. Эти документы необходимы каждому профессиональному разработчику приложений для Internet, поэтому мы рекомендуем вам посмотреть хотя бы список их названий.
Документ RFC1867 называется “Form-based file Upload in HTML”, что можно перевести как прием файлов через документы HTML.
В этом документе помимо всего прочего предлагается добавить строку FILE в качестве возможного значения параметра TYPE оператора <INPUT>, создающего органы управления в формах. Этот орган управления состоит из однострочного текстового поля и расположенной справа от него кнопки с надписью “Browse”, предназначенной для выбора локального файла.
Кроме того, в параметре ENCTYPE оператора <FORM> предлагается при передаче файлов указывать тип данных multipart/form-data, что отличается от привычного формата application/x-www-form-urlencoded.
Формат данных multipart/form-data позволяет передавать данные типа MIME и, в частности, произвольные двоичные данные, которыми в общем случае являются все файлы. Что же касается формата application/x-www-form-urlencoded, используемого по умолчанию, то он пригоден только для передачи текстовых данных.
Документы RFC носят рекомендательный характер, поэтому разработчики программного обеспечения вправе принимать их или игнорировать. В частности, спецификация удаленного приема файлов RFC1867 используется навигатором Netscape Navigator версии 2.0 и более поздней версии, но полностью игнорируется навигатором Microsoft Explorer любой версии вплоть до 3.01. Именно этим объясняется требование на сервере АО “ДиалогНаука” выполнять удаленную антивирусную проверку навигатором Netscape Navigator.
Теперь мы можем перейти к описанию расширения ISAPI fileupl.dll, выполняющего удаленный прием файлов.
Исходный текст документа HTML, содержащий форму для приема файлов и органы управления для ввода другой информации, показан в листинге 8.7.
Листинг 8.7. Файл chap8\fileupl\fileupl.htm
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<HTML>
<HEAD>
<TITLE>File Upload via HTTP</TITLE>
</HEAD>
<BODY BGCOLOR=#FFFFFF>
<FORM ENCTYPE="multipart/form-data" METHOD=POST ACTION="http://frolov/scripts/fileupl.dll">
<TABLE>
<TR>
<TD VALIGN=TOP>Text field TEXT</TD>
<TD><INPUT TYPE=text NAME="text1" VALUE="Sample of text1" SIZE=30></TD>
</TR>
<TR>
<TD VALIGN=TOP>Text field PASSWORD</TD>
<TD><INPUT TYPE=password NAME="pwd" VALUE="Sample of password"></TD>
</TR>
<TR>
<TD VALIGN=TOP> Text field TEXTAREA</TD>
<TD><TEXTAREA NAME="text2" ROWS=4 COLS=30>Sample of text</TEXTAREA></TD>
</TR>
<TR>
<TD VALIGN=TOP>Switches CHECKBOX</TD>
<TD>
<INPUT TYPE=CHECKBOX NAME="chk1" VALUE="on" CHECKED>One<BR>
<INPUT TYPE=CHECKBOX NAME="chk2" VALUE="on">Two<BR>
<INPUT TYPE=CHECKBOX NAME="chk3" VALUE="on" CHECKED>Nothing<BR>
</TD>
</TR>
<TR>
<TD VALIGN=TOP>Action:</TD>
<TD>
<INPUT TYPE=RADIO NAME="rad" VALUE="on1" CHECKED>Virus Checking<BR>
<INPUT TYPE=RADIO NAME="rad" VALUE="on2">Spell Checking<BR>
<INPUT TYPE=RADIO NAME="rad" VALUE="on3">Translate<BR>
</TD>
</TR>
<TR>
<TD VALIGN=TOP>Select Uploaded File:</TD>
<TD><INPUT TYPE=FILE NAME="fupload"></TD>
</TR>
<TR>
<TD VALIGN=TOP>List</TD>
<TD>
<SELECT NAME="sel" SIZE="1">
<OPTION Value="First Option">First Option</OPTION>
<OPTION Value="Second Option">Second Option</OPTION>
<OPTION Value="None">None Selected</OPTION>
</SELECT>
</TD>
</TR>
<TR>
<TD VALIGN=TOP>Hidden Control</TD>
<TD><INPUT TYPE=HIDDEN NAME="hid" VALUE="Hidden"></TD>
</TR>
</TABLE>
<BR><INPUT TYPE=submit VALUE="Send">
<INPUT TYPE=reset VALUE="Reset">
</FORM>
</BODY>
</HTML>
Здесь вам нужно обратить внимание на параметры оператора <FORM>, с помощью которого в документе HTML создается форма:
<FORM ENCTYPE="multipart/form-data" METHOD=POST ACTION="http://frolov/scripts/fileupl.dll">
Параметр ENCTYPE задает тип кодировки передаваемых данных как multipart/form-data. Метод передачи данных указан как POST, а в параметре ACTION находится адрес URL файла библиотеки DLL нашего расширения ISAPI.
Орган управления, предназначенный для выбора локального файла, создается оператором <INPUT> следующим образом:
<TR>
<TD VALIGN=TOP>Select Uploaded File:</TD>
<TD><INPUT TYPE=FILE NAME="fupload"></TD>
</TR>
Здесь указан тип поля FILE и имя поля fupload.
Внешний вид формы, содержащий орган управления для выбора файла, показан на рис. 8.5.
Рис. 8.5. Форма, позволяющая выбирать файл для передачи серверу WWW
На этом рисунке в поле Select Uploaded File уже выбран файл C:\UT\800.COM. Если нажать на кнопку Browse, на экране появится диалоговая панель File Upload, показанная на рис. 8.6.
Рис. 8.6. Диалоговая панель File Upload, с помощью которой можно выбрать файл для передачи серверу WWW
Теперь если выбрать файл и нажать кнопку Send, файл и данные из других полей формы будут переданы расширению ISAPI fileupl.dll. Расширение запишет принятые данные без какой-либо обработки в файл и возвратит пользователю сообщение (в виде динамически созданного документа HTML) об успешном завершении пересылки файла, показанное на рис. 8.7.
Рис. 8.7. Сообщение об успешном завершении передача файла
Рассмотрим исходные тексты расширения, приведенные в листинге 8.8.
Листинг 8.8. Файл chap8\fileupl\fileupl.c
// ===============================================
// Расширение ISAPI fileupl.c
// Загрузка файла из локального компьютера
// на диск удаленного сервера WWW
// из документа HTML
//
// (C) Фролов А.В., 1997
// E-mail: frolov@glas.apc.org
// WWW: http://www.glasnet.ru/~frolov
// или
// http://www.dials.ccas.ru/frolov
// ===============================================
#include <windows.h>
#include <httpext.h>
// Прототипы функций, определенных в приложении
LPVOID ReadClientMIME(EXTENSION_CONTROL_BLOCK *lpECB,
int *nStatus);
BOOL GetMIMEBoundary(LPVOID lpDataMIME, LPSTR lpBuffer,
DWORD dwBufSize);
// =============================================================
// Функция GetExtensionVersion
// Запись версии интерфейса ISAPI и
// строки описания расширения
// =============================================================
BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO *pVer)
{
// Записываем версию интерфейса ISAPI
pVer->dwExtensionVersion =
MAKELONG(HSE_VERSION_MINOR,HSE_VERSION_MAJOR );
// Записываем строку описания расширения
lstrcpyn(pVer->lpszExtensionDesc,
"Remote File Upload", HSE_MAX_EXT_DLL_NAME_LEN);
return TRUE;
}
// =============================================================
// Функция HttpExtensionProc
// =============================================================
DWORD WINAPI HttpExtensionProc(EXTENSION_CONTROL_BLOCK *lpECB)
{
CHAR szBuff[4096];
HANDLE hOutFile;
DWORD dwWritten;
LPVOID lpDataMIME;
int nStatus;
// Нулевой код состояния - признак успешного выполнения
lpECB->dwHttpStatusCode = 0;
// Получаем данные от навигатора в кодировке MIME
lpDataMIME = ReadClientMIME(lpECB, &nStatus);
if(lpDataMIME != NULL)
{
// Создаем файл для записи принятых данных
hOutFile = CreateFile("e:\\InetPub\\scripts\\uploaded.dat",
GENERIC_WRITE, FILE_SHARE_READ, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if(hOutFile != INVALID_HANDLE_VALUE)
{
// Выполняем запись данных в файл
WriteFile(hOutFile, lpDataMIME,
lpECB->cbTotalBytes, &dwWritten, NULL);
// Закрываем файл
CloseHandle(hOutFile);
}
// Освобождаем память, заказанную для принятых
// данных функцией ReadClientMIME
LocalFree(lpDataMIME);
}
// Создаем документ HTML с сообщением об
// успешной загрузке файла
wsprintf(szBuff,
"Content-Type: text/html\r\n\r\n"
"<HTML><HEAD><TITLE>Remote File Upload</TITLE></HEAD>\n"
"<BODY BGCOLOR=#FFFFFF><H1>Make your file upload!</H1>\n"
"<HR>\n");
strcat(szBuff, "<P>Upload finished");
strcat(szBuff, "</BODY></HTML>");
// Отправляем созданный документ HTML
if(!lpECB->ServerSupportFunction(lpECB->ConnID,
HSE_REQ_SEND_RESPONSE_HEADER, NULL, NULL, (LPDWORD)szBuff))
{
return HSE_STATUS_ERROR;
}
lpECB->dwHttpStatusCode = 200;
return HSE_STATUS_SUCCESS;
}
// =============================================================
// Функция ReadClientMIME
// Получение данных в кодировке MIME
// =============================================================
LPVOID ReadClientMIME(
EXTENSION_CONTROL_BLOCK *lpECB,
int *nStatus)
{
DWORD cbReaded;
DWORD nBufferPos;
DWORD nBytesToCopy;
LPVOID lpTemp = NULL;
// Код завершения
*nStatus = 0;
// Определяем, есть ли данные для чтения
if(lpECB->cbTotalBytes != 0)
{
// Заказываем буфер памяти для чтения принятых данных
if(!(lpTemp = (LPVOID)LocalAlloc(LPTR,
lpECB->cbTotalBytes)))
{
// Если памяти не хватает, завершаем работу
// с установкой кода ошибки и возвращаем
// значение NULL
*nStatus = HSE_STATUS_ERROR;
return NULL;
}
// Копируем в буфер предварительно считанные данные
memcpy(lpTemp, lpECB->lpbData, lpECB->cbAvailable);
// Устанавливаем указатель текущей позиции
// в буфере после скопированных данных
nBufferPos = lpECB->cbAvailable;
// Определяем, сколько данных нужно считать
// дополнительно с помощью функции ReadClient
nBytesToCopy = lpECB->cbTotalBytes - lpECB->cbAvailable;
// Если не все данные находятся в буфере предварительного
// чтения, запускаем цикл копирования оставшихся данных
if(nBytesToCopy > 0)
{
while(1)
{
// Читаем очередную порцию данных в текущую
// позицию буфера
lpECB->ReadClient(lpECB->ConnID,
(LPVOID)((LPSTR)lpTemp + nBufferPos), &cbReaded);
// Уменьшаем содержимое переменной nBytesToCopy,
// в которой находится размер непрочитанных данных
nBytesToCopy -= cbReaded;
// Продвигаем указатель текущей позиции в буфере
// на количество прочитанных байт данных
nBufferPos += cbReaded;
// Когда копировать больше нечего, прерываем цикл
if(nBytesToCopy <= 0l)
break;
}
}
// В случае успешного копирования возвращаем
// адрес буфера с прочитанными данными
return lpTemp;
}
// Если данных для чтения нет, завершаем
// работу с установкой кода ошибки
else
{
*nStatus = HSE_STATUS_ERROR;
// В случае ошибки вместо адреса буфера
// с прочитанными данными возвращается
// значение NULL
return NULL;
}
}
// =============================================================
// Функция GetMIMEBoundary
// Поиск разделителя в буфере.
// Параметры:
// lpDataMIME - адрес буфера с данными MIME
// lpBuffer - адрес буфера для записи разделителя
// dwBufSize - размер буфера с данными MIME
// =============================================================
BOOL GetMIMEBoundary(LPVOID lpDataMIME, LPSTR lpBuffer, DWORD dwBufSize)
{
LPSTR lpCurrent;
DWORD dwOffset;
BOOL fFound;
// Устанавливаем признак успешного поиска
fFound = TRUE;
// Ищем конец первой строки
for(lpCurrent = lpDataMIME,
dwOffset = 0;;lpCurrent++, dwOffset++)
{
// Сравниваем с концом строки
if(!memcmp(lpCurrent,"\r\n",2))
break;
// Если достигнут конец буфера,
// сбрасываем признак успешного поиска
// и прерываем работу цикла
if(dwOffset >= dwBufSize)
{
fFound = FALSE;
break;
}
// Копируем очередной символ разделителя
*(lpBuffer + dwOffset) = *lpCurrent;
}
// Если разделитель найден, закрываем строку
// разделителя двоичным нулем
if(fFound)
*lpBuffer = '\0';
// Возвращаем признак успешного или
// неуспешного поиска
return fFound;
}
Файл определения модуля для библиотеки DLL представлен в листинге 8.9.
Листинг 8.9. Файл chap8\fileupl\fileupl.def
LIBRARY fileupl
DESCRIPTION 'File Upload DLL'
EXPORTS
GetExtensionVersion
HttpExtensionProc
Рассмотрим функции нашего расширения ISAPI.
Функция GetExtensionVersion не имеет никаких особенностей.
Функция HttpExtensionProc в начале своей работы вызывает функцию ReadClientMIME, определенную в нашем приложении. Эта функция заказывает динамически блок памяти, достаточный для размещения принимаемых от удаленного пользователя данных, записывает в этот блок принятые данные и возвращает указатель на заказанный блок памяти. После использования вы должны освободить блок памяти функцией LocalFree.
Если данные были приняты успешно, функция HttpExtensionProc создает файл, в который будут записаны принятые данные:
hOutFile = CreateFile("e:\\InetPub\\scripts\\uploaded.dat",
GENERIC_WRITE, FILE_SHARE_READ, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
Для упрощения исходного текста мы указали путь к файлу непосредственно в файле исходных текстов. Ваше приложение может хранить путь к файлу в регистрационной базе данных или в отдельном файле параметров.
Для создания файла мы использовали функцию CreateFile, описанную нами в 26 томе “Библиотеки системного программиста”, который называется “Программирование для Windows NT. Часть 1”. Примеры использования этой и других функций, предназначенных для работы с файлами, вы можете найти в 27 томе этой же серии, который называется “Программирование для Windows NT. Часть 2”.
Запись принятых данных в файл выполняется за один вызов функции WriteFile:
WriteFile(hOutFile, lpDataMIME,
lpECB->cbTotalBytes, &dwWritten, NULL);
В качестве размера блока данных здесь указывается содержимое поля cbTotalBytes структуры типа EXTENSION_CONTROL_BLOCK. После выполнения записи файл закрывается функцией CloseHandle. Блок памяти, полученный от функции ReadClientMIME, освобождается при помощи функции LocalFree.
Далее расширение создает документ HTML в буфере szBuff и посылает его удаленному пользователю при помощи функции ServerSupportFunction с кодом операции HSE_REQ_SEND_RESPONSE_HEADER.
Теперь займемся функцией ReadClientMIME.
В качестве первого параметра эта функция получает указатель на блок EXTENSION_CONTROL_BLOCK, передаваемый функции HttpExtensionProc через единственный параметр. Второй параметр nStatus используется для передачи результата работы функции ReadClientMIME вызывающей программе.
В самом начале своей работы функция ReadClientMIME анализирует содержимое поля cbTotalBytes структуры EXTENSION_CONTROL_BLOCK, в котором находится размер принимаемых данных. Если данных для чтения нет, функция ReadClientMIME передает код ошибки HSE_STATUS_ERROR и возвращает вызывающей программе значение NULL.
Если все нормально и данные для чтения есть, функция ReadClientMIME с помощью функции LocalAlloc заказывает блок памяти размером lpECB?>cbTotalBytes байт. Описание этой функции вы найдете в только что упомянутом 26 томе “Библиотеки системного программиста”.
После этого начинается процесс копирования данных в полученный буфер.
Вначале функция ReadClientMIME копирует блок данных из буфера предварительного чтения, вызывая для этого функцию memcpy:
memcpy(lpTemp, lpECB->lpbData, lpECB->cbAvailable);
Напомним, что размер этого буфера не превышает 48 Кбайт. Буфер предварительного чтения располагается по адресу lpECB->lpbData и в нем доступно для чтения lpECB->cbAvailable байт данных.
Так как размеры передаваемого файла могут легко превысить предел 48 Кбайт, необходимо организовать цикл чтения остальных данных с помощью функции ReadClient.
Перед запуском такого цикла мы устанавливаем указатель текущей позиции в буфере lpTemp на конец блока данных, скопированных из буфера предварительного чтения:
nBufferPos = lpECB->cbAvailable;
Далее мы определяем размер данных, которые не поместились в буфере предварительного чтения и которые нужно дочитать дополнительно. Очевидно, для этого нужно вычесть из полного размера данных размер данных, записанных в буфер предварительного чтения:
nBytesToCopy = lpECB->cbTotalBytes - lpECB->cbAvailable;
После проверки значения nBytesToCopy (оно должно быть больше нуля) мы запускаем цикл чтения дополнительных данных:
while(1)
{
// Читаем очередную порцию данных в текущую
// позицию буфера
lpECB->ReadClient(lpECB->ConnID,
(LPVOID)((LPSTR)lpTemp + nBufferPos), &cbReaded);
// Уменьшаем содержимое переменной nBytesToCopy,
// в которой находится размер непрочитанных данных
nBytesToCopy -= cbReaded;
// Продвигаем указатель текущей позиции в буфере
// на количество прочитанных байт данных
nBufferPos += cbReaded;
// Когда копировать больше нечего, прерываем цикл
if(nBytesToCopy <= 0l)
break;
}
В этом цикле вызывается функция ReadClient, причем в качестве адреса, по которому она должна записать принятые данные, ей указывается адрес буфера lpTemp со смещением на текущую позицию nBufferPos. При первом проходе цикла это смещение соответствует концу области данных, скопированных из буфера предварительного чтения.
Функция ReadClient не обязательно прочитает за один прием все входные данные. Размер прочитанного ей блока данных записывается в переменную cbReaded.
Далее в цикле уменьшается содержимое переменной nBytesToCopy, хранящей количество еще не прочитанных данных. После этого указатель текущей позиции nBufferPos в буфере lpTemp продвигается вперед на количество прочитанных байт cbReaded.
Условием завершения цикла является уменьшение значения nBytesToCopy до нуля. Это произойдет, когда все данные будут приняты.
Теперь о функции GetMIMEBoundary, которая определена в файле исходных текстов, но нигде не используется. Эта функция предназначена для получения строки разделителя блоков принятого файла.
Как мы уже говорили, принятый файл имеет формат MIME. Полное описание этого формата вы найдете в документах RFC2045, RFC2046, RFC2047, RFC2048 и RFC2049. Первый из этих документов называется “Multipurpose Internet Mail Extensions”. Однако для работы с принятым файлом вы можете обойтись без полной спецификации MIME.
Ниже мы привели в сокращенном виде содержимое файла e:\\InetPub\\scripts\\uploaded.dat после приема файла известного драйвера 800.com, предназначенного для работы с нестандартными форматами дискет. Помимо этого файла в принятых данных есть содержимое всех полей формы:
-----------------------------264872619131689
Content-Disposition: form-data; name="text1"
Sample of text1
-----------------------------264872619131689
Content-Disposition: form-data; name="pwd"
Sample of password
-----------------------------264872619131689
Content-Disposition: form-data; name="text2"
Sample of text
-----------------------------264872619131689
Content-Disposition: form-data; name="chk1"
on
-----------------------------264872619131689
Content-Disposition: form-data; name="chk3"
on
-----------------------------264872619131689
Content-Disposition: form-data; name="rad"
on1
-----------------------------264872619131689
Content-Disposition: form-data; name="fupload"; filename="C:\UT\800.com"
йtf
*“*t*—*—_
Ђьv_Ђъv_ъ.я.__V3цЋЮ‹тѓжЂь_sd_SRД_x
‹ыѕI№
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Здесь располагаются двоичные данные принятого файла.
Мы сократили листинг, выкинув из него часть двоичных данных
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
ђЗ___ffЗ___ !&Ђ>__Tt____ѕ5cї__№_
юЛЃщ 't_юЛГґ_І
. Vc&ўW°й‹
_ЊИ-
-----------------------------264872619131689
Content-Disposition: form-data; name="sel"
First Option
-----------------------------264872619131689
Content-Disposition: form-data; name="hid"
Hidden
-----------------------------264872619131689—
Как видите, формат этого файла относительно несложен. Он состоит из отдельных секций, разделенных текстовой строкой. Строка состоит из символов “-“ и числа, которое каждый раз изменяется. При посылке файла навигатор сам выбирает разделитель. При помощи функции GetMIMEBoundary ваша программа может скопировать разделитель в отдельный буфер и использовать его при поиске нужной секции в принятых данных.
Каков дальнейший алгоритм получения данных из полей формы, а также принятого файла?
Он несложен и вы сможете реализовать его самостоятельно. Сканируя секции с использованием разделителя, вы можете в каждой секции искать строку “name=<ИмяПоля>”, где ИмяПоля - это имя поля, данные из которого вам нужно получить. Для сканирования лучше не пользоваться функцией strstr, так как она рассчитана только на символьные данные, а в секциях, содержащих файлы, присутствуют двоичные данные. Найдя нужное вам поле, вы можете извлечь его содержимое и записать его в память или файл на диске.
Сделаем еще одно замечание, касающееся мультизадачного режима работы расширений ISAPI.
Так как для повышения производительности расширение ISAPI загружается в адресное пространство сервера Microsoft Information Server в единственном экземпляре, оно работает в мультизадачном режиме. Это означает, что при обращении к критичным ресурсам вы должны использовать средства синхронизации задач, описанные нами в 26 томе “Библиотеки системного программиста”.
В частности, расширение fileupl.dll выполняет запись в файл, а эта операция является критической, так как всем пользователям предлагается записывать свои данные только в один файл. Чтобы избежать взаимных коллизий, можно предложить простейшее средство синхронизации - критические секции.
Перед началом записи в файл расширение ISAPI должно войти в критическую секцию, а после использования - выйти из нее. В этом случае пользователи будут работать с файлом по очереди. Для работы с критическими секциями предназначены функции InitializeCriticalSection, EnterCriticalSection, LeaveCriticalSection, DeleteCriticalSection.
Критическая секция должна быть создана в момент инициализации библиотеки DLL расширения ISAPI, поэтому вызов функции InitializeCriticalSection должен быть размещен в функции DllMain. Удаление критической секции можно выполнить в обработчике функции TerminateExtension, которая вызывается перед удалением расширения из адресного пространства сервера WWW.
Подробную информацию о работе с библиотеками DLL в среде операционной системы Microsoft Windows NT вы найдете в 27 томе “Библиотеки системного программиста”.