Skip to content

Commit f855efb

Browse files
[HttpKernel] Don't use eval() to render ESI/SSI
1 parent 1d52937 commit f855efb

File tree

5 files changed

+45
-29
lines changed

5 files changed

+45
-29
lines changed

src/Symfony/Component/HttpKernel/HttpCache/Esi.php

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,10 @@ public function process(Request $request, Response $response)
8080
$content = preg_replace('#<esi\:remove>.*?</esi\:remove>#s', '', $content);
8181
$content = preg_replace('#<esi\:comment[^>]+>#s', '', $content);
8282

83+
static $cookie;
84+
$cookie = hash('md5', $cookie ??= random_bytes(16), true);
85+
$boundary = base64_encode($cookie);
8386
$chunks = preg_split('#<esi\:include\s+(.*?)\s*(?:/|</esi\:include)>#', $content, -1, \PREG_SPLIT_DELIM_CAPTURE);
84-
$chunks[0] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[0]);
8587

8688
$i = 1;
8789
while (isset($chunks[$i])) {
@@ -95,16 +97,10 @@ public function process(Request $request, Response $response)
9597
throw new \RuntimeException('Unable to process an ESI tag without a "src" attribute.');
9698
}
9799

98-
$chunks[$i] = sprintf('<?php echo $this->surrogate->handle($this, %s, %s, %s) ?>'."\n",
99-
var_export($options['src'], true),
100-
var_export($options['alt'] ?? '', true),
101-
isset($options['onerror']) && 'continue' === $options['onerror'] ? 'true' : 'false'
102-
);
103-
++$i;
104-
$chunks[$i] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[$i]);
105-
++$i;
100+
$chunks[$i] = $boundary.$options['src']."\n".($options['alt'] ?? '')."\n".('continue' === ($options['onerror'] ?? ''))."\n";
101+
$i += 2;
106102
}
107-
$content = implode('', $chunks);
103+
$content = $boundary.implode('', $chunks).$boundary;
108104

109105
$response->setContent($content);
110106
$response->headers->set('X-Body-Eval', 'ESI');

src/Symfony/Component/HttpKernel/HttpCache/HttpCache.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -636,7 +636,21 @@ private function restoreResponseBody(Request $request, Response $response)
636636
if ($response->headers->has('X-Body-File')) {
637637
include $response->headers->get('X-Body-File');
638638
} else {
639-
eval('; ?>'.$response->getContent().'<?php ;');
639+
$content = $response->getContent();
640+
641+
if (substr($content, -24) === $boundary = substr($content, 0, 24)) {
642+
$j = strpos($content, $boundary, 24);
643+
echo substr($content, 24, $j - 24);
644+
$i = $j + 24;
645+
646+
while (false !== $j = strpos($content, $boundary, $i)) {
647+
[$uri, $alt, $ignoreErrors, $part] = explode("\n", substr($content, $i, $j - $i), 4);
648+
$i = $j + 24;
649+
650+
echo $this->surrogate->handle($this, $uri, $alt, $ignoreErrors);
651+
echo $part;
652+
}
653+
}
640654
}
641655

642656
$response->setContent(ob_get_clean());

src/Symfony/Component/HttpKernel/HttpCache/Ssi.php

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,10 @@ public function process(Request $request, Response $response)
6565
// we don't use a proper XML parser here as we can have SSI tags in a plain text response
6666
$content = $response->getContent();
6767

68+
static $cookie;
69+
$cookie = hash('md5', $cookie ??= random_bytes(16), true);
70+
$boundary = base64_encode($cookie);
6871
$chunks = preg_split('#<!--\#include\s+(.*?)\s*-->#', $content, -1, \PREG_SPLIT_DELIM_CAPTURE);
69-
$chunks[0] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[0]);
7072

7173
$i = 1;
7274
while (isset($chunks[$i])) {
@@ -80,14 +82,10 @@ public function process(Request $request, Response $response)
8082
throw new \RuntimeException('Unable to process an SSI tag without a "virtual" attribute.');
8183
}
8284

83-
$chunks[$i] = sprintf('<?php echo $this->surrogate->handle($this, %s, \'\', false) ?>'."\n",
84-
var_export($options['virtual'], true)
85-
);
86-
++$i;
87-
$chunks[$i] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[$i]);
88-
++$i;
85+
$chunks[$i] = $boundary.$options['virtual']."\n\n\n";
86+
$i += 2;
8987
}
90-
$content = implode('', $chunks);
88+
$content = $boundary.implode('', $chunks).$boundary;
9189

9290
$response->setContent($content);
9391
$response->headers->set('X-Body-Eval', 'SSI');

src/Symfony/Component/HttpKernel/Tests/HttpCache/EsiTest.php

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ public function testMultilineEsiRemoveTagsAreRemoved()
102102
$response = new Response('<esi:remove> <a href="http://www.example.com">www.example.com</a> </esi:remove> Keep this'."<esi:remove>\n <a>www.example.com</a> </esi:remove> And this");
103103
$this->assertSame($response, $esi->process($request, $response));
104104

105-
$this->assertEquals(' Keep this And this', $response->getContent());
105+
$this->assertEquals(' Keep this And this', substr($response->getContent(), 24, -24));
106106
}
107107

108108
public function testCommentTagsAreRemoved()
@@ -113,7 +113,7 @@ public function testCommentTagsAreRemoved()
113113
$response = new Response('<esi:comment text="some comment &gt;" /> Keep this');
114114
$this->assertSame($response, $esi->process($request, $response));
115115

116-
$this->assertEquals(' Keep this', $response->getContent());
116+
$this->assertEquals(' Keep this', substr($response->getContent(), 24, -24));
117117
}
118118

119119
public function testProcess()
@@ -124,23 +124,27 @@ public function testProcess()
124124
$response = new Response('foo <esi:comment text="some comment" /><esi:include src="..." alt="alt" onerror="continue" />');
125125
$this->assertSame($response, $esi->process($request, $response));
126126

127-
$this->assertEquals('foo <?php echo $this->surrogate->handle($this, \'...\', \'alt\', true) ?>'."\n", $response->getContent());
127+
$content = explode(substr($response->getContent(), 0, 24), $response->getContent());
128+
$this->assertSame(['', 'foo ', "...\nalt\n1\n", ''], $content);
128129
$this->assertEquals('ESI', $response->headers->get('x-body-eval'));
129130

130131
$response = new Response('foo <esi:comment text="some comment" /><esi:include src="foo\'" alt="bar\'" onerror="continue" />');
131132
$this->assertSame($response, $esi->process($request, $response));
132133

133-
$this->assertEquals('foo <?php echo $this->surrogate->handle($this, \'foo\\\'\', \'bar\\\'\', true) ?>'."\n", $response->getContent());
134+
$content = explode(substr($response->getContent(), 0, 24), $response->getContent());
135+
$this->assertSame(['', 'foo ', "foo'\nbar'\n1\n", ''], $content);
134136

135137
$response = new Response('foo <esi:include src="..." />');
136138
$this->assertSame($response, $esi->process($request, $response));
137139

138-
$this->assertEquals('foo <?php echo $this->surrogate->handle($this, \'...\', \'\', false) ?>'."\n", $response->getContent());
140+
$content = explode(substr($response->getContent(), 0, 24), $response->getContent());
141+
$this->assertSame(['', 'foo ', "...\n\n\n", ''], $content);
139142

140143
$response = new Response('foo <esi:include src="..."></esi:include>');
141144
$this->assertSame($response, $esi->process($request, $response));
142145

143-
$this->assertEquals('foo <?php echo $this->surrogate->handle($this, \'...\', \'\', false) ?>'."\n", $response->getContent());
146+
$content = explode(substr($response->getContent(), 0, 24), $response->getContent());
147+
$this->assertSame(['', 'foo ', "...\n\n\n", ''], $content);
144148
}
145149

146150
public function testProcessEscapesPhpTags()
@@ -151,7 +155,8 @@ public function testProcessEscapesPhpTags()
151155
$response = new Response('<?php <? <% <script language=php>');
152156
$this->assertSame($response, $esi->process($request, $response));
153157

154-
$this->assertEquals('<?php echo "<?"; ?>php <?php echo "<?"; ?> <?php echo "<%"; ?> <?php echo "<s"; ?>cript language=php>', $response->getContent());
158+
$content = explode(substr($response->getContent(), 0, 24), $response->getContent());
159+
$this->assertSame(['', '<?php <? <% <script language=php>', ''], $content);
155160
}
156161

157162
public function testProcessWhenNoSrcInAnEsi()

src/Symfony/Component/HttpKernel/Tests/HttpCache/SsiTest.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,15 @@ public function testProcess()
101101
$response = new Response('foo <!--#include virtual="..." -->');
102102
$ssi->process($request, $response);
103103

104-
$this->assertEquals('foo <?php echo $this->surrogate->handle($this, \'...\', \'\', false) ?>'."\n", $response->getContent());
104+
$content = explode(substr($response->getContent(), 0, 24), $response->getContent());
105+
$this->assertSame(['', 'foo ', "...\n\n\n", ''], $content);
105106
$this->assertEquals('SSI', $response->headers->get('x-body-eval'));
106107

107108
$response = new Response('foo <!--#include virtual="foo\'" -->');
108109
$ssi->process($request, $response);
109110

110-
$this->assertEquals("foo <?php echo \$this->surrogate->handle(\$this, 'foo\\'', '', false) ?>\n", $response->getContent());
111+
$content = explode(substr($response->getContent(), 0, 24), $response->getContent());
112+
$this->assertSame(['', 'foo ', "foo'\n\n\n", ''], $content);
111113
}
112114

113115
public function testProcessEscapesPhpTags()
@@ -118,7 +120,8 @@ public function testProcessEscapesPhpTags()
118120
$response = new Response('<?php <? <% <script language=php>');
119121
$ssi->process($request, $response);
120122

121-
$this->assertEquals('<?php echo "<?"; ?>php <?php echo "<?"; ?> <?php echo "<%"; ?> <?php echo "<s"; ?>cript language=php>', $response->getContent());
123+
$content = explode(substr($response->getContent(), 0, 24), $response->getContent());
124+
$this->assertSame(['', '<?php <? <% <script language=php>', ''], $content);
122125
}
123126

124127
public function testProcessWhenNoSrcInAnSsi()

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy