[ 'REQUEST_METHOD' => 'POST', 'REQUEST_URI' => '/contact', 'CONTENT_TYPE' => $contentType, 'HTTP_HOST' => 'localhost', ], 'body' => $body, 'expected_post' => [ '_form_id' => 'contact_form', '_token' => 'abc123token456', 'name' => 'John Doe', 'email' => 'john@example.com', ], ]; } // Test 1: Normal multipart parsing (should work with custom parsers) function testMultipartParsing() { echo "=== Test 1: Normal Multipart Parsing ===\n"; $data = createMultipartRequest(); $cache = new ParserCache(new SmartCache(new InMemoryCache())); $parser = new HttpRequestParser($cache); try { $request = $parser->parseRequest( method: $data['server']['REQUEST_METHOD'], uri: $data['server']['REQUEST_URI'], server: $data['server'], rawBody: $data['body'] ); echo "✓ Request parsed successfully\n"; echo "Method: " . $request->method->value . "\n"; echo "Parsed body data: " . json_encode($request->parsedBody->data) . "\n"; // Check if CSRF tokens are present $formId = $request->parsedBody->get('_form_id'); $token = $request->parsedBody->get('_token'); if ($formId && $token) { echo "✓ CSRF tokens found: form_id='$formId', token='$token'\n"; } else { echo "✗ CSRF tokens missing!\n"; } } catch (Exception $e) { echo "✗ Error: " . $e->getMessage() . "\n"; } echo "\n"; } // Test 2: Simulate empty php://input scenario (this tests our fallback) function testEmptyInputFallback() { echo "=== Test 2: Empty php://input Fallback ===\n"; // Simulate the scenario where php://input is empty but $_POST has data // This is what actually happens with multipart/form-data global $_POST; $originalPost = $_POST; $_POST = [ '_form_id' => 'contact_form', '_token' => 'fallback_token_123', 'name' => 'Jane Doe', 'email' => 'jane@example.com', ]; $cache = new ParserCache(new SmartCache(new InMemoryCache())); $parser = new HttpRequestParser($cache); try { $request = $parser->parseRequest( method: 'POST', uri: '/contact', server: [ 'REQUEST_METHOD' => 'POST', 'REQUEST_URI' => '/contact', 'CONTENT_TYPE' => 'multipart/form-data; boundary=test', 'HTTP_HOST' => 'localhost', ], rawBody: '' // Empty body simulates php://input being empty ); echo "✓ Request parsed with empty body\n"; echo "Parsed body data: " . json_encode($request->parsedBody->data) . "\n"; // Check if fallback worked $formId = $request->parsedBody->get('_form_id'); $token = $request->parsedBody->get('_token'); if ($formId === 'contact_form' && $token === 'fallback_token_123') { echo "✓ Fallback to \$_POST worked correctly!\n"; } else { echo "✗ Fallback failed: form_id='$formId', token='$token'\n"; } } catch (Exception $e) { echo "✗ Error: " . $e->getMessage() . "\n"; } finally { $_POST = $originalPost; // Restore original $_POST } echo "\n"; } // Run tests echo "Testing CSRF Token Handling in Multipart Requests\n"; echo "================================================\n\n"; testMultipartParsing(); testEmptyInputFallback(); echo "Test completed!\n"; echo "\n"; echo "If both tests pass, the CSRF token issue should be resolved.\n"; echo "The key improvement is that HttpRequestParser now uses \$_POST as fallback\n"; echo "when php://input is empty for multipart/form-data requests.\n";