1
0
mirror of https://github.com/moparisthebest/curl synced 2024-12-24 17:18:48 -05:00

url: Add option CURLOPT_RESOLVER_START_FUNCTION

- Add new option CURLOPT_RESOLVER_START_FUNCTION to set a callback that
  will be called every time before a new resolve request is started
  (ie before a host is resolved) with a pointer to backend-specific
  resolver data. Currently this is only useful for ares.

- Add new option CURLOPT_RESOLVER_START_DATA to set a user pointer to
  pass to the resolver start callback.

Closes https://github.com/curl/curl/pull/2311
This commit is contained in:
Francisco Sedano 2018-02-14 17:20:43 +00:00 committed by Jay Satiro
parent dd027c80fe
commit 23713645d4
16 changed files with 379 additions and 3 deletions

View File

@ -139,6 +139,10 @@ Callback for wildcard matching. See \fICURLOPT_FNMATCH_FUNCTION(3)\fP
Data pointer to pass to the wildcard matching callback. See \fICURLOPT_FNMATCH_DATA(3)\fP Data pointer to pass to the wildcard matching callback. See \fICURLOPT_FNMATCH_DATA(3)\fP
.IP CURLOPT_SUPPRESS_CONNECT_HEADERS .IP CURLOPT_SUPPRESS_CONNECT_HEADERS
Suppress proxy CONNECT response headers from user callbacks. See \fICURLOPT_SUPPRESS_CONNECT_HEADERS(3)\fP Suppress proxy CONNECT response headers from user callbacks. See \fICURLOPT_SUPPRESS_CONNECT_HEADERS(3)\fP
.IP CURLOPT_RESOLVER_START_FUNCTION
Callback to be called before a new resolve request is started. See \fICURLOPT_RESOLVER_START_FUNCTION(3)\fP
.IP CURLOPT_RESOLVER_START_DATA
Data pointer to pass to resolver start callback. See \fICURLOPT_RESOLVER_START_DATA(3)\fP
.SH ERROR OPTIONS .SH ERROR OPTIONS
.IP CURLOPT_ERRORBUFFER .IP CURLOPT_ERRORBUFFER
Error message buffer. See \fICURLOPT_ERRORBUFFER(3)\fP Error message buffer. See \fICURLOPT_ERRORBUFFER(3)\fP

View File

@ -0,0 +1,63 @@
.\" **************************************************************************
.\" * _ _ ____ _
.\" * Project ___| | | | _ \| |
.\" * / __| | | | |_) | |
.\" * | (__| |_| | _ <| |___
.\" * \___|\___/|_| \_\_____|
.\" *
.\" * Copyright (C) 1998 - 2018, 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.
.\" *
.\" **************************************************************************
.\"
.TH CURLOPT_RESOLVER_START_DATA 3 "14 Feb 2018" "libcurl 7.59.0" "curl_easy_setopt options"
.SH NAME
CURLOPT_RESOLVER_START_DATA \- custom pointer passed to the resolver start callback
.SH SYNOPSIS
#include <curl/curl.h>
CURLcode curl_easy_setopt(CURL *handle, CURLOPT_RESOLVER_START_DATA, void *pointer);
.SH DESCRIPTION
Pass a \fIpointer\fP that will be untouched by libcurl and passed as the third
argument in the resolver start callback set with
\fICURLOPT_RESOLVER_START_FUNCTION(3)\fP.
.SH DEFAULT
NULL
.SH PROTOCOLS
All
.SH EXAMPLE
.nf
static int resolver_start_cb(void *resolver_state, void *reserved,
void *userdata)
{
(void)reserved;
printf("Received resolver_state=%p userdata=%p\\n",
resolver_state, userdata);
return 0;
}
CURL *curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_RESOLVER_START_FUNCTION, resolver_start_cb);
curl_easy_setopt(curl, CURLOPT_RESOLVER_START_DATA, curl);
curl_easy_setopt(curl, CURLOPT_URL, "http://example.com");
curl_easy_perform(curl);
curl_easy_cleanup(curl);
}
.fi
.SH AVAILABILITY
Added in 7.59.0
.SH RETURN VALUE
Returns CURLE_OK
.SH "SEE ALSO"
.BR CURLOPT_RESOLVER_START_FUNCTION "(3) "

View File

@ -0,0 +1,83 @@
.\" **************************************************************************
.\" * _ _ ____ _
.\" * Project ___| | | | _ \| |
.\" * / __| | | | |_) | |
.\" * | (__| |_| | _ <| |___
.\" * \___|\___/|_| \_\_____|
.\" *
.\" * Copyright (C) 1998 - 2018, 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.
.\" *
.\" **************************************************************************
.\"
.TH CURLOPT_RESOLVER_START_FUNCTION 3 "14 Feb 2018" "libcurl 7.59.0" "curl_easy_setopt options"
.SH NAME
CURLOPT_RESOLVER_START_FUNCTION \- set callback to be called before a new resolve request is started
.SH SYNOPSIS
.nf
#include <curl/curl.h>
int resolver_start_cb(void *resolver_state, void *reserved, void *userdata);
CURLcode curl_easy_setopt(CURL *handle,
CURLOPT_RESOLVER_START_FUNCTION,
resolver_start_cb);
.SH DESCRIPTION
Pass a pointer to your callback function, which should match the prototype
shown above.
This callback function gets called by libcurl every time before a new resolve
request is started.
\fIresolver_state\fP points to a backend-specific resolver state. Currently
only the ares resolver backend has a resolver state. It can be used to set up
any desired option on the ares channel before it's used, for example setting up
socket callback options.
\fIreserved\fP is reserved.
\fIuserdata\fP is the user pointer set with the
\fICURLOPT_RESOLVER_START_DATA(3)\fP option.
The callback must return 0 on success. Returning a non-zero value will cause
the resolve to fail.
.SH DEFAULT
NULL (No callback)
.SH PROTOCOLS
All
.SH EXAMPLE
.nf
static int resolver_start_cb(void *resolver_state, void *reserved,
void *userdata)
{
(void)reserved;
printf("Received resolver_state=%p userdata=%p\\n",
resolver_state, userdata);
return 0;
}
CURL *curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_RESOLVER_START_FUNCTION, resolver_start_cb);
curl_easy_setopt(curl, CURLOPT_RESOLVER_START_DATA, curl);
curl_easy_setopt(curl, CURLOPT_URL, "http://example.com");
curl_easy_perform(curl);
curl_easy_cleanup(curl);
}
.fi
.SH AVAILABILITY
Added in 7.59.0
.SH RETURN VALUE
Returns CURLE_OK
.SH "SEE ALSO"
.BR CURLOPT_RESOLVER_START_DATA "(3) "

View File

@ -242,6 +242,8 @@ man_MANS = \
CURLOPT_REFERER.3 \ CURLOPT_REFERER.3 \
CURLOPT_REQUEST_TARGET.3 \ CURLOPT_REQUEST_TARGET.3 \
CURLOPT_RESOLVE.3 \ CURLOPT_RESOLVE.3 \
CURLOPT_RESOLVER_START_DATA.3 \
CURLOPT_RESOLVER_START_FUNCTION.3 \
CURLOPT_RESUME_FROM.3 \ CURLOPT_RESUME_FROM.3 \
CURLOPT_RESUME_FROM_LARGE.3 \ CURLOPT_RESUME_FROM_LARGE.3 \
CURLOPT_RTSP_CLIENT_CSEQ.3 \ CURLOPT_RTSP_CLIENT_CSEQ.3 \

View File

@ -597,6 +597,8 @@ CURLOPT_TLSAUTH_TYPE 7.21.4
CURLOPT_TLSAUTH_USERNAME 7.21.4 CURLOPT_TLSAUTH_USERNAME 7.21.4
CURLOPT_TRANSFERTEXT 7.1.1 CURLOPT_TRANSFERTEXT 7.1.1
CURLOPT_TRANSFER_ENCODING 7.21.6 CURLOPT_TRANSFER_ENCODING 7.21.6
CURLOPT_RESOLVER_START_FUNCTION 7.59.0
CURLOPT_RESOLVER_START_DATA 7.59.0
CURLOPT_UNIX_SOCKET_PATH 7.40.0 CURLOPT_UNIX_SOCKET_PATH 7.40.0
CURLOPT_UNRESTRICTED_AUTH 7.10.4 CURLOPT_UNRESTRICTED_AUTH 7.10.4
CURLOPT_UPLOAD 7.1 CURLOPT_UPLOAD 7.1

View File

@ -245,7 +245,9 @@ typedef size_t (*curl_write_callback)(char *buffer,
size_t nitems, size_t nitems,
void *outstream); void *outstream);
/* This callback will be called when a new resolver request is made */
typedef int (*curl_resolver_start_callback)(void *resolver_state,
void *reserved, void *userdata);
/* enumeration of file types */ /* enumeration of file types */
typedef enum { typedef enum {
@ -1833,6 +1835,12 @@ typedef enum {
/* Head start in milliseconds to give happy eyeballs. */ /* Head start in milliseconds to give happy eyeballs. */
CINIT(HAPPY_EYEBALLS_TIMEOUT_MS, LONG, 271), CINIT(HAPPY_EYEBALLS_TIMEOUT_MS, LONG, 271),
/* Function that will be called before a resolver request is made */
CINIT(RESOLVER_START_FUNCTION, FUNCTIONPOINT, 272),
/* User data to pass to the resolver start callback. */
CINIT(RESOLVER_START_DATA, OBJECTPOINT, 273),
CURLOPT_LASTENTRY /* the last unused */ CURLOPT_LASTENTRY /* the last unused */
} CURLoption; } CURLoption;

View File

@ -54,6 +54,9 @@ __extension__ ({ \
if(_curl_is_write_cb_option(_curl_opt)) \ if(_curl_is_write_cb_option(_curl_opt)) \
if(!_curl_is_write_cb(value)) \ if(!_curl_is_write_cb(value)) \
_curl_easy_setopt_err_write_callback(); \ _curl_easy_setopt_err_write_callback(); \
if((_curl_opt) == CURLOPT_RESOLVER_START_FUNCTION) \
if(!_curl_is_resolver_start_callback(value)) \
_curl_easy_setopt_err_resolver_start_callback(); \
if((_curl_opt) == CURLOPT_READFUNCTION) \ if((_curl_opt) == CURLOPT_READFUNCTION) \
if(!_curl_is_read_cb(value)) \ if(!_curl_is_read_cb(value)) \
_curl_easy_setopt_err_read_cb(); \ _curl_easy_setopt_err_read_cb(); \
@ -170,6 +173,10 @@ _CURL_WARNING(_curl_easy_setopt_err_string,
) )
_CURL_WARNING(_curl_easy_setopt_err_write_callback, _CURL_WARNING(_curl_easy_setopt_err_write_callback,
"curl_easy_setopt expects a curl_write_callback argument for this option") "curl_easy_setopt expects a curl_write_callback argument for this option")
_CURL_WARNING(_curl_easy_setopt_err_resolver_start_callback,
"curl_easy_setopt expects a "
"curl_resolver_start_callback argument for this option"
)
_CURL_WARNING(_curl_easy_setopt_err_read_cb, _CURL_WARNING(_curl_easy_setopt_err_read_cb,
"curl_easy_setopt expects a curl_read_callback argument for this option") "curl_easy_setopt expects a curl_read_callback argument for this option")
_CURL_WARNING(_curl_easy_setopt_err_ioctl_cb, _CURL_WARNING(_curl_easy_setopt_err_ioctl_cb,
@ -354,6 +361,7 @@ _CURL_WARNING(_curl_easy_getinfo_err_curl_off_t,
(option) == CURLOPT_SSH_KEYDATA || \ (option) == CURLOPT_SSH_KEYDATA || \
(option) == CURLOPT_SSL_CTX_DATA || \ (option) == CURLOPT_SSL_CTX_DATA || \
(option) == CURLOPT_WRITEDATA || \ (option) == CURLOPT_WRITEDATA || \
(option) == CURLOPT_RESOLVER_START_DATA || \
0) 0)
/* evaluates to true if option takes a POST data argument (void* or char*) */ /* evaluates to true if option takes a POST data argument (void* or char*) */
@ -504,6 +512,11 @@ _CURL_WARNING(_curl_easy_getinfo_err_curl_off_t,
(__builtin_types_compatible_p(__typeof__(func), type) || \ (__builtin_types_compatible_p(__typeof__(func), type) || \
__builtin_types_compatible_p(__typeof__(func) *, type)) __builtin_types_compatible_p(__typeof__(func) *, type))
/* evaluates to true if expr is of type curl_resolver_start_callback */
#define _curl_is_resolver_start_callback(expr) \
(_curl_is_NULL(expr) || \
_curl_callback_compatible((expr), curl_resolver_start_callback))
/* evaluates to true if expr is of type curl_read_callback or "similar" */ /* evaluates to true if expr is of type curl_read_callback or "similar" */
#define _curl_is_read_cb(expr) \ #define _curl_is_read_cb(expr) \
(_curl_is_NULL(expr) || \ (_curl_is_NULL(expr) || \

View File

@ -58,6 +58,7 @@
#include "strerror.h" #include "strerror.h"
#include "url.h" #include "url.h"
#include "inet_ntop.h" #include "inet_ntop.h"
#include "multiif.h"
#include "warnless.h" #include "warnless.h"
/* The last 3 #include files should be in this order */ /* The last 3 #include files should be in this order */
#include "curl_printf.h" #include "curl_printf.h"
@ -481,6 +482,17 @@ int Curl_resolv(struct connectdata *conn,
if(!Curl_ipvalid(conn)) if(!Curl_ipvalid(conn))
return CURLRESOLV_ERROR; return CURLRESOLV_ERROR;
/* notify the resolver start callback */
if(data->set.resolver_start) {
int st;
Curl_set_in_callback(data, true);
st = data->set.resolver_start(data->state.resolver, NULL,
data->set.resolver_start_client);
Curl_set_in_callback(data, false);
if(st)
return CURLRESOLV_ERROR;
}
/* If Curl_getaddrinfo() returns NULL, 'respwait' might be set to a /* If Curl_getaddrinfo() returns NULL, 'respwait' might be set to a
non-zero value indicating that we need to wait for the response to the non-zero value indicating that we need to wait for the response to the
resolve call */ resolve call */

View File

@ -2110,6 +2110,21 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option,
data->set.fclosesocket = va_arg(param, curl_closesocket_callback); data->set.fclosesocket = va_arg(param, curl_closesocket_callback);
break; break;
case CURLOPT_RESOLVER_START_FUNCTION:
/*
* resolver start callback function: called before a new resolver request
* is started
*/
data->set.resolver_start = va_arg(param, curl_resolver_start_callback);
break;
case CURLOPT_RESOLVER_START_DATA:
/*
* resolver start callback data pointer. Might be NULL.
*/
data->set.resolver_start_client = va_arg(param, void *);
break;
case CURLOPT_CLOSESOCKETDATA: case CURLOPT_CLOSESOCKETDATA:
/* /*
* socket callback data pointer. Might be NULL. * socket callback data pointer. Might be NULL.

View File

@ -1681,6 +1681,10 @@ struct UserDefined {
struct Curl_http2_dep *stream_dependents; struct Curl_http2_dep *stream_dependents;
bool abstract_unix_socket; bool abstract_unix_socket;
curl_resolver_start_callback resolver_start; /* optional callback called
before resolver start */
void *resolver_start_client; /* pointer to pass to resolver start callback */
}; };
struct Names { struct Names {

View File

@ -1326,6 +1326,10 @@
d c 30270 d c 30270
d CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS... d CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS...
d c 00271 d c 00271
d CURLOPT_RESOLVER_START_FUNCTION...
d c 20272
d CURLOPT_RESOLVER_START_DATA...
d c 10273
* *
/if not defined(CURL_NO_OLDIES) /if not defined(CURL_NO_OLDIES)
d CURLOPT_FILE c 10001 d CURLOPT_FILE c 10001

View File

@ -80,7 +80,7 @@ test617 test618 test619 test620 test621 test622 test623 test624 test625 \
test626 test627 test628 test629 test630 test631 test632 test633 test634 \ test626 test627 test628 test629 test630 test631 test632 test633 test634 \
test635 test636 test637 test638 test639 test640 test641 test642 \ test635 test636 test637 test638 test639 test640 test641 test642 \
test643 test644 test645 test646 test647 test648 test649 test650 test651 \ test643 test644 test645 test646 test647 test648 test649 test650 test651 \
test652 test653 test654 \ test652 test653 test654 test655 \
\ \
test700 test701 test702 test703 test704 test705 test706 test707 test708 \ test700 test701 test702 test703 test704 test705 test706 test707 test708 \
test709 test710 test711 test712 test713 test714 test715 \ test709 test710 test711 test712 test713 test714 test715 \

50
tests/data/test655 Normal file
View File

@ -0,0 +1,50 @@
<testcase>
<info>
<keywords>
HTTP
</keywords>
</info>
#
# Server-side
<reply>
<data>
HTTP/1.1 200 OK
Date: Thu, 09 Nov 2010 14:49:00 GMT
Server: test-server/fake swsclose
Connection: close
Content-Type: text/html
hello
</data>
<datacheck>
hello
</datacheck>
</reply>
# Client-side
<client>
<server>
http
</server>
# tool is what to use instead of 'curl'
<tool>
lib655
</tool>
<name>
resolver start callback
</name>
<command>
http://%HOSTIP:%HTTPPORT/655
</command>
</client>
#
# Verify data after the test has been "shot"
<verify>
<errorcode>
0
</errorcode>
</verify>
</testcase>

View File

@ -20,7 +20,7 @@ noinst_PROGRAMS = chkhostname libauthretry libntlmconnect \
lib559 lib560 lib562 lib564 lib565 lib566 lib567 lib568 lib569 lib570 \ lib559 lib560 lib562 lib564 lib565 lib566 lib567 lib568 lib569 lib570 \
lib571 lib572 lib573 lib574 lib575 lib576 lib578 lib579 lib582 \ lib571 lib572 lib573 lib574 lib575 lib576 lib578 lib579 lib582 \
lib583 lib585 lib586 lib587 lib589 lib590 lib591 lib597 lib598 lib599 \ lib583 lib585 lib586 lib587 lib589 lib590 lib591 lib597 lib598 lib599 \
lib643 lib644 lib645 lib650 lib651 lib652 lib653 lib654 \ lib643 lib644 lib645 lib650 lib651 lib652 lib653 lib654 lib655 \
lib1500 lib1501 lib1502 lib1503 lib1504 lib1505 lib1506 lib1507 lib1508 \ lib1500 lib1501 lib1502 lib1503 lib1504 lib1505 lib1506 lib1507 lib1508 \
lib1509 lib1510 lib1511 lib1512 lib1513 lib1514 lib1515 lib1517 \ lib1509 lib1510 lib1511 lib1512 lib1513 lib1514 lib1515 lib1517 \
lib1520 lib1521 \ lib1520 lib1521 \
@ -327,6 +327,9 @@ lib653_CPPFLAGS = $(AM_CPPFLAGS)
lib654_SOURCES = lib654.c $(SUPPORTFILES) lib654_SOURCES = lib654.c $(SUPPORTFILES)
lib654_CPPFLAGS = $(AM_CPPFLAGS) lib654_CPPFLAGS = $(AM_CPPFLAGS)
lib655_SOURCES = lib655.c $(SUPPORTFILES)
lib655_CPPFLAGS = $(AM_CPPFLAGS)
lib1500_SOURCES = lib1500.c $(SUPPORTFILES) $(TESTUTIL) lib1500_SOURCES = lib1500.c $(SUPPORTFILES) $(TESTUTIL)
lib1500_LDADD = $(TESTUTIL_LIBS) lib1500_LDADD = $(TESTUTIL_LIBS)
lib1500_CPPFLAGS = $(AM_CPPFLAGS) lib1500_CPPFLAGS = $(AM_CPPFLAGS)

112
tests/libtest/lib655.c Normal file
View File

@ -0,0 +1,112 @@
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2017, 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 "test.h"
#include "memdebug.h"
const char TEST_DATA_STRING[] = "Test data";
static int cb_count = 0;
static int
resolver_alloc_cb_fail(void *resolver_state, void *reserved, void *userdata)
{
(void)resolver_state;
(void)reserved;
cb_count++;
if(strcmp(userdata, TEST_DATA_STRING)) {
fprintf(stderr, "Invalid test data received");
exit(1);
}
return 1;
}
static int
resolver_alloc_cb_pass(void *resolver_state, void *reserved, void *userdata)
{
(void)resolver_state;
(void)reserved;
cb_count++;
if(strcmp(userdata, TEST_DATA_STRING)) {
fprintf(stderr, "Invalid test data received");
exit(1);
}
return 0;
}
int test(char *URL)
{
CURL *curl;
CURLcode res = CURLE_OK;
if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
fprintf(stderr, "curl_global_init() failed\n");
return TEST_ERR_MAJOR_BAD;
}
curl = curl_easy_init();
if(!curl) {
fprintf(stderr, "curl_easy_init() failed\n");
res = TEST_ERR_MAJOR_BAD;
goto test_cleanup;
}
/* First set the URL that is about to receive our request. */
test_setopt(curl, CURLOPT_URL, URL);
test_setopt(curl, CURLOPT_RESOLVER_START_DATA, TEST_DATA_STRING);
test_setopt(curl, CURLOPT_RESOLVER_START_FUNCTION, resolver_alloc_cb_fail);
/* this should fail */
res = curl_easy_perform(curl);
if(res != CURLE_COULDNT_RESOLVE_HOST) {
fprintf(stderr, "curl_easy_perform should have returned "
"CURLE_COULDNT_RESOLVE_HOST but instead returned error %d\n", res);
if(res == CURLE_OK)
res = TEST_ERR_FAILURE;
goto test_cleanup;
}
test_setopt(curl, CURLOPT_RESOLVER_START_FUNCTION, resolver_alloc_cb_pass);
/* this should succeed */
res = curl_easy_perform(curl);
if(res) {
fprintf(stderr, "curl_easy_perform failed.\n");
goto test_cleanup;
}
if(cb_count != 2) {
fprintf(stderr, "Unexpected number of callbacks: %d\n", cb_count);
res = TEST_ERR_FAILURE;
goto test_cleanup;
}
test_cleanup:
/* always cleanup */
curl_easy_cleanup(curl);
curl_global_cleanup();
return (int)res;
}

View File

@ -132,6 +132,7 @@ static curl_chunk_end_callback chunk_end_cb;
static curl_fnmatch_callback fnmatch_cb; static curl_fnmatch_callback fnmatch_cb;
static curl_closesocket_callback closesocketcb; static curl_closesocket_callback closesocketcb;
static curl_xferinfo_callback xferinfocb; static curl_xferinfo_callback xferinfocb;
static curl_resolver_start_callback resolver_start_cb;
int test(char *URL) int test(char *URL)
{ {