CurlFtp.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600
  1. #include "CurlFtp.h"
  2. #include <iostream>
  3. #include <regex>
  4. #include <iosfwd>
  5. #include "fmtlog.h"
  6. #if defined(_WIN32)
  7. #endif /* _WIN32 */
  8. /* ==================================================================================
  9. * *********************************** 全局变量 *************************************
  10. * ==================================================================================
  11. */
  12. /* 是否有初始化实例,主要用于静态函数调用curl_global_cleanup()函数前判断,有实例就不调用 */
  13. static bool hasInstace = false;
  14. /* ==================================================================================
  15. * *********************************** 全局函数 *************************************
  16. * ==================================================================================
  17. */
  18. /**
  19. * @brief 写入回调函数
  20. *
  21. * @param contents curl的数据缓冲区
  22. * @param size 数据大小,单位是size_t
  23. * @param nmemb size_t的单位字节数
  24. * @param userStr 用户提供的字符串,格式可以是任意的
  25. * @return size_t 总共获取到的数据大小,单位是字节
  26. */
  27. static size_t writeStringCallback(void *contents, size_t size, size_t nmemb, std::string *userStr)
  28. {
  29. size_t newLength = size * nmemb;
  30. size_t oldLength = userStr->size();
  31. try
  32. {
  33. userStr->resize(oldLength + newLength);
  34. }
  35. catch(std::bad_alloc &e)
  36. {
  37. //handle memory problem
  38. return 0;
  39. }
  40. std::copy_n((char*)contents, newLength, userStr->begin() + oldLength);
  41. return size * nmemb;
  42. }
  43. /**
  44. * @brief 写入回调函数,listFiles需要调用
  45. *
  46. * @param buffer curl下载回来的数据
  47. * @param size 数据大小
  48. * @param nmemb 数据单位字节数
  49. * @param userp 用户传进来的容器
  50. * @return int 返回拷贝的字节数
  51. */
  52. static int writeStringListCallback(void* buffer, size_t size, size_t nmemb, void* userp)
  53. {
  54. std::vector<std::string>* fileList = static_cast<std::vector<std::string>*>(userp);
  55. std::string line(static_cast<char*>(buffer), size * nmemb);
  56. // printf("line = %s\n", line.c_str());
  57. fileList->push_back(line);
  58. return size * nmemb;
  59. }
  60. /**
  61. * @brief 写入文件回调函数
  62. *
  63. * @param contents 下载到的数据内容
  64. * @param size 数据大小
  65. * @param nmemb 数据单位
  66. * @param pFile 文件指针
  67. * @return size_t 写入的大小
  68. */
  69. static size_t writeDataCallBack(void* contents, size_t size, size_t nmemb, FILE* pFile)
  70. {
  71. size_t written = fwrite(contents, size, nmemb, pFile);
  72. return written;
  73. }
  74. /* 使用Windows API进行编码转换 */
  75. #if defined(_WIN32)
  76. static char* GBToUTF8(const char* gb2312)
  77. {
  78. int len = MultiByteToWideChar(CP_ACP, 0, gb2312, -1, NULL, 0);
  79. wchar_t* wstr = new wchar_t[len+1];
  80. memset(wstr, 0, len+1);
  81. MultiByteToWideChar(CP_ACP, 0, gb2312, -1, wstr, len);
  82. len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL);
  83. char* str = new char[len+1];
  84. memset(str, 0, len+1);
  85. WideCharToMultiByte(CP_UTF8, 0, wstr, -1, str, len, NULL, NULL);
  86. if(wstr) delete[] wstr;
  87. return str;
  88. }
  89. #endif /* _WIN32 */
  90. /**
  91. * @brief 解析字符串文件信息
  92. *
  93. * @param strSrc 读取到的字符串
  94. * @param fileList 文件信息列表
  95. * @return true
  96. * @return false
  97. */
  98. static bool parseFileInfo(const std::string& strSrc, std::vector<CF_FileInfo>& fileInfo)
  99. {
  100. #if defined(_WIN32)
  101. auto str1 = GBToUTF8(strSrc.c_str());
  102. std::string str2 = str1;
  103. #else
  104. std::string str2 = strSrc;
  105. #endif /* _WIN32 */
  106. // FMTLOG_DEBUG("\n{}", str2);
  107. /* 正则表达式,匹配多个空格 */
  108. std::regex reg(R"(( )+)");
  109. /* 匹配以非空格开头的字符,空格隔开,中间任意字符,空格隔开,非空格组成的结尾
  110. * (文件类型) ... (文件大小,$5) ... (文件名)
  111. */
  112. std::regex reg1(R"(^([^ ]+) (\d*) (\w*) (\w*) (\d*) (.*) ([^ ]+)$)");
  113. // FMTLOG_INFO("\n-------------------\n");
  114. /* 匹配换行符,分割每一行 */
  115. std::regex reg2(R"(\n)");
  116. /* 匹配文件夹 */
  117. std::regex reg3(R"(^d.*)");
  118. /* 匹配文件 */
  119. std::regex reg4(R"(^-.*$)");
  120. /* -1 表示对正则表达式之前的子序列感兴趣
  121. * 0 表示对正则表达式本身感兴趣,输出的就是换行符 */
  122. std::sregex_token_iterator it2(str2.begin(), str2.end(), reg2, -1);
  123. /* 这里取出每一行 */
  124. for(; it2 != std::sregex_token_iterator(); ++it2)
  125. {
  126. /* 去掉多余的空格 */
  127. auto line = std::regex_replace(it2->str(), reg, " ");
  128. // FMTLOG_INFO("{}", line);
  129. CF_FileInfo fi;
  130. /* 取出文件类型 */
  131. std::string strFileType = std::regex_replace(line, reg1, "$1");
  132. if(std::regex_match(strFileType, reg3))
  133. {
  134. fi.type = CF_FileType::DIR;
  135. }else if (std::regex_match(strFileType, reg4))
  136. {
  137. fi.type = CF_FileType::FILE;
  138. }
  139. /* 取出文件大小 */
  140. std::string strFileSize = std::regex_replace(line, reg1, "$5");
  141. fi.size = std::stoull(strFileSize);
  142. /* 取出文件名 */
  143. std::string strFileName = std::regex_replace(line, reg1, "$7");
  144. fi.name = strFileName;
  145. /* 加入队列 */
  146. fileInfo.push_back(fi);
  147. }
  148. return true;
  149. }
  150. /* ==================================================================================
  151. * *********************************** 成员函数 *************************************
  152. * ================================================================================== */
  153. CurlFtp::CurlFtp()
  154. {
  155. /* 调用初始化函数,这个函数可以重复调用 */
  156. curl_global_init(CURL_GLOBAL_DEFAULT);
  157. /* 初始化curl */
  158. hasInstace = true;
  159. }
  160. CurlFtp::~CurlFtp()
  161. {
  162. /* 清理curl */
  163. curl_global_cleanup();
  164. hasInstace = false;
  165. }
  166. /**
  167. * @brief 列出FTP文件夹
  168. *
  169. * @param ftpUrl 需要列出文件夹的FTP地址
  170. * @param username
  171. * @param password
  172. * @return std::string
  173. */
  174. std::string CurlFtp::listDir(const std::string &ftpUrl, const std::string &username, const std::string &password)
  175. {
  176. CURL *curl;
  177. CURLcode res;
  178. bool result = false;
  179. std::string retList;
  180. /* 1. 初始化curl,这个函数需要在调用任何curl函数之前 */
  181. curl_global_init(CURL_GLOBAL_DEFAULT);
  182. /* 2. 获取一个curl句柄 */
  183. curl = curl_easy_init();
  184. if(curl)
  185. {
  186. /* 3. 设置curl选项 */
  187. curl_easy_setopt(curl, CURLOPT_URL, ftpUrl.c_str());
  188. curl_easy_setopt(curl, CURLOPT_USERNAME, username.c_str());
  189. curl_easy_setopt(curl, CURLOPT_PASSWORD, password.c_str());
  190. // curl_easy_setopt(curl, CURLOPT_DIRLISTONLY, 1L); // We only want the directory listing
  191. curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeStringCallback);
  192. curl_easy_setopt(curl, CURLOPT_WRITEDATA, &retList);
  193. /* 4. 发送信息,一直等待知道返回 */
  194. res = curl_easy_perform(curl);
  195. // printf("res = %d\n", res);
  196. if(res != CURLE_OK)
  197. {
  198. fprintf(stderr, "getDirList() failed, error code %d, :%s\n",res, curl_easy_strerror(res));
  199. } else
  200. {
  201. // std::cout << "Directory list: \n" << retList << std::endl;
  202. }
  203. /* 5. 清理curl */
  204. curl_easy_cleanup(curl);
  205. }
  206. if(hasInstace == false)
  207. {
  208. curl_global_cleanup();
  209. }
  210. return retList;
  211. }
  212. /* 列出文件夹中的所有文件 */
  213. bool CurlFtp::listFiles(const std::string &ftpUrl, const std::string &username, const std::string &password, std::vector<std::string>& fileList)
  214. {
  215. CURL *curl;
  216. CURLcode res;
  217. bool result = false;
  218. curl_global_init(CURL_GLOBAL_DEFAULT);
  219. curl = curl_easy_init();
  220. if(curl)
  221. {
  222. curl_easy_setopt(curl, CURLOPT_URL, (ftpUrl).c_str());
  223. curl_easy_setopt(curl, CURLOPT_USERNAME, username.c_str());
  224. curl_easy_setopt(curl, CURLOPT_PASSWORD, password.c_str());
  225. curl_easy_setopt(curl, CURLOPT_DIRLISTONLY, 1L);
  226. curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeStringListCallback);
  227. curl_easy_setopt(curl, CURLOPT_WRITEDATA, &fileList);
  228. res = curl_easy_perform(curl);
  229. if(res != CURLE_OK)
  230. {
  231. fprintf(stderr, "Failed to get file list, error code :%d ,%s\n", res, curl_easy_strerror(res));
  232. }
  233. else
  234. {
  235. // for(const std::string& filename : fileList)
  236. // {
  237. // printf("%s\n", filename.c_str());
  238. // }
  239. result = true;
  240. }
  241. curl_easy_cleanup(curl);
  242. }
  243. if(hasInstace == false)
  244. {
  245. curl_global_cleanup();
  246. }
  247. return result;
  248. }
  249. /* 创建文件夹 */
  250. bool CurlFtp::createDir(const std::string &ftpUrl, const std::string &username, const std::string &password, const std::string &dirName)
  251. {
  252. CURL *curl;
  253. CURLcode res;
  254. bool result = false;
  255. curl_global_init(CURL_GLOBAL_DEFAULT);
  256. curl = curl_easy_init();
  257. if(curl)
  258. {
  259. curl_easy_setopt(curl, CURLOPT_URL, ftpUrl.c_str());
  260. curl_easy_setopt(curl, CURLOPT_USERNAME, username.c_str());
  261. curl_easy_setopt(curl, CURLOPT_PASSWORD, password.c_str());
  262. // Create a list of FTP commands to be executed on the server
  263. struct curl_slist *headerlist = NULL;
  264. std::string mkdir = "MKD " + dirName;
  265. headerlist = curl_slist_append(headerlist, mkdir.c_str());
  266. // Set the list of FTP commands to be executed on the server before the transfer
  267. curl_easy_setopt(curl, CURLOPT_QUOTE, headerlist);
  268. // Tell libcurl to not include the body in the output
  269. curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);
  270. res = curl_easy_perform(curl);
  271. if(res != CURLE_OK)
  272. {
  273. fprintf(stderr, "createDir() failed, error code :%d ,%s\n", res, curl_easy_strerror(res));
  274. result = false;
  275. }else
  276. {
  277. result = true;
  278. }
  279. curl_easy_cleanup(curl);
  280. curl_slist_free_all(headerlist);
  281. }
  282. if(hasInstace == false)
  283. {
  284. curl_global_cleanup();
  285. }
  286. return result;
  287. }
  288. /* 设置IP和端口 */
  289. bool CurlFtp::setFtpIPAndPort(const std::string& IP, const int port)
  290. {
  291. /*先判断是否有ftp器前缀,通过正则表达式,取出IP地址和端口号,去掉前缀和后面的'/ */
  292. m_IP = IP;
  293. m_port = port;
  294. m_ftpUrl = "ftp://" + m_IP + ":" + std::to_string(m_port);
  295. FMTLOG_INFO("ftpUrl = {}", m_ftpUrl);
  296. return true;
  297. }
  298. /* 设置用户名和密码 */
  299. bool CurlFtp::setFtpUsernameAndPassword(const std::string& username, const std::string& password)
  300. {
  301. m_username = username;
  302. m_password = password;
  303. return true;
  304. }
  305. /* 列出文件列表 */
  306. bool CurlFtp::getFileList(std::string dir, std::vector<std::string>& fileList)
  307. {
  308. if(m_IP.empty())
  309. {
  310. FMTLOG_WARN("IP or port is empty");
  311. return false;
  312. }
  313. /* 检查dir,添加前缀“/” */
  314. auto dirTmp = checkDirPath(dir);
  315. CURL *curl = nullptr;
  316. curl = curl_easy_init();
  317. if(curl == nullptr)
  318. {
  319. FMTLOG_ERROR("curl init failed !");
  320. return false;
  321. }
  322. std::vector<CF_FileInfo> listInfo;
  323. /* 获取文件信息 */
  324. listAll(curl, dirTmp, listInfo);
  325. for(const CF_FileInfo& fi : listInfo)
  326. {
  327. // FMTLOG_INFO("type = {}, size = {}, name = {}", (int)fi.type, fi.size, fi.name);
  328. if(fi.type == CF_FileType::FILE)
  329. {
  330. fileList.push_back(fi.name);
  331. }
  332. }
  333. curl_easy_cleanup(curl);
  334. return true;
  335. }
  336. /* 获取文件夹列表 */
  337. bool CurlFtp::getDirList(std::string dir, std::vector<std::string>& dirList)
  338. {
  339. if(m_IP.empty())
  340. {
  341. FMTLOG_WARN("IP or port is empty");
  342. return false;
  343. }
  344. /* 检查dir,添加前缀“/” */
  345. auto dirTmp = checkDirPath(dir);
  346. CURL *curl = nullptr;
  347. curl = curl_easy_init();
  348. if(curl == nullptr)
  349. {
  350. FMTLOG_ERROR("curl init failed !");
  351. return false;
  352. }
  353. std::vector<CF_FileInfo> listInfo;
  354. /* 获取文件信息 */
  355. listAll(curl, dirTmp, listInfo);
  356. for(const CF_FileInfo& fi : listInfo)
  357. {
  358. // FMTLOG_INFO("type = {}, size = {}, name = {}", (int)fi.type, fi.size, fi.name);
  359. if(fi.type == CF_FileType::DIR)
  360. {
  361. dirList.push_back(fi.name);
  362. }
  363. }
  364. curl_easy_cleanup(curl);
  365. return true;
  366. }
  367. /* 判断文件夹是否存在 */
  368. bool CurlFtp::isDirExist(const std::string& dir)
  369. {
  370. if(m_ftpUrl.empty())
  371. {
  372. FMTLOG_ERROR("ftpUrl is empty");
  373. return false;
  374. }
  375. /* 检查传入的文件夹 */
  376. auto dirTmp = checkDirPath(dir);
  377. CURL* curl = nullptr;
  378. curl = curl_easy_init();
  379. if(curl == nullptr)
  380. {
  381. FMTLOG_ERROR("curl init failed !");
  382. return false;
  383. }
  384. std::string ftpUrl = m_ftpUrl + dirTmp;
  385. curl_easy_setopt(curl, CURLOPT_URL, ftpUrl.c_str());
  386. curl_easy_setopt(curl, CURLOPT_USERNAME, m_username.c_str());
  387. curl_easy_setopt(curl, CURLOPT_PASSWORD, m_password.c_str());
  388. /* 获取文件夹是否存在,不需要接收文件 */
  389. curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);
  390. CURLcode res = curl_easy_perform(curl);
  391. bool result = true;
  392. if(res != CURLE_OK)
  393. {
  394. FMTLOG_ERROR("Failed to get file list, error code: {} ,{}", (int)res, curl_easy_strerror(res));
  395. result = false;
  396. }
  397. return result;
  398. }
  399. /* 下载文件 */
  400. bool CurlFtp::downloadFile(const std::string& remoteFile, const std::string& localFile)
  401. {
  402. if(m_ftpUrl.empty())
  403. {
  404. FMTLOG_ERROR("ftpUrl is empty");
  405. return false;
  406. }
  407. CURL* curl = nullptr;
  408. curl = curl_easy_init();
  409. if(curl == nullptr)
  410. {
  411. FMTLOG_ERROR("curl init failed !");
  412. return false;
  413. }
  414. /* 检查传入的文件是否符合规范 */
  415. std::string ftpUrl = m_ftpUrl + remoteFile;
  416. return true;
  417. }
  418. /**
  419. * @brief 列出文件列表,这个需要的参数很多,无法设置成静态函数
  420. *
  421. * @param curl CURL句柄
  422. * @param dir 文件夹,相对路径,不带有IP和端口号
  423. * @param fileList 返回值,文件列表
  424. * @return true
  425. * @return false
  426. */
  427. bool CurlFtp::listAll(CURL* curl, std::string dir, std::vector<CF_FileInfo>& fileInfoList)
  428. {
  429. if(m_IP.empty())
  430. {
  431. FMTLOG_ERROR("IP is empty");
  432. return false;
  433. }
  434. if(curl == nullptr)
  435. {
  436. FMTLOG_ERROR("curl is nullptr");
  437. return false;
  438. }
  439. bool result = false;
  440. std::string strSrc;
  441. /* 先设置FTP地址 */
  442. std::string ftpUrl = m_ftpUrl + dir;
  443. curl_easy_setopt(curl, CURLOPT_URL, ftpUrl.c_str());
  444. curl_easy_setopt(curl, CURLOPT_PORT, m_port);
  445. /* 设置用户名和密码 */
  446. curl_easy_setopt(curl, CURLOPT_USERNAME, m_username.c_str());
  447. curl_easy_setopt(curl, CURLOPT_PASSWORD, m_password.c_str());
  448. /* 设置列出文件命令,只列出文件名称,不携带信息 */
  449. // curl_easy_setopt(m_curl, CURLOPT_DIRLISTONLY, 1L);
  450. /* 设置回调函数 */
  451. curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeStringCallback);
  452. /* 设置需要写入的容器,回调函数的第四个参数 */
  453. curl_easy_setopt(curl, CURLOPT_WRITEDATA, &strSrc);
  454. /* 设置超时时间 */
  455. curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L);
  456. curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L);
  457. /* 发送请求 */
  458. CURLcode res = curl_easy_perform(curl);
  459. if(res != CURLE_OK)
  460. {
  461. FMTLOG_ERROR("Failed to get file list, error code: {} ,{}", (int)res, curl_easy_strerror(res));
  462. FMTLOG_ERROR("ftpUrl = {}", ftpUrl);
  463. return false;
  464. }
  465. /* 解析字符串 */
  466. parseFileInfo(strSrc, fileInfoList);
  467. return true;
  468. }
  469. /* 检查文件夹路径是否合规,不合规就修改 */
  470. std::string CurlFtp::checkDirPath(const std::string& dir)
  471. {
  472. std::string dirTmp = dir;
  473. /* 检查是否以“/”开头 */
  474. std::regex reg(R"(^/[.]*)");
  475. if(!std::regex_match(dirTmp, reg))
  476. {
  477. dirTmp = "/" + dirTmp;
  478. }
  479. /* 如果有重复的“/”,替换成一个 “/” */
  480. std::regex reg1(R"(//)");
  481. dirTmp = std::regex_replace(dirTmp, reg1, "/");
  482. /* 检查是否以“/”结束 */
  483. std::regex reg2(R"([.]*/$)");
  484. if(!std::regex_match(dirTmp, reg2))
  485. {
  486. dirTmp = dirTmp + "/";
  487. }
  488. return dirTmp;
  489. }
  490. /* 检查文件路径是否合规 */
  491. std::string CurlFtp::checkFilePath(const std::string& file)
  492. {
  493. std::string dirTmp = file;
  494. /* 检查是否以“/”结束,如果是以“/”结尾,返回空字符串,并报错 */
  495. std::regex reg2(R"([.]*/$)");
  496. if(std::regex_match(dirTmp, reg2))
  497. {
  498. FMTLOG_ERROR("File path is not correct, end with '/'");
  499. return std::string();
  500. }
  501. /* 检查是否以“/”开头 */
  502. std::regex reg(R"(^/[.]*)");
  503. if(!std::regex_match(dirTmp, reg))
  504. {
  505. dirTmp = "/" + dirTmp;
  506. }
  507. /* 如果有重复的“/”,替换成一个 “/” */
  508. std::regex reg1(R"(//)");
  509. dirTmp = std::regex_replace(dirTmp, reg1, "/");
  510. return dirTmp;
  511. }