Окончание. Начало # 3 1999 г. c. 78
Исходные тексты программы FUPLOAD
Исходные тексты программы FUPLOAD расположены в трех файлах: fileupl.c, parse.c и process.c. Файл fileupl.c представлен (с сокращениями) в листинге 2. Более полный вариант можно найти на сервере www.pcworld.ru.
Функция GetExtensionVersion реализована стандартным для приложений ISAPI образом и не представляет особого интереса. Что же касается функции HttpExtensionProc, то именно она получает управление, когда посетитель отправляет заполненную форму нашему расширению сервера Web.
Первым делом функция HttpExtensionProc вызывает функцию readClientData, принимающую данные от браузера. Эти данные сохраняются в буфере с адресом lpData. Если данные приняты успешно, функция HttpExtensionProc получает имя временного файла, вызывая для этого функцию GetTempFileName. Заметим, что эта операция выполняется внутри критической секции, так как к нашему расширению могут одновременно обратиться несколько пользователей.
Принятые данные, а также имя временного файла передаются функции parseData. В ее задачу входит разбор блоков данных с выделением имен и значений полей. Функция parseData формирует начальную часть документа HTML, посылаемого посетителю в ответ на отправку формы. Указанный документ готовится в буфере szBuf.
Вторая задача функции parseData — выделение из принятых данных файла, заданного посетителем в форме, и его сохранение во временном файле. Последнее действие функции HttpExtensionProc — вызов функции processData, ей передается путь к временному файлу и адрес буфера, в который следует записать результат обработки файла. Функция processData записывает этот результат как финальную часть документа HTML.
Сформированный документ HTML отправляется пользователю методом replyOK.
Листинг файла fileupl.c снабжен комментариями и в дополнительном описании не нуждается.
Определенные затруднения может вызвать реализация процедуры разбора данных, присланных браузером. Соответствующие функции определены в файле parse.c. (см. листинг 3).
Для сокращения листинга функции parseData, выполняющей основную работу по сканированию принятых данных, мы подготовили набор макрокоманд.
Макрокоманда IS_BOUNDARY проверяет, не установлен ли указатель lpCurrent (перемещающийся в процессе сканирования по буферу принятых данных) на разделитель блоков. В качестве разделителя берется первая строка принятых данных, выделяемая функцией GetMIMEBoundary.
Макрокоманда CHECK_DATA_END вызывается в процессе сканирования для проверки достижения конца буфера с принятыми данными.
С помощью макрокоманды FIND_BOUNDARY мы реализовали поиск разделителя блоков. Макрокоманда FIND_FIELD_NAME предназначена для поиска строки «name=» в заголовке блока, а FIND_ HEADER_END — для поиска конца заголовка блока.
При помощи макрокоманды GET_FIELD_NAME мы извлекаем имя поля, а при помощи макрокоманды GET_FIELD_DATA — данные, записанные пользователем в поле формы.
Макрокоманды GET_FILE_NAME и GET_FILE используются, соответственно, для получения пути к файлу, отправленному пользователем, и для извлечения данных этого файла с записью во временный файл.
Остановимся подробнее на работе функции parseData.
Прежде всего эта функция вызывает
функцию GetMIMEBoundary.
Выделенный с ее помощью
разделитель
записывается в массив szBoundary.
Дополнительно мы формируем еще один массив — szFileBoundary,
который
используется при поиске конца файла в
принятых от
посетителя данных.
Далее функция parseData формирует в буфере szBuf начальный фрагмент документа HTML, отправляемого посетителю в ответ на присланную форму. В процессе подготовки этого документа мы пользуемся временным буфером szBuf1.
Сканирование принятых от браузера данных выполняется в цикле. При этом адрес начала буфера находится в указателе lpUploadedData, а адрес текущей позиции — в указателе lpCurrent. Переменную i мы используем для подсчета числа байтов обработанных данных.
Цикл сканирования начинает свою работу с поиска разделителя, имени поля и копирования этого имени в массив szFieldName. Полученное имя поля записывается в выходной документ HTML. Далее мы проверяем, относится ли текущий блок к файлу. Если в заголовке блока есть строка ?filename=?, мы считаем, что блок содержит файл. В этом случае функция parseData копирует имя файла макрокомандой GET_FILE_NAME в массив szFieldFileName и записывает его в выходной документ HTML. Вслед за этим функция parseData находит конец заголовка блока и копирует данные во временный файл. Обычные поля формы обрабатываются макрокомандой GET_FIELD_DATA. Их содержимое копируется в выходной документ HTML. Завершив сканирование, функция parseData возвращает указатель на буфер с указанным документом.
Теперь немного о функции processData, исходный текст которой приведен в листинге 4. Эта функция получает управление, когда данные, переданные посетителем, уже приняты и разобраны, а содержимое переданного файла скопировано во временный файл.
Изменив функцию processData, вы можете выполнить произвольную обработку полученного файла. После обработки файл следует удалить функцией DeleteFile, как это сделано в листинге 4. Результат обработки файла функция processData записывает в буфер szBuf как завершающий фрагмент документа HTML.
ОБ АВТОРАХ
Братья Александр Вячеславович и Григорий Вячеславович Фроловы — авторы серий книг «Библиотека системного программиста» и «Персональный компьютер. Шаг за шагом». E-mail: frolov@glasnet.ru; Web: http://www.glasnet.ru/~frolovЛистинг 2
Получение и обработка данных (файл fileupl.c)
#include #include // =============================================== // Прототипы функций, определенных в приложении // =============================================== . . . // =============================================== // Функция GetExtensionVersion // =============================================== BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO *pVer) { . . . } // =============================================== // Функция HttpExtensionProc // =============================================== DWORD WINAPI HttpExtensionProc(EXTENSION_CONTROL_BLOCK *lpECB) { LPVOID lpData; int nStatus = 0; CHAR szFileName[512]; CHAR szBuf[4096]; CHAR szBuf1[4096]; CRITICAL_SECTION csGetTempFilename; // Получаем данные от браузера lpData = readClientData(lpECB, &nStatus); if(lpData != NULL && nStatus == 0) { InitializeCriticalSection(&csGetTempFilename); EnterCriticalSection(&csGetTempFilename); GetTempFileName("c:upload", "$up", 0, szFileName); LeaveCriticalSection(&csGetTempFilename); DeleteCriticalSection(&csGetTempFilename); parseData(lpData, lpECB->cbTotalBytes, szBuf, szFileName); processData(szFileName, szBuf1); strcat(szBuf, szBuf1); return replyOK(lpECB, szBuf); } else return replyError(lpECB); } // =============================================== // Функция readClientData // Получение данных от браузера // =============================================== LPVOID readClientData( EXTENSION_CONTROL_BLOCK *lpECB, int *nStatus) { DWORD cbReaded; DWORD nBufferPos; DWORD nBytesToCopy; LPVOID lpTemp = NULL; // Код завершения *nStatus = 0; // Нулевой код состояния - признак // успешного выполнения lpECB->dwHttpStatusCode = 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; cbReaded = nBytesToCopy; // Если не все данные находятся в буфере // предварительного чтения, запускаем цикл // копирования оставшихся данных if(nBytesToCopy > 0) { while(1) { // Читаем очередную порцию данных в текущую // позицию буфера if(!lpECB->ReadClient(lpECB->ConnID, (LPVOID)((LPSTR)lpTemp + nBufferPos), &cbReaded)) { DWORD rc = GetLastError(); traceUpload("ReadClient Error", rc); *nStatus = HSE_STATUS_ERROR; return NULL; break; } // Уменьшаем содержимое переменной // nBytesToCopy, в которой находится // размер непрочитанных данных nBytesToCopy -= cbReaded; // Продвигаем указатель текущей // позиции в буфере на количество // прочитанных байт данных nBufferPos += cbReaded; // Когда копировать больше нечего, // прерываем цикл if(nBytesToCopy <= 0l) break; } } // В случае успешного копирования возвращаем // адрес буфера с прочитанными данными return lpTemp; } // Если данных для чтения нет, завершаем // работу с установкой кода ошибки else { *nStatus = HSE_STATUS_ERROR; // В случае ошибки вместо адреса буфера // с прочитанными данными возвращается // значение NULL return NULL; } } // =============================================== // replyOK // =============================================== DWORD replyOK(EXTENSION_CONTROL_BLOCK *lpECB, LPSTR szBuf) { // Отправляем созданный документ HTML if(!lpECB->ServerSupportFunction(lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, NULL, NULL, (LPDWORD)szBuf)) { return HSE_STATUS_ERROR; } lpECB->dwHttpStatusCode = 200; return HSE_STATUS_SUCCESS; } // =============================================== // replyError // Отправка сообщения об ошибке // =============================================== DWORD replyError(EXTENSION_CONTROL_BLOCK *lpECB) { . . . }
Листинг 3
Разбор данных (файл parse.c)
#include #include #include #define IS_BOUNDARY (memcmp(lpCurrent, szBoundary, strlen(szBoundary)) == 0) #define CHECK_DATA_END if(i >= dwDataSize) { bEndOfFile = TRUE; break; } #define FIND_BOUNDARY for(;; i++, lpCurrent++) { CHECK_DATA_END if(IS_BOUNDARY) break; } if(bEndOfFile) break; #define FIND_FIELD_NAME for(;; i++, lpCurrent++) { CHECK_DATA_END if(memcmp(lpCurrent, ?name=?, 5) == 0) break; } if(bEndOfFile) break; lpCurrent += 6; i += 6; #define FIND_HEADER_END for(;; i++, lpCurrent++) { CHECK_DATA_END if(memcmp(lpCurrent, ? ?, 4) == 0) break; } if(bEndOfFile) break; lpCurrent += 4; i += 4; #define GET_FIELD_NAME for(j=0;; j++, i++, lpCurrent++) { CHECK_DATA_END szFieldName[j] = *lpCurrent; if(*lpCurrent == ???) break; } if(bEndOfFile) break; szFieldName[j] = ? ?; #define GET_FILE_NAME for(j=0;; j++, i++, lpCurrent++) { CHECK_DATA_END szFieldFileName[j] = *lpCurrent; if(*lpCurrent == ???) break; } if(bEndOfFile) break; szFieldFileName[j] = ? ?; #define GET_FIELD_DATA for(j=0;; j++, i++, lpCurrent++) { CHECK_DATA_END szFieldValue[j] = *lpCurrent; if(IS_BOUNDARY) break; } if(bEndOfFile) break; szFieldValue[j] = ? ?; #define GET_FILE downloaded = fopen(szOutFile, ?wb?); while(TRUE) { if(memcmp(lpCurrent, szFileBoundary, strlen(szFileBoundary)) == 0) break; fputc(*lpCurrent, downloaded); lpCurrent++; i++; CHECK_DATA_END } fclose(downloaded); // ==================================================== // Функция 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,? ?,2)) break; // Если достигнут конец буфера, // сбрасываем признак успешного поиска // и прерываем работу цикла if(dwOffset >= dwBufSize) { fFound = FALSE; break; } // Копируем очередной символ разделителя *(lpBuffer + dwOffset) = *lpCurrent; } // Если разделитель найден, закрываем строку // разделителя двоичным нулем if(fFound) *(lpBuffer + dwOffset)= ? ?; // Возвращаем признак успешного или // неуспешного поиска return fFound; } // ==================================================== // Функция parseData // ==================================================== LPSTR parseData(LPVOID lpUploadedData, DWORD dwDataSize, LPSTR szBuf, LPSTR szOutFile) { FILE *downloaded; CHAR szBuf1[1024]; CHAR szBoundary[256]; CHAR szFileBoundary[256]; CHAR szFieldName[256]; CHAR szFieldValue[4096]; CHAR szFieldFileName[512]; LPSTR lpCurrent; DWORD i, j; BOOL bEndOfFile = FALSE; if(!GetMIMEBoundary(lpUploadedData, szBoundary, dwDataSize)) return NULL; strcpy(szFileBoundary, ? ?); strcat(szFileBoundary, szBoundary); strcpy(szBuf, ?Content-type: text/html ? ? ? ?? ?? ?
Form processing results
?); wsprintf(szBuf1, ?Boundary: %s
?,
szBoundary);
strcat(szBuf, szBuf1);
lpCurrent = lpUploadedData;
i = 0;
while(TRUE)
{
FIND_BOUNDARY
FIND_FIELD_NAME
GET_FIELD_NAME
wsprintf(szBuf1, ?
name: %s?,
szFieldName);
strcat(szBuf, szBuf1);
lpCurrent++;
i++;
// Проверяем, есть ли имя файла
if(memcmp(lpCurrent, ?; filename=?, 10) == 0)
{
lpCurrent += 12;
// Копируем имя файла
GET_FILE_NAME
wsprintf(szBuf1, ?, filename: %s%?,
szFieldFileName);
strcat(szBuf, szBuf1);
FIND_HEADER_END
GET_FILE
wsprintf(szBuf1, ?, saved as: %s%
?,
szOutFile);
strcat(szBuf, szBuf1);
}
// Обычное поле. Получаем данные
else
{
lpCurrent += 4;
GET_FIELD_DATA
wsprintf(szBuf1, ?, value: %s%
?,
szFieldValue);
strcat(szBuf, szBuf1);
}
if(i > dwDataSize)
break;
}
return szBuf;
}
Листинг 4
Шаблон функции для обработки данных (файл process.c)
#include // ==================================================== // Функция processData // ==================================================== LPSTR processData(LPSTR szFileName, LPSTR szBuf) { wsprintf(szBuf, ?
Processing file: %s ?, szFileName); // . . . // Добавьте ваш код для обработки загруженного // файла на сервере Web // . . . // DeleteFile(szFileName); strcat(szBuf, ?
?); return szBuf; }