C++ libcurl how to send FTP file usign STOR and REST - c++

I have an XML that I want to incrementally upload to the ftp server.
The problem is that it does not work for me with an APPEND, because I have to eliminate the final labels. For example.
File on the server
< xml>< root>< a>ssss< /a>< b>kkkkk< /b>< /root>
File in local
< xml>< root>< a>ssss< /a>< b>kkkkk< /b>< c>hhhhh< /c>< /root>
I need to upload from before < /root>.
< xml>< root>< a>ssss< /a>< b>kkkkk< /b>< /root>
----------------------------------------^
My code is the following
size_t read_callback(void *ptr, size_t size, size_t nmemb, void *stream) {
curl_off_t nread;
size_t retcode = fread(ptr, size, nmemb, (FILE*) stream);
nread = (curl_off_t) retcode;
fprintf(stderr, "*** We read %" CURL_FORMAT_CURL_OFF_T " bytes from file\n", nread);
return retcode;
}
bool uploadFile(std::string urlFtp, std::string rutaFtp, std::string archivoOrigen, std::string userPassword, long posicion) {
bool resultado = false;
try {
CURL *curl;
CURLcode res;
FILE *hd_src;
struct stat file_info;
curl_off_t fsize;
/* get the file size of the local file */
if (stat(archivoOrigen.c_str(), &file_info)) {
LOGF_ERROR("Couldn't open '%s': %s\n", archivoOrigen.c_str(), strerror(errno));
return false;
}
fsize = (curl_off_t) file_info.st_size;
LOGF_INFO("Local file size: %" CURL_FORMAT_CURL_OFF_T " bytes.\n", fsize);
hd_src = fopen(archivoOrigen.c_str(), "rb"); // get a FILE * of the same file
curl_global_init(CURL_GLOBAL_ALL); // In windows, this will init the winsock stuff
curl = curl_easy_init(); // get a curl handle
if (curl) {
// build a list of commands to pass to libcurl
struct curl_slist *headerlist = NULL;
std::string cadena(rutaFtp);
curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback); // we want to use our own read function
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); // enable uploading
cadena = urlFtp;
cadena.append(rutaFtp);
curl_easy_setopt(curl, CURLOPT_URL, cadena.c_str()); // specify target
curl_easy_setopt(curl, CURLOPT_USERPWD, userPassword.c_str());
if (posicion > 0) {
curl_easy_setopt(curl, CURLOPT_RESUME_FROM, posicion);
}
curl_easy_setopt(curl, CURLOPT_READDATA, hd_src); // now specify which file to upload
curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t) fsize);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 15L);
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
res = curl_easy_perform(curl); // Now run off and do what you've been told!
if (res != CURLE_OK)
LOGF_ERROR("curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
else
resultado = true;
curl_slist_free_all(headerlist); // clean up the FTP commands list
curl_easy_cleanup(curl);
}
fclose(hd_src); // close the local file
curl_global_cleanup();
} catch (std::exception &e) {
LOGF_ERROR(e.what());
}
return resultado;
}

I have not been able to solve the problem with CURL. In order to solve it I created a class in C ++ that makes the upload. I noticed the documentation of RFC rfc3659 page 13.
5.5. REST Example
Assume that the transfer of a largish file has previously been
interrupted after 802816 octets had been received, that the previous
transfer was with TYPE=I, and that it has been verified that the file
on the server has not since changed.
C> TYPE I
S> 200 Type set to I.
C> PORT 127,0,0,1,15,107
S> 200 PORT command successful.
C> REST 802816
S> 350 Restarting at 802816. Send STORE or RETRIEVE
C> RETR cap60.pl198.tar
S> 150 Opening BINARY mode data connection
[...]
S> 226 Transfer complete.
Instead of putting the RETR I have put a STOR and it works correctly.

Related

Can't login via FTP with cUrl

I'm implementing a function that should upload a file. I'm using cUrl and Qt/C++.
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() provides the full path
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;
/* get the file size of the local file */
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);
/* get a FILE * of the same file */
hd_src = fopen(localFile, "rb");
/* In windows, this will init the winsock stuff */
curl_global_init(CURL_GLOBAL_ALL);
/* get a curl handle */
curl = curl_easy_init();
if (curl) {
/* build a list of commands to pass to libcurl */
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());
/* If you intend to use this on windows with a libcurl DLL, you must use CURLOPT_WRITEFUNCTION as well */
}
/* Now run off and do what you have been told! */
result = curl_easy_perform(curl);
/* Check for errors */
if (result != CURLE_OK)
fprintf(stderr, "curl_easy_perform() failed: %s\n",
curl_easy_strerror(result));
/* clean up the FTP commands list */
curl_slist_free_all(headerlist);
/* always cleanup */
curl_easy_cleanup(curl);
}
fclose(hd_src); /* close the local file */
curl_global_cleanup();
return 0;
}
It returns: curl_easy_perform() failed: Access denied to remote resource
However, my web-hosting returns:
What's wrong? I triple checked all input.
The file is definitely found, the curl writes in the terminal Local file size: 575131 bytes., though I don't ask it.
If I change the pass, it returns curl_easy_perform() failed: Login denied and if the URL is different or I try uploading anything it says curl_easy_perform() failed: URL using bad/illegal format or missing URL
This is the third approach to sending files via FTP, I need a help.

Aborting an SFTP upload leaves corrupted data at the end of the partially uploaded file, making resume upload impossible

My application uses libcurl to upload files to an SFTP server, and sometimes I need to close the application before the current upload is complete. I would like to abort the upload, and resume it later. However, when the upload is aborted there is corrupted data at the end of the partially uploaded file on the server. The amount various, up to about 10 bytes. This makes resuming the upload by appending data impossible.
See some sample code below. Press Ctrl-C to abort the upload, then manually download the partial file and compare it with the original file. The partial file will usually contain incorrect data at the end.
#include <stdio.h>
#include <signal.h>
#include "curl.h"
// Handle Ctrl-C to abort.
static bool s_bAborted = false;
static void __cdecl ctrlCHandler(int)
{
printf("Aborting...\n");
s_bAborted = true;
}
// File reading function.
static size_t readFunction(char * ptr, size_t size, size_t nmemb, void * stream)
{
FILE * pFile = (FILE *)stream;
if (ferror(pFile))
{
return CURL_READFUNC_ABORT;
}
return fread(ptr, size, nmemb, pFile);
}
// Progress function so transfers can be aborted.
static int progressFunction(void * clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
{
if (s_bAborted)
{
return 1;
}
return CURL_PROGRESSFUNC_CONTINUE;
}
int main()
{
// Add your local file and server URL.
// Note: This problem was observed using an SFTP server.
const char * szLocalFile = "Test.bin";
const char * szRemoteUrl = "sftp://user:password#host/Test.bin";
curl_global_init(CURL_GLOBAL_ALL);
CURL * curl = curl_easy_init();
FILE * pFile = fopen(szLocalFile, "rb");
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
curl_easy_setopt(curl, CURLOPT_URL, szRemoteUrl);
curl_easy_setopt(curl, CURLOPT_READFUNCTION, readFunction);
curl_easy_setopt(curl, CURLOPT_READDATA, pFile);
curl_easy_setopt(curl, CURLOPT_APPEND, 0L);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, progressFunction);
printf("Uploading... Press Ctrl-C to abort\n");
signal(SIGINT, ctrlCHandler);
CURLcode eResult = curl_easy_perform(curl);
fclose(pFile);
if (eResult == CURLE_OK)
{
printf("Uploaded OK\n");
}
else if (eResult == CURLE_ABORTED_BY_CALLBACK)
{
printf("Upload aborted\n");
}
else
{
printf("Error: Upload failed: %d\n", eResult);
}
curl_easy_cleanup(curl);
curl_global_cleanup();
// After aborting the upload, use something else to download the partial file.
// The last few bytes of the partial file are usually incorrect,
// i.e. different to the original file in that position.
// This makes resuming the upload impossible, since it will append after incorrect bytes.
}
Please can anyone help? Thank you.
Following the advice of rustyx above, I modified it to abort using the read function, and removed the progress callback. This appears to fix the problem (after testing about 15 times).
Note: This method means the partial file is always a multiple of 64 kB. When aborting from the progress callback it was any random size. Perhaps this has something to do with it.
See updated code below:
#include <stdio.h>
#include <signal.h>
#include "curl.h"
// Handle Ctrl-C to abort.
static bool s_bAborted = false;
static void __cdecl ctrlCHandler(int)
{
s_bAborted = true;
}
// File reading function.
static size_t readFunction(char * ptr, size_t size, size_t nmemb, void * stream)
{
FILE * pFile = (FILE *)stream;
if (s_bAborted || ferror(pFile))
{
return CURL_READFUNC_ABORT;
}
return fread(ptr, size, nmemb, pFile);
}
int main()
{
// Add your local file and server URL.
// Note: This problem was observed using an SFTP server.
const char * szLocalFile = "Test.bin";
const char * szRemoteUrl = "sftp://user:password#host/Test.bin";
curl_global_init(CURL_GLOBAL_ALL);
CURL * curl = curl_easy_init();
FILE * pFile = fopen(szLocalFile, "rb");
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
curl_easy_setopt(curl, CURLOPT_URL, szRemoteUrl);
curl_easy_setopt(curl, CURLOPT_READFUNCTION, readFunction);
curl_easy_setopt(curl, CURLOPT_READDATA, pFile);
curl_easy_setopt(curl, CURLOPT_APPEND, 0L);
printf("Uploading... Press Ctrl-C to abort\n");
signal(SIGINT, ctrlCHandler);
CURLcode eResult = curl_easy_perform(curl);
fclose(pFile);
if (eResult == CURLE_OK)
{
printf("Uploaded OK\n");
}
else if (eResult == CURLE_ABORTED_BY_CALLBACK)
{
printf("Upload aborted\n");
}
else
{
printf("Error: Upload failed: %d\n", eResult);
}
curl_easy_cleanup(curl);
curl_global_cleanup();
}

How to include an h file locally, without visual studio?

I have a simple cpp code that uses curl.h as it's header file.
I use vscode, and not interested on linking via gui or w/e
How can I make the include statement work properly with the single project?
I got the header file from https://github.com/curl/curl/blob/master/include/curl/curl.h
Folder structure is like this:
/Folder
+-> curl.h
+-> curli.cpp
the code:
#include "curl.h"
using namespace std;
#define YOUR_URL "https://www.insertyourURLhere.com/"
#define USER_AND_PWD "user:password"
static string gs_strLastResponse;
// Callback to gather the response from the server. Comes in chunks (typically 16384 characters at a time), so needs to be stitched together.
size_t function_pt(void *ptr, size_t size, size_t nmemb, void * /*stream*/)
{
gs_strLastResponse += (const char*)ptr;
return size * nmemb;
}
bool CallServerWithCurl(string strData1, strData2, string& strErrorDescription)
{
CURL* curl = curl_easy_init();
if (curl == NULL)
{
strErrorDescription = "Unable to initialise Curl";
return false;
}
curl_easy_setopt(curl, CURLOPT_URL, (YOUR_URL + "get_result").c_str());
curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_easy_setopt(curl, CURLOPT_USERPWD, MY_USER_AND_PWD); // set user name and password for the authentication
char* data1 = curl_easy_escape(curl, strData1.c_str(), 0);
char* data2 = curl_easy_escape(curl, strData2.c_str(), 0);
string strArguments = "id=" + data1 + "&task=" + data2;
const char* my_data = strArguments.c_str();
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, my_data);
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)strlen(my_data)); // if we don't provide POSTFIELDSIZE, libcurl will strlen() by itself
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); // enable verbose for easier tracing
gs_strLastResponse = "";
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, function_pt); // set a callback to capture the server's response
CURLcode res = curl_easy_perform(curl);
// we have to call twice, first call authenticates, second call does the work
res = curl_easy_perform(curl);
if (res != CURLE_OK)
{
strErrorDescription = "Curl call to server failed";
return false;
}
if (!DoSomethingWithServerResponse(gs_strLastResponse))
{
strErrorDescription = "Curl call to server returned an unexpected response";
return false;
}
// extract some transfer info
double speed_upload, total_time;
curl_easy_getinfo(curl, CURLINFO_SPEED_UPLOAD, &speed_upload);
curl_easy_getinfo(curl, CURLINFO_TOTAL_TIME, &total_time);
fprintf(stderr, "Speed: %.3f bytes/sec during %.3f seconds\n", speed_upload, total_time);
curl_easy_cleanup(curl);
return true;
}

Libcurl error with FTP upload in ubuntu C++

In windows, this code works file, but now, I want to convert it into ubuntu:
// callback read function to upload file from local to ftp server
size_t read_callback (void* ptr, size_t size, size_t nmemb, FILE *stream){
//return fread(ptr,size,nmemb, (FILE*) stream);
return fread(ptr,size,nmemb,stream);
}
// get file name from a path
string FTPClientConnector::getFileName(string path){
int length = path.size();
for(int i = length - 1; i >= 0; i--){
if(path[i] == '/' || path[i] == '\\'){
return path.substr(i+1, length-i-1);
}
}
}
//function to upload a file to FTP server
int FTPClientConnector::uploadFile(string filePath, string serverPath ){
CURL *curl;
CURLcode res;
FILE *hd_src;
struct stat file_info;
curl_off_t fsize;
char* local_file = new char[filePath.size()+1];
std::copy(filePath.begin(), filePath.end(), local_file);
local_file[filePath.size()] = '\0';
// stat the local file
if(stat(local_file, &file_info)){
printf("couldn't open file\n");
delete local_file;
return -1;
}
// convert URL and username and password to connect to remote server
string urlPath = this->hostName + serverPath;
urlPath += getFileName(filePath);
char *url = new char[urlPath.size() + 1];
std::copy(urlPath.begin(), urlPath.end(), url);
url[urlPath.size()] = '\0';
string userAndPassString = this->userName + ":" + this->password;
char* usernameAndPassword = new char[userAndPassString.size() +1];
std::copy(userAndPassString.begin(), userAndPassString.end(), usernameAndPassword);
usernameAndPassword[userAndPassString.size()] = '\0';
// get the file to open
hd_src = fopen(local_file, "rb");
curl_global_init(CURL_GLOBAL_ALL);
curl = curl_easy_init();
if(curl){
/* specify target */
curl_easy_setopt(curl,CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_PORT, this->port);
curl_easy_setopt(curl, CURLOPT_USERPWD, usernameAndPassword);
/* we want to use our own read function */
curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback);
/* enable uploading */
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
/* now specify which file to upload */
curl_easy_setopt(curl, CURLOPT_READDATA, hd_src);
/* Now run off and do what you've been told! */
res = curl_easy_perform(curl);
if(res != CURLE_OK){
printf("Upload file failed!\n");
delete local_file;
delete url;
delete usernameAndPassword;
return -1;
}
curl_easy_cleanup(curl);
}
fclose(hd_src);
delete local_file;
delete url;
delete usernameAndPassword;
return 0;
}
This is what I call in main.cpp:
FTPClientConnector connector(host,user,password,port);
connector.uploadFile("xml/kingfisher.xml", "/xml_test_upload");
The code above doesn't work in Ubuntu with errors:
220 ProFTPD 1.3.4a Server (Debian) [::ffff:10.244.31.244]
500 PUT not understood
500 AUTHORIZATION: not understood
500 HOST: not understood
550 */*: Forbidden command argument
500 TRANSFER-ENCODING: not understood
500 EXPECT: not understood
500 Invalid command: try being more creative
500 2A2 not understood
Edit: This is my Makefile:
uploader:
g++ -o uploader FTPClientConnector.cpp main.cpp -lcurl
The output seems to indicate that you speak HTTP to a FTP server. Make sure your URL properly uses a FTP:// prefix for FTP, as without a protocol prefix libcurl guesses which protocol you want and it defaults to HTTP...
It appears from your comments that you need to use IPv4. Add this to your list of setopt calls:
curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);

uploading file with libcurl

Take a look at the following code
static size_t reader(void *ptr, size_t size, size_t nmemb, FILE *stream) {
size_t retcode = fread(ptr, size, nmemb, stream);
cout << "*** We read " << retcode << " bytes from file" << endl;
return retcode;
}
void upload() { //upload() is called from ouside
FILE *pFile;
pFile = fopen("map.txt" , "r");
struct stat file_info;
stat("map.txt", &file_info);
size_t size = (size_t)file_info.st_size;
uploadFile(pFile, size);
}
bool uploadFile(void* data, size_t datasize) {
CURL *curl;
CURLcode res;
curl = curl_easy_init();
if (curl) {
char *post_params = ...;
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_params);
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long) strlen(post_params));
curl_easy_setopt(curl, CURLOPT_READFUNCTION, reader);
curl_easy_setopt(curl, CURLOPT_READDATA, data);
curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t) datasize);
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
}
return true;
}
When the code is executed, the following is outputed
*** We read 490 bytes from file
*** We read 0 bytes from file
after that the app does nothing (even not exiting).
Can someone point out at what's wrong here?
Will be grateful for any help!!!
There's some serious confusions shown in this code. Let me try to explain:
CURLOPT_UPLOAD - this will ask libcurl to PUT the file when the protocol of choice is HTTP
CURLOPT_POSTFIELDS - tells libcurl to POST the data that is provided in the additional argument (which has the size set with CURLOPT_POSTFIELDSIZE)
CURLOPT_READFUNCTION - provides libcurl an alternative way to get data than CURLOPT_POSTFIELDS to allow a POST that reads the data from a file. When using CURLOPT_UPLOAD this is the only way to provide data.
So in the end the questions left for you are:
Do you want PUT or POST?
Do you want to provide the data as a string or do you want it provided with a callback?