From 84d934776386b8c750cd9d18d8ff82e5fa491e66 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 3 Sep 2024 14:33:41 +0200 Subject: [PATCH 1/2] [FrameworkBundle][HttpKernel] Add support for `SYMFONY_TRUSTED_PROXIES`, `SYMFONY_TRUSTED_HEADERS`, `SYMFONY_TRUST_X_SENDFILE_TYPE_HEADER` and `SYMFONY_TRUSTED_HOSTS` env vars --- .../Bundle/FrameworkBundle/CHANGELOG.md | 1 + .../DependencyInjection/Configuration.php | 17 ++++++------- .../FrameworkExtension.php | 25 +++---------------- .../DependencyInjection/ConfigurationTest.php | 11 +++----- .../Bundle/FrameworkBundle/composer.json | 2 +- src/Symfony/Component/HttpKernel/CHANGELOG.md | 1 + src/Symfony/Component/HttpKernel/Kernel.php | 25 ++++++++++++++++--- .../Component/HttpKernel/Tests/KernelTest.php | 19 ++++++++++++++ 8 files changed, 58 insertions(+), 43 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 193f4570cf92e..43ac96174e479 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -12,6 +12,7 @@ CHANGELOG * Deprecate making `cache.app` adapter taggable, use the `cache.app.taggable` adapter instead * Enable `json_decode_detailed_errors` in the default serializer context in debug mode by default when `seld/jsonlint` is installed * Register `Symfony\Component\Serializer\NameConverter\SnakeCaseToCamelCaseNameConverter` as a service named `serializer.name_converter.snake_case_to_camel_case` if available + * Add support for `SYMFONY_TRUSTED_PROXIES`, `SYMFONY_TRUSTED_HEADERS`, `SYMFONY_TRUST_X_SENDFILE_TYPE_HEADER` and `SYMFONY_TRUSTED_HOSTS` env vars 7.1 --- diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 44bbe6718b2c7..0b78390a21c0e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -90,7 +90,7 @@ public function getConfigTreeBuilder(): TreeBuilder ->end() ->scalarNode('trust_x_sendfile_type_header') ->info('Set true to enable support for xsendfile in binary file responses.') - ->defaultFalse() + ->defaultValue('%env(bool:default::SYMFONY_TRUST_X_SENDFILE_TYPE_HEADER)%') ->end() ->scalarNode('ide')->defaultValue($this->debug ? '%env(default::SYMFONY_IDE)%' : null)->end() ->booleanNode('test')->end() @@ -108,26 +108,23 @@ public function getConfigTreeBuilder(): TreeBuilder ->prototype('scalar')->end() ->end() ->arrayNode('trusted_hosts') - ->beforeNormalization()->ifString()->then(fn ($v) => [$v])->end() + ->beforeNormalization()->ifString()->then(static fn ($v) => $v ? [$v] : [])->end() ->prototype('scalar')->end() + ->defaultValue(['%env(default::SYMFONY_TRUSTED_HOSTS)%']) ->end() ->variableNode('trusted_proxies') ->beforeNormalization() ->ifTrue(fn ($v) => 'private_ranges' === $v || 'PRIVATE_SUBNETS' === $v) ->then(fn () => IpUtils::PRIVATE_SUBNETS) ->end() + ->defaultValue(['%env(default::SYMFONY_TRUSTED_PROXIES)%']) ->end() ->arrayNode('trusted_headers') ->fixXmlConfig('trusted_header') ->performNoDeepMerging() - ->defaultValue(['x-forwarded-for', 'x-forwarded-port', 'x-forwarded-proto']) - ->beforeNormalization()->ifString()->then(fn ($v) => $v ? array_map('trim', explode(',', $v)) : [])->end() - ->enumPrototype() - ->values([ - 'forwarded', - 'x-forwarded-for', 'x-forwarded-host', 'x-forwarded-proto', 'x-forwarded-port', 'x-forwarded-prefix', - ]) - ->end() + ->beforeNormalization()->ifString()->then(static fn ($v) => $v ? [$v] : [])->end() + ->prototype('scalar')->end() + ->defaultValue(['%env(default::SYMFONY_TRUSTED_HEADERS)%']) ->end() ->scalarNode('error_controller') ->defaultValue('error_controller') diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 75cfe1d1f0d84..1bc701a5a1ddd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -312,14 +312,14 @@ public function load(array $configs, ContainerBuilder $container): void $container->setParameter('kernel.http_method_override', $config['http_method_override']); $container->setParameter('kernel.trust_x_sendfile_type_header', $config['trust_x_sendfile_type_header']); - $container->setParameter('kernel.trusted_hosts', $config['trusted_hosts']); + $container->setParameter('kernel.trusted_hosts', [0] === array_keys($config['trusted_hosts']) ? $config['trusted_hosts'][0] : $config['trusted_hosts']); $container->setParameter('kernel.default_locale', $config['default_locale']); $container->setParameter('kernel.enabled_locales', $config['enabled_locales']); $container->setParameter('kernel.error_controller', $config['error_controller']); if (($config['trusted_proxies'] ?? false) && ($config['trusted_headers'] ?? false)) { - $container->setParameter('kernel.trusted_proxies', $config['trusted_proxies']); - $container->setParameter('kernel.trusted_headers', $this->resolveTrustedHeaders($config['trusted_headers'])); + $container->setParameter('kernel.trusted_proxies', \is_array($config['trusted_proxies']) && [0] === array_keys($config['trusted_proxies']) ? $config['trusted_proxies'][0] : $config['trusted_proxies']); + $container->setParameter('kernel.trusted_headers', [0] === array_keys($config['trusted_headers']) ? $config['trusted_headers'][0] : $config['trusted_headers']); } if (!$container->hasParameter('debug.file_link_format')) { @@ -3103,25 +3103,6 @@ private function registerHtmlSanitizerConfiguration(array $config, ContainerBuil } } - private function resolveTrustedHeaders(array $headers): int - { - $trustedHeaders = 0; - - foreach ($headers as $h) { - $trustedHeaders |= match ($h) { - 'forwarded' => Request::HEADER_FORWARDED, - 'x-forwarded-for' => Request::HEADER_X_FORWARDED_FOR, - 'x-forwarded-host' => Request::HEADER_X_FORWARDED_HOST, - 'x-forwarded-proto' => Request::HEADER_X_FORWARDED_PROTO, - 'x-forwarded-port' => Request::HEADER_X_FORWARDED_PORT, - 'x-forwarded-prefix' => Request::HEADER_X_FORWARDED_PREFIX, - default => 0, - }; - } - - return $trustedHeaders; - } - public function getXsdValidationBasePath(): string|false { return \dirname(__DIR__).'/Resources/config/schema'; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index 820f4bf11be85..4f0023cf2f658 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -704,19 +704,16 @@ protected static function getBundleDefaultConfig() return [ 'http_method_override' => false, 'handle_all_throwables' => true, - 'trust_x_sendfile_type_header' => false, + 'trust_x_sendfile_type_header' => '%env(bool:default::SYMFONY_TRUST_X_SENDFILE_TYPE_HEADER)%', 'ide' => '%env(default::SYMFONY_IDE)%', 'default_locale' => 'en', 'enabled_locales' => [], 'set_locale_from_accept_language' => false, 'set_content_language_from_locale' => false, 'secret' => 's3cr3t', - 'trusted_hosts' => [], - 'trusted_headers' => [ - 'x-forwarded-for', - 'x-forwarded-port', - 'x-forwarded-proto', - ], + 'trusted_hosts' => ['%env(default::SYMFONY_TRUSTED_HOSTS)%'], + 'trusted_proxies' => ['%env(default::SYMFONY_TRUSTED_PROXIES)%'], + 'trusted_headers' => ['%env(default::SYMFONY_TRUSTED_HEADERS)%'], 'csrf_protection' => [ 'enabled' => false, ], diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index a6552ca5b7f38..b514179d40176 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -26,7 +26,7 @@ "symfony/error-handler": "^6.4|^7.0", "symfony/event-dispatcher": "^6.4|^7.0", "symfony/http-foundation": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", + "symfony/http-kernel": "^7.2", "symfony/polyfill-mbstring": "~1.0", "symfony/filesystem": "^7.1", "symfony/finder": "^6.4|^7.0", diff --git a/src/Symfony/Component/HttpKernel/CHANGELOG.md b/src/Symfony/Component/HttpKernel/CHANGELOG.md index 1f1dfcd81225e..1fc103b48dc1a 100644 --- a/src/Symfony/Component/HttpKernel/CHANGELOG.md +++ b/src/Symfony/Component/HttpKernel/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * Remove `@internal` flag and add `@final` to `ServicesResetter` * Add support for `SYMFONY_DISABLE_RESOURCE_TRACKING` env var + * Add support for configuring trusted proxies/headers/hosts via env vars 7.1 --- diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 38a8c330c957b..7e8b002079c10 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -393,7 +393,7 @@ protected function initializeContainer(): void $class = $this->getContainerClass(); $buildDir = $this->warmupDir ?: $this->getBuildDir(); $skip = $_SERVER['SYMFONY_DISABLE_RESOURCE_TRACKING'] ?? ''; - $skip = filter_var($skip, \FILTER_VALIDATE_BOOLEAN, \FILTER_NULL_ON_FAILURE) ?? explode(',', $skip); + $skip = filter_var($skip, \FILTER_VALIDATE_BOOLEAN, \FILTER_NULL_ON_FAILURE) ?? explode(',', $skip); $cache = new ConfigCache($buildDir.'/'.$class.'.php', $this->debug, null, \is_array($skip) && ['*'] !== $skip ? $skip : ($skip ? [] : null)); $cachePath = $cache->getPath(); @@ -745,11 +745,30 @@ private function preBoot(): ContainerInterface $container = $this->container; if ($container->hasParameter('kernel.trusted_hosts') && $trustedHosts = $container->getParameter('kernel.trusted_hosts')) { - Request::setTrustedHosts($trustedHosts); + Request::setTrustedHosts(\is_array($trustedHosts) ? $trustedHosts : preg_split('/\s*+,\s*+(?![^{]*})/', $trustedHosts)); } if ($container->hasParameter('kernel.trusted_proxies') && $container->hasParameter('kernel.trusted_headers') && $trustedProxies = $container->getParameter('kernel.trusted_proxies')) { - Request::setTrustedProxies(\is_array($trustedProxies) ? $trustedProxies : array_map('trim', explode(',', $trustedProxies)), $container->getParameter('kernel.trusted_headers')); + $trustedHeaders = $container->getParameter('kernel.trusted_headers'); + + if (\is_string($trustedHeaders)) { + $trustedHeaders = array_map('trim', explode(',', $trustedHeaders)); + } + + if (\is_array($trustedHeaders)) { + $trustedHeaderSet = 0; + + foreach ($trustedHeaders as $header) { + if (!\defined($const = Request::class.'::HEADER_'.strtr(strtoupper($header), '-', '_'))) { + throw new \InvalidArgumentException(\sprintf('The trusted header "%s" is not supported.', $header)); + } + $trustedHeaderSet |= \constant($const); + } + } else { + $trustedHeaderSet = $trustedHeaders ?? (Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PROTO); + } + + Request::setTrustedProxies(\is_array($trustedProxies) ? $trustedProxies : array_map('trim', explode(',', $trustedProxies)), $trustedHeaderSet); } return $container; diff --git a/src/Symfony/Component/HttpKernel/Tests/KernelTest.php b/src/Symfony/Component/HttpKernel/Tests/KernelTest.php index e379e41bf5900..df4608a8e08d7 100644 --- a/src/Symfony/Component/HttpKernel/Tests/KernelTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/KernelTest.php @@ -523,6 +523,25 @@ public function getContainerClass(): string $this->assertMatchesRegularExpression('/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*TestDebugContainer$/', $kernel->getContainerClass()); } + public function testTrustedParameters() + { + $kernel = new CustomProjectDirKernel(function (ContainerBuilder $container) { + $container->setParameter('kernel.trusted_hosts', '^a{2,3}.com$, ^b{2,}.com$'); + $container->setParameter('kernel.trusted_proxies', 'a,b'); + $container->setParameter('kernel.trusted_headers', 'x-forwarded-for'); + }); + $kernel->boot(); + + try { + $this->assertSame(['{^a{2,3}.com$}i', '{^b{2,}.com$}i'], Request::getTrustedHosts()); + $this->assertSame(['a', 'b'], Request::getTrustedProxies()); + $this->assertSame(Request::HEADER_X_FORWARDED_FOR, Request::getTrustedHeaderSet()); + } finally { + Request::setTrustedHosts([]); + Request::setTrustedProxies([], 0); + } + } + /** * Returns a mock for the BundleInterface. */ From afd4b3d29fa792296ee43dec03f9cfdd5a45b155 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 3 Sep 2024 16:18:00 +0200 Subject: [PATCH 2/2] [HttpFoundation][FrameworkBundle] Add BinaryFileResponse::setXSendfileHeader() and corresponding config option to help configure upload acceleration --- src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md | 1 + .../DependencyInjection/Configuration.php | 4 ++++ .../DependencyInjection/FrameworkExtension.php | 1 + .../Bundle/FrameworkBundle/FrameworkBundle.php | 4 ++++ .../DependencyInjection/ConfigurationTest.php | 1 + src/Symfony/Bundle/FrameworkBundle/composer.json | 2 +- .../HttpFoundation/BinaryFileResponse.php | 14 ++++++++++++-- src/Symfony/Component/HttpFoundation/CHANGELOG.md | 1 + 8 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 43ac96174e479..ea01b8d9f11c9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -13,6 +13,7 @@ CHANGELOG * Enable `json_decode_detailed_errors` in the default serializer context in debug mode by default when `seld/jsonlint` is installed * Register `Symfony\Component\Serializer\NameConverter\SnakeCaseToCamelCaseNameConverter` as a service named `serializer.name_converter.snake_case_to_camel_case` if available * Add support for `SYMFONY_TRUSTED_PROXIES`, `SYMFONY_TRUSTED_HEADERS`, `SYMFONY_TRUST_X_SENDFILE_TYPE_HEADER` and `SYMFONY_TRUSTED_HOSTS` env vars + * Add option `framework.x_sendfile_header` to configure the name of the header that should be used for file uploads acceleration, if supported by the web-server 7.1 --- diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 0b78390a21c0e..052a066da2a7c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -92,6 +92,10 @@ public function getConfigTreeBuilder(): TreeBuilder ->info('Set true to enable support for xsendfile in binary file responses.') ->defaultValue('%env(bool:default::SYMFONY_TRUST_X_SENDFILE_TYPE_HEADER)%') ->end() + ->scalarNode('x_sendfile_header') + ->info('The name of the header that should be used for file uploads acceleration, if supported by the web-server.') + ->defaultValue('%env(default::SYMFONY_X_SENDFILE_HEADER)%') + ->end() ->scalarNode('ide')->defaultValue($this->debug ? '%env(default::SYMFONY_IDE)%' : null)->end() ->booleanNode('test')->end() ->scalarNode('default_locale')->defaultValue('en')->end() diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 1bc701a5a1ddd..22ce0885db490 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -312,6 +312,7 @@ public function load(array $configs, ContainerBuilder $container): void $container->setParameter('kernel.http_method_override', $config['http_method_override']); $container->setParameter('kernel.trust_x_sendfile_type_header', $config['trust_x_sendfile_type_header']); + $container->setParameter('kernel.x_sendfile_header', $config['x_sendfile_header']); $container->setParameter('kernel.trusted_hosts', [0] === array_keys($config['trusted_hosts']) ? $config['trusted_hosts'][0] : $config['trusted_hosts']); $container->setParameter('kernel.default_locale', $config['default_locale']); $container->setParameter('kernel.enabled_locales', $config['enabled_locales']); diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php index 26784bec367d2..6d693d4714157 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -106,6 +106,10 @@ public function boot(): void if ($this->container->hasParameter('kernel.trust_x_sendfile_type_header') && $this->container->getParameter('kernel.trust_x_sendfile_type_header')) { BinaryFileResponse::trustXSendfileTypeHeader(); } + + if ($this->container->hasParameter('kernel.x_sendfile_header') && $header = $this->container->getParameter('kernel.x_sendfile_header')) { + BinaryFileResponse::setXSendfileHeader($header); + } } public function build(ContainerBuilder $container): void diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index 4f0023cf2f658..3f384df53b9b5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -705,6 +705,7 @@ protected static function getBundleDefaultConfig() 'http_method_override' => false, 'handle_all_throwables' => true, 'trust_x_sendfile_type_header' => '%env(bool:default::SYMFONY_TRUST_X_SENDFILE_TYPE_HEADER)%', + 'x_sendfile_header' => '%env(default::SYMFONY_X_SENDFILE_HEADER)%', 'ide' => '%env(default::SYMFONY_IDE)%', 'default_locale' => 'en', 'enabled_locales' => [], diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index b514179d40176..6a731c989ee4f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -25,7 +25,7 @@ "symfony/deprecation-contracts": "^2.5|^3", "symfony/error-handler": "^6.4|^7.0", "symfony/event-dispatcher": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-foundation": "^7.2", "symfony/http-kernel": "^7.2", "symfony/polyfill-mbstring": "~1.0", "symfony/filesystem": "^7.1", diff --git a/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php b/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php index bf4e446e330ed..87781184362c7 100644 --- a/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php +++ b/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php @@ -26,6 +26,7 @@ class BinaryFileResponse extends Response { protected static bool $trustXSendfileTypeHeader = false; + protected static ?string $xSendfileHeader = null; protected File $file; protected ?\SplTempFileObject $tempFileObject = null; @@ -208,9 +209,10 @@ public function prepare(Request $request): static $this->headers->set('Accept-Ranges', $request->isMethodSafe() ? 'bytes' : 'none'); } - if (self::$trustXSendfileTypeHeader && $request->headers->has('X-Sendfile-Type')) { + $type = self::$trustXSendfileTypeHeader && $request->headers->has('X-Sendfile-Type') ? $request->headers->get('X-Sendfile-Type') : self::$xSendfileHeader; + + if (null !== $type) { // Use X-Sendfile, do not send any content. - $type = $request->headers->get('X-Sendfile-Type'); $path = $this->file->getRealPath(); // Fall back to scheme://path for stream wrapped locations. if (false === $path) { @@ -370,6 +372,14 @@ public static function trustXSendfileTypeHeader(): void self::$trustXSendfileTypeHeader = true; } + /** + * Trust X-Sendfile header and the likes. + */ + public static function setXSendfileHeader(string $name): void + { + self::$xSendfileHeader = $name; + } + /** * If this is set to true, the file will be unlinked after the request is sent * Note: If the X-Sendfile header is used, the deleteFileAfterSend setting will not be used. diff --git a/src/Symfony/Component/HttpFoundation/CHANGELOG.md b/src/Symfony/Component/HttpFoundation/CHANGELOG.md index c3814fddd62b7..091d066fd165f 100644 --- a/src/Symfony/Component/HttpFoundation/CHANGELOG.md +++ b/src/Symfony/Component/HttpFoundation/CHANGELOG.md @@ -7,6 +7,7 @@ CHANGELOG * Add optional `$requests` parameter to `RequestStack::__construct()` * Add optional `$v4Bytes` and `$v6Bytes` parameters to `IpUtils::anonymize()` * Add `PRIVATE_SUBNETS` as a shortcut for private IP address ranges to `Request::setTrustedProxies()` + * Add `BinaryFileResponse::setXSendfileHeader()` 7.1 ---