发送文件POST C++

3
我正在尝试使用C++将一个文本文件通过POST发送到我的upload.php表单,该表单在我的本地Web服务器上使用PHP。
这是我用于请求的PHP代码:
<?php
$uploaddir = 'upload/';

if (is_uploaded_file(isset($_FILES['file']['tmp_name'])?($_FILES['file'['tmp_name']):0)) 
{
    $uploadfile = $uploaddir . basename($_FILES['file']['name']);
    echo "File ". $_FILES['file']['name'] ." uploaded successfully. ";

    if (move_uploaded_file($_FILES['file']['tmp_name'], $uploadfile)) 
    {
        echo "File was moved! ";
    }
    else
    {
        print_r($_FILES);
    }
}
else 
{
    print_r($_FILES);
}
?>

“upload”目录存在于与upload.php(上面的内容)相同的目录中。
以下是我用来准备HTTP请求并发送它的代码:
#include <windows.h>
#include <wininet.h>
#include <iostream>
#include <tchar.h>

#pragma comment(lib,"wininet.lib")
#define ERROR_OPEN_FILE       10
#define ERROR_MEMORY          11
#define ERROR_SIZE            12
#define ERROR_INTERNET_OPEN   13
#define ERROR_INTERNET_CONN   14
#define ERROR_INTERNET_REQ    15
#define ERROR_INTERNET_SEND   16

using namespace std;

int main()
{
 // Local variables
 static char *filename   = "test.txt";   //Filename to be loaded
 static char *filepath   = "C:\\wamp\\www\\post\\test.txt";   //Filename to be loaded
 static char *type       = "text/plain";
 static char boundary[]  = "--BOUNDARY---";            //Header boundary
 static char nameForm[]  = "file";     //Input form name
 static char iaddr[]     = "localhost";        //IP address
 static char url[]       = "/post/upload.php";         //URL

 char hdrs[512]={'-'};                  //Headers
 char * buffer;                   //Buffer containing file + headers
 char * content;                  //Buffer containing file
 FILE * pFile;                    //File pointer
 long lSize;                      //File size
 size_t result;                   

 // Open file
 pFile = fopen ( filepath , "rb" );
 if (pFile==NULL) 
 {
     printf("ERROR_OPEN_FILE");
     getchar();
     return ERROR_OPEN_FILE;
 }
 printf("OPEN_FILE\n");

 // obtain file size:
 fseek (pFile , 0 , SEEK_END);
 lSize = ftell (pFile);
 rewind (pFile);

 // allocate memory to contain the whole file:
 content = (char*) malloc (sizeof(char)*(lSize+1));
 if (content == NULL) 
 {
     printf("ERROR_MEMORY");
     getchar();
     return ERROR_OPEN_FILE;
 }
 printf("MEMORY_ALLOCATED\t \"%d\" \n",&lSize);
 // copy the file into the buffer:
 result = fread (content,1,lSize,pFile);
 if (result != lSize) 
 {
     printf("ERROR_SIZE");
     getchar();
     return ERROR_OPEN_FILE;
 }
 printf("SIZE_OK\n");

 content[lSize] = '\0';

 // terminate
 fclose (pFile);
 printf("FILE_CLOSE\n");
 //allocate memory to contain the whole file + HEADER
 buffer = (char*) malloc (sizeof(char)*lSize + 2048);

 //print header
 sprintf(hdrs,"Content-Type: multipart/form-data; boundary=%s",boundary);
 sprintf(buffer,"%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\n",boundary,nameForm,filename);
 sprintf(buffer,"%sContent-Type: %s\r\n",buffer,type);
 sprintf(buffer,"%s\r\n%s",buffer,content);
 sprintf(buffer,"%s\r\n--%s--\r\n",buffer,boundary);

 printf("%s", buffer);

 //Open internet connection
 HINTERNET hSession = InternetOpen("WINDOWS",INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
 if(hSession==NULL) 
 {
     printf("ERROR_INTERNET_OPEN");
     getchar();
     return ERROR_OPEN_FILE;
 }
 printf("INTERNET_OPENED\n");

 HINTERNET hConnect = InternetConnect(hSession, iaddr,INTERNET_DEFAULT_HTTP_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 1);
 if(hConnect==NULL) 
 {
     printf("ERROR_INTERNET_CONN");
     getchar();
     return ERROR_INTERNET_CONN;
 }
 printf("INTERNET_CONNECTED\n");

 HINTERNET hRequest = HttpOpenRequest(hConnect, (const char*)"POST",_T(url),NULL, NULL, NULL,INTERNET_FLAG_RELOAD, 1);
 if(hRequest==NULL) 
  {
     printf("ERROR_INTERNET_REQ");
     getchar();

 }
 printf("INTERNET_REQ_OPEN\n");

 BOOL sent= HttpSendRequest(hRequest, hdrs, strlen(hdrs), buffer, strlen(buffer));

 if(!sent) 
 {
     printf("ERROR_INTERNET_SEND");
     getchar();
     return ERROR_INTERNET_CONN;
 }
 printf("INTERNET_SEND_OK\n");

 InternetCloseHandle(hSession);
 InternetCloseHandle(hConnect);
 InternetCloseHandle(hRequest);

 getchar();
 return 0;
}

当我执行upload.exe(上面的内容)时,我会得到以下输出:
OPEN_FILE
MEMORY_ALLOCATED    "1832340"
SIZE_OK
FILE_CLOSE
---BOUNDARY---
Content Disposition: form-data; name="file"; filename="test.txt"
Content-Type: text/plain
test
---BOUNDARY---
INTERNET_OPENED
INTERNET_CONNECTED
INTERNET_REQ_OPEN
INTERNET_SEND_OK

这是PHP错误日志:
[12-Nov-2014 20:09:58 Europe/Paris] PHP Stack trace:
[12-Nov-2014 20:09:58 Europe/Paris] PHP   1. {main}() C:\wamp\www\post\upload.php:0

我对这意思有点困惑。这是一个错误吗?

似乎一切都通过了(注意:test.txt的内容为“test”)。但是当我查看upload目录时,test.txt文件不在其中。该目录为空。有人可以帮我理解问题是什么吗?谢谢!

BUMP 没有人知道怎么做吗,还是不可能?如果不可能,那就告诉我,这样我就不用再浪费时间搜索了。


1
不要只是发送文件并停止,而是在PHP中读取服务器的响应或将状态记录到文件中。这样,在PHP中,您就可以了解正在发生什么,以便查看服务器正在做什么。 - nickb
@nickb 像我刚才根据你的要求提供的那个一样? - sysenter
条件通常是真或假,因为它是三元运算符。所以关于if语句,它只是在说如果成功返回true,如果失败返回false。在失败的情况下,它会直接退出并转到else语句。所以似乎没有问题。 - sysenter
@sysenter 我不确定,我不知道你在C++中使用的API。但是,你只需要发送请求,然后什么也不做。我的下一步是读取服务器返回的响应。你应该能够获得HTTP状态码以及它可能提供的任何内容。如果PHP代码可以从简单的HTML表单中工作,那么问题不在于PHP,我们可以假设问题出在其他地方。 - nickb
好的,没看到你尝试了HTML并且它起作用了。那么,你是否使用Wireshark比较过C++客户端和HTML表单之间的HTTP请求?这可能会让你对请求中的问题所在有一个想法。 - SatA
显示剩余13条评论
1个回答

1

在测试您的C++客户端时,我发现变量content包含文件内容但不以NULL结尾,因此当您使用sprintf复制它时,会复制随机字节直到出现NULL

将分配更改为以下内容:

 content = (char*) malloc (sizeof(char)*(lSize+1));

阅读文件内容后执行以下操作:
content[lSize] = '\0';

此外,我相信BOUNDARY应该从新的一行开始。因此,请进行以下更改:
 sprintf(buffer,"%s\r\n--%s--\r\n",buffer,boundary);

编辑:

通过比较普通的HTML表单请求,我可以看到内容应该在两行之后开始,因此也要更改这个:

sprintf(buffer,"%s\r\n%s",buffer,content);

编辑2:

在使用PHP代码进行测试后,我发现C++代码存在更多问题。(顺便提一下,PHP代码中有一个笔误:if语句中缺少]

边界没有设置正确。

MIME类型的格式应该像这样:

--BOUNDARY
Content-Disposition: form-data; name="file"; filename="test2.txt"
Content-Type: text/plain

test
--BOUNDARY

HTTP头应该像这样: Content-Type: multipart/form-data; boundary=BOUNDARY
因此,sprintf应该如下所示:
sprintf(hdrs,"Content-Type: multipart/form-data; boundary=%s",boundary);
sprintf(buffer,"--%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\n",boundary,nameForm,filename);
sprintf(buffer,"%sContent-Type: %s\r\n",buffer,type);
sprintf(buffer,"%s\r\n%s",buffer,content);
sprintf(buffer,"%s\r\n--%s\r\n",buffer,boundary);

在完成本帖中的所有修复后,代码应该可以正常工作。 总结一下:
  1. 我建议使用虚拟机设置来更轻松地调试这些问题,因为这样您就可以使用Wireshark\Fidler\Burp。(您也可以尝试配置本地主机的代理,但我自己从未尝试过。另外,您可以尝试使用您的外部Internet IP并配置路由器进行端口转发,但这对于这种任务来说太复杂了)。
  2. 如果这不起作用,NetCat可能有所帮助。它不像其他工具那样易于使用和友好,但可以完成工作。
  3. 只需将工作示例与您的请求输出进行比较,即可准确显示缺少了什么。我使用Beyond Compare进行了比较。始终使用比较软件,因为人眼很容易被欺骗。
  4. 最明显的是阅读Mime规范并查看缺少了什么。

你是否也在本地主机上测试了客户端到服务器接口?尽管在大多数字符串中,在EOF处保留一个额外的字节通常是明智的,但在这种情况下并不重要,因为EOF本身已经被假定为一个空字节。在我的测试中,你的代码结果与之前相同! - sysenter
运行您的代码后,我在NetCat端收到了以下输出:http://pastebin.com/kngJEjuL,所以我建议的更改应该可以解决这些问题。我现在无法测试PHP。 - SatA
仍然没有变化。我得到了和你一样的输出,但是它没有将文件上传到目录中。 - sysenter
输出结果错误。在Content-Disposition头部上方缺少必需的前导--(是的,在已声明的边界中除了前导--之外)。那一行需要改为----BOUNDARY---而不是--BOUNDARY---。此外,在文本文件内容结束之前,关闭MIME边界之前缺少换行符。 - Remy Lebeau
Content-Type: text/plain头部和文本文件内容之间缺少必需的换行符。 - Remy Lebeau

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接