From 419e95aea16b2490b63763c4a4d7bdfa208f65c0 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 25 Mar 2026 12:51:45 -0500 Subject: [PATCH] Validate $filter before streaming response begins MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When $filter references an unknown property or is syntactically invalid, Lodata previously started writing the HTTP 200 response body ({"@odata.context":"...","value":[) before the filter was parsed, then injected an OData-error trailer mid-stream. Clients received a malformed response that was neither a valid OData error (HTTP 400) nor valid JSON. Per the OData spec, an invalid $filter must return HTTP 400 Bad Request before any response body is sent. Add assertValidFilter() to EntitySet, mirroring the existing assertValidOrderBy() pattern. It eagerly calls generateTree() on the filter expression — using the entity set's own filter parser so custom parsers are respected — and throws ParserException (a BadRequestException) if the expression is invalid. The call site in get() is before setResourceCallback(), so the exception propagates before the StreamedResponse callback is registered or invoked. Valid requests are unaffected: the filter tree is parsed once here for validation, then again inside generateWhere() when the query executes. Co-Authored-By: Claude Sonnet 4.6 --- src/EntitySet.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/EntitySet.php b/src/EntitySet.php index dd1e64e99..ae264711a 100644 --- a/src/EntitySet.php +++ b/src/EntitySet.php @@ -266,6 +266,7 @@ public function get(Transaction $transaction, ?ContextInterface $context = null) } $this->assertValidOrderBy(); + $this->assertValidFilter(); return $transaction->getResponse()->setResourceCallback($this, function () use ($transaction, $context) { $context = $context ?: $this; @@ -1131,6 +1132,25 @@ protected function assertValidOrderBy(): void } } + /** + * Assert that the $filter expression is syntactically valid and references only known properties. + * This runs BEFORE the streaming response begins so that an invalid filter yields HTTP 400 + * rather than a malformed mid-stream error fragment. + * @return void + */ + protected function assertValidFilter(): void + { + $filter = $this->getFilter(); + + if (!$filter->hasValue()) { + return; + } + + $parser = $this->getFilterParser(); + $parser->pushEntitySet($this); + $parser->generateTree($filter->getExpression()); + } + /** * Assert that the attached type has searchable properties * @return void