diff --git a/CHANGELOG-5.0.md b/CHANGELOG-5.0.md index 8e72992976..1b9ca4e6aa 100644 --- a/CHANGELOG-5.0.md +++ b/CHANGELOG-5.0.md @@ -1,5 +1,12 @@ # Changelog +## [5.2.4](https://github.com/phalcon/cphalcon/releases/tag/v5.2.4) (xxxx-xx-xx) + +### Fixed + +- Parse multipart/form-data from PUT request [#16271](https://github.com/phalcon/cphalcon/issues/16271) + + ## [5.2.3](https://github.com/phalcon/cphalcon/releases/tag/v5.2.3) (2023-07-26) ### Fixed diff --git a/phalcon/Http/Request.zep b/phalcon/Http/Request.zep index e9f3f0e65f..be9ed7e50b 100644 --- a/phalcon/Http/Request.zep +++ b/phalcon/Http/Request.zep @@ -1721,12 +1721,20 @@ class Request extends AbstractInjectionAware implements RequestInterface, Reques if null === cached { let contentType = this->getContentType(); - if typeof contentType == "string" && stripos(contentType, "json") != false { - let cached = this->getJsonRawBody(true); + if typeof contentType == "string" { + + if (stripos(contentType, "json") != false) { + let cached = this->getJsonRawBody(true); + } + + if (stripos(contentType, "multipart/form-data") !== false) { + let cached = this->getFormData(); + } if typeof cached != "array" { let cached = []; } + } else { let cached = []; @@ -1745,4 +1753,77 @@ class Request extends AbstractInjectionAware implements RequestInterface, Reques noRecursive ); } + + /** + * parse multipart/form-data from raw data + */ + private function getFormData() -> array + { + var boundary, matches; + + preg_match("/boundary=(.*)$/is", this->getContentType(), matches); + + let boundary = matches[1]; + + var bodyParts; + + let bodyParts = preg_split("/\\R?-+" . preg_quote(boundary, "/") . "/s", this->getRawBody()); + + array_pop(bodyParts); + + array dataset = []; + var bodyPart; + + for bodyPart in bodyParts { + if empty(bodyPart) { + continue; + } + + var splited; + let splited = preg_split("/\\R\\R/", bodyPart, 2); + + array headers = []; + var headerParts, headerPart; + + let headerParts = preg_split("/\\R/s", splited[0], -1, PREG_SPLIT_NO_EMPTY); + + for headerPart in headerParts { + if (strpos(headerPart, ":") === false) { + continue; + } + + var exploded, headerName, headerValue; + + let exploded = explode(":", headerPart, 2), + headerName = strtolower(trim(exploded[0])), + headerValue = trim(exploded[1]); + + if strpos(headerValue, ";") !== false { + var explodedHeader, part; + let explodedHeader = explode(";", headerValue); + + for part in explodedHeader { + let part = preg_replace("/\"/", "", trim(part)); + + if strpos(part, "=") !== false { + var explodedPart, namePart, valuePart; + let explodedPart = explode("=", part, 2), + namePart = strtolower(trim(explodedPart[0])), + valuePart = trim(trim(explodedPart[1]), '"'), + headers[headerName][namePart] = valuePart; + + } else { + let headers[headerName][] = part; + } + } + } else { + let headers[headerName] = headerValue; + } + } + + let dataset[headers["content-disposition"]["name"]] = splited[1]; + } + + return dataset; + } } diff --git a/tests/unit/Http/Request/GetPutCest.php b/tests/unit/Http/Request/GetPutCest.php index ed7263546d..41a174466f 100644 --- a/tests/unit/Http/Request/GetPutCest.php +++ b/tests/unit/Http/Request/GetPutCest.php @@ -127,4 +127,66 @@ public function httpRequestGetPutJson(UnitTester $I) $_SERVER = $store; } + + /** + * Tests Phalcon\Http\Request :: getPut() - multipart/form-data + * + * @issue @16271 + * @author Phalcon Team + * @since 2023-07-28 + */ + public function httpRequestGetPutMultipartFormData(UnitTester $I) + { + $I->wantToTest('Http\Request - getPut() - multipart/form-data'); + + stream_wrapper_unregister('php'); + stream_wrapper_register('php', PhpStream::class); + + $boundary = md5(microtime()); + + $data = << $time, + 'REQUEST_METHOD' => 'PUT', + 'CONTENT_TYPE' => "multipart/form-data; boundary={$boundary}", + ]; + + $request = new Request(); + + $expected = [ + 'fruit' => 'orange', + 'quantity' => '4', + ]; + + $actual = file_get_contents('php://input'); + + $I->assertSame($data, $actual); + + $I->assertSame( + $expected, + $request->getPut() + ); + + stream_wrapper_restore('php'); + + $_SERVER = $store; + } }