通过 libcurl 以二进制形式传输的文件已损坏

File transferred as binary via libcurl is corrupted

还有很多讨论二进制文件损坏的话题,但它们似乎都与我的问题无关。

我有一个 C 程序 downloads/reads 文件。

因为我没有把我得到的所有文件都写到一个文件中,所以我使用 curl 的函数而是将数据存储到一个字符串中。稍后我可以随意将此字符串写入文件,也可以不写入文件。

我有一个二进制文件。 我把它放在 FTP.

如果我通过像 filezilla 这样的 ftp 客户端下载它,它包含正确的东西(也就是说,我在 cat 我编译的二进制文件时得到的相同字符) 如果我使用 curl 命令行下载文件,它也包含正确的内容。

如果我用我的程序下载这样一个文件,它只会包含一个字符串,如 "ELF" 后跟 3 个 unwritable/unreadable 个字符。

重要的是要注意,这只发生在二进制文件中。文本文件 transferred/read 只是文件。 同样重要的是要知道从 curl 传递到我的函数的数据似乎已经是错误的:如果我将数据的 printf 放入我的写入函数中,我会看到相同的 ELF + 3 不可读的字符字符串,因此我稍后将其写入文件的方法没有问题。

当我使用 verbose 时,curl 说它处于二进制模式,但二进制文件没有正确传输。

这是我目前所拥有的,适用于任何非二进制文件,否则将永远是垃圾。提前致谢:

struct string 
{
  char *ptr;
  size_t len;
};

char *usr_psswd(char *user, char *psswd)
{
    char *usrpsswd;

    usrpsswd = (char *)malloc(strlen(user) + strlen(psswd) + 2);
    int i = 0;
    int j = 0;

    while (user[i])
    {
        usrpsswd[i] = user[i];
        ++i;
    }
    usrpsswd[i++] = ':';
    while (psswd[j])
    {
        usrpsswd[i] = psswd[j];
        ++i;
        ++j;
    }
    usrpsswd[i] = 0;
    return usrpsswd;
}

void init_string(struct string *s) 
{
  s->len = 0;
  s->ptr = malloc(s->len+1);
  if (s->ptr == NULL) 
  {
    fprintf(stderr, "malloc() failed\n");
    exit(EXIT_FAILURE);
  }
  s->ptr[0] = '[=10=]';
}

size_t writefunc(void *ptr, size_t size, size_t nmemb, struct string *s)
{
    size_t new_len = s->len + size*nmemb;
    s->ptr = realloc(s->ptr, new_len+1);
    if (s->ptr == NULL) 
    {
        fprintf(stderr, "realloc() failed\n");
        exit(EXIT_FAILURE);
    }
    memcpy(s->ptr+s->len, ptr, size*nmemb);
    s->ptr[new_len] = '[=10=]';
    s->len = new_len;
    return size*nmemb;
}

char *curl_get(char *addr, t_data *data)
{
  CURL *curl;
  CURLcode res;
  char *rtrn;
  curl = curl_easy_init();
  if(curl) 
  {
    struct string s;
    init_string(&s);
    curl_easy_setopt(curl, CURLOPT_URL, addr);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &s);
    curl_easy_setopt(curl, CURLOPT_PORT, 21);
    curl_easy_setopt(curl, CURLOPT_USERPWD, usr_psswd(data->login, data->password));
    res = curl_easy_perform(curl);
    if(res != CURLE_OK)
    {
        printf("curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
        free(s.ptr);
        curl_easy_cleanup(curl);
        return NULL;
    }
    rtrn = strdup(s.ptr);
    free(s.ptr);
    curl_easy_cleanup(curl);
  }
  return rtrn;
}

您的问题是您将二进制数据视为字符串。

strdup 函数与任何其他字符串函数一样工作:它查找字符串终止符以找到源字符串的结尾。字符串终止符 '[=11=]' 是字节值 0。因此,如果二进制数据包含任何零字节(非常有可能),那么这将被视为 "string".

的结尾

简单的解决方案?只需执行 return s.ptr; 但请注意,无法使用 returned 指针找出数据的长度。因此,更好的解决方案可能是 return s 本身(因为它包含指向数据的指针及其大小)。

您看到的大多数问题是由于使用了专为处理字符串而设计的技术,但这些技术正应用于二进制文件。

编写必须在某些时候与二进制数据和文件内容一起使用的代码时,最好遵循几条规则

1) Variables used to contain binary data should prefer unsigned char over char. For example:

char *usr_psswd(char *user, char *psswd){...  

应该写成

unsigned char *usr_psswd(unsigned char *user, size_t lenUser, unsigned char *psswd, size_t lenPsswd){...  

注意:下面介绍了包含数组长度的原因。

More on the rational of using unsigned char with binary data

2) Avoid use of string functions such as strdup(), strlen(), etc. They are all written to look for the terminating null byte to indicated the end of a C string. For example: >

usrpsswd = (char *)malloc(strlen(user) + strlen(psswd) + 2);

应该写成:

 usrpsswd = malloc(lenUser + lenPasswd + 1);//No need for null terminator. (+1 for delimiter, per comments)
                                        //usrpasswrd should be unsigned char *
                                        //Casting return of malloc not recommended. in C.  

More on reliable ways to get array lengths in C.