文件传输之https
温馨提示
当我们用openssl 生成自己签名的证书用于测试的时候,记得把证书安装到系统根凭证目录下里否则使用 wininet库的时候会返回无效的证书。
请求的时候添加无效证书标识,只是对应的网络验证。
服务端
1.使用wininet+ssl开发的。监听的8080端口。
2.目前能接受的http请求头大小为4096字节。先解析请求头中Content-Length字段,再进行动态分配内存。所以客户端需要带上Content-Length字段。才行。
3.客户端上传文件是通过**”post“**请求上传的body要以 body: 开头后面填写内容。否则无法解析上传内容。
4.客户端下载文件是通过**”Get“**请求。 文件路径是 save_dir + "uploaded_file.txt"; save_dir 可以按需求更改。
客户端
1.HttpOpenRequest函数 OpenRequestFlags需要加上INTERNET_FLAG_SECURE才会使用https请求,由于我是本地测试的所以还需要加上INTERNET_FLAG_IGNORE_CERT_CN_INVALID,否则通讯通不过。
DWORD dwOpenRequestFlags =
// Enable HTTPS
INTERNET_FLAG_SECURE |
INTERNET_FLAG_IGNORE_CERT_CN_INVALID;
HINTERNET hRequest = HttpOpenRequest(hConnect, L"POST", path, NULL, NULL, NULL, dwOpenRequestFlags, 0);
ssl_server
#include <winsock2.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <iostream>
#include <fstream>
#include <filesystem>
#include <string>
#include <sstream>
#pragma comment(lib, "ws2_32.lib")
const std::string save_dir = "C:/testsave/";
void ensure_directory_exists(const std::string& path) {
if (!std::filesystem::exists(path)) {
std::filesystem::create_directories(path);
}
}
void handle_file_upload(SSL* ssl,std::string& file_content) {
int Index = file_content.find("body:");
if (Index>=0)
{
file_content = file_content.substr(Index +5);
std::string save_path = save_dir + "uploaded_file.txt";
std::ofstream ofs(save_path, std::ios::binary);
ofs << file_content;
ofs.close();
std::string response = "File uploaded successfully!";
SSL_write(ssl, response.c_str(), response.size());
}
else
{
std::cerr << "post no find body: sign" << std::endl;
}
}
void handle_file_download(SSL* ssl) {
std::string file_path = save_dir + "uploaded_file.txt";
if (std::filesystem::exists(file_path)) {
std::ifstream ifs(file_path, std::ios::binary);
std::stringstream buffer;
buffer << ifs.rdbuf();
std::string file_content = buffer.str();
SSL_write(ssl, file_content.c_str(), file_content.size());
}
else {
std::string response = "File not found!";
SSL_write(ssl, response.c_str(), response.size());
}
}
// Extract Content-Length from the request header
bool extract_content_length(const std::string& req_str, size_t& content_length) {
size_t pos = req_str.find("Content-Length: ");
if (pos == std::string::npos) {
return false; // If Content-Length field is not found
}
pos += 15; // The value follows after "Content-Length: "
size_t end_pos = req_str.find("\r\n", pos); // Find the end of the line
if (end_pos == std::string::npos) {
return false;
}
// Extract the Content-Length value
std::string length_str = req_str.substr(pos, end_pos - pos);
std::istringstream(length_str) >> content_length;
return true;
}
void process_request(SSL* ssl) {
const size_t buffer_size = 4096;
std::vector<char> request(buffer_size, 0); // Dynamic buffer
int bytes_read = 0;
std::string req_str;
// Use a loop to process chunked reading
while (true) {
bytes_read = SSL_read(ssl, request.data(), request.size() - 1); // Leave one byte for '\0'
if (bytes_read <= 0) {
// If reading fails or there is no data, exit
std::cerr << "SSL_read failed, bytes_read: " << bytes_read << std::endl;
break;
}
// Append the read data to req_str
req_str.append(request.data(), bytes_read);
// Check if the HTTP request header has been fully read
if (req_str.find("\r\n\r\n") != std::string::npos) {
// If the end of the HTTP header is found, the header is fully read
break;
}
}
// Parse Content-Length
size_t content_length = 0;
if (!extract_content_length(req_str, content_length)) {
std::cerr << "Failed to find Content-Length in the request header." << std::endl;
return;
}
std::cout << "Content-Length: " << content_length << std::endl;
// Read the request body data
size_t total_bytes_read = 0;
while (total_bytes_read < content_length && content_length >= 4095) {
// Read fixed-size chunks until the body is completely read
bytes_read = SSL_read(ssl, request.data(), request.size() - 1);
if (bytes_read <= 0) {
std::cerr << "Failed to read request body." << std::endl;
break;
}
total_bytes_read += bytes_read;
// Append the read data to req_str
req_str.append(request.data(), bytes_read);
std::cout << "Bytes read: " << total_bytes_read << "/" << content_length << std::endl;
// Process/save the request body data
// You can call functions like handle_file_upload here
}
if (total_bytes_read == content_length) {
std::cout << "Request body fully read." << std::endl;
}
else {
std::cerr << "Failed to read the complete request body." << std::endl;
}
// Handle based on the request method
if (req_str.find("POST") != std::string::npos) {
handle_file_upload(ssl, req_str);
}
else if (req_str.find("GET") != std::string::npos) {
handle_file_download(ssl);
}
}
int main() {
SSL_load_error_strings();
OpenSSL_add_ssl_algorithms();
const SSL_METHOD* method = SSLv23_server_method();
SSL_CTX* ctx = SSL_CTX_new(method);
if (!ctx) {
std::cerr << "Unable to create SSL context" << std::endl;
ERR_print_errors_fp(stderr);
return -1;
}
SSL_CTX_use_certificate_file(ctx, "server.crt", SSL_FILETYPE_PEM);
SSL_CTX_use_PrivateKey_file(ctx, "server.key", SSL_FILETYPE_PEM);
WSADATA wsaData;
int result = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (result != 0) {
std::cerr << "WSAStartup failed: " << result << std::endl;
return -1;
}
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == INVALID_SOCKET) {
int error_code = WSAGetLastError();
std::cerr << "Socket creation failed with error: " << error_code << std::endl;
WSACleanup();
return -1;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
int error_code = WSAGetLastError();
std::cerr << "Bind failed with error: " << error_code << std::endl;
closesocket(server_fd);
WSACleanup();
return -1;
}
if (listen(server_fd, 1) == SOCKET_ERROR) {
int error_code = WSAGetLastError();
std::cerr << "Listen failed with error: " << error_code << std::endl;
closesocket(server_fd);
WSACleanup();
return -1;
}
ensure_directory_exists(save_dir);
while (1) {
int client = accept(server_fd, NULL, NULL);
if (client == INVALID_SOCKET) {
int error_code = WSAGetLastError();
std::cerr << "Accept failed with error: " << error_code << std::endl;
continue;
}
SSL* ssl = SSL_new(ctx);
SSL_set_fd(ssl, client);
int r = SSL_accept(ssl);
if (r <= 0) {
int err_SSL_get_error = SSL_get_error(ssl, r);
int err_GetLastError = GetLastError();
int err_WSAGetLastError = WSAGetLastError();
int err_ERR_get_error = ERR_get_error();
std::cout << "[DEBUG] SSL_accept() : Failed with return "
<< r << std::endl;
std::cout << "[DEBUG] SSL_get_error() returned : "
<< err_SSL_get_error << std::endl;
std::cout << "[DEBUG] Error string : "
<< ERR_error_string(err_SSL_get_error, NULL)
<< std::endl;
std::cout << "[DEBUG] WSAGetLastError() returned : "
<< err_WSAGetLastError << std::endl;
std::cout << "[DEBUG] GetLastError() returned : "
<< err_GetLastError << std::endl;
std::cout << "[DEBUG] ERR_get_error() returned : "
<< err_ERR_get_error << std::endl;
std::cout << "+--------------------------------------------------+"
<< std::endl;
break;
}
else {
process_request(ssl);
}
SSL_shutdown(ssl);
SSL_free(ssl);
closesocket(client);
}
closesocket(server_fd);
SSL_CTX_free(ctx);
EVP_cleanup();
WSACleanup();
return 0;
}
ssl_client
HttpsFileTransfer.h
#pragma once
#include <windows.h>
#include <wininet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <stdio.h>
#include <iostream>
#include <fstream>
#include <vector>
#pragma comment(lib, "wininet.lib")
#pragma comment(lib, "libssl.lib")
#pragma comment(lib, "libcrypto.lib")
class HttpsFileTransfer {
public:
void handle_file_upload(const wchar_t* server, const wchar_t* path, const char* file) {
wchar_t szScheme[MAX_PATH] = { 0 };
wchar_t szHostName[MAX_PATH] = { 0 };
wchar_t szUserName[MAX_PATH] = { 0 };
wchar_t szPassword[MAX_PATH] = { 0 };
wchar_t szUrlPath[MAX_PATH] = { 0 };
wchar_t szExtraInfo[MAX_PATH] = { 0 };
WORD port = 0;
// Crack the URL to extract components
if (FALSE == UrlCrack((wchar_t*)server, szScheme, szHostName, szUserName, szPassword, szUrlPath, szExtraInfo, &port, MAX_PATH))
{
return;
}
HINTERNET hSession = InternetOpen(L"HTTP_Uploader", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
HINTERNET hConnect = InternetConnect(hSession, szHostName, port, szUserName, szPassword, INTERNET_SERVICE_HTTP, 0, 0);
DWORD dwOpenRequestFlags =
// Enable HTTPS
INTERNET_FLAG_SECURE |
INTERNET_FLAG_IGNORE_CERT_CN_INVALID;
HINTERNET hRequest = HttpOpenRequest(hConnect, L"POST", path, NULL, NULL, NULL, dwOpenRequestFlags, 0);
std::vector<char> fileData;
std::string file_content = "body:";
readBinaryFile(file, fileData);
for (auto data : fileData)
{
file_content += data;
}
// Send the file as the request body
if (!HttpSendRequest(hRequest, NULL, 0, (LPVOID)file_content.c_str(), file_content.size())) {
std::cerr << "Error uploading file: " << GetLastError() << std::endl;
}
else {
std::cout << "File uploaded successfully!" << std::endl;
}
// Close the handles
InternetCloseHandle(hRequest);
InternetCloseHandle(hConnect);
InternetCloseHandle(hSession);
}
void handle_file_download(const wchar_t* server, const wchar_t* path, const char* file) {
wchar_t szScheme[MAX_PATH] = { 0 };
wchar_t szHostName[MAX_PATH] = { 0 };
wchar_t szUserName[MAX_PATH] = { 0 };
wchar_t szPassword[MAX_PATH] = { 0 };
wchar_t szUrlPath[MAX_PATH] = { 0 };
wchar_t szExtraInfo[MAX_PATH] = { 0 };
WORD port = 0;
// Crack the URL to extract components
if (FALSE == UrlCrack((wchar_t*)server, szScheme, szHostName, szUserName, szPassword, szUrlPath, szExtraInfo, &port, MAX_PATH))
{
return;
}
HINTERNET hSession = InternetOpen(L"HTTP_Downloader", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
HINTERNET hConnect = InternetConnect(hSession, szHostName, port, szUserName, szPassword, INTERNET_SERVICE_HTTP, 0, 0);
DWORD dwOpenRequestFlags =
// Enable HTTPS
INTERNET_FLAG_SECURE |
INTERNET_FLAG_IGNORE_CERT_CN_INVALID;
HINTERNET hRequest = HttpOpenRequest(hConnect, L"GET", path, NULL, NULL, NULL, dwOpenRequestFlags, 0);
std::string file_content = "1";
// Send a request to start the download
if (!HttpSendRequest(hRequest, NULL, 0, (LPVOID)file_content.c_str(), file_content.size()))
{
std::cerr << "Error downloading file: " << GetLastError() << std::endl;
}
else {
DWORD bytes_read = 0;
char buffer[4096];
std::ofstream ofs(file, std::ios::binary);
// Read and write the file content
while (InternetReadFile(hRequest, buffer, sizeof(buffer), &bytes_read) && bytes_read != 0) {
ofs.write(buffer, bytes_read);
}
std::cout << "File downloaded successfully!" << std::endl;
}
// Close the handles
InternetCloseHandle(hRequest);
InternetCloseHandle(hConnect);
InternetCloseHandle(hSession);
}
void ensure_directory_exists(const std::wstring& path) {
if (!CreateDirectory(path.c_str(), NULL) && GetLastError() != ERROR_ALREADY_EXISTS) {
std::cerr << "Error creating directory: " << GetLastError() << std::endl;
}
}
private:
bool readBinaryFile(const std::string& filePath, std::vector<char>& buffer) {
// Open the file in binary mode for reading
std::ifstream ifs(filePath, std::ios::binary);
// Check if the file was opened successfully
if (!ifs.is_open()) {
std::cerr << "Failed to open file: " << filePath << std::endl;
return false;
}
// Get the file size
ifs.seekg(0, std::ios::end);
std::streamsize fileSize = ifs.tellg();
ifs.seekg(0, std::ios::beg);
// Allocate buffer based on the file size
buffer.resize(fileSize);
// Read the file content into the buffer
if (!ifs.read(buffer.data(), fileSize)) {
std::cerr << "Failed to read file contents." << std::endl;
return false;
}
// Close the file
ifs.close();
return true;
}
void add_content_length_header(HINTERNET hRequest, size_t contentLength) {
// Construct the Content-Length header
char contentLengthHeader[128]; // Reserved space, usually Content-Length is not very large
sprintf(contentLengthHeader, "Content-Length: %zu\r\n", contentLength); // Format the Content-Length header
// Add the Content-Length header
if (!HttpAddRequestHeadersA(hRequest, contentLengthHeader, -1L, HTTP_ADDREQ_FLAG_ADD)) {
std::cerr << "Failed to add Content-Length header. Error: " << GetLastError() << std::endl;
}
else {
std::cout << "Successfully added Content-Length header: " << contentLengthHeader << std::endl;
}
}
BOOL UrlCrack(wchar_t* pszUrl, wchar_t* pszScheme, wchar_t* pszHostName, wchar_t* pszUserName, wchar_t* pszPassword, wchar_t* pszUrlPath, wchar_t* pszExtraInfo, WORD* nPort, DWORD dwBufferSize)
{
BOOL bRet = FALSE;
URL_COMPONENTS uc = { 0 };
RtlZeroMemory(&uc, sizeof(uc));
RtlZeroMemory(pszScheme, dwBufferSize);
RtlZeroMemory(pszHostName, dwBufferSize);
RtlZeroMemory(pszUserName, dwBufferSize);
RtlZeroMemory(pszPassword, dwBufferSize);
RtlZeroMemory(pszUrlPath, dwBufferSize);
RtlZeroMemory(pszExtraInfo, dwBufferSize);
uc.dwStructSize = sizeof(uc);
uc.dwSchemeLength = dwBufferSize - 1;
uc.dwHostNameLength = dwBufferSize - 1;
uc.dwUserNameLength = dwBufferSize - 1;
uc.dwPasswordLength = dwBufferSize - 1;
uc.dwUrlPathLength = dwBufferSize - 1;
uc.dwExtraInfoLength = dwBufferSize - 1;
uc.lpszScheme = pszScheme;
uc.lpszHostName = pszHostName;
uc.lpszUserName = pszUserName;
uc.lpszPassword = pszPassword;
uc.lpszUrlPath = pszUrlPath;
uc.lpszExtraInfo = pszExtraInfo;
bRet = InternetCrackUrl(pszUrl, 0, 0, &uc);
if (FALSE == bRet)
{
return bRet;
}
*nPort = uc.nPort;
return bRet;
}
void disable_ssl_verification(HINTERNET hConnect) {
// Use InternetSetOption to disable SSL certificate verification
BOOL optionValue = TRUE;
if (!InternetSetOption(hConnect, INTERNET_OPTION_SECURITY_FLAGS, &optionValue, sizeof(optionValue))) {
std::cerr << "Failed to disable SSL verification" << std::endl;
}
}
};
ssl_client.cpp
#include "HttpsFileTransfer.h"
const std::wstring save_dir = L"C:/testsave/";
int main() {
SSL_load_error_strings();
OpenSSL_add_ssl_algorithms();
// Example usage
HttpsFileTransfer https;
https.ensure_directory_exists(save_dir);
https.handle_file_upload(L"https://127.0.0.1:8080", L"", "C:/test/123.txt");
https.handle_file_download(L"https://127.0.0.1:8080", L"", "C:/testsave/downloadfile.txt");
EVP_cleanup();
return 0;
}