You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Qt/C++使用cUrl实现FTP上传提示远程资源访问拒绝问题

Qt/C++ libcurl FTP上传访问拒绝问题排查

问题背景

基于Qt/C++开发文件上传功能,采用libcurl库实现FTP协议文件上传逻辑,初始核心实现代码如下:

int FtpController::uploadFile()
{
#define UPLOAD_FILE_AS "temp.txt"
#define RENAME_FILE_TO "renamed-and-fine.txt"

    CURL* curl;
    curl = curl_easy_init();
    CURLcode result;
    FILE* hd_src;
    const auto str = getFileName().toStdString(); // getFileName()提供本地文件全路径
    const auto* const localFile = str.c_str();
    struct stat file_info;
    unsigned long fsize;

    struct curl_slist* headerlist = nullptr;
    static const char buf_1[] = "RNFR " UPLOAD_FILE_AS;
    static const char buf_2[] = "RNTO " RENAME_FILE_TO;

    /* 获取本地文件大小 */
    if (stat(localFile, &file_info)) {
        printf("Couldn't open '%s': %s\n", localFile, strerror(errno));
        return 1;
    }
    fsize = (unsigned long)file_info.st_size;

    printf("Local file size: %lu bytes.\n", fsize);

    /* 打开本地文件 */
    hd_src = fopen(localFile, "rb");

    /* Windows平台下初始化winsock */
    curl_global_init(CURL_GLOBAL_ALL);

    /* 创建curl句柄 */
    curl = curl_easy_init();
    if (curl) {
        /* 构造FTP命令列表 */
        headerlist = curl_slist_append(headerlist, buf_1);
        headerlist = curl_slist_append(headerlist, buf_2);
        {
            curl_easy_setopt(curl, CURLOPT_PORT, _params.port);
            curl_easy_setopt(curl, CURLOPT_USERNAME, _params.username.toStdString().c_str());
            curl_easy_setopt(curl, CURLOPT_PASSWORD, _settingsController->getSystemSettings()->getPass().toStdString().c_str());
            curl_easy_setopt(curl, CURLOPT_TIMEOUT, _params.timeout);
            curl_easy_setopt(curl, CURLOPT_URL, _params.url.toString().toStdString().c_str());
        }
        /* 执行请求 */
        result = curl_easy_perform(curl);
        /* 错误检查 */
        if (result != CURLE_OK)
            fprintf(stderr, "curl_easy_perform() failed: %s\n",
                curl_easy_strerror(result));

        /* 释放FTP命令列表 */
        curl_slist_free_all(headerlist);

        /* 清理curl句柄 */
        curl_easy_cleanup(curl);
    }
    fclose(hd_src); /* 关闭本地文件 */

    curl_global_cleanup();
    return 0;
}

问题现象

  • 代码运行返回错误:curl_easy_perform() failed: Access denied to remote resource
  • 服务商侧FTP日志显示请求已成功登录
  • 本地文件读取正常,终端可正常输出文件大小信息
  • 传入错误密码时返回Login denied错误,URL格式非法时返回格式错误,说明账号校验、基础URL格式校验环节正常

根因分析

代码存在4个核心问题,直接触发访问拒绝错误:

  • 野指针问题:所有通过toStdString().c_str()获取的字符串指针均为悬垂指针。toStdString()返回的是临时std::string对象,当前curl_easy_setopt调用执行完成后临时对象立刻析构,内部字符数组内存被释放,libcurl后续读取URL、用户名、密码参数时会读到乱码,直接触发访问异常。
  • 缺失上传核心配置:代码未开启FTP上传模式(未设置CURLOPT_UPLOAD = 1L)、未绑定本地文件读取句柄(未设置CURLOPT_READDATA)、未传入上传文件大小(未设置CURLOPT_INFILESIZE_LARGE),libcurl根本不知道要执行上传操作,默认会对目标URL执行下载/列目录请求,如果目标路径是无读权限的目录,就会返回访问拒绝。
  • 重命名逻辑无效:代码构造了FTP重命名命令列表,但没有通过CURLOPT_POSTQUOTE(上传完成后执行)参数将命令列表绑定到curl实例,这些命令完全不会生效;就算绑定,若未指定为上传后执行,提前对不存在的文件发起重命名请求也会触发FTP服务器拒绝。
  • 初始化顺序错误+资源泄漏:代码在curl_global_init执行前就调用了一次curl_easy_init(),拿到的句柄既没使用也没释放,既造成内存泄漏,在Windows平台还可能因为winsock未初始化引发不可预期的错误。

修复方案

按照以下步骤修改代码即可解决问题:

  1. 所有传给libcurl的字符串参数,先保存为生命周期覆盖整个上传流程的局部变量,避免临时对象析构导致野指针。
  2. 补全FTP上传必备配置项:开启上传模式、绑定本地文件句柄、传入文件大小。
  3. 调整初始化顺序:先执行curl_global_init,再创建curl句柄,删除冗余的提前初始化代码。
  4. 若需要保留上传后重命名的逻辑,将重命名命令列表通过CURLOPT_POSTQUOTE绑定到curl实例,确保文件上传完成后再执行重命名;不需要则直接删除重命名相关代码。
  5. 确认传入的URL完整:URL必须包含上传后的目标文件名,例如ftp://your-ftp-ip/temp.txt,不能只写到FTP根目录。

修复后的核心代码参考:

int FtpController::uploadFile()
{
#define UPLOAD_FILE_AS "temp.txt"
#define RENAME_FILE_TO "renamed-and-fine.txt"

    CURL* curl = nullptr;
    CURLcode result;
    FILE* hd_src = nullptr;
    const std::string localFilePath = getFileName().toStdString();
    const char* localFile = localFilePath.c_str();
    struct stat file_info;

    struct curl_slist* postCmdList = nullptr;
    // 提前保存所有传给curl的字符串,保证生命周期覆盖全程
    const std::string username = _params.username.toStdString();
    const std::string password = _settingsController->getSystemSettings()->getPass().toStdString();
    // 注意:URL必须拼接上上传后的目标文件名,不要只写目录
    const std::string ftpUrl = _params.url.toString().toStdString() + "/" + UPLOAD_FILE_AS;

    /* 获取本地文件大小 */
    if (stat(localFile, &file_info)) {
        printf("Couldn't open '%s': %s\n", localFile, strerror(errno));
        return 1;
    }
    const curl_off_t fsize = (curl_off_t)file_info.st_size;
    printf("Local file size: %lu bytes.\n", (unsigned long)fsize);

    /* 打开本地文件 */
    hd_src = fopen(localFile, "rb");
    if (!hd_src) {
        printf("Open local file failed: %s\n", strerror(errno));
        return 1;
    }

    /* 全局初始化顺序调整到最前 */
    curl_global_init(CURL_GLOBAL_ALL);

    /* 创建curl句柄,删除之前冗余的初始化调用 */
    curl = curl_easy_init();
    if (curl) {
        // 如需上传后重命名,构造POST命令列表(上传完成后执行)
        static const char buf_1[] = "RNFR " UPLOAD_FILE_AS;
        static const char buf_2[] = "RNTO " RENAME_FILE_TO;
        postCmdList = curl_slist_append(postCmdList, buf_1);
        postCmdList = curl_slist_append(postCmdList, buf_2);

        // 基础参数配置
        curl_easy_setopt(curl, CURLOPT_PORT, _params.port);
        curl_easy_setopt(curl, CURLOPT_USERNAME, username.c_str());
        curl_easy_setopt(curl, CURLOPT_PASSWORD, password.c_str());
        curl_easy_setopt(curl, CURLOPT_TIMEOUT, _params.timeout);
        curl_easy_setopt(curl, CURLOPT_URL, ftpUrl.c_str());

        // 补全上传核心配置
        curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); // 开启上传模式
        curl_easy_setopt(curl, CURLOPT_READDATA, hd_src); // 绑定本地读文件句柄
        curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, fsize); // 告知服务器上传文件大小
        curl_easy_setopt(curl, CURLOPT_POSTQUOTE, postCmdList); // 绑定上传后执行的命令

        /* 执行请求 */
        result = curl_easy_perform(curl);
        /* 错误检查 */
        if (result != CURLE_OK)
            fprintf(stderr, "curl_easy_perform() failed: %s\n",
                curl_easy_strerror(result));

        /* 释放FTP命令列表 */
        curl_slist_free_all(postCmdList);
        /* 清理curl句柄 */
        curl_easy_cleanup(curl);
    }

    fclose(hd_src); /* 关闭本地文件 */
    curl_global_cleanup();
    return result == CURLE_OK ? 0 : 1;
}

内容的提问来源于stack exchange,提问作者Mykola Tetiuk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 22:30:57