1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\Bridges\HttpDI;
9:
10: use Nette;
11:
12:
13: 14: 15:
16: class HttpExtension extends Nette\DI\CompilerExtension
17: {
18: public $defaults = [
19: 'proxy' => [],
20: 'headers' => [
21: 'X-Powered-By' => 'Nette Framework',
22: 'Content-Type' => 'text/html; charset=utf-8',
23: ],
24: 'frames' => 'SAMEORIGIN',
25: 'csp' => [],
26: 'csp-report' => [],
27: ];
28:
29:
30: private $cliMode;
31:
32:
33: public function __construct($cliMode = false)
34: {
35: $this->cliMode = $cliMode;
36: }
37:
38:
39: public function loadConfiguration()
40: {
41: $builder = $this->getContainerBuilder();
42: $config = $this->validateConfig($this->defaults);
43:
44: $builder->addDefinition($this->prefix('requestFactory'))
45: ->setClass(Nette\Http\RequestFactory::class)
46: ->addSetup('setProxy', [$config['proxy']]);
47:
48: $builder->addDefinition($this->prefix('request'))
49: ->setClass(Nette\Http\Request::class)
50: ->setFactory('@Nette\Http\RequestFactory::createHttpRequest');
51:
52: $builder->addDefinition($this->prefix('response'))
53: ->setClass(Nette\Http\Response::class);
54:
55: $builder->addDefinition($this->prefix('context'))
56: ->setClass(Nette\Http\Context::class)
57: ->addSetup('::trigger_error', ['Service http.context is deprecated.', E_USER_DEPRECATED]);
58:
59: if ($this->name === 'http') {
60: $builder->addAlias('nette.httpRequestFactory', $this->prefix('requestFactory'));
61: $builder->addAlias('nette.httpContext', $this->prefix('context'));
62: $builder->addAlias('httpRequest', $this->prefix('request'));
63: $builder->addAlias('httpResponse', $this->prefix('response'));
64: }
65: }
66:
67:
68: public function afterCompile(Nette\PhpGenerator\ClassType $class)
69: {
70: if ($this->cliMode) {
71: return;
72: }
73:
74: $initialize = $class->getMethod('initialize');
75: $config = $this->getConfig();
76: $headers = $config['headers'];
77:
78: if (isset($config['frames']) && $config['frames'] !== true) {
79: $frames = $config['frames'];
80: if ($frames === false) {
81: $frames = 'DENY';
82: } elseif (preg_match('#^https?:#', $frames)) {
83: $frames = "ALLOW-FROM $frames";
84: }
85: $headers['X-Frame-Options'] = $frames;
86: }
87:
88: foreach (['csp', 'csp-report'] as $key) {
89: if (empty($config[$key])) {
90: continue;
91: }
92: $value = '';
93: foreach ($config[$key] as $type => $policy) {
94: $value .= $type;
95: foreach ((array) $policy as $item) {
96: $value .= preg_match('#^[a-z-]+\z#', $item) ? " '$item'" : " $item";
97: }
98: $value .= '; ';
99: }
100: if (strpos($value, "'nonce'")) {
101: $value = Nette\DI\ContainerBuilder::literal(
102: 'str_replace(?, ? . (isset($cspNonce) \? $cspNonce : $cspNonce = base64_encode(Nette\Utils\Random::generate(16, "\x00-\xFF"))), ?)',
103: ["'nonce", "'nonce-", $value]
104: );
105: }
106: $headers['Content-Security-Policy' . ($key === 'csp' ? '' : '-Report-Only')] = $value;
107: }
108:
109: foreach ($headers as $key => $value) {
110: if ($value != null) {
111: $initialize->addBody('$this->getService(?)->setHeader(?, ?);', [$this->prefix('response'), $key, $value]);
112: }
113: }
114: }
115: }
116: