PluginProbe
Packeta / trunk
Packeta vtrunk
2.3.2 2.3.1 trunk 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.3.0 1.3.1 1.3.2 1.4 1.4.1 1.4.2 1.4.3 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 All 56 releases
packeta / deps / nette / http / src / Bridges / HttpDI / HttpExtension.php

HttpExtension.php in Packeta trunk, at deps/nette/http/src/Bridges/HttpDI/HttpExtension.php

128 lines 5.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * This file is part of the Nette Framework (https://nette.org)
5 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6 */
7 declare (strict_types=1);
8 namespace Packetery\Nette\Bridges\HttpDI;
9
10 use Packetery\Nette;
11 use Packetery\Nette\Schema\Expect;
12 /**
13 * HTTP extension for Nette DI.
14 */
15 class HttpExtension extends \Packetery\Nette\DI\CompilerExtension
16 {
17 /** @var bool */
18 private $cliMode;
19 public function __construct(bool $cliMode = \false)
20 {
21 $this->cliMode = $cliMode;
22 }
23 public function getConfigSchema() : \Packetery\Nette\Schema\Schema
24 {
25 return Expect::structure([
26 'proxy' => Expect::anyOf(Expect::arrayOf('string'), Expect::string()->castTo('array'))->firstIsDefault()->dynamic(),
27 'headers' => Expect::arrayOf('scalar|null')->default(['X-Powered-By' => 'Nette Framework 3', 'Content-Type' => 'text/html; charset=utf-8'])->mergeDefaults(),
28 'frames' => Expect::anyOf(Expect::string(), Expect::bool(), null)->default('SAMEORIGIN'),
29 // X-Frame-Options
30 'csp' => Expect::arrayOf('array|scalar|null'),
31 // Content-Security-Policy
32 'cspReportOnly' => Expect::arrayOf('array|scalar|null'),
33 // Content-Security-Policy-Report-Only
34 'featurePolicy' => Expect::arrayOf('array|scalar|null'),
35 // Feature-Policy
36 'cookiePath' => Expect::string()->dynamic(),
37 'cookieDomain' => Expect::string()->dynamic(),
38 'cookieSecure' => Expect::anyOf('auto', null, \true, \false)->firstIsDefault()->dynamic(),
39 // Whether the cookie is available only through HTTPS
40 'disableNetteCookie' => Expect::bool(\false),
41 ]);
42 }
43 public function loadConfiguration()
44 {
45 $builder = $this->getContainerBuilder();
46 $config = $this->config;
47 $builder->addDefinition($this->prefix('requestFactory'))->setFactory(\Packetery\Nette\Http\RequestFactory::class)->addSetup('setProxy', [$config->proxy]);
48 $request = $builder->addDefinition($this->prefix('request'))->setFactory('@\\Packetery\\Nette\\Http\\RequestFactory::fromGlobals');
49 $response = $builder->addDefinition($this->prefix('response'))->setFactory(\Packetery\Nette\Http\Response::class);
50 if ($config->cookiePath !== null) {
51 $response->addSetup('$cookiePath', [$config->cookiePath]);
52 }
53 if ($config->cookieDomain !== null) {
54 $value = $config->cookieDomain === 'domain' ? $builder::literal('$this->getService(?)->getUrl()->getDomain(2)', [$request->getName()]) : $config->cookieDomain;
55 $response->addSetup('$cookieDomain', [$value]);
56 }
57 if ($config->cookieSecure !== null) {
58 $value = $config->cookieSecure === 'auto' ? $builder::literal('$this->getService(?)->isSecured()', [$request->getName()]) : $config->cookieSecure;
59 $response->addSetup('$cookieSecure', [$value]);
60 }
61 if ($this->name === 'http') {
62 $builder->addAlias('nette.httpRequestFactory', $this->prefix('requestFactory'));
63 $builder->addAlias('httpRequest', $this->prefix('request'));
64 $builder->addAlias('httpResponse', $this->prefix('response'));
65 }
66 if (!$this->cliMode) {
67 $this->sendHeaders();
68 }
69 }
70 private function sendHeaders()
71 {
72 $config = $this->config;
73 $headers = \array_map('strval', $config->headers);
74 if (isset($config->frames) && $config->frames !== \true && !isset($headers['X-Frame-Options'])) {
75 $frames = $config->frames;
76 if ($frames === \false) {
77 $frames = 'DENY';
78 } elseif (\preg_match('#^https?:#', $frames)) {
79 $frames = "ALLOW-FROM {$frames}";
80 }
81 $headers['X-Frame-Options'] = $frames;
82 }
83 foreach (['csp', 'cspReportOnly'] as $key) {
84 if (empty($config->{$key})) {
85 continue;
86 }
87 $value = self::buildPolicy($config->{$key});
88 if (\strpos($value, "'nonce'")) {
89 $this->initialization->addBody('$cspNonce = base64_encode(random_bytes(16));');
90 $value = \Packetery\Nette\DI\ContainerBuilder::literal('str_replace(?, ? . $cspNonce, ?)', ["'nonce", "'nonce-", $value]);
91 }
92 $headers['Content-Security-Policy' . ($key === 'csp' ? '' : '-Report-Only')] = $value;
93 }
94 if (!empty($config->featurePolicy)) {
95 $headers['Feature-Policy'] = self::buildPolicy($config->featurePolicy);
96 }
97 $this->initialization->addBody('$response = $this->getService(?);', [$this->prefix('response')]);
98 foreach ($headers as $key => $value) {
99 if ($value !== '') {
100 $this->initialization->addBody('$response->setHeader(?, ?);', [$key, $value]);
101 }
102 }
103 if (!$config->disableNetteCookie) {
104 $this->initialization->addBody('\\Packetery\\Nette\\Http\\Helpers::initCookie($this->getService(?), $response);', [$this->prefix('request')]);
105 }
106 }
107 private static function buildPolicy(array $config) : string
108 {
109 $nonQuoted = ['require-sri-for' => 1, 'sandbox' => 1];
110 $value = '';
111 foreach ($config as $type => $policy) {
112 if ($policy === \false) {
113 continue;
114 }
115 $policy = $policy === \true ? [] : (array) $policy;
116 $value .= $type;
117 foreach ($policy as $item) {
118 if (\is_array($item)) {
119 $item = \key($item) . ':';
120 }
121 $value .= !isset($nonQuoted[$type]) && \preg_match('#^[a-z-]+$#D', $item) ? " '{$item}'" : " {$item}";
122 }
123 $value .= '; ';
124 }
125 return $value;
126 }
127 }
128