69

I am building an application (on windows using Dev-C++) and I want it to download a file. I am doing this using libcurl (I have already installed the source code using packman). I found a working example (http://siddhantahuja.wordpress.com/2009/04/12/how-to-download-a-file-from-a-url-and-save-onto-local-directory-in-c-using-libcurl/) but it doesn't close the file after download is complete. I would like an example of how to download a file in C.

starball
  • 20,030
  • 7
  • 43
  • 238

2 Answers2

211

The example you are using is wrong. See the man page for easy_setopt. In the example write_data uses its own FILE, *outfile, and not the fp that was specified in CURLOPT_WRITEDATA. That's why closing fp causes problems - it's not even opened.

This is more or less what it should look like (no libcurl available here to test)

#include <stdio.h>
#include <curl/curl.h>
/* For older cURL versions you will also need 
#include <curl/types.h>
#include <curl/easy.h>
*/
#include <string>

size_t write_data(void *ptr, size_t size, size_t nmemb, FILE *stream) {
    size_t written = fwrite(ptr, size, nmemb, stream);
    return written;
}

int main(void) {
    CURL *curl;
    FILE *fp;
    CURLcode res;
    char *url = "http://localhost/aaa.txt";
    char outfilename[FILENAME_MAX] = "C:\\bbb.txt";
    curl = curl_easy_init();
    if (curl) {
        fp = fopen(outfilename,"wb");
        curl_easy_setopt(curl, CURLOPT_URL, url);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
        res = curl_easy_perform(curl);
        /* always cleanup */
        curl_easy_cleanup(curl);
        fclose(fp);
    }
    return 0;
}

Updated: as suggested by @rsethc types.h and easy.h aren't present in current cURL versions anymore.

fvu
  • 32,488
  • 6
  • 61
  • 79
  • 1
    I tried this (under OSX) but I'm getting: warning: deprecated conversion from string constant to ‘char*’ Undefined symbols: "___gxx_personality_v0", referenced from: _main in ccHRyU1T.o write_data(void*, unsigned long, unsigned long, __sFILE*)in ccHRyU1T.o CIE in ccHRyU1T.o ld: symbol(s) not found collect2: ld returned 1 exit status Help please!! – TCB13 Aug 10 '11 at 23:17
  • @TCB13 the program I wrote is C, not C++. Recompile as C and the problems should both go away. – fvu Aug 11 '11 at 08:36
  • 1
    Libcurl has it own internal function for writing data to file, which is functionally identical to the above `write_data`. There's no need to write that `write_data` and no need to set `CURLOPT_WRITEFUNCTION`. Just set `CURLOPT_WRITEDATA` and leave `CURLOPT_WRITEFUNCTION` untouched - and you will get the same functionality. – AnT stands with Russia Jun 13 '18 at 23:54
  • 1
    on my computer I had to take out `#include `. I tried compiling as C and C++. On both the example immediately returns and the file is blank – PPP Aug 20 '20 at 05:53
  • wireshark inspection shows that the page returns a 302 moved permanently, then you get a 0 byte output. Calling a website like pudim.com.br that does return 200 works. – Guerlando OCs Aug 20 '20 at 06:42
  • does this work with ssl ? Can somebody help me – Ahmed Can Unbay Feb 17 '21 at 15:50
  • as @AnT-StoptheUkroNazis said, there is no need to code the WRITEFUNCTION by yourself if you want it to write to a FILE *, the docs of FILEDATA says: **If you use the CURLOPT_WRITEFUNCTION option, this is the pointer you will get in that callback's 4th argument. If you do not use a write callback, you must make pointer a 'FILE *' (cast to 'void *') as libcurl will pass this to fwrite(3) when writing data.** – masterxilo Jun 25 '22 at 13:50
  • In other words: *CURLOPT_WRITEFUNCTION defaults to a function writing to the `FILE*` passed as `CURLOPT_WRITEDATA`!* – masterxilo Jun 25 '22 at 14:11
  • Another note on that: I just tried libcurl on Windows and with the default CURLOPT_WRITEFUNCTION (if not configuring one), curl gives `Failure writing output to destination`, so it seems on Windows the function is required. So it's probably safer to always configure it explicitly. – masterxilo Jun 26 '22 at 16:19
30

Just for those interested you can avoid writing custom function by passing NULL as last parameter (if you do not intend to do extra processing of returned data).
In this case default internal function is used.

Details
http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTWRITEDATA

Example

#include <stdio.h>
#include <curl/curl.h>

int main(void)
{
    CURL *curl;
    FILE *fp;
    CURLcode res;
    char *url = "http://stackoverflow.com";
    char outfilename[FILENAME_MAX] = "page.html";
    curl = curl_easy_init();                                                                                                                                                                                                                                                           
    if (curl)
    {   
        fp = fopen(outfilename,"wb");
        curl_easy_setopt(curl, CURLOPT_URL, url);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, NULL);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
        res = curl_easy_perform(curl);
        curl_easy_cleanup(curl);
        fclose(fp);
    }   
    return 0;
}
Petar
  • 1,034
  • 1
  • 12
  • 18
  • 1
    This doesn't work on Windows. On windows you must set the CURLOPT_WRITEFUNCTION non-null otherwise a crash occurs. See https://curl.haxx.se/libcurl/c/CURLOPT_WRITEDATA.html – Phil Rosenberg Jun 15 '16 at 16:08
  • @PhilRosenberg well this sounds unfortunate, since here https://curl.haxx.se/libcurl/c/CURLOPT_WRITEFUNCTION.html#DESCRIPTION it clearly states: "Set this option to NULL to get the internal default function used instead of your callback. " So this "If you're using libcurl as a win32 DLL, you MUST use the CURLOPT_WRITEFUNCTION if you set this option or you will experience crashes." should have no effect, since again internal function should be used. – Petar Jun 17 '16 at 09:28
  • 1
    This examples immediately returns, I get a blank file – PPP Aug 20 '20 at 05:53
  • wireshark inspection shows that the page returns a 302 moved permanently, then you get a 0 byte output. Calling a website like pudim.com.br that does return 200 works. – Guerlando OCs Aug 20 '20 at 06:42
  • 2
    @GuerlandoOCs, that is not directly related to example, for redirect support you should use https://curl.se/libcurl/c/CURLOPT_FOLLOWLOCATION.html – Petar Feb 23 '21 at 16:14
  • Since you set the `res` variable it would be worth to use it, with pedantic settings: `error: variable 'res' set but not used [-Werror=unused-but-set-variable]` – Antonio Apr 13 '23 at 09:44