CURLOPT_PREREQFUNCTION: add new callback

Triggered before a request is made but after a connection is set up

Changes:

- callback: Update docs and callback for pre-request callback
- Add documentation for CURLOPT_PREREQDATA and CURLOPT_PREREQFUNCTION,
- Add redirect test and callback failure test
- Note that the function may be called multiple times on a redirection
- Disable new 2086 test due to Windows weirdness

Closes #7477
This commit is contained in:
Max Dymond 2021-07-22 15:32:30 +01:00 committed by Daniel Stenberg
parent 06981ba7f6
commit a517378de5
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
21 changed files with 602 additions and 4 deletions

View File

@ -144,6 +144,10 @@ Suppress proxy CONNECT response headers from user callbacks. See \fICURLOPT_SUPP
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
.IP CURLOPT_PREREQFUNCTION
Callback to be called after a connection is established but before a request is made on that connection. See \fICURLOPT_PREREQFUNCTION(3)\fP
.IP CURLOPT_PREREQDATA
Data pointer to pass to the CURLOPT_PREREQFUNCTION callback. See \fICURLOPT_PREREQDATA(3)\fP
.SH ERROR OPTIONS
.IP CURLOPT_ERRORBUFFER
Error message buffer. See \fICURLOPT_ERRORBUFFER(3)\fP

View File

@ -0,0 +1,61 @@
.\" **************************************************************************
.\" * _ _ ____ _
.\" * Project ___| | | | _ \| |
.\" * / __| | | | |_) | |
.\" * | (__| |_| | _ <| |___
.\" * \___|\___/|_| \_\_____|
.\" *
.\" * Copyright (C) 2021, Max Dymond, <max.dymond@microsoft.com>, 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.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_PREREQDATA 3 "2 Aug 2021" "libcurl 7.80.0" "curl_easy_setopt options"
.SH NAME
CURLOPT_PREREQDATA \- custom pointer passed to the pre-request callback
.SH SYNOPSIS
#include <curl/curl.h>
CURLcode curl_easy_setopt(CURL *handle, CURLOPT_PREREQDATA, void *pointer);
.SH DESCRIPTION
Pass a \fIpointer\fP that will be untouched by libcurl and passed as the first
argument in the pre-request callback set with
\fICURLOPT_PREREQFUNCTION(3)\fP.
.SH DEFAULT
NULL
.SH PROTOCOLS
All
.SH EXAMPLE
.nf
static int prereq_callback(void *clientp,
char *conn_primary_ip,
char *conn_local_ip,
int conn_primary_port,
int conn_local_port)
{
printf("Connection made to %s:%s\n", conn_primary_ip, conn_primary_port);
return CURL_PREREQFUNC_OK;
}
{
struct data prereq_data;
curl_easy_setopt(CURL *handle, CURLOPT_PREREQFUNCTION, prereq_callback);
curl_easy_setopt(CURL *handle, CURLOPT_PREREQDATA, &prereq_data);
}
.fi
.SH AVAILABILITY
Added in 7.80.0
.SH RETURN VALUE
Returns CURLE_OK if the option is supported, and CURLE_UNKNOWN_OPTION if not.
.SH "SEE ALSO"
.BR CURLOPT_PREREQFUNCTION "(3) "

View File

@ -0,0 +1,104 @@
.\" **************************************************************************
.\" * _ _ ____ _
.\" * Project ___| | | | _ \| |
.\" * / __| | | | |_) | |
.\" * | (__| |_| | _ <| |___
.\" * \___|\___/|_| \_\_____|
.\" *
.\" * Copyright (C) 2021, Max Dymond, <max.dymond@microsoft.com>, 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.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_PREREQFUNCTION 3 "2 Aug 2021" "libcurl 7.80.0" "curl_easy_setopt options"
.SH NAME
CURLOPT_PREREQFUNCTION \- user callback called when a connection has been
established, but before a request has been made.
.SH SYNOPSIS
.nf
#include <curl/curl.h>
/* These are the return codes for the pre-request callback. */
#define CURL_PREREQFUNC_OK 0
#define CURL_PREREQFUNC_ABORT 1 /* fail the entire transfer */
int prereq_callback(void *clientp,
char *conn_primary_ip,
char *conn_local_ip,
int conn_primary_port,
int conn_local_port);
CURLcode curl_easy_setopt(CURL *handle, CURLOPT_PREREQFUNCTION, prereq_callback);
.SH DESCRIPTION
Pass a pointer to your callback function, which should match the prototype
shown above.
This function gets called by libcurl after a connection has been established
or a connection has been reused (including any SSL handshaking), but before any
request is actually made on the connection. For example, for HTTP, this
callback is called once a connection has been established to the server, but
before a GET/HEAD/POST/etc request has been sent.
This function may be called multiple times if redirections are enabled and are
being followed (see \fICURLOPT_FOLLOWLOCATION(3)\fP).
This function is passed the following information:
.IP conn_primary_ip
A nul-terminated pointer to a C string containing the primary IP of the remote
server established with this connection. For FTP, this is the IP for the control
connection. IPv6 addresses are represented without surrounding brackets.
.IP conn_local_ip
A nul-terminated pointer to a C string containing the originating IP for this
connection. IPv6 addresses are represented without surrounding brackets.
.IP conn_primary_port
The primary port number on the remote server established with this connection.
For FTP, this is the port for the control connection. This can be a TCP or a
UDP port number dependending on the protocol.
.IP conn_local_port
The originating port number for this connection. This can be a TCP or a UDP port
number dependending on the protocol.
.RE
\fIclientp\fP is the pointer you set with \fICURLOPT_PREREQDATA(3)\fP.
The callback function must return \fICURL_PREREQFUNC_OK\fP on success, or
\fICURL_PREREQFUNC_ABORT\fP to cause the transfer to fail.
.SH DEFAULT
By default, this is NULL and unused.
.SH PROTOCOLS
ALL
.SH EXAMPLE
.nf
static int prereq_callback(void *clientp,
char *conn_primary_ip,
char *conn_local_ip,
int conn_primary_port,
int conn_local_port)
{
printf("Connection made to %s:%s\n", conn_primary_ip, conn_primary_port);
return CURL_PREREQFUNC_OK;
}
{
struct data prereq_data;
curl_easy_setopt(CURL *handle, CURLOPT_PREREQFUNCTION, prereq_callback);
curl_easy_setopt(CURL *handle, CURLOPT_PREREQDATA, &prereq_data);
}
.fi
.SH AVAILABILITY
Added in 7.80.0
.SH RETURN VALUE
Returns CURLE_OK if the option is supported, and CURLE_UNKNOWN_OPTION if not.
.SH "SEE ALSO"
.BR CURLOPT_PREREQDATA "(3) "

View File

@ -254,6 +254,8 @@ man_MANS = \
CURLOPT_POSTQUOTE.3 \
CURLOPT_POSTREDIR.3 \
CURLOPT_PREQUOTE.3 \
CURLOPT_PREREQDATA.3 \
CURLOPT_PREREQFUNCTION.3 \
CURLOPT_PRE_PROXY.3 \
CURLOPT_PRIVATE.3 \
CURLOPT_PROGRESSDATA.3 \

View File

@ -531,6 +531,8 @@ CURLOPT_POSTFIELDSIZE_LARGE 7.11.1
CURLOPT_POSTQUOTE 7.1
CURLOPT_POSTREDIR 7.19.1
CURLOPT_PREQUOTE 7.9.5
CURLOPT_PREREQDATA 7.80.0
CURLOPT_PREREQFUNCTION 7.80.0
CURLOPT_PRE_PROXY 7.52.0
CURLOPT_PRIVATE 7.10.3
CURLOPT_PROGRESSDATA 7.1
@ -964,6 +966,8 @@ CURL_POLL_INOUT 7.14.0
CURL_POLL_NONE 7.14.0
CURL_POLL_OUT 7.14.0
CURL_POLL_REMOVE 7.14.0
CURL_PREREQFUNC_ABORT 7.79.0
CURL_PREREQFUNC_OK 7.79.0
CURL_PROGRESSFUNC_CONTINUE 7.68.0
CURL_PROGRESS_BAR 7.1.1 - 7.4.1
CURL_PROGRESS_STATS 7.1.1 - 7.4.1

View File

@ -470,6 +470,20 @@ typedef int (*curl_debug_callback)
size_t size, /* size of the data pointed to */
void *userptr); /* whatever the user please */
/* This is the CURLOPT_PREREQFUNCTION callback prototype. */
typedef int (*curl_prereq_callback)(void *clientp,
char *conn_primary_ip,
char *conn_local_ip,
int conn_primary_port,
int conn_local_port);
/* Return code for when the pre-request callback has terminated without
any errors */
#define CURL_PREREQFUNC_OK 0
/* Return code for when the pre-request callback wants to abort the
request */
#define CURL_PREREQFUNC_ABORT 1
/* All possible error codes from all sorts of curl functions. Future versions
may return other values, stay prepared.
@ -2105,6 +2119,13 @@ typedef enum {
/* used by scp/sftp to verify the host's public key */
CURLOPT(CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256, CURLOPTTYPE_STRINGPOINT, 311),
/* Function that will be called immediately before the initial request
is made on a connection (after any protocol negotiation step). */
CURLOPT(CURLOPT_PREREQFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 312),
/* Data passed to the CURLOPT_PREREQFUNCTION callback */
CURLOPT(CURLOPT_PREREQDATA, CURLOPTTYPE_CBPOINT, 313),
CURLOPT_LASTENTRY /* the last unused */
} CURLoption;

View File

@ -356,6 +356,6 @@ struct curl_easyoption Curl_easyopts[] = {
*/
int Curl_easyopts_check(void)
{
return ((CURLOPT_LASTENTRY%10000) != (311 + 1));
return ((CURLOPT_LASTENTRY%10000) != (313 + 1));
}
#endif

View File

@ -2028,6 +2028,28 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
break;
case MSTATE_DO:
if(data->set.fprereq) {
int prereq_rc;
/* call the prerequest callback function */
Curl_set_in_callback(data, true);
prereq_rc = data->set.fprereq(data->set.prereq_userp,
data->info.conn_primary_ip,
data->info.conn_local_ip,
data->info.conn_primary_port,
data->info.conn_local_port);
Curl_set_in_callback(data, false);
if(prereq_rc != CURL_PREREQFUNC_OK) {
failf(data, "operation aborted by pre-request callback");
/* failure in pre-request callback - don't do any other processing */
result = CURLE_ABORTED_BY_CALLBACK;
Curl_posttransfer(data);
multi_done(data, result, FALSE);
stream_error = TRUE;
break;
}
}
if(data->set.connect_only) {
/* keep connection open for application to use the socket */
connkeep(data->conn, "CONNECT_ONLY");

View File

@ -3013,6 +3013,12 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
return result;
break;
#endif
case CURLOPT_PREREQFUNCTION:
data->set.fprereq = va_arg(param, curl_prereq_callback);
break;
case CURLOPT_PREREQDATA:
data->set.prereq_userp = va_arg(param, void *);
break;
default:
/* unknown tag and its companion, just ignore: */
result = CURLE_UNKNOWN_OPTION;

View File

@ -1652,6 +1652,8 @@ struct UserDefined {
curl_closesocket_callback fclosesocket; /* function for closing the
socket */
void *closesocket_client;
curl_prereq_callback fprereq; /* pre-initial request callback */
void *prereq_userp; /* pre-initial request user data */
void *seek_client; /* pointer to pass to the seek callback */
/* the 3 curl_conv_callback functions below are used on non-ASCII hosts */

View File

@ -29,6 +29,8 @@
# test 1801 causes problems on Mac OS X and github
# https://github.com/curl/curl/issues/380
1801
# test 2086 causes issues on Windows only
2086
#
#
# Tests that are disabled here for Hyper are SUPPOSED to work but

View File

@ -229,7 +229,7 @@ test2064 test2065 test2066 test2067 test2068 test2069 \
test2064 test2065 test2066 test2067 test2068 test2069 test2070 \
test2071 test2072 test2073 test2074 test2075 test2076 test2077 \
test2078 \
test2080 test2081 \
test2080 test2081 test2082 test2083 test2084 test2085 test2086 \
\
test2100 \
\

51
tests/data/test2082 Normal file
View File

@ -0,0 +1,51 @@
<testcase>
<info>
<keywords>
HTTP
</keywords>
</info>
# Server-side
<reply>
<data nocheck="yes">
HTTP/1.1 200 OK
Date: Tue, 09 Nov 2010 14:49:00 GMT
Server: test-server/fake
Content-Type: text/html
Content-Length: 0
</data>
</reply>
# Client-side
<client>
<server>
http
</server>
<name>
Pre-request callback for HTTP
</name>
<tool>
libprereq
</tool>
<command>
%HOSTIP:%HTTPPORT/%TESTNUMBER
</command>
</client>
# Verify data after the test has been "shot"
<verify>
<stripfile>
s/^Local port = \d+/Local port = stripped/
</stripfile>
<stdout>
Connected to %HOSTIP
Connected from %CLIENTIP
Remote port = %HTTPPORT
Local port = stripped
Returning = 0
</stdout>
</verify>
</testcase>

45
tests/data/test2083 Normal file
View File

@ -0,0 +1,45 @@
<testcase>
<info>
<keywords>
FTP
</keywords>
</info>
# Server-side
<reply>
<data>
</data>
</reply>
# Client-side
<client>
<server>
ftp
</server>
<name>
Pre-request callback for FTP
</name>
<tool>
libprereq
</tool>
<command>
ftp://%HOSTIP:%FTPPORT/test-%TESTNUMBER/
</command>
</client>
# Verify data after the test has been "shot"
<verify>
<stripfile>
s/^Local port = \d+/Local port = stripped/
</stripfile>
<stdout>
Connected to %HOSTIP
Connected from %CLIENTIP
Remote port = %FTPPORT
Local port = stripped
Returning = 0
</stdout>
</verify>
</testcase>

54
tests/data/test2084 Normal file
View File

@ -0,0 +1,54 @@
<testcase>
<info>
<keywords>
HTTP
</keywords>
</info>
# Server-side
<reply>
<data nocheck="yes">
HTTP/1.1 200 OK
Date: Tue, 09 Nov 2010 14:49:00 GMT
Server: test-server/fake
Content-Type: text/html
Content-Length: 0
</data>
</reply>
# Client-side
<client>
<server>
http
</server>
<name>
Pre-request callback for HTTP with callback terminating transfer
</name>
<tool>
libprereq
</tool>
<command>
%HOSTIP:%HTTPPORT/%TESTNUMBER#err
</command>
</client>
# Verify data after the test has been "shot"
<verify>
<errorcode>
42
</errorcode>
<stripfile>
s/^Local port = \d+/Local port = stripped/
</stripfile>
<stdout>
Connected to %HOSTIP
Connected from %CLIENTIP
Remote port = %HTTPPORT
Local port = stripped
Returning = 1
</stdout>
</verify>
</testcase>

64
tests/data/test2085 Normal file
View File

@ -0,0 +1,64 @@
<testcase>
<info>
<keywords>
HTTP
followlocation
</keywords>
</info>
# Server-side
<reply>
<data nocheck="yes">
HTTP/1.1 302 OK swsclose
Location: data2.html/%TESTNUMBER0002
Date: Tue, 09 Nov 2010 14:49:00 GMT
Connection: close
</data>
<data2 nocheck="yes">
HTTP/1.1 200 OK swsclose
Location: this should be ignored
Date: Tue, 09 Nov 2010 14:49:00 GMT
Connection: close
body
</data2>
</reply>
# Client-side
<client>
<server>
http
</server>
<name>
Pre-request callback for HTTP with location following
</name>
<tool>
libprereq
</tool>
<command>
%HOSTIP:%HTTPPORT/%TESTNUMBER#redir
</command>
</client>
# Verify data after the test has been "shot"
<verify>
<stripfile>
s/^Local port = \d+/Local port = stripped/
</stripfile>
<stdout>
Connected to %HOSTIP
Connected from %CLIENTIP
Remote port = %HTTPPORT
Local port = stripped
Returning = 0
Connected to %HOSTIP
Connected from %CLIENTIP
Remote port = %HTTPPORT
Local port = stripped
Returning = 0
</stdout>
</verify>
</testcase>

52
tests/data/test2086 Normal file
View File

@ -0,0 +1,52 @@
<testcase>
<info>
<keywords>
HTTP
IPv6
</keywords>
</info>
# Server-side
<reply>
<data nocheck="yes">
HTTP/1.1 200 OK
Date: Tue, 09 Nov 2010 14:49:00 GMT
Server: test-server/fake
Content-Type: text/html
Content-Length: 0
</data>
</reply>
# Client-side
<client>
<server>
http-ipv6
</server>
<name>
Pre-request callback for HTTP IPv6
</name>
<tool>
libprereq
</tool>
<command>
%HOST6IP:%HTTP6PORT/%TESTNUMBER#ipv6
</command>
</client>
# Verify data after the test has been "shot"
<verify>
<stripfile>
s/^Local port = \d+/Local port = stripped/
</stripfile>
<stdout>
Connected to %HOST6IP
Connected from %CLIENT6IP
Remote port = %HTTP6PORT
Local port = stripped
Returning = 0
</stdout>
</verify>
</testcase>

View File

@ -5,3 +5,4 @@ lib[56][0-9][0-9]
lib1521.c
libauthretry
libntlmconnect
libprereq

View File

@ -36,7 +36,7 @@ SUPPORTFILES = first.c test.h
# These are all libcurl test programs
noinst_PROGRAMS = chkhostname libauthretry libntlmconnect \
chkdecimalpoint \
chkdecimalpoint libprereq \
lib500 lib501 lib502 lib503 lib504 lib505 lib506 lib507 lib508 lib509 \
lib510 lib511 lib512 lib513 lib514 lib515 lib516 lib517 lib518 lib519 \
lib520 lib521 lib523 lib524 lib525 lib526 lib527 lib529 lib532 \
@ -81,6 +81,10 @@ libntlmconnect_CPPFLAGS = $(AM_CPPFLAGS)
libauthretry_SOURCES = libauthretry.c $(SUPPORTFILES)
libauthretry_CPPFLAGS = $(AM_CPPFLAGS)
libprereq_SOURCES = libprereq.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
libprereq_LDADD = $(TESTUTIL_LIBS)
libprereq_CPPFLAGS = $(AM_CPPFLAGS)
lib500_SOURCES = lib500.c $(SUPPORTFILES) $(TESTUTIL) $(TSTTRACE) $(MULTIBYTE)
lib500_LDADD = $(TESTUTIL_LIBS)
lib500_CPPFLAGS = $(AM_CPPFLAGS)
@ -494,7 +498,7 @@ lib1520_CPPFLAGS = $(AM_CPPFLAGS) -DLIB1520
nodist_lib1521_SOURCES = lib1521.c $(SUPPORTFILES)
lib1521_CPPFLAGS = $(AM_CPPFLAGS) -I$(srcdir)
lib1522_SOURCES = lib1522.c $(SUPPORTFILES) $(TESTUTIL) $(TSTTRACE)
lib1522_SOURCES = lib1522.c $(SUPPORTFILES) $(TESTUTIL) $(TSTTRACE)
lib1522_LDADD = $(TESTUTIL_LIBS)
lib1522_CPPFLAGS = $(AM_CPPFLAGS)

98
tests/libtest/libprereq.c Normal file
View File

@ -0,0 +1,98 @@
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 2021, Max Dymond, <max.dymond@microsoft.com>
*
* 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.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"
typedef struct prcs {
int prereq_retcode;
int ipv6;
} PRCS;
static int prereq_callback(void *clientp,
char *conn_primary_ip,
char *conn_local_ip,
int conn_primary_port,
int conn_local_port)
{
PRCS *prereq_cb = (PRCS *)clientp;
if(prereq_cb->ipv6) {
printf("Connected to [%s]\n", conn_primary_ip);
printf("Connected from [%s]\n", conn_local_ip);
}
else {
printf("Connected to %s\n", conn_primary_ip);
printf("Connected from %s\n", conn_local_ip);
}
printf("Remote port = %d\n", conn_primary_port);
printf("Local port = %d\n", conn_local_port);
printf("Returning = %d\n", prereq_cb->prereq_retcode);
return prereq_cb->prereq_retcode;
}
int test(char *URL)
{
PRCS prereq_cb;
CURLcode ret = CURLE_OK;
CURL *curl = NULL;
prereq_cb.prereq_retcode = CURL_PREREQFUNC_OK;
prereq_cb.ipv6 = 0;
curl_global_init(CURL_GLOBAL_ALL);
curl = curl_easy_init();
if(curl) {
if(strstr(URL, "#ipv6")) {
/* The IP addresses should be surrounded by brackets! */
prereq_cb.ipv6 = 1;
}
if(strstr(URL, "#err")) {
/* Set the callback to exit with failure */
prereq_cb.prereq_retcode = CURL_PREREQFUNC_ABORT;
}
curl_easy_setopt(curl, CURLOPT_URL, URL);
curl_easy_setopt(curl, CURLOPT_PREREQFUNCTION, prereq_callback);
curl_easy_setopt(curl, CURLOPT_PREREQDATA, &prereq_cb);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, stderr);
if(strstr(URL, "#redir")) {
/* Enable follow-location */
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
}
ret = curl_easy_perform(curl);
if(ret) {
fprintf(stderr, "%s:%d curl_easy_perform() failed with code %d (%s)\n",
__FILE__, __LINE__, ret, curl_easy_strerror(ret));
goto test_cleanup;
}
}
test_cleanup:
curl_easy_cleanup(curl);
curl_global_cleanup();
return ret;
}

View File

@ -136,6 +136,7 @@ static curl_xferinfo_callback xferinfocb;
static curl_hstsread_callback hstsreadcb;
static curl_hstswrite_callback hstswritecb;
static curl_resolver_start_callback resolver_start_cb;
static curl_prereq_callback prereqcb;
int test(char *URL)
{