【发布时间】:2017-04-15 16:46:14
【问题描述】:
我正在通过 CGI 界面制作上传表单。我正在用 C 编写它,不想使用任何外部库(即 cgic)。
我认为程序已经完成,因为第一个测试文件正确上传。但它们是 ASCII 文件。当我使用二进制文件(JPG)进行测试时。似乎 STDIN 正试图将二进制数据作为 ASCII 读取,这对诸如 \0 之类的字符产生了问题,该字符出现在 ASCII 文件的末尾,但它是二进制文件中的常见字符。上传 1.9MB 文件的结果是 38kB 文件。
在搜索如何将 STDIN 流更改为二进制时,我参考了命令 freopen 并告诉我使用 NULL 作为文件的参数。 example 1
上面写着:
如果文件名是空指针,freopen() 函数将尝试 将流的模式更改为模式指定的模式,就好像 当前与流关联的文件的名称已被使用。 在这种情况下,与流关联的文件描述符不需要 如果对 freopen() 的调用成功,则关闭。这是 实现定义允许更改哪些模式(如果有), 以及在什么情况下。
但是当我使用man 3 freopen 查看系统上的手册页时,它并没有说
这一点都没有。此外,阅读手册页,我发现
二进制选项(将“b”添加到模式)不再被识别,并且
只为过时的合规性而存在:
模式字符串还可以包括 字母“b”作为最后一个字符或作为中间字符 上述任何两个字符串中的字符。 这完全是为了与 C89 兼容,没有任何效果; 'b' 在所有符合 POSIX 的系统上都被忽略,包括 Linux。
所以现在我完全迷路了。如何更改 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");
}
【问题讨论】:
-
这不是流,而是你的代码。 Linux 和其他 POSIXy 系统中的所有文件句柄始终是“二进制”;他们根本不按摩流内容。您应该使用
fread()来读取 POST 数据,而不是fgets()或getline()或getdelim(),因为 POST 数据本质上是二进制的,而不是文本行。 -
@NominalAnimal 我正在使用
fread来读取 STDIN。 -
您是否也使用
fwrite来写入反转义(如果是 MIME 类型 application/x-www-form-urlencoded)或分隔(如果是 MIME 类型 multipart/form-data)数据到你的输出文件?你看,我在 POSIXy 系统上使用的 CGI 实现都没有任何特定字符的问题。问题肯定出在您的代码中(您选择不显示),而不是在任何库函数中。 -
@NominalAnimal 是的,我使用 fwrite 将数据写入文件。我已经在上面添加了我的代码。
-
您将
fread()数据视为字符串(通过使用strstr()、strlen()等)。这是行不通的,因为他们遇到的第一个 nul 字符 ('\0') 认为数据的结尾。 数据不是字符串。这就是为什么您只处理 POST 数据块的初始部分。
标签: c linux cgi stdin binary-data