通过 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.
还有很多讨论二进制文件损坏的话题,但它们似乎都与我的问题无关。
我有一个 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
overchar
. 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.