Skip to content
This repository was archived by the owner on Jan 29, 2020. It is now read-only.

Commit 1891a74

Browse files
committed
Merge branch 'hotfix/21'
Close #21
2 parents e643101 + ef97674 commit 1891a74

File tree

3 files changed

+75
-1
lines changed

3 files changed

+75
-1
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ Versions 0.3.0 and prior were released as "weierophinney/problem-details".
2929
aspect of the payload. When these values are encountered, the response factory
3030
now will instead return `Resource of type {resource type}`.
3131

32+
- [#21](https://github.com/zendframework/zend-problem-details/pull/21) provides
33+
a defence for `$additional` data keys that would otherwise create malformed
34+
XML tag names.
35+
3236
## 0.5.0 - 2017-10-09
3337

3438
### Added

src/ProblemDetailsResponseFactory.php

+29-1
Original file line numberDiff line numberDiff line change
@@ -289,12 +289,40 @@ protected function generateJsonResponse(array $payload) : ResponseInterface
289289
);
290290
}
291291

292+
/**
293+
* Ensure all keys in this associative array are valid XML tag names by replacing invalid
294+
* characters with an `_`.
295+
*/
296+
private function cleanKeysForXml(array $input): array
297+
{
298+
$return = [];
299+
foreach ($input as $key => $value) {
300+
$startCharacterPattern =
301+
'[A-Z]|_|[a-z]|[\xC0-\xD6]|[\xD8-\xF6]|[\xF8-\x{2FF}]|[\x{370}-\x{37D}]|[\x{37F}-\x{1FFF}]|'
302+
. '[\x{200C}-\x{200D}]|[\x{2070}-\x{218F}]|[\x{2C00}-\x{2FEF}]|[\x{3001}-\x{D7FF}]|[\x{F900}-\x{FDCF}]'
303+
. '|[\x{FDF0}-\x{FFFD}]';
304+
$characterPattern = $startCharacterPattern . '|\-|\.|[0-9]|\xB7|[\x{300}-\x{36F}]|[\x{203F}-\x{2040}]';
305+
306+
$key = preg_replace('/(?!'.$characterPattern.')./u', '_', $key);
307+
$key = preg_replace('/^(?!'.$startCharacterPattern.')./u', '_', $key);
308+
309+
if (is_array($value)) {
310+
$value = $this->cleanKeysForXml($value);
311+
}
312+
$return[$key] = $value;
313+
}
314+
return $return;
315+
}
316+
292317
protected function generateXmlResponse(array $payload) : ResponseInterface
293318
{
294319
// Ensure any objects are flattened to arrays first
295320
$content = json_decode(json_encode($payload), true);
296321

297-
$converter = new ArrayToXml($content, 'problem');
322+
// ensure all keys are valid XML can be json_encoded
323+
$cleanedContent = $this->cleanKeysForXml($content);
324+
325+
$converter = new ArrayToXml($cleanedContent, 'problem');
298326
$dom = $converter->toDom();
299327
$root = $dom->firstChild;
300328
$root->setAttribute('xmlns', 'urn:ietf:rfc:7807');

test/ProblemDetailsResponseFactoryTest.php

+42
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,48 @@ public function testCreateResponseFromThrowableCreatesExpectedTypeWithExtraInfor
105105
$this->assertArrayHasKey('exception', $payload);
106106
}
107107

108+
/**
109+
* @dataProvider acceptHeaders
110+
*/
111+
public function testCreateResponseRemovesInvalidCharactersFromXmlKeys(string $header, string $expectedType) : void
112+
{
113+
$this->request->getHeaderLine('Accept')->willReturn($header);
114+
115+
$additional = [
116+
'foo' => [
117+
'A#-' => 'foo',
118+
'-A-' => 'foo',
119+
'#B-' => 'foo',
120+
],
121+
];
122+
123+
$response = $this->factory->createResponse(
124+
$this->request->reveal(),
125+
500,
126+
'Unknown error occurred',
127+
'Title',
128+
'Type',
129+
$additional
130+
);
131+
132+
$this->assertInstanceOf(ResponseInterface::class, $response);
133+
$this->assertEquals($expectedType, $response->getHeaderLine('Content-Type'));
134+
135+
$payload = $this->getPayloadFromResponse($response);
136+
137+
if (stripos($expectedType, 'xml')) {
138+
$expectedKeyNames = [
139+
'A_-',
140+
'_A-',
141+
'_B-',
142+
];
143+
} else {
144+
$expectedKeyNames = array_keys($additional['foo']);
145+
}
146+
147+
$this->assertEquals(array_keys($payload['foo']), $expectedKeyNames);
148+
}
149+
108150
public function testCreateResponseFromThrowableWillPullDetailsFromProblemDetailsExceptionInterface() : void
109151
{
110152
$e = $this->prophesize(RuntimeException::class)->willImplement(ProblemDetailsExceptionInterface::class);

0 commit comments

Comments
 (0)