【问题标题】:How to change STDIN stream to binary如何将 STDIN 流更改为二进制
【发布时间】: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”作为最后一个字符或作为中间字符 上述任何两个字符串中的字符。 这完全是为了与 C8​​9 兼容,没有任何效果; '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


【解决方案1】:

好吧,看来@NominalAnimal 说的是正确的。您可以将二进制数据存储在字符串中,但是当您使用 string.h 库中的任何函数时,它几乎总是会更改存储在该字符串中的内容(如果数据是二进制的)。

简单的解决方案是创建一个单独的函数,该函数接受一个指向二进制数据的指针,并在该函数中进行字符串搜索,返回所需的相关信息。这样,原始数据永远不会改变。

【讨论】:

    【解决方案2】:

    'stdin' 是 STDIN_FILENO 的宏,等于 0。另见 'unistd.h'。 您没有显示您的代码,但我认为当您遇到“\0”或非 ascii 字符时您会停下来,因为您说您使用的是“fread()”。

    你必须在 fread() 函数返回 0 时停止,这意味着它停止读取:它遇到了 EOF。

    【讨论】:

    • 名称 stdin 与 POSIX 名称 STDIN_FILENO 没有真正的关系。它们都引用标准输入,但方式非常不同。
    • 我不相信会是这样。我从不测试'\0',这都是基于fread 的返回值以及FEOFFERROR 标志是否已设置。
    猜你喜欢
    • 1970-01-01
    • 2021-07-20
    • 2018-12-03
    • 1970-01-01
    • 2019-01-28
    • 1970-01-01
    • 2015-08-10
    • 2010-11-29
    • 2016-06-26
    相关资源
    最近更新 更多