Skip to content

Commit a401441

Browse files
Copilotanderly
andcommitted
Fix batch response decoding error - check if regex match exists before accessing
Co-authored-by: anderly <[email protected]>
1 parent dd21982 commit a401441

File tree

2 files changed

+161
-2
lines changed

2 files changed

+161
-2
lines changed

src/ODataResponse.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,9 @@ private function decodeBody()
9090
$decodedBody = json_decode($this->body, true, 512, JSON_BIGINT_AS_STRING);
9191
if ($decodedBody === null) {
9292
$matches = null;
93-
preg_match('~\{(?:[^{}]|(?R))*\}~', $this->body, $matches);
94-
$decodedBody = json_decode($matches[0], true, 512, JSON_BIGINT_AS_STRING);
93+
if (preg_match('~\{(?:[^{}]|(?R))*\}~', $this->body, $matches)) {
94+
$decodedBody = json_decode($matches[0], true, 512, JSON_BIGINT_AS_STRING);
95+
}
9596
if ($decodedBody === null) {
9697
$decodedBody = array();
9798
}

tests/BatchResponseTest.php

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
<?php
2+
3+
namespace SaintSystems\OData\Tests;
4+
5+
// If running standalone, load autoloader
6+
if (basename(__FILE__) === basename($_SERVER['PHP_SELF'])) {
7+
require_once __DIR__ . '/../vendor/autoload.php';
8+
}
9+
10+
use SaintSystems\OData\ODataResponse;
11+
use SaintSystems\OData\ODataRequest;
12+
13+
// Check if PHPUnit is available, if not provide a fallback
14+
if (class_exists('PHPUnit\Framework\TestCase')) {
15+
class BatchResponseTestBase extends \PHPUnit\Framework\TestCase {}
16+
} else {
17+
// Fallback base class for when PHPUnit is not available
18+
class BatchResponseTestBase {
19+
protected function createMock($className) {
20+
return new \stdClass();
21+
}
22+
protected function assertIsArray($value) {
23+
if (!is_array($value)) throw new \Exception("Expected array");
24+
}
25+
protected function assertEquals($expected, $actual, $message = '') {
26+
if ($expected !== $actual) throw new \Exception($message ?: "Assertion failed: $expected !== $actual");
27+
}
28+
protected function assertTrue($value, $message = '') {
29+
if (!$value) throw new \Exception($message ?: "Assertion failed: Expected true");
30+
}
31+
}
32+
}
33+
34+
class BatchResponseTest extends BatchResponseTestBase
35+
{
36+
/**
37+
* Test that batch responses with multipart MIME content don't crash
38+
* This reproduces the issue reported in GitHub where SAP SuccessFactors
39+
* batch responses cause "Undefined array key 0" error
40+
*/
41+
public function testBatchResponseWithMultipartContent()
42+
{
43+
// Simulate a batch response from SAP SuccessFactors
44+
$batchResponseBody = <<<EOT
45+
--batch_f20d8021-677a-4f93-8e62-862b6f383d15
46+
Content-Type: application/http
47+
Content-Transfer-Encoding: binary
48+
49+
HTTP/1.1 200 OK
50+
Content-Type: application/json;charset=utf-8
51+
DataServiceVersion: 2.0
52+
Record-Count: 5
53+
X-SF-Record-Count-Recursive: 5
54+
successfactors-message: X-SF-Correlation-Id, successfactors-sourcetype are missing in request headers
55+
Content-Length: 76038
56+
57+
{
58+
"d" : {
59+
"results" : [
60+
{"id": 1, "name": "Test 1"},
61+
{"id": 2, "name": "Test 2"}
62+
]
63+
}
64+
}
65+
66+
--batch_f20d8021-677a-4f93-8e62-862b6f383d15--
67+
EOT;
68+
69+
$request = $this->createMock(ODataRequest::class);
70+
71+
// This should not throw an exception
72+
$response = new ODataResponse($request, $batchResponseBody, 200, []);
73+
74+
// The response should be created successfully
75+
$this->assertTrue(true, "ODataResponse should handle batch responses without crashing");
76+
77+
// The body should be decoded to at least an empty array (no crash)
78+
$body = $response->getBody();
79+
$this->assertIsArray($body);
80+
}
81+
82+
/**
83+
* Test that responses with no JSON content don't crash
84+
*/
85+
public function testResponseWithNoJsonContent()
86+
{
87+
$nonJsonResponse = "This is plain text with no JSON at all";
88+
89+
$request = $this->createMock(ODataRequest::class);
90+
91+
// This should not throw an exception
92+
$response = new ODataResponse($request, $nonJsonResponse, 200, []);
93+
94+
// The body should be decoded to an empty array
95+
$body = $response->getBody();
96+
$this->assertIsArray($body);
97+
$this->assertEquals([], $body);
98+
}
99+
100+
/**
101+
* Test that responses with embedded JSON still work
102+
*/
103+
public function testResponseWithEmbeddedJson()
104+
{
105+
$responseWithEmbeddedJson = 'Some text before {"value": "test"} some text after';
106+
107+
$request = $this->createMock(ODataRequest::class);
108+
109+
// This should not throw an exception
110+
$response = new ODataResponse($request, $responseWithEmbeddedJson, 200, []);
111+
112+
// The body should extract the JSON portion
113+
$body = $response->getBody();
114+
$this->assertIsArray($body);
115+
$this->assertEquals('test', $body['value']);
116+
}
117+
118+
/**
119+
* Test normal JSON response still works correctly
120+
*/
121+
public function testNormalJsonResponse()
122+
{
123+
$jsonResponse = json_encode(['value' => 'test', 'count' => 5]);
124+
125+
$request = $this->createMock(ODataRequest::class);
126+
$response = new ODataResponse($request, $jsonResponse, 200, []);
127+
128+
$body = $response->getBody();
129+
$this->assertIsArray($body);
130+
$this->assertEquals('test', $body['value']);
131+
$this->assertEquals(5, $body['count']);
132+
}
133+
}
134+
135+
// If running standalone (not via PHPUnit), execute the tests
136+
if (!class_exists('PHPUnit\Framework\TestCase') && basename(__FILE__) === basename($_SERVER['PHP_SELF'])) {
137+
echo "Running BatchResponse tests...\n\n";
138+
139+
$test = new BatchResponseTest();
140+
$methods = [
141+
'testBatchResponseWithMultipartContent',
142+
'testResponseWithNoJsonContent',
143+
'testResponseWithEmbeddedJson',
144+
'testNormalJsonResponse'
145+
];
146+
147+
foreach ($methods as $method) {
148+
try {
149+
echo "Running $method... ";
150+
$test->$method();
151+
echo "✓ PASSED\n";
152+
} catch (\Exception $e) {
153+
echo "✗ FAILED: " . $e->getMessage() . "\n";
154+
}
155+
}
156+
157+
echo "\nTests completed.\n";
158+
}

0 commit comments

Comments
 (0)