From ebcafe73b313e70b19e4f7b806e020e59f84c5b1 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Wed, 18 Apr 2001 06:51:30 +0000 Subject: [PATCH] Cris Bailiff's and Georg Horn's big improvements --- perl/Curl_easy/Changes | 34 +++ perl/Curl_easy/Makefile.PL | 2 +- perl/Curl_easy/easy.pm | 130 +++++++++- perl/Curl_easy/easy.xs | 502 +++++++++++++++++++++++++++++++++++-- perl/Curl_easy/test.pl | 296 +++++++++++++++++++--- 5 files changed, 893 insertions(+), 71 deletions(-) diff --git a/perl/Curl_easy/Changes b/perl/Curl_easy/Changes index a38cc34a4..647017c54 100644 --- a/perl/Curl_easy/Changes +++ b/perl/Curl_easy/Changes @@ -1,6 +1,40 @@ Revision history for Perl extension Curl::easy. Check out the file README for more info. +1.1.3 Wed Apr 18 2001: - Cris Bailiff + - Change/shorten module function names: + Curl::easy::curl_easy_setopt becomes Curl::easy::setopt etc. + This requires minor changes to existing scripts.... + - Added callback function support to pass arbitrary SV * (including + FILE globs) from perl through libcurl to the perl callback. + - Make callbacks still work with existing scripts which use STDIO + - Initial support for libcurl 7.7.2 HEADERFUNCTION callback feature + - Minor API cleanups/changes in the callback function signatures + - Added Curl::easy::version function to return curl version string + - Callback documentation added in easy.pm + - More tests in test.pl + +1.1.2 Mon Apr 16 2001: - Georg Horn + - Added support for callback functions. This is for the curl_easy_setopt() + options WRITEFUNCTION, READFUNCTION, PROGRESSFUNCTION and PASSWDFUNCTION. + Still missing, but not really neccessary: Passing a FILE * pointer, + that is passed in from libcurl, on to the perl callback function. + - Various cleanups, fixes and enhancements to easy.xs and test.pl. + +1.1.1 Thu Apr 12 2001: + - Made more options of curl_easy_setopt() work: Options that require + a list of curl_slist structs to be passed in, like CURLOPT_HTTPHEADER, + are now working by passing a perl array containing the list elements. + As always, look at the test script test.pl for an example. + +1.1.0 Wed Apr 11 2001: + - tested against libcurl 7.7 + - Added new function Curl::easy::internal_setopt(). By calling + Curl::easy::internal_setopt(Curl::easy::USE_INTERNAL_VARS, 1); + the headers and content of the fetched page are no longer stored + into files (or written to stdout) but are stored into internal + Variables $Curl::easy::headers and $Curl::easy::content. + 1.0.2 Tue Oct 10 2000: - runs with libcurl 7.4 - modified curl_easy_getinfo(). It now calls curl_getinfo() that has diff --git a/perl/Curl_easy/Makefile.PL b/perl/Curl_easy/Makefile.PL index c0d6c2ddf..58a8528ad 100644 --- a/perl/Curl_easy/Makefile.PL +++ b/perl/Curl_easy/Makefile.PL @@ -8,7 +8,7 @@ WriteMakefile( 'NAME' => 'Curl::easy', 'VERSION_FROM' => 'easy.pm', # finds $VERSION 'LIBS' => ['-lcurl '], # e.g., '-lm' - 'DEFINE' => '-Wall', # e.g., '-DHAVE_SOMETHING' + 'DEFINE' => '', # e.g., '-DHAVE_SOMETHING' 'INC' => '', # e.g., '-I/usr/include/other' 'clean' => {FILES => "head.out body.out"} ); diff --git a/perl/Curl_easy/easy.pm b/perl/Curl_easy/easy.pm index 126be14a9..e484a28f2 100644 --- a/perl/Curl_easy/easy.pm +++ b/perl/Curl_easy/easy.pm @@ -29,6 +29,7 @@ CURLOPT_FTPASCII CURLOPT_FTPLISTONLY CURLOPT_FTPPORT CURLOPT_HEADER +CURLOPT_HEADERFUNCTION CURLOPT_HTTPHEADER CURLOPT_HTTPPOST CURLOPT_HTTPPROXYTUNNEL @@ -44,6 +45,8 @@ CURLOPT_NETRC CURLOPT_NOBODY CURLOPT_NOPROGRESS CURLOPT_NOTHING +CURLOPT_PASSWDDATA +CURLOPT_PASSWDFUNCTION CURLOPT_PORT CURLOPT_POST CURLOPT_POSTFIELDS @@ -88,8 +91,14 @@ CURLINFO_SPEED_DOWNLOAD CURLINFO_SPEED_UPLOAD CURLINFO_HEADER_SIZE CURLINFO_REQUEST_SIZE + +USE_INTERNAL_VARS ); -$VERSION = '1.0.1'; + +$VERSION = '1.1.3'; + +$Curl::easy::headers = ""; +$Curl::easy::content = ""; sub AUTOLOAD { # This AUTOLOAD is used to 'autoload' constants from the constant() @@ -116,21 +125,122 @@ Curl::easy - Perl extension for libcurl =head1 SYNOPSIS use Curl::easy; - - $CURL = curl_easy_init(); - $CURLcode = curl_easy_setopt($CURL, CURLoption, Value); - $CURLcode = curl_easy_perform($CURL); - curl_easy_cleanup($CURL); - - + + $curl = Curl::easy::init(); + $CURLcode = Curl::easy::setopt($curl, CURLoption, Value); + $CURLcode = Curl::easy::perform($curl); + Curl::easy::cleanup($curl); + =head1 DESCRIPTION - + This perl module provides an interface to the libcurl C library. See http://curl.haxx.se/ for more information on cURL and libcurl. + +=head1 FILES and CALLBACKS + +Curl::easy supports the various options of curl_easy_setopt which require either a FILE * or +a callback function. + +The perl callback functions are handled through a C wrapper which takes care of converting +from C to perl variables and back again. This wrapper simplifies some C arguments to make +them behave in a more 'perl' like manner. In particular, the read and write callbacks do not +look just like the 'fread' and 'fwrite' C functions - perl variables do not need separate length +parameters, and perl functions can return a list of variables, instead of needing a pointer +to modify. The details are described below. + +=head2 FILE handles (GLOBS) + +Curl options which take a FILE, such as CURLOPT_FILE, CURLOPT_WRITEHEADER, CURLOPT_INFILE +can be passed a perl file handle: + + open BODY,">body.out"; + $CURLcode = Curl::easy::setopt($curl, CURLOPT_FILE, BODY); + +=head2 WRITE callback + +The CUROPT_WRITEFUNCTION option may be set which will cause libcurl to callback to +the given subroutine: + + sub chunk { my ($data,$pointer)=@_; ...; return length($data) } + $CURLcode = Curl::easy::setopt($curl, CURLOPT_WRITEFUNCTION, \&chunk ); + $CURLcode = Curl::easy::setopt($curl, CURLOPT_FILE, ); + +In this case, the subroutine will be passed whatever is defined by CURLOPT_FILE. This can be +a ref to a scalar, or a GLOB or anything else you like. + +The callback function must return the number of bytes 'handled' ( length($data) ) or the transfer +will abort. A transfer can be aborted by returning a 'length' of '-1'. + +The option CURLOPT_WRITEHEADER can be set to pass a different '$pointer' into the CURLOPT_WRITEFUNCTION +for header values. This lets you collect the headers and body separately: + + my $headers=""; + my $body=""; + sub chunk { my ($data,$pointer)=@_; ${$pointer}.=$data; return length($data) } + + $CURLcode = Curl::easy::setopt($curl, CURLOPT_WRITEFUNCTION, \&chunk ); + $CURLcode = Curl::easy::setopt($curl, CURLOPT_WRITEHEADER, \$header ); + $CURLcode = Curl::easy::setopt($curl, CURLOPT_FILE, \$body ); + +If you have libcurl > 7.7.1, then you could instead set CURLOPT_HEADERFUNCTION to a different callback, +and have the header collected that way. + +=head2 READ callback + +Curl::easy supports CURLOPT_READFUNCTION. This function should look something like this: + + sub read_callback { + my ($maxlength,$pointer)=@_; + + .... + + return $data; + } + +The subroutine must return an empty string "" at the end of the data. Note that this function +isn't told how much data to provide - $maxlength is just the maximum size of the buffer +provided by libcurl. If you are doing an HTTP POST or PUT for example, it is important that this +function only returns as much data as the 'Content-Length' header specifies, followed by a +an empty (0 length) buffer. + +=head2 PROGRESS callback + +Curl::easy supports CURLOPT_PROGRESSFUNCTION. This function should look something like this: + + sub prog_callb + { + my ($clientp,$dltotal,$dlnow,$ultotal,$ulnow)=@_; + .... + return 0; + } + +The function should return 0 normally, or -1 which will abort/cancel the transfer. $clientp is whatever +value/scalar is set using the CURLOPT_PROGRESSDATA option. + +=head2 PASSWD callback + +Curl::easy supports CURLOPT_PASSWDFUNCTION. This function should look something like this: + + sub passwd_callb + { + my ($clientp,$prompt,$buflen)=@_; + ... + return (0,$data); + } + +$clientp is whatever scalar is set using the CURLOPT_PASSWDDATA option. +$prompt is a text string which can be used to prompt for a password. +$buflen is the maximum accepted password reply. + +The function must return 0 (for 'OK') and the password data as a list. Return (-1,"") to +indicate an error. =head1 AUTHOR - + Georg Horn + +Additional callback,pod and tes work by Cris Bailiff +and Forrest Cahoon =head1 SEE ALSO diff --git a/perl/Curl_easy/easy.xs b/perl/Curl_easy/easy.xs index c7f19b026..4fff2b332 100644 --- a/perl/Curl_easy/easy.xs +++ b/perl/Curl_easy/easy.xs @@ -7,6 +7,17 @@ #include #include +#if (LIBCURL_VERSION_NUM<0x070702) +#define CURLOPT_HEADERFUNCTION 79 +#define header_callback_func write_callback_func +#else +#define header_callback_func writeheader_callback_func +#endif + +/* Lists that can be set via curl_easy_setopt() */ + +static struct curl_slist *httpheader = NULL, *quote = NULL, *postquote = NULL; + /* Buffer and varname for option CURLOPT_ERRORBUFFER */ @@ -14,6 +25,341 @@ static char errbuf[CURL_ERROR_SIZE]; static char *errbufvarname = NULL; +/* Callback functions */ + +static SV *read_callback = NULL, *write_callback = NULL, + *progress_callback = NULL, *passwd_callback = NULL, + *header_callback = NULL; + /* *closepolicy_callback = NULL; */ + + +/* For storing the content */ + +static char *contbuf = NULL, *bufptr = NULL; +static int bufsize = 32768, contlen = 0; + + +/* Internal options for this perl module */ + +#define USE_INTERNAL_VARS 0x01 + +static int internal_options = 0; + + +/* Setup these global vars */ + +static void init_globals(void) +{ + if (httpheader) curl_slist_free_all(httpheader); + if (quote) curl_slist_free_all(quote); + if (postquote) curl_slist_free_all(postquote); + httpheader = quote = postquote = NULL; + if (errbufvarname) free(errbufvarname); + errbufvarname = NULL; + if (contbuf == NULL) { + contbuf = malloc(bufsize + 1); + } + bufptr = contbuf; + *bufptr = '\0'; + contlen = 0; + internal_options = 0; +} + + +/* Register a callback function */ + +static void register_callback(SV **callback, SV *function) +{ + if (*callback == NULL) { + /* First time, create new SV */ + *callback = newSVsv(function); + } else { + /* Been there, done that. Just overwrite the SV */ + SvSetSV(*callback, function); + } +} + +/* generic fwrite callback, which decides which callback to call */ +static size_t +fwrite_wrapper (const void *ptr, + size_t size, + size_t nmemb, + void *stream, + void *call_function) +{ + dSP ; + int count,status; + SV *sv; + + if (call_function) { + /* then we are doing a callback to perl */ + + ENTER ; + SAVETMPS ; + + PUSHMARK(SP) ; + + if (stream == stdout) { + sv = newSViv(0); /* FIXME: should cast stdout to GLOB somehow? */ + } else { /* its already an SV */ + sv = stream; + } + + if (ptr != NULL) { + XPUSHs(sv_2mortal(newSVpvn(ptr, size * nmemb))); + } else { + XPUSHs(sv_2mortal(newSVpv("",0))); + } + XPUSHs(sv_2mortal(newSVsv(sv))); /* CURLOPT_FILE SV* */ + PUTBACK ; + + count = call_sv((SV *)call_function, G_SCALAR); + + SPAGAIN; + if (count != 1) + croak("Big trouble, perl_call_sv(write_callback) didn't return status\n"); + + status = POPi; + + PUTBACK ; + + FREETMPS ; + LEAVE ; + return status; + + } else { + /* default to a normal 'fwrite' */ + /* stream could be a FILE * or an SV * */ + FILE *f; + + if (stream == stdout) { /* the only possible FILE ? Think so*/ + f = stream; + } else { /* its a GLOB */ + f = IoIFP(sv_2io(stream)); /* may barf if not a GLOB */ + } + + return fwrite(ptr,size,nmemb,f); + } +} + +/* Write callback for calling a perl callback */ +size_t +write_callback_func( const void *ptr, size_t size, + size_t nmemb, void *stream) +{ + return fwrite_wrapper(ptr,size,nmemb,stream, + write_callback); +} + +/* header callback for calling a perl callback */ +size_t +writeheader_callback_func( const void *ptr, size_t size, + size_t nmemb, void *stream) +{ + return fwrite_wrapper(ptr,size,nmemb,stream, + header_callback); +} + +size_t +read_callback_func( void *ptr, size_t size, + size_t nmemb, void *stream) +{ + dSP ; + + int count; + SV *sv; + STRLEN len; + size_t maxlen,mylen; + char *p; + + maxlen = size*nmemb; + + if (read_callback) { + /* we are doing a callback to perl */ + + ENTER ; + SAVETMPS ; + + PUSHMARK(SP) ; + + if (stream == stdin) { + sv = newSViv(0); /* should cast stdin to GLOB somehow? */ + } else { /* its an SV */ + sv = stream; + } + + XPUSHs(sv_2mortal(newSViv(maxlen))); /* send how many bytes please */ + XPUSHs(sv_2mortal(newSVsv(sv))); /* CURLOPT_INFILE SV* */ + PUTBACK ; + + count = call_sv(read_callback, G_SCALAR); + + SPAGAIN; + if (count != 1) + croak("Big trouble, perl_call_sv(read_callback) didn't return data\n"); + + sv = POPs; + p = SvPV(sv,len); + + /* only allowed to return the number of bytes asked for */ + mylen = len= bufsize) { + bufsize *= 2; + contbuf = realloc(contbuf, bufsize + 1); + bufptr = contbuf + contlen; + } + contlen += size; + for (i = 0; i < size; i++) { + *bufptr++ = *data++; + } + *bufptr = '\0'; + return size; +} + + static int constant(char *name, int arg) { @@ -97,6 +443,7 @@ constant(char *name, int arg) case 'G': case 'H': if (strEQ(name, "HEADER")) return CURLOPT_HEADER; + if (strEQ(name, "HEADERFUNCTION")) return CURLOPT_HEADERFUNCTION; if (strEQ(name, "HTTPHEADER")) return CURLOPT_HTTPHEADER; if (strEQ(name, "HTTPPOST")) return CURLOPT_HTTPPOST; if (strEQ(name, "HTTPPROXYTUNNEL")) return CURLOPT_HTTPPROXYTUNNEL; @@ -124,6 +471,8 @@ constant(char *name, int arg) break; case 'O': case 'P': + if (strEQ(name, "PASSWDDATA")) return CURLOPT_PASSWDDATA; + if (strEQ(name, "PASSWDFUNCTION")) return CURLOPT_PASSWDFUNCTION; if (strEQ(name, "PORT")) return CURLOPT_PORT; if (strEQ(name, "POST")) return CURLOPT_POST; if (strEQ(name, "POSTFIELDS")) return CURLOPT_POSTFIELDS; @@ -173,12 +522,13 @@ constant(char *name, int arg) break; } } + if (strEQ(name, "USE_INTERNAL_VARS")) return USE_INTERNAL_VARS; errno = EINVAL; return 0; } -MODULE = Curl::easy PACKAGE = Curl::easy +MODULE = Curl::easy PACKAGE = Curl::easy PREFIX = curl_easy_ int constant(name,arg) @@ -189,56 +539,167 @@ constant(name,arg) void * curl_easy_init() CODE: - if (errbufvarname) free(errbufvarname); - errbufvarname = NULL; + init_globals(); RETVAL = curl_easy_init(); + curl_easy_setopt(RETVAL, CURLOPT_HEADERFUNCTION, header_callback_func); + curl_easy_setopt(RETVAL, CURLOPT_WRITEFUNCTION, write_callback_func); OUTPUT: RETVAL +char * +curl_easy_version() +CODE: + RETVAL=curl_version(); +OUTPUT: + RETVAL int curl_easy_setopt(curl, option, value) void * curl int option -char * value +SV * value CODE: if (option < CURLOPTTYPE_OBJECTPOINT) { + /* This is an option specifying an integer value: */ - long value = (long)SvIV(ST(2)); - RETVAL = curl_easy_setopt(curl, option, value); + RETVAL = curl_easy_setopt(curl, option, (long)SvIV(value)); + } else if (option == CURLOPT_FILE || option == CURLOPT_INFILE || - option == CURLOPT_WRITEHEADER) { - /* This is an option specifying a FILE * value: */ - FILE * value = IoIFP(sv_2io(ST(2))); - RETVAL = curl_easy_setopt(curl, option, value); + option == CURLOPT_WRITEHEADER || option == CURLOPT_PROGRESSDATA || + option == CURLOPT_PASSWDDATA) { + /* This is an option specifying an SV * value: */ + RETVAL = curl_easy_setopt(curl, option, newSVsv(ST(2))); + } else if (option == CURLOPT_ERRORBUFFER) { - SV *sv; + /* Pass in variable name for storing error messages... */ RETVAL = curl_easy_setopt(curl, option, errbuf); if (errbufvarname) free(errbufvarname); - errbufvarname = strdup(value); - sv = perl_get_sv(errbufvarname, TRUE | GV_ADDMULTI); + errbufvarname = strdup((char *)SvPV(value, PL_na)); + } else if (option == CURLOPT_WRITEFUNCTION || option == - CURLOPT_READFUNCTION || option == CURLOPT_PROGRESSFUNCTION) { + CURLOPT_READFUNCTION || option == CURLOPT_PROGRESSFUNCTION || + option == CURLOPT_PASSWDFUNCTION || option == CURLOPT_HEADERFUNCTION) { /* This is an option specifying a callback function */ - /* not yet implemented */ + switch (option) { + case CURLOPT_WRITEFUNCTION: + register_callback(&write_callback, value); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback_func); + break; + case CURLOPT_READFUNCTION: + register_callback(&read_callback, value); + curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback_func); + break; + case CURLOPT_HEADERFUNCTION: + register_callback(&header_callback, value); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback_func); + case CURLOPT_PROGRESSFUNCTION: + register_callback(&progress_callback, value); + curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, progress_callback_func); + break; + case CURLOPT_PASSWDFUNCTION: + register_callback(&passwd_callback, value); + curl_easy_setopt(curl, CURLOPT_PASSWDFUNCTION, passwd_callback_func); + break; + /* awaiting a prototype for the closepolicy function callback + case CURLOPT_CLOSEFUNCTION: + register_callback(&closepolicy_callback, value); + curl_easy_setopt(curl, CURLOPT_CLOSEFUNCTION, closepolicy_callback_func); + break; + */ + } RETVAL = -1; + + } else if (option == CURLOPT_HTTPHEADER || option == CURLOPT_QUOTE || + option == CURLOPT_POSTQUOTE) { + /* This is an option specifying a list of curl_slist structs: */ + AV *array = (AV *)SvRV(value); + struct curl_slist **slist = NULL; + /* We have to find out which list to use... */ + switch (option) { + case CURLOPT_HTTPHEADER: + slist = &httpheader; break; + case CURLOPT_QUOTE: + slist = "e; break; + case CURLOPT_POSTQUOTE: + slist = &postquote; break; + } + /* ...store the values into it... */ + for (;;) { + SV *sv = av_shift(array); + int len = 0; + char *str = SvPV(sv, len); + if (len == 0) break; + *slist = curl_slist_append(*slist, str); + } + /* ...and pass the list into curl_easy_setopt() */ + RETVAL = curl_easy_setopt(curl, option, *slist); } else { - /* default, option specifying a char * value: */ - RETVAL = curl_easy_setopt(curl, option, value); + /* This is an option specifying a char * value: */ + RETVAL = curl_easy_setopt(curl, option, SvPV(value, PL_na)); } OUTPUT: RETVAL +int +internal_setopt(option, value) +int option +int value +CODE: + if (value == 1) { + internal_options |= option; + } else { + internal_options &= !option; + } + RETVAL = 0; +OUTPUT: + RETVAL + + int curl_easy_perform(curl) void * curl CODE: + if (internal_options & USE_INTERNAL_VARS) { + /* Use internal callback which just stores the content into a buffer. */ + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, internal_write_callback); + curl_easy_setopt(curl, CURLOPT_HEADER, 1); + } RETVAL = curl_easy_perform(curl); if (RETVAL && errbufvarname) { + /* If an error occurred and a varname for error messages has been + specified, store the error message. */ SV *sv = perl_get_sv(errbufvarname, TRUE | GV_ADDMULTI); sv_setpv(sv, errbuf); } + if (!RETVAL && (internal_options & USE_INTERNAL_VARS)) { + /* No error and internal variable for the content are to be used: + Split the data into headers and content and store them into + perl variables. */ + SV *head_sv = perl_get_sv("Curl::easy::headers", TRUE | GV_ADDMULTI); + SV *cont_sv = perl_get_sv("Curl::easy::content", TRUE | GV_ADDMULTI); + char *p = contbuf; + int nl = 0, found = 0; + while (p < bufptr) { + if (nl && (*p == '\n' || *p == '\r')) { + /* found empty line, end of headers */ + *p++ = '\0'; + sv_setpv(head_sv, contbuf); + while (*p == '\n' || *p == '\r') { + p++; + } + sv_setpv(cont_sv, p); + found = 1; + break; + } + nl = (*p == '\n'); + p++; + } + if (!found) { + sv_setpv(head_sv, ""); + sv_setpv(cont_sv, contbuf); + } + } OUTPUT: RETVAL @@ -249,6 +710,10 @@ void * curl int option double value CODE: +#ifdef __GNUC__ + /* a(void) warnig about unnused variable */ + (void) value; +#endif switch (option & CURLINFO_TYPEMASK) { case CURLINFO_STRING: { char * value = (char *)SvPV(ST(2), PL_na); @@ -282,8 +747,7 @@ curl_easy_cleanup(curl) void * curl CODE: curl_easy_cleanup(curl); - if (errbufvarname) free(errbufvarname); - errbufvarname = NULL; + init_globals(); RETVAL = 0; OUTPUT: RETVAL diff --git a/perl/Curl_easy/test.pl b/perl/Curl_easy/test.pl index a93b05692..1d52e3c24 100644 --- a/perl/Curl_easy/test.pl +++ b/perl/Curl_easy/test.pl @@ -8,11 +8,14 @@ # Change 1..1 below to 1..last_test_to_print . # (It may become useful if the test is moved to ./t subdirectory.) +use Benchmark; +use strict; -BEGIN { $| = 1; print "1..5\n"; } -END {print "not ok 1\n" unless $loaded;} +BEGIN { $| = 1; print "1..13\n"; } +END {print "not ok 1\n" unless $::loaded;} use Curl::easy; -$loaded = 1; + +$::loaded = 1; print "ok 1\n"; ######################### End of black magic. @@ -21,81 +24,292 @@ print "ok 1\n"; # (correspondingly "not ok 13") depending on the success of chunk 13 # of the test code): +print "Testing curl version ",&Curl::easy::version(),"\n"; + # Read URL to get -$defurl = "http://www/"; -$url = ""; +my $defurl = "http://localhost/cgi-bin/printenv"; +my $url = ""; print "Please enter an URL to fetch [$defurl]: "; $url = ; if ($url =~ /^\s*\n/) { $url = $defurl; } -# Use this for simple benchmarking -#for ($i=0; $i<1000; $i++) { - # Init the curl session -if (($curl = Curl::easy::curl_easy_init()) != 0) { +my $curl; +if (($curl = Curl::easy::init()) != 0) { print "ok 2\n"; } else { print "ko 2\n"; } -# Set URL to get -if (Curl::easy::curl_easy_setopt($curl, Curl::easy::CURLOPT_URL, $url) == 0) { - print "ok 3\n"; -} else { - print "ko 3\n"; -} # No progress meter please -Curl::easy::curl_easy_setopt($curl, Curl::easy::CURLOPT_NOPROGRESS, 1); +# !! Need this on for all tests, as once disabled, can't re-enable it... +#Curl::easy::setopt($curl, CURLOPT_NOPROGRESS, 1); # Shut up completely -Curl::easy::curl_easy_setopt($curl, Curl::easy::CURLOPT_MUTE, 1); +Curl::easy::setopt($curl, CURLOPT_MUTE, 1); # Follow location headers -Curl::easy::curl_easy_setopt($curl, Curl::easy::CURLOPT_FOLLOWLOCATION, 1); +Curl::easy::setopt($curl, CURLOPT_FOLLOWLOCATION, 1); # Set timeout -Curl::easy::curl_easy_setopt($curl, Curl::easy::CURLOPT_TIMEOUT, 30); +Curl::easy::setopt($curl, CURLOPT_TIMEOUT, 30); # Set file where to read cookies from -Curl::easy::curl_easy_setopt($curl, Curl::easy::CURLOPT_COOKIEFILE, "cookies"); +Curl::easy::setopt($curl, CURLOPT_COOKIEFILE, "cookies"); # Set file where to store the header open HEAD, ">head.out"; -Curl::easy::curl_easy_setopt($curl, Curl::easy::CURLOPT_WRITEHEADER, HEAD); +Curl::easy::setopt($curl, CURLOPT_WRITEHEADER, *HEAD); +print "ok 3\n"; # Set file where to store the body -open BODY, ">body.out"; -Curl::easy::curl_easy_setopt($curl, Curl::easy::CURLOPT_FILE, BODY); +# Send body to stdout - test difference between FILE * and SV * +#open BODY, ">body.out"; +#Curl::easy::setopt($curl, CURLOPT_FILE,*BODY); +print "ok 4\n"; +# Add some additional headers to the http-request: +my @myheaders; +$myheaders[0] = "Server: www"; +$myheaders[1] = "User-Agent: Perl interface for libcURL"; +Curl::easy::setopt($curl, Curl::easy::CURLOPT_HTTPHEADER, \@myheaders); + # Store error messages in variable $errbuf # NOTE: The name of the variable is passed as a string! -# curl_easy_setopt() creates a perl variable with that name, and -# curl_easy_perform() stores the errormessage into it if an error occurs. -Curl::easy::curl_easy_setopt($curl, Curl::easy::CURLOPT_ERRORBUFFER, "errbuf"); +# setopt() creates a perl variable with that name, and +# perform() stores the errormessage into it if an error occurs. + +Curl::easy::setopt($curl, CURLOPT_ERRORBUFFER, "errbuf"); +Curl::easy::setopt($curl, CURLOPT_URL, $url); +print "ok 5\n"; + +my $bytes; +my $realurl; +my $httpcode; +my $errbuf; # Go get it -if (Curl::easy::curl_easy_perform($curl) == 0) { - Curl::easy::curl_easy_getinfo($curl, Curl::easy::CURLINFO_SIZE_DOWNLOAD, $bytes); - print "ok 4: $bytes bytes read\n"; - print "check out the files head.out and body.out\n"; - print "for the headers and content of the URL you just fetched...\n"; - Curl::easy::curl_easy_getinfo($curl, Curl::easy::CURLINFO_EFFECTIVE_URL, $realurl); - Curl::easy::curl_easy_getinfo($curl, Curl::easy::CURLINFO_HTTP_CODE, $httpcode); +if (Curl::easy::perform($curl) == 0) { + Curl::easy::getinfo($curl, CURLINFO_SIZE_DOWNLOAD, $bytes); + print "ok 6: $bytes bytes read\n"; + Curl::easy::getinfo($curl, CURLINFO_EFFECTIVE_URL, $realurl); + Curl::easy::getinfo($curl, CURLINFO_HTTP_CODE, $httpcode); print "effective fetched url (http code: $httpcode) was: $url\n"; +} else { + # We can acces the error message in $errbuf here + print "not ok 6: '$errbuf'\n"; + die "basic url access failed"; +} + +# cleanup +#close HEAD; +# test here - BODY is still expected to be the output +# Curl-easy-1.0.2.pm core dumps if we 'perform' with a closed output FD... +#close BODY; +#exit; +# +# The header callback will only be called if your libcurl has the +# CURLOPT_HEADERFUNCTION supported, otherwise your headers +# go to CURLOPT_WRITEFUNCTION instead... +# + +my $header_called=0; +sub header_callback { print "header callback called\n"; $header_called=1; return length($_[0])}; + +# test for sub reference and head callback +Curl::easy::setopt($curl, CURLOPT_HEADERFUNCTION, \&header_callback); +print "ok 7\n"; # so far so good + +if (Curl::easy::perform($curl) != 0) { + print "not "; +}; +print "ok 8\n"; + +print "next test will fail on libcurl < 7.7.2\n"; +print "not " if (!$header_called); # ok if you have a libcurl <7.7.2 +print "ok 9\n"; + +my $body_called=0; +sub body_callback { + my ($chunk,$handle)=@_; + print "body callback called with ",length($chunk)," bytes\n"; + print "data=$chunk\n"; + $body_called++; + return length($chunk); # OK +} + +# test for ref to sub and body callback +my $body_ref=\&body_callback; +Curl::easy::setopt($curl, CURLOPT_WRITEFUNCTION, $body_ref); + +if (Curl::easy::perform($curl) != 0) { + print "not "; +}; +print "ok 10\n"; + +print "not " if (!$body_called); +print "ok 11\n"; + +my $body_abort_called=0; +sub body_abort_callback { + my ($chunk,$sv)=@_; + print "body abort callback called with ",length($chunk)," bytes\n"; + $body_abort_called++; + return -1; # signal a failure +} + +# test we can abort a request mid-way +my $body_abort_ref=\&body_abort_callback; +Curl::easy::setopt($curl, CURLOPT_WRITEFUNCTION, $body_abort_ref); + +if (Curl::easy::perform($curl) == 0) { # reverse test - this should have failed + print "not "; +}; +print "ok 12\n"; + +print "not " if (!$body_abort_called); # should have been called +print "ok 13\n"; + +# reset to a working 'write' function for next tests +Curl::easy::setopt($curl,CURLOPT_WRITEFUNCTION, sub { return length($_[0])} ); + +# inline progress function +# tests for inline subs and progress callback +# - progress callback must return 'true' on each call. + +my $progress_called=0; +sub prog_callb +{ + my ($clientp,$dltotal,$dlnow,$ultotal,$ulnow)=@_; + print "\nperl progress_callback has been called!\n"; + print "clientp: $clientp, dltotal: $dltotal, dlnow: $dlnow, ultotal: $ultotal, "; + print "ulnow: $ulnow\n"; + $progress_called++; + return 0; +} + +Curl::easy::setopt($curl, CURLOPT_PROGRESSFUNCTION, \&prog_callb); + +# Turn progress meter back on - this doesn't work - once its off, its off. +Curl::easy::setopt($curl, CURLOPT_NOPROGRESS, 0); + +if (Curl::easy::perform($curl) != 0) { + print "not "; +}; +print "ok 14\n"; + +print "not " if (!$progress_called); +print "ok 15\n"; + +my $read_max=10; + +sub read_callb +{ + my ($maxlen,$sv)=@_; + print "\nperl read_callback has been called!\n"; + print "max data size: $maxlen\n"; + print "(upload needs $read_max bytes)\n"; + print "context: ".$sv."\n"; + if ($read_max > 0) { + print "\nEnter max ", $read_max, " characters to be uploaded.\n"; + my $data = ; + chomp $data; + $read_max=$read_max-length($data); + return $data; + } else { + return ""; + } +} + +# +# test post/read callback functions - requires a url which accepts posts, or it fails! +# + +Curl::easy::setopt($curl,CURLOPT_READFUNCTION,\&read_callb); +Curl::easy::setopt($curl,CURLOPT_INFILESIZE,$read_max ); +Curl::easy::setopt($curl,CURLOPT_UPLOAD,1 ); +Curl::easy::setopt($curl,CURLOPT_CUSTOMREQUEST,"POST" ); + +if (Curl::easy::perform($curl) != 0) { + print "not "; +}; +print "ok 16\n"; + +sub passwd_callb +{ + my ($clientp,$prompt,$buflen)=@_; + print "\nperl passwd_callback has been called!\n"; + print "clientp: $clientp, prompt: $prompt, buflen: $buflen\n"; + print "\nEnter max $buflen characters for $prompt "; + my $data = ; + chomp($data); + return (0,$data); +} + +Curl::easy::cleanup($curl); + +# Now do an ftp upload: + +$defurl = "ftp://horn\@localhost//tmp/bla"; +print "\n\nPlease enter an URL for ftp upload [$defurl]: "; +$url = ; +if ($url =~ /^\s*\n/) { + $url = $defurl; +} + +# Init the curl session +if (($curl = Curl::easy::init()) != 0) { + print "ok 17\n"; +} else { + print "not ok 17\n"; +} + +# Set URL to get +if (Curl::easy::setopt($curl, Curl::easy::CURLOPT_URL, $url) == 0) { + print "ok 18\n"; +} else { + print "not ok 18\n"; + +} + +# Tell libcurl to to an upload +Curl::easy::setopt($curl, Curl::easy::CURLOPT_UPLOAD, 1); + +# No progress meter please +#Curl::easy::setopt($curl, Curl::easy::CURLOPT_NOPROGRESS, 1); + +# Use our own progress callback +Curl::easy::setopt($curl, Curl::easy::CURLOPT_PROGRESSFUNCTION, \&prog_callb); + +# Shut up completely +Curl::easy::setopt($curl, Curl::easy::CURLOPT_MUTE, 1); + +# Store error messages in $errbuf +Curl::easy::setopt($curl, Curl::easy::CURLOPT_ERRORBUFFER, "errbuf"); + +$read_max=10; +# Use perl read callback to read data to be uploaded +Curl::easy::setopt($curl, Curl::easy::CURLOPT_READFUNCTION, + \&read_callb); + +# Use perl passwd callback to read password for login to ftp server +Curl::easy::setopt($curl, Curl::easy::CURLOPT_PASSWDFUNCTION, \&passwd_callb); + +print "ok 19\n"; + +# Go get it +if (Curl::easy::perform($curl) == 0) { + Curl::easy::getinfo($curl, Curl::easy::CURLINFO_SIZE_UPLOAD, $bytes); + print "ok 20: $bytes bytes transferred\n\n"; } else { # We can acces the error message in $errbuf here - print "ko 4: '$errbuf'\n"; + print "not ok 20: '$errbuf'\n"; } # Cleanup -close HEAD; -close BODY; -Curl::easy::curl_easy_cleanup($curl); -print "ok 5\n"; - -# Use this for simple benchmarking -#} +Curl::easy::cleanup($curl); +print "ok 21\n";