// 定义HTTP接口返回值类型
enum XHTTPRET_T
{
	XHTTPRET_SUCCESS = 0,                          // 操作成功
	XHTTPRET_ERROR_PARAMETER,                      // 函数参数错误
	XHTTPRET_ERROR_OPENFILE,                       // 打开文件错误
	XHTTPRET_ERROR_CURLEASYINIT,                   // curl_easy_init失败
	XHTTPRET_ERROR_CURLEASYPERFORM,                // curl_easy_perform失败	
	XHTTPRET_ERROR_PERFORMOPERATIONTIMEDOUT,       // curl_easy_perform失败,curlCode=CURLE_OPERATION_TIMEDOUT
	XHTTPRET_ERROR_READUPLOADFILE,                 // 读取上传文件失败
};

// 上传二进制数据,POST模拟表单提交上传
XHTTPLIB_API
XHTTPRET_T
XHTTP_UploadBinaryDataBuf(
	IN  LPCWSTR lpURL,                                         // URL 
	IN  const BYTE *pBinaryDataBuf,                            // 二进制数据
	IN  DWORD dwcbDataBufSize,                                 // 二进制数据字节大小
	IN  LPCWSTR lpContentType = NULL,                          // Content-Type类型,NULL表示二进制流application/octet-stream,可以在网上查询需要上传的类型
	IN  LPCWSTR lpFormInputCtrlName = NULL,                    // 表单提交的Input控件名称,对应Content-Disposition中的name字段
	IN  LPCWSTR lpFormInputCtrlFileName = NULL,                // 对应Content-Disposition中的filename字段
	OUT string *pRetBuf = NULL,                                // 输出返回结果数据,注意返回的是数据不是字符串
	OUT LONG *pCurlRetCode = NULL                              // 输出CURL执行结果,出错时便于分析
	);

// HTTP上传文件,POST模拟表单提交上传文件
XHTTPLIB_API
XHTTPRET_T
XHTTP_UploadFile(
	IN  LPCWSTR lpURL,                                         // URL 
	IN  LPCWSTR lpFile,                                        // 待上传文件路径
	IN  LPCWSTR lpContentType = NULL,                          // Content-Type类型,NULL表示二进制流application/octet-stream,可以在网上查询需要上传的类型
	IN  LPCWSTR lpFormInputCtrlName = NULL,                    // 表单提交的Input控件名称,对应Content-Disposition中的name字段
	OUT string *pRetBuf = NULL,                                // 输出返回结果数据,注意返回的是数据不是字符串
	OUT LONG *pCurlRetCode = NULL                              // 输出CURL执行结果,出错时便于分析
	);


//////////////////////////////////////////////////////////////////////////
// 数据写入string中回调函数
static size_t XCURLIN_WriteDataToStringFunctionCallback(char *ptr, size_t size, size_t nmemb, string *pRetBuf)  
{  
	// 参数有效性
	if (ptr==NULL || size==0 || nmemb==0 || pRetBuf==NULL)
	{
		return 0;
	}

	// 追加数据到string
	size_t szBytes = size * nmemb;
	pRetBuf->append(ptr, szBytes);
	return szBytes;
} 

// 上传二进制数据,POST模拟表单提交上传
XHTTPLIB_API
XHTTPRET_T
XHTTP_UploadBinaryDataBuf(
	IN  LPCWSTR lpURL,                                         // URL 
	IN  const BYTE *pBinaryDataBuf,                            // 二进制数据
	IN  DWORD dwcbDataBufSize,                                 // 二进制数据字节大小
	IN  LPCWSTR lpContentType,                                 // Content-Type类型,NULL表示二进制流application/octet-stream,可以在网上查询需要上传的类型
	IN  LPCWSTR lpFormInputCtrlName,                           // 表单提交的Input控件名称,对应Content-Disposition中的name字段
	IN  LPCWSTR lpFormInputCtrlFileName,                       // 对应Content-Disposition中的filename字段
	OUT string *pRetBuf,                                       // 输出返回结果数据,注意返回的是数据不是字符串
	OUT LONG *pCurlRetCode                                     // 输出CURL执行结果,出错时便于分析
	)
{	
	// 初始化输出参数
	if (pRetBuf != NULL)
	{
		pRetBuf->clear();
	}
	if (pCurlRetCode != NULL)
	{
		*pCurlRetCode = CURLE_OK;
	}

	// 参数有效性
	string strUrlUtf8 = STRF_UnicodeToUtf8(lpURL);
	if (   strUrlUtf8.length() == 0
		|| pBinaryDataBuf == NULL
		|| dwcbDataBufSize == 0
		)
	{
		return XHTTPRET_ERROR_PARAMETER;
	}

	// POST模拟表单提交上传文件要点:
	// 1、准备Headers,特别注意指定boundary,发送数据时需要使用boundary作为分隔标识;
	// 2、准备first_boundary、delimiter_boundary、end_boundary,发送多个数据时需要用delimiter_boundary把数据分隔开,boundary需要在正文POST发送;
	// 3、准备Content-Disposition、Content-Type数据,这两个字段数据需要在正文POST发送;
	// 4、拼接POST数据:first_boundary + content_dispos + content_type + 文件数据 + end_boundary;
	// 5、发送POST数据
	// 申请句柄
	CURL *pcurl = curl_easy_init();
	if (pcurl == NULL)
	{
		return XHTTPRET_ERROR_CURLEASYINIT;
	}

	//////////////////////////////////////////////////////////////////////////
	// 1、准备Headers,特别注意指定boundary,后续发送数据时需要使用boundary作为分隔标识;
	// 生成boundary,后追加14位数字随机串
	string strBoundaryUtf8 = string("-----------------------------") + STRF_GetRandStrUtf8(SF_RANDSTR_DIGITAL, 14, FALSE);

	// 准备HTTP请求头数据
	string str_header_content_type = string("Content-Type: multipart/form-data; boundary=") + strBoundaryUtf8;
	string str_header_referer = string("Referer: ") + strUrlUtf8;
	string str_header_accept = "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
	string str_header_accept_lan = "Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3";
	string str_header_accept_encoding = "Accept-Encoding: gzip, deflate";

	// 2、准备first_boundary、delimiter_boundary、end_boundary,发送多个数据时需要用到delimiter_boundary;
	string str_post_first_boundary = string("--") + strBoundaryUtf8 + string("\r\n");
	string str_post_delimiter_boundary = string("\r\n--") + strBoundaryUtf8 + string("\r\n");
	string str_post_end_boundary = string("\r\n--") + strBoundaryUtf8 + string("--\r\n");

	// 3、准备Content-Disposition、Content-Type数据
	// 准备Content-Disposition,形如:Content-Disposition: form-data; name="text"; filename="123.jpg"
	// 其中name字段为表单上传时Input控件名称,
	string strFormNameUtf8 = STRF_UnicodeToUtf8(lpFormInputCtrlName);
	if (strFormNameUtf8.length() == 0)
	{
		strFormNameUtf8 = "FormInputName";
	}

	// filename为待上传文件名称
	wstring wstrUploadFileName = (lpFormInputCtrlFileName!=NULL) ? wstring(lpFormInputCtrlFileName) : L"UploadFile.tmp";
	string strFormFileNameUtf8 = STRF_WStrToUTF8(wstrUploadFileName);

	// 拼接Content-Disposition数据
	string str_post_content_disposition = string("Content-Disposition: form-data; name=\"") + strFormNameUtf8 + string("\"; filename=\"") + strFormFileNameUtf8 + string("\"\r\n");

	// 准备Content-Type,形如:Content-Type: image/jpeg
	string strContentTypeUtf8 = STRF_UnicodeToUtf8(lpContentType);
	if (strContentTypeUtf8.length() == 0)
	{
		strContentTypeUtf8 = "application/octet-stream";
	}
	string str_post_content_type = string("Content-Type: ") + strContentTypeUtf8 + string("\r\n\r\n");
	
	// 4、拼接POST数据:first_boundary + content_dispos + content_type + 文件数据 + end_boundary 	
	string strPostDataUtf8 = str_post_first_boundary + str_post_content_disposition + str_post_content_type;
	strPostDataUtf8.append((const char*)pBinaryDataBuf, dwcbDataBufSize);
	strPostDataUtf8 += str_post_end_boundary;

	//////////////////////////////////////////////////////////////////////////
	// 5、发送POST数据
	// 设置Headers
	curl_slist *pHeaderList = NULL;
	pHeaderList = curl_slist_append(pHeaderList, str_header_content_type.c_str()); 
	pHeaderList = curl_slist_append(pHeaderList, str_header_referer.c_str()); 
	pHeaderList = curl_slist_append(pHeaderList, str_header_accept.c_str()); 
	pHeaderList = curl_slist_append(pHeaderList, str_header_accept_lan.c_str()); 
	pHeaderList = curl_slist_append(pHeaderList, str_header_accept_encoding.c_str()); 
	curl_easy_setopt(pcurl, CURLOPT_HTTPHEADER, pHeaderList); 		

	// 设置返回结果写入string
	if (pRetBuf != NULL)
	{
		curl_easy_setopt(pcurl, CURLOPT_WRITEFUNCTION, XCURLIN_WriteDataToStringFunctionCallback); 
		curl_easy_setopt(pcurl, CURLOPT_WRITEDATA, pRetBuf);  
	}	

	// 设置POST数据
	curl_easy_setopt(pcurl, CURLOPT_POST, 1L);
	curl_easy_setopt(pcurl, CURLOPT_POSTFIELDSIZE, strPostDataUtf8.size());
	curl_easy_setopt(pcurl, CURLOPT_POSTFIELDS, strPostDataUtf8.c_str());

	// 设置其他参数
	curl_easy_setopt(pcurl, CURLOPT_HEADER, 0L);
	curl_easy_setopt(pcurl, CURLOPT_NOSIGNAL, 1L);
	curl_easy_setopt(pcurl, CURLOPT_NOPROGRESS, 1L);
	curl_easy_setopt(pcurl, CURLOPT_FOLLOWLOCATION, 1L);	
	curl_easy_setopt(pcurl, CURLOPT_SSL_VERIFYPEER, 0L);
	curl_easy_setopt(pcurl, CURLOPT_SSL_VERIFYHOST, 2L);
	curl_easy_setopt(pcurl, CURLOPT_URL, strUrlUtf8.c_str());

	// 发起请求
	CURLcode curlRetCode = curl_easy_perform(pcurl);

	// 释放句柄
	curl_easy_cleanup(pcurl); 

	// 释放头列表
	if (pHeaderList != NULL)
	{
		curl_slist_free_all(pHeaderList);
	}

	// 得到输出参数
	if (pCurlRetCode != NULL)
	{
		*pCurlRetCode = curlRetCode;
	}	

	// 返回成功
	if (curlRetCode == CURLE_OK)
	{
		return XHTTPRET_SUCCESS;
	}

	// 超时返回单独的值	
	return (curlRetCode == CURLE_OPERATION_TIMEDOUT) ? XHTTPRET_ERROR_PERFORMOPERATIONTIMEDOUT : XHTTPRET_ERROR_CURLEASYPERFORM;
}

// HTTP上传文件,POST模拟表单提交上传文件
XHTTPLIB_API
XHTTPRET_T
XHTTP_UploadFile(
	IN  LPCWSTR lpURL,                                         // URL 
	IN  LPCWSTR lpFile,                                        // 待上传文件路径
	IN  LPCWSTR lpContentType,                                 // Content-Type类型,NULL表示二进制流application/octet-stream,可以在网上查询需要上传的类型
	IN  LPCWSTR lpFormInputCtrlName,                           // 表单提交的Input控件名称,对应Content-Disposition中的name字段
	OUT string *pRetBuf,                                       // 输出返回结果数据,注意返回的是数据不是字符串
	OUT LONG *pCurlRetCode                                     // 输出CURL执行结果,出错时便于分析
	)
{	
	// 初始化输出参数
	if (pRetBuf != NULL)
	{
		pRetBuf->clear();
	}
	if (pCurlRetCode != NULL)
	{
		*pCurlRetCode = CURLE_OK;
	}

	// 读取文件到虚拟内存
	DWORD dwFileSize = 0;
	BYTE* pVirtualBuf = FILEF_ReadFileToVirtualMemory(lpFile, &dwFileSize);
	if (pVirtualBuf==NULL || dwFileSize==0)
	{
		return XHTTPRET_ERROR_READUPLOADFILE;
	}

	// 取得上传文件名称
	wstring wstrUploadFileName = L"";
	FILEF_GetFileFullName(lpFile, wstrUploadFileName);
	if (wstrUploadFileName.length() == 0)
	{
		wstrUploadFileName = L"UploadFile.tmp";
	}

	// 执行上传二进制数据
	XHTTPRET_T xRet = XHTTP_UploadBinaryDataBuf(lpURL, pVirtualBuf, dwFileSize, lpContentType, lpFormInputCtrlName, wstrUploadFileName.c_str(), pRetBuf, pCurlRetCode);

	// 释放文件数据
	::VirtualFree(pVirtualBuf, 0, MEM_RELEASE);
	pVirtualBuf = NULL;	

	// 返回结果
	return xRet;	
}