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未初始化引发不可预期的错误。
修复方案
按照以下步骤修改代码即可解决问题:
- 所有传给libcurl的字符串参数,先保存为生命周期覆盖整个上传流程的局部变量,避免临时对象析构导致野指针。
- 补全FTP上传必备配置项:开启上传模式、绑定本地文件句柄、传入文件大小。
- 调整初始化顺序:先执行
curl_global_init,再创建curl句柄,删除冗余的提前初始化代码。 - 若需要保留上传后重命名的逻辑,将重命名命令列表通过
CURLOPT_POSTQUOTE绑定到curl实例,确保文件上传完成后再执行重命名;不需要则直接删除重命名相关代码。 - 确认传入的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
相关产品推荐
相关产品推荐

