-
Notifications
You must be signed in to change notification settings - Fork 69
/
Copy pathFTPClient.cpp
1459 lines (1220 loc) · 46.3 KB
/
FTPClient.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**
* @file FTPClient.cpp
* @brief implementation of the FTP client class
* @author Mohamed Amine Mzoughi <mohamed-amine.mzoughi@laposte.net>
*/
#include "FTPClient.h"
#include <iterator>
#include <stdexcept>
#define UNUSED(x) static_cast<void>(x);
namespace embeddedmz {
// Static members initialization
#ifdef DEBUG_CURL
std::string CFTPClient::s_strCurlTraceLogDirectory;
#endif
/**
* @brief constructor of the FTP client object
*
* @param Logger - a callabck to a logger function void(const std::string&)
*
*/
CFTPClient::CFTPClient(LogFnCallback Logger)
:
m_bActive(false),
m_bNoSignal(true),
m_bInsecure(false),
m_uPort(0),
m_eFtpProtocol(FTP_PROTOCOL::FTP),
m_eSettingsFlags(NO_FLAGS),
m_pCurlSession(nullptr),
m_iCurlTimeout(0),
m_bProgressCallbackSet(false),
m_oLog(std::move(Logger)),
m_curlHandle(CurlHandle::instance())
{
if (!m_oLog) {
throw std::logic_error{"Invalid logger clb applied"};
}
}
/**
* @brief destructor of the FTP client object
*
*/
CFTPClient::~CFTPClient() {
if (m_pCurlSession != nullptr) {
if (m_eSettingsFlags & ENABLE_LOG) m_oLog(LOG_WARNING_OBJECT_NOT_CLEANED);
CleanupSession();
}
}
/**
* @brief starts a new FTP session, initializes the cURL API session
*
* if a new session was already started, the method has no effect.
*
* @param [in] strHost server address
* @param [in] uPort the remote port
* @param [in] strLogin username
* @param [in] strPassword password
* @param [in] eFtpProtocol the protocol used to connect to the server (FTP,
* FTPS, FTPES, SFTP)
* @param [in] eSettingsFlags optional use | operator to choose multiple options
*
* @retval true Successfully initialized the session.
* @retval false The session is already initialized.
* call CleanupSession() before initializing a new one or the Curl API is not
* initialized.
*
* Example Usage:
* @code
* m_pFTPClient->InitSession("ftp://127.0.0.1", 21, "username", "password");
* @endcode
*/
bool CFTPClient::InitSession(const std::string &strHost, const unsigned &uPort, const std::string &strLogin,
const std::string &strPassword, const FTP_PROTOCOL &eFtpProtocol /* = FTP */,
const SettingsFlag &eSettingsFlags /* = NO_FLAGS */) {
if (strHost.empty()) {
if (m_eSettingsFlags & ENABLE_LOG) m_oLog(LOG_ERROR_EMPTY_HOST_MSG);
return false;
}
if (m_pCurlSession) {
if (m_eSettingsFlags & ENABLE_LOG) m_oLog(LOG_ERROR_CURL_ALREADY_INIT_MSG);
return false;
}
m_pCurlSession = curl_easy_init();
m_strServer = strHost;
m_uPort = uPort;
m_strUserName = strLogin;
m_strPassword = strPassword;
m_eFtpProtocol = eFtpProtocol;
m_eSettingsFlags = eSettingsFlags;
return (m_pCurlSession != nullptr);
}
/**
* @brief cleans the current FTP session
*
* if a session was not already started, the method has no effect
*
* @retval true Successfully cleaned the current session.
* @retval false The session is not initialized.
*
* Example Usage:
* @code
* objFTPClient.CleanupSession();
* @endcode
*/
bool CFTPClient::CleanupSession() {
if (!m_pCurlSession) {
if (m_eSettingsFlags & ENABLE_LOG) m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG);
return false;
}
#ifdef DEBUG_CURL
if (m_ofFileCurlTrace.is_open()) {
m_ofFileCurlTrace.close();
}
#endif
curl_easy_cleanup(m_pCurlSession);
m_pCurlSession = nullptr;
return true;
}
/**
* @brief sets the progress function callback and the owner of the client
*
* @param [in] pOwner pointer to the object owning the client, nullptr otherwise
* @param [in] fnCallback callback to progress function
*
*/
void CFTPClient::SetProgressFnCallback(void *pOwner, const ProgressFnCallback &fnCallback, const bool enable /*= true*/) {
m_ProgressStruct.pOwner = pOwner;
m_fnProgressCallback = fnCallback;
m_ProgressStruct.pCurl = m_pCurlSession;
m_ProgressStruct.dLastRunTime = 0;
m_bProgressCallbackSet = enable;
}
/**
* @brief sets the HTTP Proxy address to tunnel the operation through it
*
* @param [in] strProxy URI of the HTTP Proxy
*
*/
void CFTPClient::SetProxy(const std::string &strProxy) {
if (strProxy.empty()) return;
std::string strUri = strProxy;
std::transform(strUri.begin(), strUri.end(), strUri.begin(), ::toupper);
if (strUri.compare(0, 5, "HTTP:") != 0)
m_strProxy = "http://" + strProxy;
else
m_strProxy = strProxy;
};
/**
* @brief sets the HTTP Proxy user and password
*
* @param [in] strProxyUserPwd string of the HTTP Proxy user:password
*
*/
void CFTPClient::SetProxyUserPwd(const std::string &strProxyUserPwd) {
m_strProxyUserPwd = strProxyUserPwd;
};
/**
* @brief generates a URI
*
* '/' will be duplicated to be interpreted correctly by the Curl API.
*
* @param [in] strRemoteFile URL of the file.
*
* @retval std::string A complete URI containing the requested resource.
*
* Example Usage:
* @code
* ParseURL("documents/info.txt"); //returns
* "ftp://127.0.0.1//documents//info.txt"
* @endcode
*/
std::string CFTPClient::ParseURL(const std::string &strRemoteFile) const {
std::string strURL = m_strServer + "/" + strRemoteFile;
ReplaceString(strURL, "/", "//");
ReplaceString(strURL, " ", "%20"); //fixes folders with spaces not working
std::string strUri = strURL;
// boost::to_upper(strUri);
std::transform(strUri.begin(), strUri.end(), strUri.begin(), ::toupper);
if (strUri.compare(0, 4, "FTP:") != 0 && strUri.compare(0, 5, "SFTP:") != 0) {
switch (m_eFtpProtocol) {
case FTP_PROTOCOL::FTP:
case FTP_PROTOCOL::FTPES:
default:
strURL = "ftp://" + strURL;
break;
case FTP_PROTOCOL::FTPS:
strURL = "ftps://" + strURL;
break;
case FTP_PROTOCOL::SFTP:
strURL = "sftp://" + strURL;
break;
}
}
return strURL;
}
/**
* @brief creates a remote directory
*
* @param [in] strNewDir the remote direcotry to be created encoded in UTF-8 format.
*
* @retval true Successfully created a directory.
* @retval false The directory couldn't be created.
*
* Example Usage:
* @code
* m_pFTPClient->CreateDir("upload/bookmarks");
* // Will create bookmarks directory under the upload dir (must exist).
* @endcode
*/
bool CFTPClient::CreateDir(const std::string &strNewDir) const {
if (strNewDir.empty()) return false;
if (!m_pCurlSession) {
if (m_eSettingsFlags & ENABLE_LOG) m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG);
return false;
}
// Reset is mandatory to avoid bad surprises
curl_easy_reset(m_pCurlSession);
struct curl_slist *headerlist = nullptr;
std::string strRemoteFolder;
std::string strRemoteNewFolderName;
std::string strBuf;
bool bRet = false;
if (m_eFtpProtocol == FTP_PROTOCOL::SFTP) {
strRemoteFolder = ParseURL("");
strRemoteNewFolderName = strNewDir;
// Append the rmdir command
strBuf += "mkdir ";
} else {
// Splitting folder name
std::size_t uFound = strNewDir.find_last_of("/");
if (uFound != std::string::npos) {
strRemoteFolder = ParseURL(strNewDir.substr(0, uFound)) + "//";
strRemoteNewFolderName = strNewDir.substr(uFound + 1);
} else // the dir. to be created is located in the root directory
{
strRemoteFolder = ParseURL("");
strRemoteNewFolderName = strNewDir;
}
// Append the MKD command
strBuf += "MKD ";
}
// Specify target
curl_easy_setopt(m_pCurlSession, CURLOPT_URL, strRemoteFolder.c_str());
strBuf += strRemoteNewFolderName;
headerlist = curl_slist_append(headerlist, strBuf.c_str());
curl_easy_setopt(m_pCurlSession, CURLOPT_POSTQUOTE, headerlist);
curl_easy_setopt(m_pCurlSession, CURLOPT_NOBODY, 1L);
curl_easy_setopt(m_pCurlSession, CURLOPT_HEADER, 1L);
curl_easy_setopt(m_pCurlSession, CURLOPT_FTP_CREATE_MISSING_DIRS, CURLFTP_CREATE_DIR);
/* enable TCP keep-alive for this transfer */
curl_easy_setopt(m_pCurlSession, CURLOPT_TCP_KEEPALIVE, 0L);
CURLcode res = Perform();
// Check for errors
if (res != CURLE_OK) {
if (m_eSettingsFlags & ENABLE_LOG)
m_oLog(StringFormat(LOG_ERROR_CURL_MKDIR_FORMAT, strRemoteNewFolderName.c_str(), res, curl_easy_strerror(res)));
} else
bRet = true;
// clean up the FTP commands list
curl_slist_free_all(headerlist);
return bRet;
}
/**
* @brief removes an empty remote directory
*
* if the remote directory ain't empty, the method will fail.
* yhe user must use RemoveFile() on all directory's files then use RemoveDir to
* delete the latter.
*
* @param [in] strDir the remote direcotry to be removed encoded in UTF-8 format.
*
* @retval true Successfully removed a directory.
* @retval false The directory couldn't be removed.
*
* Example Usage:
* @code
* m_pFTPClient->RemoveDir("upload/bookmarks");
* // Will remove bookmarks directory, upload must exist.
* @endcode
*/
bool CFTPClient::RemoveDir(const std::string &strDir) const {
if (strDir.empty()) return false;
if (!m_pCurlSession) {
if (m_eSettingsFlags & ENABLE_LOG) m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG);
return false;
}
// Reset is mandatory to avoid bad surprises
curl_easy_reset(m_pCurlSession);
struct curl_slist *headerlist = nullptr;
std::string strRemoteFolder;
std::string strRemoteFolderName;
std::string strBuf;
bool bRet = false;
if (m_eFtpProtocol == FTP_PROTOCOL::SFTP) {
strRemoteFolder = ParseURL("");
strRemoteFolderName = strDir;
// Append the rmdir command
strBuf += "rmdir ";
} else {
// Splitting folder name
std::size_t uFound = strDir.find_last_of("/");
if (uFound != std::string::npos) {
strRemoteFolder = ParseURL(strDir.substr(0, uFound)) + "//";
strRemoteFolderName = strDir.substr(uFound + 1);
} else // the dir. to be removed is located in the root directory
{
strRemoteFolder = ParseURL("");
strRemoteFolderName = strDir;
}
// Append the rmd command
strBuf += "RMD ";
}
// Specify target
curl_easy_setopt(m_pCurlSession, CURLOPT_URL, strRemoteFolder.c_str());
strBuf += strRemoteFolderName;
headerlist = curl_slist_append(headerlist, strBuf.c_str());
curl_easy_setopt(m_pCurlSession, CURLOPT_POSTQUOTE, headerlist);
curl_easy_setopt(m_pCurlSession, CURLOPT_NOBODY, 1L);
curl_easy_setopt(m_pCurlSession, CURLOPT_HEADER, 1L);
CURLcode res = Perform();
if (res != CURLE_OK) {
if (m_eSettingsFlags & ENABLE_LOG)
m_oLog(StringFormat(LOG_ERROR_CURL_RMDIR_FORMAT, strRemoteFolderName.c_str(), res, curl_easy_strerror(res)));
} else
bRet = true;
// clean up the FTP commands list
curl_slist_free_all(headerlist);
return bRet;
}
/**
* @brief deletes a remote file
*
* @param [in] strRemoteFile the URL of the remote file encoded in UTF-8 format.
*
* @retval true Successfully deleted the file.
* @retval false The file couldn't be deleted.
*
* Example Usage:
* @code
* m_pFTPClient->RemoveFile("documents/Config.txt");
* // e.g. : Will delete ftp://127.0.0.1/documents/Config.txt
* @endcode
*/
bool CFTPClient::RemoveFile(const std::string &strRemoteFile) const {
if (strRemoteFile.empty()) return false;
if (!m_pCurlSession) {
if (m_eSettingsFlags & ENABLE_LOG) m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG);
return false;
}
// Reset is mandatory to avoid bad surprises
curl_easy_reset(m_pCurlSession);
struct curl_slist *headerlist = nullptr;
std::string strRemoteFolder;
std::string strRemoteFileName;
std::string strBuf;
bool bRet = false;
if (m_eFtpProtocol == FTP_PROTOCOL::SFTP) {
strRemoteFolder = ParseURL("");
strRemoteFileName = strRemoteFile;
// Append the rm command
strBuf += "rm ";
} else {
// Splitting file name
std::size_t uFound = strRemoteFile.find_last_of("/");
if (uFound != std::string::npos) {
strRemoteFolder = ParseURL(strRemoteFile.substr(0, uFound)) + "//";
strRemoteFileName = strRemoteFile.substr(uFound + 1);
} else // the file to be deleted is located in the root directory
{
strRemoteFolder = ParseURL("");
strRemoteFileName = strRemoteFile;
}
// Append the delete command
strBuf += "DELE ";
}
// Specify target
curl_easy_setopt(m_pCurlSession, CURLOPT_URL, strRemoteFolder.c_str());
strBuf += strRemoteFileName;
headerlist = curl_slist_append(headerlist, strBuf.c_str());
curl_easy_setopt(m_pCurlSession, CURLOPT_POSTQUOTE, headerlist);
curl_easy_setopt(m_pCurlSession, CURLOPT_NOBODY, 1L);
curl_easy_setopt(m_pCurlSession, CURLOPT_HEADER, 1L);
CURLcode res = Perform();
// Check for errors
if (res != CURLE_OK) {
if (m_eSettingsFlags & ENABLE_LOG)
m_oLog(StringFormat(LOG_ERROR_CURL_REMOVE_FORMAT, strRemoteFile.c_str(), res, curl_easy_strerror(res)));
} else
bRet = true;
// clean up the FTP commands list
curl_slist_free_all(headerlist);
return bRet;
}
/**
* @brief requests the mtime (epoch) and the size of a remote file.
*
* @param [in] strRemoteFile URN of the remote file encoded in UTF-8 format.
* @param [out] oFileInfo time_t will be updated with the file's mtime and size.
*
* @retval true Successfully gathered the file info time and updated
* "oFileInfo"
* @retval false The infos couldn't be requested.
*
* Example Usage:
* @code
* struct FileInfo& oFileInfo;
* bool bRes = oFTPClient.GetFileTime("pictures/muahahaha.jpg", tFileTime);
* if (bRes) { assert(oFileInfo.tFileMTime > 0); assert(oFileInfo.dFileSize >
* 0); } std::cout << ctime(oFileInfo.tFileMTime) << std::endl; // Sat Aug 06
* 15:04:45 2016
* @endcode
*/
bool CFTPClient::Info(const std::string &strRemoteFile, struct FileInfo &oFileInfo) const {
if (strRemoteFile.empty()) return false;
if (!m_pCurlSession) {
if (m_eSettingsFlags & ENABLE_LOG) m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG);
return false;
}
// Reset is mandatory to avoid bad surprises
curl_easy_reset(m_pCurlSession);
bool bRes = false;
oFileInfo.tFileMTime = 0;
oFileInfo.dFileSize = 0.0;
curl_easy_setopt(m_pCurlSession, CURLOPT_URL, ParseURL(strRemoteFile).c_str());
/* No download if the file */
curl_easy_setopt(m_pCurlSession, CURLOPT_NOBODY, 1L);
/* Ask for filetime */
curl_easy_setopt(m_pCurlSession, CURLOPT_FILETIME, 1L);
/* No header output: TODO 14.1 http-style HEAD output for ftp */
curl_easy_setopt(m_pCurlSession, CURLOPT_HEADERFUNCTION, ThrowAwayCallback);
curl_easy_setopt(m_pCurlSession, CURLOPT_HEADER, 0L);
CURLcode res = Perform();
if (CURLE_OK == res) {
long lFileTime = -1;
res = curl_easy_getinfo(m_pCurlSession, CURLINFO_FILETIME, &lFileTime);
if (CURLE_OK == res && lFileTime >= 0) {
oFileInfo.tFileMTime = static_cast<time_t>(lFileTime);
bRes = true;
}
res = curl_easy_getinfo(m_pCurlSession, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &oFileInfo.dFileSize);
if (CURLE_OK != res || oFileInfo.dFileSize < 0.0) {
bRes = false;
}
} else if (m_eSettingsFlags & ENABLE_LOG)
m_oLog(StringFormat(LOG_ERROR_CURL_FILETIME_FORMAT, strRemoteFile.c_str(), res, curl_easy_strerror(res)));
return bRes;
}
/**
* @brief lists a remote folder
* the list can contain only names or can be detailed
* entries/names will be separated with LF ('\n')
*
* @param [in] strRemoteFolder URL of the remote location to be listed encoded in UTF-8 format.
* @param [out] strList will contain the directory entries (detailed or not) encoded in UTF-8 format.
* @param [in] bOnlyNames detailed list or only names
*
* @retval true Successfully listed the remote folder and updated FtpFiles.
* @retval false The remote folder couldn't be listed.
*
* Example Usage:
* @code
* std::string strList;
* m_pFTPClient->GetFileList("/", strList, false);
* // lists the root of the remote FTP server with all the infos.
* @endcode
*/
bool CFTPClient::List(const std::string &strRemoteFolder, std::string &strList, bool bOnlyNames /* = true */) const {
if (strRemoteFolder.empty()) return false;
if (!m_pCurlSession) {
if (m_eSettingsFlags & ENABLE_LOG) m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG);
return false;
}
// Reset is mandatory to avoid bad surprises
curl_easy_reset(m_pCurlSession);
bool bRet = false;
std::string strRemoteFile = ParseURL(strRemoteFolder);
curl_easy_setopt(m_pCurlSession, CURLOPT_URL, strRemoteFile.c_str());
if (bOnlyNames) curl_easy_setopt(m_pCurlSession, CURLOPT_DIRLISTONLY, 1L);
curl_easy_setopt(m_pCurlSession, CURLOPT_WRITEFUNCTION, WriteInStringCallback);
curl_easy_setopt(m_pCurlSession, CURLOPT_WRITEDATA, &strList);
CURLcode res = Perform();
if (CURLE_OK == res)
bRet = true;
else if (m_eSettingsFlags & ENABLE_LOG)
m_oLog(StringFormat(LOG_ERROR_CURL_FILELIST_FORMAT, strRemoteFolder.c_str(), res, curl_easy_strerror(res)));
return bRet;
}
/**
* @brief downloads a remote file
*
* @param [in] strLocalFile complete path of the downloaded file encoded in UTF-8 format.
* @param [in] strRemoteFile URL of the remote file encoded in UTF-8 format.
*
* @retval true Successfully downloaded the file.
* @retval false The file couldn't be downloaded. Check the log messages for
* more information.
*
* Example Usage:
* @code
* m_pFTPClient->DownloadFile("C:\\Items_To_Upload\\imagination.jpg",
* "upload/pictures/imagination.jpg");
* @endcode
*/
bool CFTPClient::DownloadFile(const std::string &strLocalFile, const std::string &strRemoteFile) const {
if (strLocalFile.empty() || strRemoteFile.empty()) return false;
if (!m_pCurlSession) {
if (m_eSettingsFlags & ENABLE_LOG) m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG);
return false;
}
// Reset is mandatory to avoid bad surprises
curl_easy_reset(m_pCurlSession);
bool bRet = false;
std::string strFile = ParseURL(strRemoteFile);
std::ofstream ofsOutput;
ofsOutput.open(
#ifdef LINUX
strLocalFile, // UTF-8
#else
Utf8ToUtf16(strLocalFile),
#endif
std::ofstream::out | std::ofstream::binary | std::ofstream::trunc);
if (ofsOutput) {
curl_easy_setopt(m_pCurlSession, CURLOPT_URL, strFile.c_str());
curl_easy_setopt(m_pCurlSession, CURLOPT_WRITEFUNCTION, WriteToFileCallback);
curl_easy_setopt(m_pCurlSession, CURLOPT_WRITEDATA, &ofsOutput);
CURLcode res = Perform();
if (res != CURLE_OK) {
if (m_eSettingsFlags & ENABLE_LOG)
m_oLog(StringFormat(LOG_ERROR_CURL_GETFILE_FORMAT, m_strServer.c_str(), strRemoteFile.c_str(), res, curl_easy_strerror(res)));
} else
bRet = true;
ofsOutput.close();
if (!bRet) remove(strLocalFile.c_str());
} else if (m_eSettingsFlags & ENABLE_LOG)
m_oLog(LOG_ERROR_FILE_GETFILE_FORMAT);
return bRet;
}
/**
* @brief downloads a remote file to memory
*
* @param [in] strRemoteFile URI of remote file encoded in UTF-8 format.
* @param [out] data vector of bytes
*
* @retval true Successfully downloaded the file.
* @retval false The file couldn't be downloaded. Check the log messages for
* more information.
*/
bool CFTPClient::DownloadFile(const std::string &strRemoteFile, std::vector<char> &data) const {
if (strRemoteFile.empty()) return false;
if (!m_pCurlSession) {
if (m_eSettingsFlags & ENABLE_LOG) m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG);
return false;
}
curl_easy_reset(m_pCurlSession);
std::string strFile = ParseURL(strRemoteFile);
data.clear();
curl_easy_setopt(m_pCurlSession, CURLOPT_URL, strFile.c_str());
curl_easy_setopt(m_pCurlSession, CURLOPT_WRITEFUNCTION, WriteToMemory);
curl_easy_setopt(m_pCurlSession, CURLOPT_WRITEDATA, &data);
CURLcode res = Perform();
if (res != CURLE_OK) {
if (m_eSettingsFlags & ENABLE_LOG)
m_oLog(StringFormat(LOG_ERROR_CURL_GETFILE_FORMAT, m_strServer.c_str(), strRemoteFile.c_str(), res, curl_easy_strerror(res)));
return false;
} else
return true;
}
/**
* @brief downloads all elements according that match the wildcarded URL
*
* @param [in] strLocalFile Complete path where the elements will be downloaded encoded in UTF-8 format.
* @param [in] strRemoteWildcard Wildcarded pattern to be downloaded encoded in UTF-8 format.
*
* @retval true All the elements have been downloaded.
* @retval false Some or all files or dir have not been downloaded or resp.
* created.
*
* Example Usage:
* @code
* m_pFTPClient->DownloadWildcard("", "");
* @endcode
*/
bool CFTPClient::DownloadWildcard(const std::string &strLocalDir, const std::string &strRemoteWildcard) const {
if (strLocalDir.empty() || strRemoteWildcard.empty()) return false;
if (!m_pCurlSession) {
if (m_eSettingsFlags & ENABLE_LOG) m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG);
return false;
}
// Reset is mandatory to avoid bad surprises
curl_easy_reset(m_pCurlSession);
bool bRet = false;
WildcardTransfersCallbackData data;
#ifdef LINUX
data.strOutputPath = strLocalDir + ((strLocalDir.back() != '/') ? "/" : "");
#else
data.strOutputPath = strLocalDir + ((strLocalDir.back() != '\\') ? "\\" : "");
#endif
std::string strPattern = ParseURL(strRemoteWildcard);
struct stat info;
if (stat(data.strOutputPath.c_str(), &info) == 0 && (info.st_mode & S_IFDIR)) // S_ISDIR() doesn't exist on windows
{
/* 0. turn on wildcard matching */
curl_easy_setopt(m_pCurlSession, CURLOPT_WILDCARDMATCH, 1L);
/* 1. callback is called before download of concrete file started */
curl_easy_setopt(m_pCurlSession, CURLOPT_CHUNK_BGN_FUNCTION, FileIsComingCallback);
/* 2. this callback will write contents into files */
curl_easy_setopt(m_pCurlSession, CURLOPT_WRITEFUNCTION, WriteItCallback);
/* 3. callback is called after data from the file have been transferred */
curl_easy_setopt(m_pCurlSession, CURLOPT_CHUNK_END_FUNCTION, FileIsDownloadedCallback);
/* put transfer data into callbacks */
curl_easy_setopt(m_pCurlSession, CURLOPT_CHUNK_DATA, &data);
curl_easy_setopt(m_pCurlSession, CURLOPT_WRITEDATA, &data);
/* curl_easy_setopt(m_pCurlSession, CURLOPT_VERBOSE, 1L); */
/* set an URL containing wildcard pattern (only in the last part) */
curl_easy_setopt(m_pCurlSession, CURLOPT_URL, strPattern.c_str());
/* and start transfer! */
CURLcode res = Perform();
/* in case we have an empty FTP folder, error 78 will be returned */
if (res != CURLE_OK && res != CURLE_REMOTE_FILE_NOT_FOUND) {
if (m_eSettingsFlags & ENABLE_LOG)
m_oLog(
StringFormat(LOG_ERROR_CURL_GETWILD_FORMAT, m_strServer.c_str(), strRemoteWildcard.c_str(), res, curl_easy_strerror(res)));
}
/* folders need to be copied integrally */
else if (!data.vecDirList.empty() && strRemoteWildcard.back() == '*') {
std::string strBaseUrl = strRemoteWildcard.substr(0, strRemoteWildcard.length() - 1);
if (!strBaseUrl.empty() && strBaseUrl.back() != '/') strBaseUrl += "/";
// recursively download directories
bRet = true;
for (const auto &Dir : data.vecDirList) {
if ((Dir == ".") || (Dir == "..")) continue;
if (!DownloadWildcard(data.strOutputPath + Dir, strBaseUrl + Dir + "/*")) {
m_oLog(
StringFormat(LOG_ERROR_CURL_GETWILD_REC_FORMAT, (strBaseUrl + Dir + "/*").c_str(), (data.strOutputPath + Dir).c_str()));
bRet = false;
}
}
} else
bRet = true;
} else if (m_eSettingsFlags & ENABLE_LOG)
m_oLog(StringFormat(LOG_ERROR_DIR_GETWILD_FORMAT, data.strOutputPath.c_str()));
return bRet;
}
/**
* @brief uploads a user data using readFn to a remote folder.
*
* @param [in] readFn Reading function, corresponds to <a href="https://curl.se/libcurl/c/CURLOPT_READFUNCTION.html">CURLOPT_READFUNCTION</a>.
* @param [in] userData user data passed to readFn as last parameter.
* @param [in] strRemoteFile Complete URN of the remote location (with the file
* name) encoded in UTF-8 format.
* @param [in] bCreateDir Enable or disable creation of remote missing
* directories contained in the URN.
*
* @retval true Data successfully uploaded.
* @retval false Data couldn't be uploaded. Check the log messages for more
* information.
*/
bool CFTPClient::UploadFile(CFTPClient::CurlReadFn readFn, void *userData, const std::string &strRemoteFile,
const bool &bCreateDir, curl_off_t fileSize) const {
if (readFn == nullptr || strRemoteFile.empty())
return false;
if (!m_pCurlSession) {
if (m_eSettingsFlags & ENABLE_LOG) m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG);
return false;
}
// Reset is mandatory to avoid bad surprises
curl_easy_reset(m_pCurlSession);
std::string strLocalRemoteFile = ParseURL(strRemoteFile);
bool bRes = false;
/* specify target */
curl_easy_setopt(m_pCurlSession, CURLOPT_URL, strLocalRemoteFile.c_str());
/* we want to use our own read function */
curl_easy_setopt(m_pCurlSession, CURLOPT_READFUNCTION, readFn);
/* now specify which file to upload */
curl_easy_setopt(m_pCurlSession, CURLOPT_READDATA, userData);
/* Set the size of the file to upload (optional). If you give a *_LARGE
option you MUST make sure that the type of the passed-in argument is a
curl_off_t. If you use CURLOPT_INFILESIZE (without _LARGE) you must
make sure that to pass in a type 'long' argument. */
curl_easy_setopt(m_pCurlSession, CURLOPT_INFILESIZE_LARGE, fileSize);
/* enable uploading */
curl_easy_setopt(m_pCurlSession, CURLOPT_UPLOAD, 1L);
if (bCreateDir) curl_easy_setopt(m_pCurlSession, CURLOPT_FTP_CREATE_MISSING_DIRS, CURLFTP_CREATE_DIR);
CURLcode res = Perform();
if (res != CURLE_OK) {
if (m_eSettingsFlags & ENABLE_LOG)
m_oLog(StringFormat(LOG_ERROR_CURL_UPLOAD_FORMAT, strRemoteFile.c_str(), res, curl_easy_strerror(res)));
} else
bRes = true;
return bRes;
}
/**
* @brief uploads data from a stream to a remote folder.
*
* @param [in] inputStream Stream containing data which will be uploaded.
* @param [in] strRemoteFile Complete URN of the remote location (with the file
* name) encoded in UTF-8 format.
* @param [in] bCreateDir Enable or disable creation of remote missing
* directories contained in the URN.
* @param [in] fileSize
*
* @retval true Successfully uploaded the inputStream.
* @retval false The inputStream couldn't be uploaded. Check the log messages for more
* information.
*/
bool CFTPClient::UploadFile(std::istream &inputStream, const std::string &strRemoteFile, const bool &bCreateDir,
curl_off_t fileSize) const {
if ( !inputStream )
return false;
return UploadFile(ReadFromStreamCallback, static_cast<void *>(&inputStream), strRemoteFile, bCreateDir, fileSize);
}
/**
* @brief uploads a local file to a remote folder.
*
* @param [in] strLocalFile Complete path of the file to upload encoded in UTF-8 format.
* @param [in] strRemoteFile Complete URN of the remote location (with the file
* name) encoded in UTF-8 format.
* @param [in] bCreateDir Enable or disable creation of remote missing
* directories contained in the URN.
*
* @retval true Successfully uploaded the file.
* @retval false The file couldn't be uploaded. Check the log messages for more
* information.
*
* Example Usage:
* @code
* m_pFTPClient->UploadFile("C:\\Items_To_Upload\\imagination.jpg",
* "upload/pictures/imagination.jpg", true);
* // C:\\Items_To_Upload\\imagination.jpg will be uploaded to
* ftp://127.0.0.1/upload/pictures/imagination.jpg
* // "upload" and "pictures" remote directories will be created
* // if they don't exist and if the connected user has the proper rights.
* @endcode
*/
bool CFTPClient::UploadFile(const std::string &strLocalFile, const std::string &strRemoteFile, const bool &bCreateDir) const {
if (strLocalFile.empty() || strRemoteFile.empty()) return false;
std::ifstream InputFile;
struct stat file_info;
bool bRes = false;
/* get the file size of the local file */
#ifdef LINUX
if (stat(strLocalFile.c_str(), &file_info) == 0) {
InputFile.open(strLocalFile, std::ifstream::in | std::ifstream::binary);
#else
static_assert(sizeof(struct stat) == sizeof(struct _stat64i32), "Oh oh !");
std::wstring wstrLocalFile = Utf8ToUtf16(strLocalFile);
if (_wstat64i32(wstrLocalFile.c_str(), reinterpret_cast<struct _stat64i32*>(&file_info)) == 0) {
InputFile.open(wstrLocalFile, std::ifstream::in | std::ifstream::binary);
#endif
if (!InputFile) {
if (m_eSettingsFlags & ENABLE_LOG) m_oLog(StringFormat(LOG_ERROR_FILE_UPLOAD_FORMAT, strLocalFile.c_str()));
return false;
}
bRes = UploadFile(InputFile, strRemoteFile, bCreateDir, file_info.st_size);
}
InputFile.close();
return bRes;
}
bool CFTPClient::AppendFile(const std::string &strLocalFile, const size_t fileOffset, const std::string &strRemoteFile,
const bool &bCreateDir) const {
if (strLocalFile.empty() || strRemoteFile.empty()) return false;
if (!m_pCurlSession) {
if (m_eSettingsFlags & ENABLE_LOG) m_oLog(LOG_ERROR_CURL_NOT_INIT_MSG);
return false;
}
// Reset is mandatory to avoid bad surprises
curl_easy_reset(m_pCurlSession);
std::ifstream InputFile;
std::string strLocalRemoteFile = ParseURL(strRemoteFile);
struct stat file_info;
bool bRes = false;
/* get the file size of the local file */
#ifdef LINUX
if (stat(strLocalFile.c_str(), &file_info) == 0) {
InputFile.open(strLocalFile, std::ifstream::in | std::ifstream::binary);
#else
static_assert(sizeof(struct stat) == sizeof(struct _stat64i32), "Oh oh !");
std::wstring wstrLocalFile = Utf8ToUtf16(strLocalFile);
if (_wstat64i32(wstrLocalFile.c_str(), reinterpret_cast<struct _stat64i32 *>(&file_info)) == 0) {
InputFile.open(wstrLocalFile, std::ifstream::in | std::ifstream::binary);
#endif
if (!InputFile) {
if (m_eSettingsFlags & ENABLE_LOG) m_oLog(StringFormat(LOG_ERROR_FILE_UPLOAD_FORMAT, strLocalFile.c_str()));
return false;
}
// check of the offset is less than the file size
if (fileOffset >= file_info.st_size) {
if (m_eSettingsFlags & ENABLE_LOG)
m_oLog("ERROR Incorrect offset !"); // if this code is OK use existing coding style for log msgs
return false;
}
InputFile.seekg(fileOffset, InputFile.beg); // Sets the position of the next character to be extracted from the input stream.
/* specify target */
curl_easy_setopt(m_pCurlSession, CURLOPT_URL, strLocalRemoteFile.c_str());
/* we want to use our own read function */
curl_easy_setopt(m_pCurlSession, CURLOPT_READFUNCTION, ReadFromStreamCallback);
/* now specify which file to upload */
curl_easy_setopt(m_pCurlSession, CURLOPT_READDATA, &InputFile);
/* Set the size of the file to upload (optional). If you give a *_LARGE
option you MUST make sure that the type of the passed-in argument is a
curl_off_t. If you use CURLOPT_INFILESIZE (without _LARGE) you must
make sure that to pass in a type 'long' argument. */
curl_easy_setopt(m_pCurlSession, CURLOPT_INFILESIZE_LARGE, static_cast<curl_off_t>(file_info.st_size - fileOffset)); // Important !
/* enable uploading */
curl_easy_setopt(m_pCurlSession, CURLOPT_UPLOAD, 1L);
curl_easy_setopt(m_pCurlSession, CURLOPT_APPEND, 1L);
if (bCreateDir) curl_easy_setopt(m_pCurlSession, CURLOPT_FTP_CREATE_MISSING_DIRS, CURLFTP_CREATE_DIR);
// TODO add the possibility to rename the file upon upload finish....
CURLcode res = Perform();
if (res != CURLE_OK) {
if (m_eSettingsFlags & ENABLE_LOG)
m_oLog(StringFormat(LOG_ERROR_CURL_UPLOAD_FORMAT, strLocalFile.c_str(), res, curl_easy_strerror(res)));
} else
bRes = true;
}
InputFile.close();
return bRes;
}