1--TEST-- 2Curl option CURLOPT_DEBUGFUNCTION 3--EXTENSIONS-- 4curl 5--FILE-- 6<?php 7include 'server.inc'; 8 9$allowedTypes = [ 10 CURLINFO_TEXT, 11 CURLINFO_HEADER_IN, 12 CURLINFO_HEADER_OUT, 13 CURLINFO_DATA_IN, 14 CURLINFO_DATA_OUT, 15 CURLINFO_SSL_DATA_OUT, 16 CURLINFO_SSL_DATA_IN, 17]; 18 19var_dump(CURLOPT_DEBUGFUNCTION); 20 21$host = curl_cli_server_start(); 22 23echo "\n===Testing with regular CURLOPT_VERBOSE with verbose=false===\n"; 24$ch = curl_init(); 25curl_setopt($ch, CURLOPT_URL, "{$host}/get.inc?test=file"); 26curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 27curl_setopt($ch, CURLOPT_VERBOSE, 0); 28var_dump(curl_setopt($ch, CURLOPT_DEBUGFUNCTION, function() { 29 echo 'This should not be called'; 30})); 31curl_exec($ch); 32curl_setopt($ch, CURLOPT_VERBOSE, 1); 33 34echo "\n===Testing with regular CURLOPT_VERBOSE on stderr===\n"; 35$ch = curl_init(); 36curl_setopt($ch, CURLOPT_URL, "{$host}/get.inc?test=file"); 37curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 38curl_setopt($ch, CURLOPT_VERBOSE, 1); 39$stderr = fopen('php://temp', 'wb+'); 40curl_setopt($ch, CURLOPT_STDERR, $stderr); 41$curlVerboseOutput = curl_exec($ch); 42rewind($stderr); 43$receivedOutput = stream_get_contents($stderr); 44fclose($stderr); 45var_dump(str_contains($receivedOutput, 'Host')); 46 47echo "\n===Testing with CURLOPT_DEBUGFUNCTION happy path===\n"; 48$stderr = fopen('php://temp', 'wb+'); 49curl_setopt($ch, CURLOPT_STDERR, $stderr); 50 51$debugOutput = []; 52var_dump(curl_setopt($ch, CURLOPT_DEBUGFUNCTION, function(CurlHandle $curlHandle, int $type, string $data) use ($allowedTypes, &$debugOutput) { 53 if (!in_array($type, $allowedTypes)) { 54 throw new Exception('Unexpected type value: '. $type); 55 } 56 $debugOutput[$type][] = $data; 57})); 58 59$result = curl_exec($ch); 60rewind($stderr); 61$receivedOutputWithDebugFunction = stream_get_contents($stderr); 62fclose($stderr); 63echo "Received stderr empty:\n"; 64var_dump($result); 65var_dump($receivedOutputWithDebugFunction); 66 67// Header-out should be an exact match 68var_dump(str_contains($receivedOutput, $debugOutput[CURLINFO_HEADER_OUT][0])); 69 70// Header-in fields should match, except for the "Date" header that is dynamic. 71foreach ($debugOutput[CURLINFO_HEADER_IN] as $headerReceived) { 72 if (str_starts_with($headerReceived, 'Date')) { 73 continue; 74 } 75 if (!str_contains($receivedOutput, $headerReceived)) { 76 throw new \Exception('DEBUGFUNCTION header field does not match the previous verbose debug message'); 77 } 78} 79 80echo "\n===Test attempting to set CURLINFO_HEADER_OUT while CURLOPT_DEBUGFUNCTION in effect throws===\n"; 81$ch = curl_init(); 82var_dump(curl_setopt($ch, CURLINFO_HEADER_OUT, true)); 83var_dump(curl_setopt($ch, CURLOPT_DEBUGFUNCTION, null)); 84var_dump(curl_setopt($ch, CURLINFO_HEADER_OUT, true)); 85var_dump(curl_setopt($ch, CURLOPT_DEBUGFUNCTION, 'strlen')); 86try { 87 var_dump(curl_setopt($ch, CURLINFO_HEADER_OUT, true)); 88} 89catch (\ValueError $e) { 90 var_dump($e->getMessage()); 91} 92$chCopy = curl_copy_handle($ch); 93try { 94 var_dump(curl_setopt($chCopy, CURLINFO_HEADER_OUT, true)); 95} 96catch (\ValueError $e) { 97 var_dump($e->getMessage()); 98} 99var_dump(curl_setopt($chCopy, CURLOPT_DEBUGFUNCTION, null)); 100var_dump(curl_setopt($chCopy, CURLINFO_HEADER_OUT, true)); 101 102echo "\n===Test attempting to set CURLOPT_DEBUGFUNCTION while CURLINFO_HEADER_OUT does not throw===\n"; 103$ch = curl_init(); 104var_dump(curl_setopt($ch, CURLINFO_HEADER_OUT, true)); 105var_dump(curl_setopt($ch, CURLOPT_DEBUGFUNCTION, null)); 106var_dump(curl_setopt($ch, CURLINFO_HEADER_OUT, true)); 107var_dump(curl_setopt($ch, CURLOPT_DEBUGFUNCTION, 'strlen')); 108$chCopy = curl_copy_handle($ch); 109var_dump(curl_setopt($ch, CURLOPT_DEBUGFUNCTION, 'strlen')); 110var_dump(curl_setopt($chCopy, CURLOPT_DEBUGFUNCTION, null)); 111var_dump(curl_setopt($chCopy, CURLINFO_HEADER_OUT, 1)); 112 113echo "\n===Test CURLOPT_DEBUGFUNCTION=null works===\n"; 114$ch = curl_init("{$host}/get.inc?test=file"); 115var_dump(curl_setopt($ch, CURLOPT_DEBUGFUNCTION, null)); 116curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 117var_dump(curl_exec($ch)); 118 119echo "\n===Test CURLINFO_HEADER_OUT works while CURLOPT_DEBUGFUNCTION in effect===\n"; 120$ch = curl_init("{$host}/get.inc?test=file"); 121curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 122var_dump(curl_getinfo($ch, CURLINFO_HEADER_OUT)); 123var_dump(curl_setopt($ch, CURLINFO_HEADER_OUT, true)); 124var_dump(curl_getinfo($ch, CURLINFO_HEADER_OUT)); 125var_dump(curl_setopt($ch, CURLOPT_DEBUGFUNCTION, static function() {})); 126var_dump(curl_getinfo($ch, CURLINFO_HEADER_OUT)); 127var_dump($result = curl_exec($ch)); 128var_dump(curl_getinfo($ch, CURLINFO_HEADER_OUT)); 129 130echo "\n===Test CURLOPT_DEBUGFUNCTION can throw within callback===\n"; 131$ch = curl_init("{$host}/get.inc?test=file"); 132curl_setopt($ch, CURLOPT_DEBUGFUNCTION, static function() { 133 throw new \RuntimeException('This should get caught after verbose=true'); 134}); 135var_dump(curl_exec($ch)); 136curl_setopt($ch, CURLOPT_VERBOSE, true); 137try { 138 var_dump($result = curl_exec($ch)); 139} 140catch (\RuntimeException $e) { 141 var_dump($e->getMessage()); 142} 143var_dump(curl_getinfo($ch, CURLINFO_HEADER_OUT)); 144 145echo "\n===Test CURLOPT_DEBUGFUNCTION on shared handles work===\n"; 146$ch = curl_init("{$host}/get.inc?test=file"); 147$called = false; 148curl_setopt_array($ch, [ 149 CURLOPT_VERBOSE => true, 150 CURLOPT_DEBUGFUNCTION => static function() use (&$called) { 151 $called = true; 152 }, 153]); 154var_dump($called); 155curl_exec($ch); 156var_dump($called); 157$called = false; 158$ch2 = curl_copy_handle($ch); 159curl_exec($ch2); 160var_dump($called); 161var_dump(curl_getinfo($ch2, CURLINFO_HEADER_OUT)); 162 163echo "\nDone"; 164?> 165--EXPECTF-- 166int(20094) 167 168===Testing with regular CURLOPT_VERBOSE with verbose=false=== 169bool(true) 170 171===Testing with regular CURLOPT_VERBOSE on stderr=== 172bool(true) 173 174===Testing with CURLOPT_DEBUGFUNCTION happy path=== 175bool(true) 176Received stderr empty: 177string(0) "" 178string(0) "" 179bool(true) 180 181===Test attempting to set CURLINFO_HEADER_OUT while CURLOPT_DEBUGFUNCTION in effect throws=== 182bool(true) 183bool(true) 184bool(true) 185bool(true) 186string(87) "CURLINFO_HEADER_OUT option must not be set when the CURLOPT_DEBUGFUNCTION option is set" 187string(87) "CURLINFO_HEADER_OUT option must not be set when the CURLOPT_DEBUGFUNCTION option is set" 188bool(true) 189bool(true) 190 191===Test attempting to set CURLOPT_DEBUGFUNCTION while CURLINFO_HEADER_OUT does not throw=== 192bool(true) 193bool(true) 194bool(true) 195bool(true) 196bool(true) 197bool(true) 198bool(true) 199 200===Test CURLOPT_DEBUGFUNCTION=null works=== 201bool(true) 202string(0) "" 203 204===Test CURLINFO_HEADER_OUT works while CURLOPT_DEBUGFUNCTION in effect=== 205bool(false) 206bool(true) 207bool(false) 208bool(true) 209bool(false) 210string(0) "" 211string(%d) "GET /get.inc?test=file HTTP/%s 212Host: %s:%d 213Accept: */* 214 215" 216 217===Test CURLOPT_DEBUGFUNCTION can throw within callback=== 218bool(true) 219string(41) "This should get caught after verbose=true" 220string(%d) "GET /get.inc?test=file HTTP/%s 221Host: %s:%d 222Accept: */* 223 224" 225 226===Test CURLOPT_DEBUGFUNCTION on shared handles work=== 227bool(false) 228bool(true) 229bool(true) 230string(71) "GET /get.inc?test=file HTTP/%s 231Host: %s:%d 232Accept: */* 233 234" 235 236Done 237