2017-04-15 36 views
1

我正在通過CGI界面進行上傳表單。我用C寫它,不想使用任何外部庫(即cgic)。如何將STDIN流更改爲二進制

我認爲該程序已完成,因爲第一個測試文件正確上傳。但他們是ASCII文件。當我用二進制文件(JPG)測試時。看來STDIN試圖讀取二進制數據作爲ASCII碼,這爲ASCII碼文件末尾存在的\0等字符創建了一個問題,但它是二進制文件中的一個常見字符。上傳一個1.9MB文件的結果最終是一個38kB的文件。

當搜索如何將STDIN流更改爲二進制時,我被引用到命令freopen,並被告知使用NULL作爲該文件的參數。 example 1

它說:

如果文件名是一個空指針,調用freopen()函數將試圖 流的模式改變爲通過模式指定,如果該文件的 名目前與該流相關聯的數據已被使用。 在這種情況下,如果對freopen()的調用成功,則與該流關聯的文件描述符不需要關閉 。它是 實現定義允許哪些模式更改(如果有), 以及在什麼情況下。

但是當我檢查手冊頁我的系統上man 3 freopen,它沒有說任何的 這在所有。此外,閱讀手冊頁,我發現二進制(加「B」的模式)不再認可和 只存在於古老的合規性的的 選項:

模式字符串也可以包括 字母'b'或者作爲最後一個字符或者作爲上述任何一個雙字符字符串中的字符之間的字符 。 這是嚴格的與C89兼容並沒有任何影響; 'b' 在包括Linux在內的所有符合POSIX的系統上都被忽略。

所以現在我完全失去了。如何更改STDIN流以讀取二進制輸入?

下面是代碼:

#include <stdio.h> 
#include <stdlib.h> 
#include <libgen.h> 
#include <string.h> 
#include <sys/stat.h> 
#include <fcntl.h> 
#include <errno.h> 
#include <unistd.h> 

// Declare constants. 
#define BUF_SIZE     4096 
#define FILENAME_SIZE    500 
#define MARKER_SIZE    100 
#define RETURN_FAILURE    0 
#define RETURN_SUCCESS    1 
#define SEARCH_STRING_1    "filename=\"" 
#define SEARCH_STRING_2    "\r\n\r\n" 

// Declare global variables. 
char filename[FILENAME_SIZE + 1]; 
char *program_name; 

// Declare function prototype. 
void print_footer (void); 
void print_header (void); 
void process_input (char *data); 

int main (int argc, char *argv[]) 
{ 
// Declare variables. 
    long long ret; 
    char buf[BUF_SIZE + 1]; 

// Get program name for error reporting. 
    program_name = basename(argv[0]); 

// Prepare output for browser. 
    print_header(); 

// Protect variable against buffer overflow. 
    buf[BUF_SIZE] = '\0'; 

// Loop through all the file data. 
    while(1) 
    { 
// Read in the next block of data. 
     if((ret = (long long) fread(buf, 1, BUF_SIZE, stdin)) != BUF_SIZE) 
     { 
// Check for error. 
      if(ferror(stdin) != 0) 
      { 
       printf("%s: An error occurred while reading the input file.<br>\n", program_name); 
       process_input(NULL); 
       exit(EXIT_FAILURE); 
      } 
// Check for EOF. 
      else if(feof(stdin) != 0) 
       break; 
     } 

// Terminate and process uploaded data. 
     buf[ret] = '\0'; 
     process_input(buf); 
    } 

// Terminate and process uploaded data. 
    buf[ret] = '\0'; 
    process_input(buf); 

// Finish user output, close output file and exit. 
    print_footer(); 
    process_input(NULL); 
    exit(EXIT_SUCCESS); 
} 

void process_input (char *data) 
{ 
// Declare variables. 
    char *ptr1= NULL; 
    char *ptr2; 
    int x = 0; 
    static FILE *fp; 
    static int flag = 0; 
    static char marker[MARKER_SIZE + 1]; 

// If data is NULL, close output file. 
    if(data == NULL) 
    { 
     if(fclose(fp) == EOF) 
     { 
      printf("%s: process_input: close failed (%s)<br>\n", program_name, strerror(errno)); 
      exit(EXIT_FAILURE); 
     } 

     return; 
    } 

// Check if this is the first time through. 
    if(flag == 0) 
    { 
// Get marker. 
     if((ptr1 = strchr(data, '\n')) == NULL) 
     { 
      printf("%s: process_input: strchr(1) failed (\n)<br>\n", program_name); 
      exit(EXIT_FAILURE); 
     } 

     ptr1[0] = '\0'; 
     strcpy(marker, data); 
     ptr1[0] = '\n'; 

// Get filename. 
     if((ptr1 = strstr(data, SEARCH_STRING_1)) == NULL) 
     { 
      printf("%s: process_input: strstr(1) failed (%s)<br>\n", program_name, SEARCH_STRING_1); 
      exit(EXIT_FAILURE); 
     } 

// Advance pointer to start of filename. 
     ptr1 += 10; 

// Find end of filename. 
     if((ptr2 = strchr(ptr1, '"')) == NULL) 
     { 
      printf("%s: process_input: strchr(2) failed (\")<br>\n", program_name); 
      exit(EXIT_FAILURE); 
     } 

// Terminate and store filename. 
     ptr2[0] = '\0'; 
     strcpy(filename, ptr1); 
     ptr2[0] = '"'; 

// Remove spaces from filename. 
     while(filename[x] != '\0') 
     { 
      if(filename[x] == ' ') 
       filename[x] = '.'; 

      x++; 
     } 

// Open output file. 
     if((fp = fopen(filename, "wb")) == NULL) 
     { 
      printf("%s: process_input: fopen failed (%s) (%s)<br>\n", program_name, strerror(errno), filename); 
      exit(EXIT_FAILURE); 
     } 

// Find start of file data. 
     if((ptr1 = strstr(data, SEARCH_STRING_2)) == NULL) 
     { 
      printf("%s: process_input: strstr(2) failed (%s)<br>\n", program_name, SEARCH_STRING_2); 
      fclose(fp); 
      exit(EXIT_FAILURE); 
     } 

// Set flag. 
     flag++; 
// Advance pointer to start of file data. 
     ptr1 += 4; 

// Change STDIN stream to binary. 
     if(freopen(NULL, "rb", stdin) == NULL) 
     { 
      printf("%s: process_input: freopen failed (%s)<br>\n", program_name, strerror(errno)); 
      fclose(fp); 
      exit(EXIT_FAILURE); 
     } 
    } 
// Catch everything else. 
    else 
    { 
     ptr1 = data; 

     if((ptr2 = strstr(ptr1, marker)) != NULL) 
      ptr2[0 - 2] = '\0'; 
    } 

// Write file data. 
    if(fwrite(ptr1, 1, strlen(ptr1), fp) != strlen(ptr1)) 
    { 
     printf("%s: process_input: write failed (%s)<br>\n", program_name, strerror(errno)); 
     fclose(fp); 
     exit(EXIT_FAILURE); 
    } 
} 

void print_footer (void) 
{ 
    printf("\nMade it!\n"); 
} 

void print_header (void) 
{ 
    printf("Content-type: text/plain\r\n\r\n"); 
} 
+0

這不是流,它是你的代碼。 Linux和其他POSIXy系統中的所有文件句柄都是「二進制」的;他們根本不會按摩流內容。你應該使用'fread()'來讀取POST數據,而不是'fgets()'或'getline()'或'getdelim()',因爲POST數據本質上是二進制的,而不是文本行。 –

+0

@NominalAnimal我正在使用'fread'來讀取STDIN。 – Deanie

+0

您是否還使用'fwrite'將輸出文件中的退出(如果是MIME類型application/x-www-form-urlencoded)或分隔符(如果是MIME類型multipart/form-data)數據?你看,我在POSIXy系統上使用的CGI實現都沒有任何特定字符的問題。這個問題肯定在你的代碼中 - 你選擇不顯示 - 而不是在任何庫函數中。 –

回答

0

好吧,看起來@NominalAnimal說的是正確的。您可以將二進制數據存儲在字符串中,但在使用庫中的任何函數時,它幾乎總是會更改存儲在該字符串中的內容(如果數據是二進制的)。

簡單的解決方案是製作一個單獨的函數,它接受一個指向二進制數據的指針,並在該函數中執行字符串搜索,返回需要的相關信息。這樣,原始數據永遠不會改變。

-1

「標準輸入」是STDIN_FILENO的宏,它是埃加勒到也爲0參見「unistd.h中」。 您沒有顯示您的代碼,但我認爲當您遇到'\ 0'或非ascii字符時會停止,因爲您說您正在使用'fread()'。

當fread()函數返回0時,表示停止讀取:它遇到EOF時必須停止。

+0

名稱'stdin'與POSIX名稱STDIN_FILENO並不真正相關。他們都提到標準輸入,但方式非常不同。 –

+0

我不認爲這是事實。我從不測試''\ 0'',它都基於'fread'的返回值以及'FEOF'和'FERROR'標記是否已經設置。 – Deanie

相關問題