Skip to content

Commit 92bf973

Browse files
committed
[BUGFIX] Ensure request body is writable in ServerRequestInstruction
When deserializing a ServerRequestInstruction from array data, the body stream was recreated using the originally stored stream mode (e.g. "rb"). This caused a RuntimeException when attempting to write to a read-only stream during deserialization. This patch ensures the stream is always recreated with mode "w+b" to allow writing. The mode is no longer serialized via jsonSerialize, as it is irrelevant and may cause issues on reconstruction. This issue typically did not occur in Core usage, as many claims (e.g. from the user management module) are GET requests with data passed via the query string. However, custom extensions using POST requests with a body payload were affected. Resolves: #107117 Releases: main, 13.4, 12.4 Change-Id: If680406a9beba1dfa99157d71aad26deafcae4a0 Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/90101 Tested-by: core-ci <typo3@b13.com> Tested-by: Oli Bartsch <bo@cedev.de> Tested-by: Benni Mack <benni@typo3.org> Reviewed-by: Benni Mack <benni@typo3.org> Reviewed-by: Oli Bartsch <bo@cedev.de> Tested-by: Oliver Hader <oliver.hader@typo3.org> Reviewed-by: Oliver Hader <oliver.hader@typo3.org>
1 parent e93bbdf commit 92bf973

File tree

3 files changed

+61
-2
lines changed

3 files changed

+61
-2
lines changed

typo3/sysext/backend/Classes/Security/SudoMode/Access/ServerRequestInstruction.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public static function buildFromArray(array $data): self
7373
$target->requestTarget = $data['requestTarget'];
7474
$target->method = $data['method'];
7575
$target->uri = new Uri($data['uri']);
76-
$target->body = new Stream('php://temp', $data['body']['mode']);
76+
$target->body = new Stream('php://temp', 'w+b');
7777
$target->body->write($data['body']['contents']);
7878
$target->parsedBody = $data['parsedBody'];
7979
$target->queryParams = $data['queryParams'];
@@ -109,7 +109,6 @@ public function jsonSerialize(): array
109109
'method' => $this->method,
110110
'uri' => (string)$this->uri,
111111
'body' => [
112-
'mode' => $this->body->getMetadata('mode'),
113112
'contents' => (string)$this->body,
114113
],
115114
'parsedBody' => $this->parsedBody,
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
test-content
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the TYPO3 CMS project.
7+
*
8+
* It is free software; you can redistribute it and/or modify it under
9+
* the terms of the GNU General Public License, either version 2
10+
* of the License, or any later version.
11+
*
12+
* For the full copyright and license information, please read the
13+
* LICENSE.txt file that was distributed with this source code.
14+
*
15+
* The TYPO3 project - inspiring people to share!
16+
*/
17+
18+
namespace TYPO3\CMS\Backend\Tests\Functional\Security\SudoMode\Access;
19+
20+
use PHPUnit\Framework\Attributes\DataProvider;
21+
use PHPUnit\Framework\Attributes\Test;
22+
use Psr\Http\Message\StreamInterface;
23+
use TYPO3\CMS\Backend\Security\SudoMode\Access\ServerRequestInstruction;
24+
use TYPO3\CMS\Core\Http\ServerRequest;
25+
use TYPO3\CMS\Core\Http\Stream;
26+
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
27+
28+
final class ServerRequestInstructionTest extends FunctionalTestCase
29+
{
30+
protected bool $initializeDatabase = false;
31+
32+
public static function instructionCanBeDehydratedDataProvider(): \Generator
33+
{
34+
$body = new Stream('php://temp', 'w+b');
35+
$body->write('test-content');
36+
$body->rewind();
37+
yield 'in-memory content' => [$body];
38+
39+
$body = new Stream(__DIR__ . '/Fixtures/test-content.txt', 'r');
40+
$body->rewind();
41+
yield 'file content' => [$body];
42+
}
43+
44+
#[Test]
45+
#[DataProvider('instructionCanBeDehydratedDataProvider')]
46+
public function instructionCanBeDehydrated(StreamInterface $body): void
47+
{
48+
$headers = ['Content-Type' => ['text/plain']];
49+
$request = new ServerRequest('https://example.com', 'POST', $body, $headers);
50+
$instruction = ServerRequestInstruction::createForServerRequest($request);
51+
$json = json_encode($instruction);
52+
$data = json_decode($json, true);
53+
$result = ServerRequestInstruction::buildFromArray($data);
54+
self::assertEquals($result->getUri(), $instruction->getUri());
55+
self::assertSame($result->getMethod(), $instruction->getMethod());
56+
self::assertSame($result->getBody()->getContents(), $instruction->getBody()->getContents());
57+
self::assertSame($result->getHeaders(), $instruction->getHeaders());
58+
}
59+
}

0 commit comments

Comments
 (0)