How to resume file download through FTP using Curl in C? - c

I am trying to download file using FTP, and in between if connection terminates then it should resume from where it was stop. My problem is that using following code snippet I able to continue download if I close connection and then again connect it, but if I am do so at server site then I am not able to resume download, and program goes into infinites state.
#include <stdio.h>
#include <curl/curl.h>
/*
* This is an example showing how to get a single file from an FTP server.
* It delays the actual destination file creation until the first write
* callback so that it won't create an empty file in case the remote file
* doesn't exist or something else fails.
*/
struct FtpFile {
const char *filename;
FILE *stream;
};
static size_t my_fwrite(void *buffer, size_t size, size_t nmemb, void *stream)
{
struct FtpFile *out=(struct FtpFile *)stream;
if(out && !out->stream) {
/* open file for writing */
out->stream=fopen(out->filename, "wb");
if(!out->stream)
return -1; /* failure, can't open file to write */
}
return fwrite(buffer, size, nmemb, out->stream);
}
int main(void)
{
CURL *curl;
CURLcode res;
struct FtpFile ftpfile={
"dev.zip", /* name to store the file as if succesful */
NULL
};
curl_global_init(CURL_GLOBAL_DEFAULT);
curl = curl_easy_init();
if(curl) {
/*
* You better replace the URL with one that works!
*/
curl_easy_setopt(curl, CURLOPT_URL,
"ftp://root:password#192.168.10.1/dev.zip");
/* Define our callback to get called when there's data to be written */
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, my_fwrite);
/* Set a pointer to our struct to pass to the callback */
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ftpfile);
/* Switch on full protocol/debug output */
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
res = curl_easy_perform(curl);
/* always cleanup */
curl_easy_cleanup(curl);
if(CURLE_OK != res) {
/* we failed */
fprintf(stderr, "curl told us %d\n", res);
}
}
if(ftpfile.stream)
fclose(ftpfile.stream); /* close the local file */
curl_global_cleanup();
return 0;
}
Could any one tell me that how can I resume download if connection is closed by remote site.
Any help would be appreciated
Thanks,
Yuvi

Add a varaible to the ftpfile struct to aware your write function of the need to appeand and tell libcurl to resume the download from the end of the destination file by setting CURLOPT_RESUME_FROM to the number of bytes downloaded already:
struct FtpFile
{
const char *pcInfFil;
FILE *pFd;
int iAppend;
};
In main, if you want to resume:
curl_easy_setopt(curl, CURLOPT_RESUME_FROM , numberOfBytesToSkip);
If you the file doesn't already exist, or it is not a resumed download but a new download, be sure to set CURLOPT_RESUME_FROM back to 0.
In my_fwrite:
out->stream=fopen(out->filename, out->iAppend ? "ab":"wb");
P.S. if where you need to resume the file is larger than a long (2GB), look into CURLOPT_RESUME_FROM_LARGE and CURL_OFF_T_C()
In response to comment requesting additional information on how to know when a transfer failed:
After you call curl easy perform call:
CURLcode curl_easy_getinfo(CURL *curl, CURLINFO info, ... );
Retrieve from the curl context:
CURLINFO_HEADER_SIZE
CURLINFO_CONTENT_LENGTH_DOWNLOAD
Add them together and make sure they equal
CURLINFO_SIZE_DOWNLOAD
If not try to reperform the context.
And be sure to use the latest version of curl, it should timeout after 60 seconds of not hearing from the FTP server it is downloading from.

You may use the CURLOPT_CONNECTTIMEOUT and CURLOPT_TIMEOUT parameters to specify a connect timeout and maximum execution time on a per-handle basis.
The other way (that only works if you're using the easy interface, not the multi one) is to use a socket option callback, that you can set using CURLOPT_SOCKOPTFUNCTION. In it, you must call setsockopt() for the SO_RCVTIMEO parameter to the maximum amount of time a connection can be idle before it should be dropped. i.e. if no bytes have been received in the last 5 seconds, then drop the connection.

Related

C libcurl programs immediatly terminate on Eclipse

I am trying to make a C FTP client using libcurl. I'm writing all my code on eclipse + using minGW. I have got to the stage of including the headers/libraries (I think correctly?) but am struggling to get any sort of response out of eclipse. No errors/problems appear when I compile the code but it immediately terminates, even when putting prints etc. throughout. Any libcurl example I tried has the same issue of terminating with no response immediately.
Includes - images:
libraries include - header includes
Error-ish - image:
error
My .h files are located at C:\curl\include\curl
My .a and .dll.a files are located at C:\curl\lib
Example of non-working code:
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel#haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at https://curl.haxx.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
***************************************************************************/
#include <stdio.h>
#include <string.h>
#include <curl/curl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#ifdef WIN32
#include <io.h>
#else
#include <unistd.h>
#endif
/* <DESC>
* Performs an FTP upload and renames the file just after a successful
* transfer.
* </DESC>
*/
#define LOCAL_FILE "/tmp/uploadthis.txt"
#define UPLOAD_FILE_AS "while-uploading.txt"
#define REMOTE_URL "ftp://example.com/" UPLOAD_FILE_AS
#define RENAME_FILE_TO "renamed-and-fine.txt"
/* NOTE: if you want this example to work on Windows with libcurl as a
DLL, you MUST also provide a read callback with CURLOPT_READFUNCTION.
Failing to do so will give you a crash since a DLL may not use the
variable's memory when passed in to it from an app like this. */
static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *stream)
{
curl_off_t nread;
/* in real-world cases, this would probably get this data differently
as this fread() stuff is exactly what the library already would do
by default internally */
size_t retcode = fread(ptr, size, nmemb, stream);
nread = (curl_off_t)retcode;
fprintf(stderr, "*** We read %" CURL_FORMAT_CURL_OFF_T
" bytes from file\n", nread);
return retcode;
}
int main(void)
{
CURL *curl;
CURLcode res;
FILE *hd_src;
struct stat file_info;
curl_off_t fsize;
struct curl_slist *headerlist = NULL;
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(LOCAL_FILE, &file_info)) {
printf("Couldn't open '%s': %s\n", LOCAL_FILE, strerror(errno));
return 1;
}
fsize = (curl_off_t)file_info.st_size;
printf("Local file size: %" CURL_FORMAT_CURL_OFF_T " bytes.\n", fsize);
/* get a FILE * of the same file */
hd_src = fopen(LOCAL_FILE, "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);
/* 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);
/* specify target */
curl_easy_setopt(curl, CURLOPT_URL, REMOTE_URL);
/* pass in that last of FTP commands to run after the transfer */
curl_easy_setopt(curl, CURLOPT_POSTQUOTE, headerlist);
/* now specify which file to upload */
curl_easy_setopt(curl, CURLOPT_READDATA, hd_src);
/* 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(curl, CURLOPT_INFILESIZE_LARGE,
(curl_off_t)fsize);
/* Now run off and do what you've been told! */
res = curl_easy_perform(curl);
/* Check for errors */
if(res != CURLE_OK)
fprintf(stderr, "curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
/* 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;
}
Any help will be appreciated,
Thanks.

How to check FTP connectivity using CURL library in c?

I want to check FTP server connectivity using curl library in c program. Can anyone tell me how to do that without using any data transfer means i don't want to transfer any file to check that. I want is like CURLOPT_CONNECT_ONLY option which is available for only HTTP, SMTP and POP3 protocols not for FTP.
Curl version : 7.24
Requirement : FTP server connectivity test.
Here in below example, Only connect request will be delivered to FTP server and if server is pingable then it will give CURLE_OK return code other wise give failure response after specific timeout(60 sec). Other options you can set as per your requirement from http://curl.haxx.se/libcurl/c/ .
...
snprintf(ftp_url, BUF_LEN_512, "ftp://%s:%s#%s", uploadConf->username, uploadConf->password, uploadConf->ip);
// Reset curl lib
curl_easy_reset(curl);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, throw_away);
if (CURLE_OK != (res = curl_easy_setopt(curl, CURLOPT_URL, ftp_url)))
{
printf("Failed to check ftp url, Error : %s : %d\n", curl_easy_strerror(res), res);
}
curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);
// Connection establishment timeout
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 60);
if (CURLE_OK != (res = curl_easy_perform(curl)))
{
/* If fail to connect */
}
else
{
/* If connected succesfully */
}
static size_t throw_away(void *ptr, size_t size, size_t nmemb, void *data)
{
size_t res;
res = (size_t)(size * nmemb);
/* we are not interested in the headers itself, so we only return the size we would have saved ... */
return res;
}
Hope it will help you all to test connectivity to FTP server using libcurl in c.

a raw libcurl JSON PUT request using C

I am currently writing a REST-like client that only is required to do PUT requests.
Problem:
Running the program is not giving me the correct results on the URL's API and I do not know why.
Using curl_easy_perform(curl) does not throw an error when called. But the expected result is not generated on the URL's API.
Using curl_easy_send(curl,..,..,..) throws a : unsupported protocol error
Assumption:
I am assuming the order in which I am using the curl_easy_opts is a problem? And I am even missing a couple of key lines?
I have been reading on here of how other people do PUT requests and have been using their methods.
Summary of Program:
My program prompts the user for some string/character data, and from that, I construct the strings myself such as the header and the payload. The header and payload are both in JSON format but the payload is simply a string ( in this case, a char *str = (char *)mallo.. etc). How the header is constructed is shown below.
My header is being constructed using
struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "Accept: application/json");
//there is more content being appended to the header
The CURL function calls :
//init winsock stuff
curl_global_init(CURL_GLOBAL_ALL);
//get a curl handle
curl = curl_easy_init();
if(curl){
//append the headers
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
//specify the target URL
curl_easy_setopt(curl, CURLOPT_URL, url);
//connect ( //i added this here since curl_easy_send() says it requires it. )
curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY,1L);
//specify the request (PUT in our case)
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
//append the payload
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);
res = curl_easy_perform(curl);
//res = curl_easy_send(curl, payload, strlen(payload),&iolen);
//check for errors
if(res != CURLE_OK)
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
curl_easy_cleanup(curl);
}
You should not be using the CURLOPT_CONNECT_ONLY option or curl_easy_send() function, those are intended to be used for custom, non-HTTP protocols.
See this page for an example of how to do a PUT request with libcurl. Basically, you want to enable the CURLOPT_UPLOAD and CURLOPT_PUT options to say that you're doing a PUT request and to enable uploading a body with the request, and then you set the CURLOPT_READDATA and CURLOPT_INFILESIZE_LARGE options to tell libcurl how to read the data you're uploading and how big the data is.
In your case, if you already have the data in memory, then you don't need to read it out of a file, and you can just memcpy() it inside your read callback.
Example code copied below:
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2012, Daniel Stenberg, <daniel#haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://curl.haxx.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
***************************************************************************/
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <curl/curl.h>
/*
* This example shows a HTTP PUT operation. PUTs a file given as a command
* line argument to the URL also given on the command line.
*
* This example also uses its own read callback.
*
* Here's an article on how to setup a PUT handler for Apache:
* http://www.apacheweek.com/features/put
*/
static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *stream)
{
size_t retcode;
curl_off_t nread;
/* in real-world cases, this would probably get this data differently
as this fread() stuff is exactly what the library already would do
by default internally */
retcode = fread(ptr, size, nmemb, stream);
nread = (curl_off_t)retcode;
fprintf(stderr, "*** We read %" CURL_FORMAT_CURL_OFF_T
" bytes from file\n", nread);
return retcode;
}
int main(int argc, char **argv)
{
CURL *curl;
CURLcode res;
FILE * hd_src ;
struct stat file_info;
char *file;
char *url;
if(argc < 3)
return 1;
file= argv[1];
url = argv[2];
/* get the file size of the local file */
stat(file, &file_info);
/* get a FILE * of the same file, could also be made with
fdopen() from the previous descriptor, but hey this is just
an example! */
hd_src = fopen(file, "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) {
/* 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);
/* HTTP PUT please */
curl_easy_setopt(curl, CURLOPT_PUT, 1L);
/* specify target URL, and note that this URL should include a file
name, not only a directory */
curl_easy_setopt(curl, CURLOPT_URL, url);
/* now specify which file to upload */
curl_easy_setopt(curl, CURLOPT_READDATA, hd_src);
/* provide the size of the upload, we specicially typecast the value
to curl_off_t since we must be sure to use the correct data size */
curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE,
(curl_off_t)file_info.st_size);
/* Now run off and do what you've been told! */
res = curl_easy_perform(curl);
/* Check for errors */
if(res != CURLE_OK)
fprintf(stderr, "curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
/* always cleanup */
curl_easy_cleanup(curl);
}
fclose(hd_src); /* close the local file */
curl_global_cleanup();
return 0;
}
I agree, don't use CUSTOMREQUEST. One detail that is being missed on every related to PUT and CURL I've seen here is that you NEED to set the file size, otherwise you'll get HTTP error 411.
Use CURLOPT_INFILESIZE or CURLOPT_INFILESIZE_LARGE for that.
See more details here:
How do I send long PUT data in libcurl without using file pointers?
I know this is a very old question, but in case somebody want to use libcurl with GLib and json-glib to send a JSON with PUT request.
Code below works for me:
#include <curl/curl.h>
#include <json-glib/json-glib.h>
//this is callback function for CURLOPT_READFUNCTION:
static size_t
curlPutJson ( void *ptr, size_t size, size_t nmemb, void *_putData )
{
GString *putData = ( GString * ) _putData;
size_t realsize = ( size_t ) putData->len;
memcpy ( ptr, putData->str, realsize );
return realsize;
}
/*now inside main or other function*/
//json_to_string ( jsonNode, FALSE ) is from json-glib to stringify JSON
//created in jsonNode
GString *putData = g_string_new ( json_to_string ( mainNode, FALSE ) );
//now goes curl as usual: headers, url, other options and so on
//and 4 most important lines
curl_easy_setopt ( curl, CURLOPT_READFUNCTION, curlPutJson );
curl_easy_setopt ( curl, CURLOPT_UPLOAD, 1L );
curl_easy_setopt ( curl, CURLOPT_READDATA, putData ); //GString
curl_easy_setopt ( curl, CURLOPT_INFILESIZE, putData->len ); //type long

libcurl on Windows CE - curl_easy_perform(...) doesn't return

As part of a project, I need to implement libcurl on a Custom WindowsCE device that then talks to a server over wired LAN and GETs, POSTs and DELETEs data ( The programming language I am using is C). This is as far as I've progressed:
I found a couple of tutorials to re-implement the library for my CE device.
( http://stasyan.wordpress.com/2009/12/08/libcurl-for-windows-mobile-and-windows-ce/ , and
http://rxwen.blogspot.com/2012/05/port-libcurl-to-wince.html do a really good job.)
I was able to build a DLL and a Static libcurl library, which I then linked to my Visual Studio 2005 application.
I connected my custom CE device and the server to a switch. I am able to ping the server from the CE device and vice versa, so I know that they are both able to send and receive data to each other.
My server (Linux) uses a lighttpd web-server which works perfectly fine when I request the URL (shown in code, below) from my Windows PC which is also connected to the N/W switch.
This is the code I used for my test app:
#include <stdio.h>
#include <curl/curl.h>
int main(void)
{
printf("STARTING APP...\n");
CURL *curl;
CURLcode res;
printf("curl = curl_easy_init();\n");
curl = curl_easy_init();
if(curl)
{
printf("Enabling followlocation...\n");
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1l);
printf("Setting Verbose...\n");
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
printf("Setting Connect time out...\n");
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5);
printf("Setting Server time out...\n");
curl_easy_setopt(curl, CURLOPT_ACCEPTTIMEOUT_MS , 5000);
printf("Setting URL...\n");
curl_easy_setopt(curl, CURLOPT_URL, "http://165.26.79.10:8080");
/* Perform the request, res will get the return code */
printf("Perform(curl)...;\n");
res = curl_easy_perform(curl);
/* Check for errors */
if(res != CURLE_OK)
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
else
printf("\nGET sucessful.\n");
/* always cleanup */
curl_easy_cleanup(curl);
}
getchar();
return 0;
}
getchar();
return 0;
}
My problem is that the app never returns. It doesn't even progress past curl_easy_perform(curl).
This is the output I get (Google Drive link): [http://goo.gl/IKgiqW]
What could I possibly be doing wrong??
I managed to find out what the problem was.
The curl_easy_perform(...) command goes into an infinite loop caused due to a bug in one of the files in the compatibility library. It is referenced in a cURL development forum (google it), but I've summarized it below:
The WCE compatibility library I used (https://github.com/mauricek/wcecompat/) has a file called wce211_string.c. A function within the file, _CRTIMP char* __cdecl strrchr(const char* s, int c), uses a const char* p pointer that isn't decremented. To fix this bug, add the line: p-=1; after the line 'return (char*)p;', so the function will read as follows:
_CRTIMP char* __cdecl strrchr(const char* s, int c)
{
const char* p = s + strlen(s) - 1;
while (p >= s)
{
if (*p == c)
return (char*)p;
p -= 1;
}
return NULL;
}
This fixes the infinite looping problem.

hide curl_easy_perform

How can I hide curl_easy_perform output (in a shell)?
This is in regards to a C application.
Use CURLOPT_NOBODY in curl_easy_setopt().
Example:
...
CURL *curl;
CURLcode statusCode;
curl = curl_easy_init();
if(curl){
curl_easy_setopt(curl, CURLOPT_URL, "http://www.google.com/");
//CURLOPT_NOBODY does the trick
curl_easy_setopt(curl, CURLOPT_NOBODY, 1);
curl_easy_perform(curl);
...
Link to docs: http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTNOBODY
Set the CURLOPT_WRITEFUNCTION and/or CURLOPT_WRITEDATA options:
FILE *f = fopen("target.txt", "wb");
curl_easy_setopt(handle, CURLOPT_WRITEDATA, f);
By default, libcurl writes output to stdout. When you override this (which is what almost any application will do), it will write to another file or to pass chunks of output to a callback. See the documentation for CURLOPT_WRITEFUNCTION for more details.
As Joey said, CURLOPT_WRITEFUNCTION will allow you to completely disregard all output. Just set up a callback that does absolutely nothing if you want the data to just go away, without being written to any file descriptor.
For instance,
/* Never writes anything, just returns the size presented */
size_t my_dummy_write(char *ptr, size_t size, size_t nmemb, void *userdata)
{
return size * nmemb;
}
Then in your options:
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, &my_dummy_write);
Or, point the file handle at a NULL device (a lot easier).

Resources