This commit is contained in:
Your Name
2026-10-27 14:03:35 +08:00
parent 7986e4baf5
commit f9cbd42113
417 changed files with 71876 additions and 5344 deletions
@@ -74,8 +74,18 @@ namespace app\common\service {
$original = $options;
$url = (string) ($options[CURLOPT_URL] ?? '');
if (str_starts_with($url, 'https://storage.synthetic.test/')) {
$options[CURLOPT_URL] = 'http://127.0.0.1:' . $GLOBALS['attachmentFixturePort'] . substr($url, strlen('https://storage.synthetic.test'));
unset($options[CURLOPT_RESOLVE]);
$suffix = substr($url, strlen('https://storage.synthetic.test'));
if (!empty($GLOBALS['attachmentExerciseAddressFailover'])) {
// Preserve the production address count/order while mapping all
// network I/O to loopback. The first loopback has no listener.
$configured = explode(',', explode(':', $original[CURLOPT_RESOLVE][0], 3)[2]);
if (count($configured) !== 2) throw new \RuntimeException('Expected both validated failover addresses');
$options[CURLOPT_URL] = 'http://storage.synthetic.test:' . $GLOBALS['attachmentFixturePort'] . $suffix;
$options[CURLOPT_RESOLVE] = ['storage.synthetic.test:' . $GLOBALS['attachmentFixturePort'] . ':127.0.0.2,127.0.0.1'];
} else {
$options[CURLOPT_URL] = 'http://127.0.0.1:' . $GLOBALS['attachmentFixturePort'] . $suffix;
unset($options[CURLOPT_RESOLVE]);
}
} elseif (!str_starts_with($url, 'http://127.0.0.1:' . $GLOBALS['attachmentFixturePort'] . '/')) {
throw new \RuntimeException('The synthetic test forbids external connections');
}
@@ -527,8 +537,8 @@ namespace {
$downloaded = $run($remote);
attachmentExpect(!empty($downloaded['ok']) && count($requests()) === 2 && $requests()[0]['kind'] === 'download'
&& $requests()[0]['authorization'] === '' && $requests()[1]['authorization'] === 'Bearer fixture-only-key', 'remote bytes upload without sharing Dify key with storage');
attachmentExpect($attachmentCurlOptions[0][CURLOPT_RESOLVE] === ['storage.synthetic.test:443:8.8.8.8']
&& $attachmentCurlOptions[0][CURLOPT_FOLLOWLOCATION] === false, 'validated DNS address is pinned and redirect following disabled');
attachmentExpect($attachmentCurlOptions[0][CURLOPT_RESOLVE] === ['storage.synthetic.test:443:8.8.8.8,[2606:4700:4700::1111]']
&& $attachmentCurlOptions[0][CURLOPT_FOLLOWLOCATION] === false, 'all validated DNS addresses are pinned and redirect following disabled');
$clear(); $dnsCalls = 0;
$rebound = $run($remote, ['resolve_host' => static function () use (&$dnsCalls): array { return [++$dnsCalls === 1 ? '8.8.8.8' : '127.0.0.1']; }]);
attachmentExpect(!empty($rebound['ok']) && $dnsCalls === 1
@@ -540,7 +550,7 @@ namespace {
'resolve_host' => static function () use (&$dnsCalls): array { ++$dnsCalls; return ['198.18.0.1']; }]);
attachmentExpect(!empty($pinned['ok']) && $dnsCalls === 0 && $attachmentNativeDnsCalls === []
&& count($requests()) === 2, 'exact server pin bypasses both unsafe callable and native DNS');
attachmentExpect($attachmentCurlOptions[0][CURLOPT_RESOLVE] === ['storage.synthetic.test:443:8.8.8.8']
attachmentExpect($attachmentCurlOptions[0][CURLOPT_RESOLVE] === ['storage.synthetic.test:443:8.8.8.8,[2606:4700:4700::1111]']
&& $attachmentCurlOptions[0][CURLOPT_URL] === $remote[0]['url']
&& $attachmentCurlOptions[0][CURLOPT_SSL_VERIFYPEER] === true
&& $attachmentCurlOptions[0][CURLOPT_SSL_VERIFYHOST] === 2
@@ -550,6 +560,31 @@ namespace {
attachmentExpect(!isset($attachmentCurlOptions[1][CURLOPT_RESOLVE]), 'storage pin never alters the upload endpoint');
$clean();
foreach ([false, true] as $serverPinned) {
$clear(); $dnsCalls = 0;
$attachmentExerciseAddressFailover = true;
$addresses = ['8.8.8.8', '1.1.1.1'];
try {
$failover = $run($remote, [
'resolved_hosts' => $serverPinned ? ['storage.synthetic.test' => $addresses] : [],
'resolve_host' => static function () use (&$dnsCalls, $addresses): array { ++$dnsCalls; return $addresses; },
]);
} finally { $attachmentExerciseAddressFailover = false; }
attachmentExpect(!empty($failover['ok']) && $failover['uploaded_indices'] === [1]
&& $failover['skipped'] === [] && $dnsCalls === ($serverPinned ? 0 : 1),
'unreachable first address falls through to the next validated address without a fresh DNS lookup');
attachmentExpect($attachmentCurlOptions[0][CURLOPT_RESOLVE] === ['storage.synthetic.test:443:8.8.8.8,1.1.1.1']
&& array_column($requests(), 'kind') === ['download', 'upload']
&& $requests()[0]['authorization'] === '' && $requests()[1]['authorization'] === 'Bearer fixture-only-key',
'address failover downloads and uploads the attachment once without leaking upload credentials');
attachmentExpect($requests()[1]['hash'] === hash_file('sha256', $root . '/public/uploads/first.png')
&& $attachmentCurlOptions[0][CURLOPT_SSL_VERIFYPEER] === true
&& $attachmentCurlOptions[0][CURLOPT_SSL_VERIFYHOST] === 2
&& $attachmentCurlOptions[0][CURLOPT_FOLLOWLOCATION] === false,
'failover preserves original bytes, TLS verification and disabled redirects');
$clean();
}
$clear();
$missingPinnedSource = $run(array_merge($remote, [['type' => 'image',
'url' => 'https://storage.synthetic.test/missing-object']]),