А.В. Фролов, Г.В. Фролов


Окончание. Начало # 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; }