文件传输之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;
}