diff --git a/docs/TODO.md b/docs/TODO.md index aa6611a0c6..77735ff701 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -124,11 +124,6 @@ for each redirect. [curl issue 6743](https://github.com/curl/curl/issues/6743) -## CURLINFO_PAUSE_STATE - -Return information about the transfer's current pause state, in both -directions. See [curl issue 2588](https://github.com/curl/curl/issues/2588) - ## Expose tried IP addresses that failed When libcurl fails to connect to a host, it could offer the application the diff --git a/docs/libcurl/curl_easy_getinfo.md b/docs/libcurl/curl_easy_getinfo.md index 6fc5555b38..bf9fba8a53 100644 --- a/docs/libcurl/curl_easy_getinfo.md +++ b/docs/libcurl/curl_easy_getinfo.md @@ -189,6 +189,10 @@ See CURLINFO_NUM_CONNECTS(3) The errno from the last failure to connect. See CURLINFO_OS_ERRNO(3) +## CURLINFO_PAUSE_STATE + +The current pause state of the transfer. See CURLINFO_PAUSE_STATE(3) + ## CURLINFO_POSTTRANSFER_TIME_T The time it took from the start until the last byte is sent by libcurl. diff --git a/docs/libcurl/opts/CURLINFO_PAUSE_STATE.md b/docs/libcurl/opts/CURLINFO_PAUSE_STATE.md new file mode 100644 index 0000000000..547b17a97a --- /dev/null +++ b/docs/libcurl/opts/CURLINFO_PAUSE_STATE.md @@ -0,0 +1,79 @@ +--- +c: Copyright (C) Daniel Stenberg, , et al. +SPDX-License-Identifier: curl +Title: CURLINFO_PAUSE_STATE +Section: 3 +Source: libcurl +See-also: + - curl_easy_getinfo (3) + - curl_easy_pause (3) +Protocol: + - All +Added-in: 8.22.0 +--- + +# NAME + +CURLINFO_PAUSE_STATE - get the current pause state + +# SYNOPSIS + +~~~c +#include + +CURLcode curl_easy_getinfo(CURL *handle, CURLINFO_PAUSE_STATE, + unsigned int *bitmask); +~~~ + +# DESCRIPTION + +Pass a pointer to a long to receive a bitmask describing the current pause +state of the transfer. The bitmask uses the same values as curl_easy_pause(3): + +CURLPAUSE_RECV + +: Receiving is paused. + +CURLPAUSE_SEND + +: Sending is paused. + +If neither direction is paused, the returned value is zero. + +This includes pauses set with curl_easy_pause(3) as well as pauses requested +by read or write callbacks returning CURL_READFUNC_PAUSE or +CURL_WRITEFUNC_PAUSE. + +This may be used from within a callback during an active transfer. An easy +handle must not be used from multiple threads simultaneously. + +# %PROTOCOLS% + +# EXAMPLE + +~~~c +int main(void) +{ + CURL *curl = curl_easy_init(); + if(curl) { + long paused; + CURLcode result = curl_easy_getinfo(curl, CURLINFO_PAUSE_STATE, &paused); + if(!result) { + if(paused & CURLPAUSE_RECV) + printf("Receiving is paused\n"); + if(paused & CURLPAUSE_SEND) + printf("Sending is paused\n"); + } + curl_easy_cleanup(curl); + } +} +~~~ + +# %AVAILABILITY% + +# RETURN VALUE + +curl_easy_getinfo(3) returns a CURLcode indicating success or error. + +CURLE_OK (0) means everything was OK, non-zero means an error occurred, see +libcurl-errors(3). diff --git a/docs/libcurl/opts/Makefile.inc b/docs/libcurl/opts/Makefile.inc index f0dc077a94..38d875e03d 100644 --- a/docs/libcurl/opts/Makefile.inc +++ b/docs/libcurl/opts/Makefile.inc @@ -58,6 +58,7 @@ man_MANS = \ CURLINFO_NAMELOOKUP_TIME_T.3 \ CURLINFO_NUM_CONNECTS.3 \ CURLINFO_OS_ERRNO.3 \ + CURLINFO_PAUSE_STATE.3 \ CURLINFO_PRETRANSFER_TIME.3 \ CURLINFO_PRETRANSFER_TIME_T.3 \ CURLINFO_POSTTRANSFER_TIME_T.3 \ diff --git a/docs/libcurl/symbols-in-versions b/docs/libcurl/symbols-in-versions index 65279d6426..c7ca6327f6 100644 --- a/docs/libcurl/symbols-in-versions +++ b/docs/libcurl/symbols-in-versions @@ -470,6 +470,7 @@ CURLINFO_NONE 7.4.1 CURLINFO_NUM_CONNECTS 7.12.3 CURLINFO_OFF_T 7.55.0 CURLINFO_OS_ERRNO 7.12.2 +CURLINFO_PAUSE_STATE 8.22.0 CURLINFO_POSTTRANSFER_TIME_T 8.10.0 CURLINFO_PRETRANSFER_TIME 7.4.1 CURLINFO_PRETRANSFER_TIME_T 7.61.0 diff --git a/include/curl/curl.h b/include/curl/curl.h index 0797c92cad..b91299e60c 100644 --- a/include/curl/curl.h +++ b/include/curl/curl.h @@ -3020,7 +3020,8 @@ typedef enum { CURLINFO_HTTPAUTH_USED = CURLINFO_LONG + 69, CURLINFO_PROXYAUTH_USED = CURLINFO_LONG + 70, CURLINFO_SIZE_DELIVERED = CURLINFO_OFF_T + 71, - CURLINFO_LASTONE = 71 + CURLINFO_PAUSE_STATE = CURLINFO_LONG + 72, + CURLINFO_LASTONE = 72 } CURLINFO; /* CURLINFO_RESPONSE_CODE is the new name for the option previously known as diff --git a/lib/getinfo.c b/lib/getinfo.c index fde4aa4ef2..677ac4881f 100644 --- a/lib/getinfo.c +++ b/lib/getinfo.c @@ -28,6 +28,7 @@ #include "cfilters.h" #include "vtls/vtls.h" #include "connect.h" /* Curl_getconnectinfo() */ +#include "transfer.h" #include "bufref.h" #include "curlx/strparse.h" @@ -370,6 +371,13 @@ static CURLcode getinfo_long(struct Curl_easy *data, CURLINFO info, #endif ; break; + case CURLINFO_PAUSE_STATE: + *param_longp = 0; + if(Curl_xfer_recv_is_paused(data)) + *param_longp |= CURLPAUSE_RECV; + if(Curl_xfer_send_is_paused(data)) + *param_longp |= CURLPAUSE_SEND; + break; default: return CURLE_UNKNOWN_OPTION; } diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 76ac5be1bf..1fd5885f31 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -288,7 +288,7 @@ test3223 test3224 test3225 test3226 test3227 test3228 test3229 \ \ test3300 test3301 test3302 test3303 test3304 test3305 test3306 \ \ -test3400 test3401 \ +test3400 test3401 test3403 \ \ test4000 test4001 \ \ diff --git a/tests/data/test3403 b/tests/data/test3403 new file mode 100644 index 0000000000..131c1746ad --- /dev/null +++ b/tests/data/test3403 @@ -0,0 +1,62 @@ + + + + +HTTP +HTTP GET +CURLINFO_PAUSE_STATE +CURLPAUSE_RECV +CURLPAUSE_SEND +chunked Transfer-Encoding +DELAY + + + +# Server-side + + +HTTP/1.1 200 OK swsclose%CR +Transfer-Encoding: chunked%CR +%CR +4%CR +data%CR +5%CR +d474 +%CR +0%CR +%CR + + +Got bytes but pausing! +datad474 + + +writedelay: 10 + + +# Client-side + + +http + + +lib%TESTNUMBER + + +CURLINFO_PAUSE_STATE during paused transfer + + +http://%HOSTIP:%HTTPPORT/%TESTNUMBER + + + +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Accept: */* + + + + diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc index 89f6a82546..57ca22810b 100644 --- a/tests/libtest/Makefile.inc +++ b/tests/libtest/Makefile.inc @@ -125,4 +125,5 @@ TESTS_C = \ lib3010.c lib3025.c lib3026.c lib3027.c lib3033.c lib3034.c \ lib3100.c lib3101.c lib3102.c lib3103.c lib3104.c lib3105.c \ lib3207.c lib3208.c \ + lib3403.c \ lib5000.c lib5004.c diff --git a/tests/libtest/lib3403.c b/tests/libtest/lib3403.c new file mode 100644 index 0000000000..7d5b40da8f --- /dev/null +++ b/tests/libtest/lib3403.c @@ -0,0 +1,162 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "first.h" + +#include "testtrace.h" + +struct t3403_state { + CURL *curl; + int errors; + int write_calls; + int xfer_while_recv_paused; + int checked_unpaused; + int checked_send_paused; +}; + +static int check_pause_state(struct t3403_state *st, long expected, + const char *label) +{ + long state = -1; + CURLcode result = curl_easy_getinfo(st->curl, CURLINFO_PAUSE_STATE, &state); + + if(result != CURLE_OK) { + curl_mfprintf(stderr, "%s: getinfo failed (%d)\n", label, (int)result); + st->errors++; + return 1; + } + if(state != expected) { + curl_mfprintf(stderr, "%s: expected %ld, got %ld\n", + label, expected, state); + st->errors++; + return 1; + } + curl_mfprintf(stderr, "%s: pause state %ld OK\n", label, state); + return 0; +} + +static int t3403_xferinfo(void *userp, + curl_off_t dltotal, + curl_off_t dlnow, + curl_off_t ultotal, + curl_off_t ulnow) +{ + struct t3403_state *st = (struct t3403_state *)userp; + + (void)dltotal; + (void)dlnow; + (void)ultotal; + (void)ulnow; + + if(st->write_calls == 1 && !st->xfer_while_recv_paused) { + st->xfer_while_recv_paused = 1; + check_pause_state(st, CURLPAUSE_RECV, "recv paused via write callback"); + curl_easy_pause(st->curl, CURLPAUSE_CONT); + return 0; + } + + if(st->write_calls >= 2 && !st->checked_unpaused) { + st->checked_unpaused = 1; + check_pause_state(st, 0, "unpaused after CONT"); + curl_easy_pause(st->curl, CURLPAUSE_SEND); + return 0; + } + + if(st->checked_unpaused && !st->checked_send_paused) { + st->checked_send_paused = 1; + check_pause_state(st, CURLPAUSE_SEND, "send paused explicitly"); + curl_easy_pause(st->curl, CURLPAUSE_CONT); + } + + return 0; +} + +static size_t t3403_write_cb(char *ptr, size_t size, size_t nmemb, void *userp) +{ + struct t3403_state *st = (struct t3403_state *)userp; + size_t len = size * nmemb; + + st->write_calls++; + if(st->write_calls == 1) { + if(len) + curl_mprintf("Got bytes but pausing!\n"); + return CURL_WRITEFUNC_PAUSE; + } + + fwrite(ptr, size, nmemb, stdout); + return len; +} + +static CURLcode test_lib3403(const char *URL) +{ + CURL *curl = NULL; + CURLcode result = CURLE_OK; + struct t3403_state st; + + start_test_timing(); + + memset(&st, 0, sizeof(st)); + + global_init(CURL_GLOBAL_ALL); + + easy_init(curl); + st.curl = curl; + + check_pause_state(&st, 0, "before transfer"); + + easy_setopt(curl, CURLOPT_URL, URL); + easy_setopt(curl, CURLOPT_WRITEFUNCTION, t3403_write_cb); + easy_setopt(curl, CURLOPT_WRITEDATA, &st); + easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, t3403_xferinfo); + easy_setopt(curl, CURLOPT_XFERINFODATA, &st); + easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); + + debug_config.nohex = TRUE; + debug_config.tracetime = TRUE; + easy_setopt(curl, CURLOPT_DEBUGDATA, &debug_config); + easy_setopt(curl, CURLOPT_DEBUGFUNCTION, libtest_debug_cb); + easy_setopt(curl, CURLOPT_VERBOSE, 1L); + + result = curl_easy_perform(curl); + + check_pause_state(&st, 0, "after transfer"); + + if(!st.xfer_while_recv_paused || !st.checked_unpaused || + !st.checked_send_paused) { + curl_mfprintf(stderr, "missing pause state checks " + "(recv=%d unpaused=%d send=%d)\n", + st.xfer_while_recv_paused, st.checked_unpaused, + st.checked_send_paused); + st.errors++; + } + + if(st.errors) + result = TEST_ERR_FAILURE; + +test_cleanup: + + curl_easy_cleanup(curl); + curl_global_cleanup(); + + return result; +}