Skip to content

Commit 1b5ebea

Browse files
ext/curl: expand CURLOPT_SEEKFUNCTION test coverage
Add curl_seekfunction_error.phpt covering the callback error paths — a non-int return (TypeError), an out-of-range return (ValueError), a throwing callback, the null reset, and a non-callable scalar — none of which were previously exercised. Add curl_seekfunction_gc.phpt, which builds a seek-closure/handle reference cycle so cycle collection (curl_get_gc) is verified to reclaim it. Strengthen curl_seekfunction.phpt to assert the callback argument contract (handle, offset, origin). Also rename the awkward $offset_ parameter to $position and tidy the curl_seek default-return comment and the copy test description.
1 parent 5ff006e commit 1b5ebea

5 files changed

Lines changed: 112 additions & 9 deletions

File tree

ext/curl/interface.c

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -839,10 +839,7 @@ static size_t curl_read(char *data, size_t size, size_t nmemb, void *ctx)
839839
static int curl_seek(void *clientp, curl_off_t offset, int origin)
840840
{
841841
php_curl *ch = (php_curl *)clientp;
842-
/* Default to CANTSEEK so libcurl can fall back (or fail the rewind
843-
cleanly) when no callback is set or the callback misbehaves, rather
844-
than resending the body from the wrong offset. */
845-
int rval = CURL_SEEKFUNC_CANTSEEK;
842+
int rval = CURL_SEEKFUNC_CANTSEEK; /* safe default if unset or the callback misbehaves */
846843

847844
#if PHP_CURL_DEBUG
848845
fprintf(stderr, "curl_seek() called\n");

ext/curl/tests/curl_copy_handle_seek.phpt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
--TEST--
2-
Test curl_copy_handle() preserves CURLOPT_SEEKFUNCTION
2+
Test curl_copy_handle() with CURLOPT_SEEKFUNCTION
33
--EXTENSIONS--
44
curl
55
--FILE--
@@ -21,12 +21,12 @@ curl_setopt($ch, CURLOPT_READFUNCTION, function ($ch, $fd, int $length) use ($bo
2121
$offset += strlen($chunk);
2222
return $chunk;
2323
});
24-
curl_setopt($ch, CURLOPT_SEEKFUNCTION, function ($ch, int $offset_, int $origin) use (&$offset, &$seekCalls) {
24+
curl_setopt($ch, CURLOPT_SEEKFUNCTION, function ($ch, int $position, int $origin) use (&$offset, &$seekCalls) {
2525
if ($origin !== SEEK_SET) {
2626
return CURL_SEEKFUNC_CANTSEEK;
2727
}
2828
$seekCalls++;
29-
$offset = $offset_;
29+
$offset = $position;
3030
return CURL_SEEKFUNC_OK;
3131
});
3232

ext/curl/tests/curl_seekfunction.phpt

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ $host = curl_cli_server_start();
1010
$body = 'Hello cURL seek!';
1111
$offset = 0;
1212
$seekCalls = 0;
13+
$argsChecked = false;
1314

1415
$ch = curl_init("{$host}/get.inc?test=redirect");
1516
curl_setopt($ch, CURLOPT_UPLOAD, true);
@@ -21,12 +22,18 @@ curl_setopt($ch, CURLOPT_READFUNCTION, function ($ch, $fd, int $length) use ($bo
2122
$offset += strlen($chunk);
2223
return $chunk;
2324
});
24-
curl_setopt($ch, CURLOPT_SEEKFUNCTION, function ($ch, int $offset_, int $origin) use (&$offset, &$seekCalls) {
25+
curl_setopt($ch, CURLOPT_SEEKFUNCTION, function ($ch, int $position, int $origin) use (&$offset, &$seekCalls, &$argsChecked) {
26+
if (!$argsChecked) {
27+
$argsChecked = true;
28+
var_dump($ch instanceof CurlHandle);
29+
var_dump($position === 0);
30+
var_dump($origin === SEEK_SET);
31+
}
2532
if ($origin !== SEEK_SET) {
2633
return CURL_SEEKFUNC_CANTSEEK;
2734
}
2835
$seekCalls++;
29-
$offset = $offset_;
36+
$offset = $position;
3037
return CURL_SEEKFUNC_OK;
3138
});
3239

@@ -39,3 +46,6 @@ var_dump(str_contains($response, $body));
3946
--EXPECT--
4047
bool(true)
4148
bool(true)
49+
bool(true)
50+
bool(true)
51+
bool(true)
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
--TEST--
2+
CURLOPT_SEEKFUNCTION callback error handling and option validation
3+
--EXTENSIONS--
4+
curl
5+
--FILE--
6+
<?php
7+
include 'server.inc';
8+
$host = curl_cli_server_start();
9+
10+
// Drive a 307-redirect upload so libcurl invokes the seek callback to rewind
11+
// the body; $seek is the callback under test.
12+
function run_upload(string $host, callable $seek): void
13+
{
14+
$offset = 0;
15+
$body = 'Hello cURL seek!';
16+
$ch = curl_init("{$host}/get.inc?test=redirect");
17+
curl_setopt($ch, CURLOPT_UPLOAD, true);
18+
curl_setopt($ch, CURLOPT_INFILESIZE, strlen($body));
19+
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
20+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
21+
curl_setopt($ch, CURLOPT_READFUNCTION, function ($ch, $fd, int $length) use ($body, &$offset) {
22+
$chunk = substr($body, $offset, $length);
23+
$offset += strlen($chunk);
24+
return $chunk;
25+
});
26+
curl_setopt($ch, CURLOPT_SEEKFUNCTION, $seek);
27+
curl_exec($ch);
28+
}
29+
30+
echo "Returning a non-int:\n";
31+
try {
32+
run_upload($host, fn($ch, $offset, $origin) => 'not an int');
33+
} catch (\TypeError $e) {
34+
echo $e->getMessage(), "\n";
35+
}
36+
37+
echo "\nReturning an out-of-range int:\n";
38+
try {
39+
run_upload($host, fn($ch, $offset, $origin) => 42);
40+
} catch (\ValueError $e) {
41+
echo $e->getMessage(), "\n";
42+
}
43+
44+
echo "\nThrowing from the callback:\n";
45+
try {
46+
run_upload($host, function ($ch, $offset, $origin) {
47+
throw new \RuntimeException('boom from seek');
48+
});
49+
} catch (\RuntimeException $e) {
50+
echo $e->getMessage(), "\n";
51+
}
52+
53+
echo "\nSetting the callback to null:\n";
54+
var_dump(curl_setopt(curl_init(), CURLOPT_SEEKFUNCTION, null));
55+
56+
echo "\nSetting a non-callable scalar:\n";
57+
try {
58+
curl_setopt(curl_init(), CURLOPT_SEEKFUNCTION, 42);
59+
} catch (\TypeError $e) {
60+
echo $e->getMessage(), "\n";
61+
}
62+
?>
63+
--EXPECT--
64+
Returning a non-int:
65+
The CURLOPT_SEEKFUNCTION callback must return one of CURL_SEEKFUNC_OK, CURL_SEEKFUNC_FAIL or CURL_SEEKFUNC_CANTSEEK
66+
67+
Returning an out-of-range int:
68+
The CURLOPT_SEEKFUNCTION callback must return one of CURL_SEEKFUNC_OK, CURL_SEEKFUNC_FAIL or CURL_SEEKFUNC_CANTSEEK
69+
70+
Throwing from the callback:
71+
boom from seek
72+
73+
Setting the callback to null:
74+
bool(true)
75+
76+
Setting a non-callable scalar:
77+
curl_setopt(): Argument #3 ($value) must be a valid callback for option CURLOPT_SEEKFUNCTION, no array or string given
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
--TEST--
2+
CURLOPT_SEEKFUNCTION callback participates in cycle collection (curl_get_gc)
3+
--EXTENSIONS--
4+
curl
5+
--FILE--
6+
<?php
7+
// A seek callback that captures its own handle forms a reference cycle
8+
// (handle -> seek fcc -> closure -> handle). The cycle collector can only
9+
// reclaim it if curl_get_gc() exposes the seek fcc.
10+
$ch = curl_init();
11+
curl_setopt($ch, CURLOPT_SEEKFUNCTION, function ($handle, $offset, $origin) use ($ch) {
12+
return CURL_SEEKFUNC_OK;
13+
});
14+
unset($ch);
15+
16+
var_dump(gc_collect_cycles() > 0);
17+
?>
18+
--EXPECT--
19+
bool(true)

0 commit comments

Comments
 (0)