Skip to content

Comments

Fix: URL-encoded special characters in $filter and $orderby break OData parsing#3080

Open
Copilot wants to merge 15 commits intomainfrom
copilot/fix-filter-clause-issue
Open

Fix: URL-encoded special characters in $filter and $orderby break OData parsing#3080
Copilot wants to merge 15 commits intomainfrom
copilot/fix-filter-clause-issue

Conversation

Copy link
Contributor

Copilot AI commented Jan 23, 2026

Why make this change?

Users reported errors when filtering on strings containing special characters like ampersand (&). When using properly URL-encoded requests (e.g., ?$filter=region eq 'filter %26 test'), DAB returns "unterminated string literal" errors instead of correctly processing the filter.

HttpUtility.ParseQueryString() decodes query parameter values, causing double-decoding when DAB constructs OData filter strings. A URL like ?$filter=title eq 'A %26 B' becomes title eq 'A & B', where the literal & is interpreted as a query parameter separator, truncating the filter to title eq 'A and producing parsing failures.

What is this change?

Preserve URL encoding for OData parameters by extracting raw values before ParseQueryString() decodes them:

  1. RestRequestContext: Added RawQueryString property to store original encoded query string
  2. RestService: Populate RawQueryString alongside ParsedQueryString
  3. RequestParser: New ExtractRawQueryParameter() (internal static method) extracts encoded values by splitting on unencoded & separators
    • Method is internal (not private) for direct testing without reflection
    • Includes comprehensive documentation explaining it assumes URL-encoded input where %26 has not been decoded to &
    • Explicitly documents that it splits on unencoded & (parameter separators) while preserving encoded %26 in values
  4. $filter/$orderby parsing: Use raw values instead of decoded values when constructing OData query strings
  5. Error handling: Added explicit null checks that throw DataApiBuilderException if parameter extraction fails unexpectedly

Database-agnostic: operates at HTTP parsing layer before any database-specific processing.

How was this tested?

  • Integration Tests - 2 targeted tests using existing data across all DB types (MsSql, PostgreSQL, MySQL, DwSql)
    • FindTestWithFilterContainingSpecialCharacters: Tests $filter with % character using existing book (SOME%CONN)
    • FindTestWithOrderByContainingSpecialCharacters: Tests $orderby URL encoding preservation
    • Query strings use proper URL encoding (e.g., ?$filter=title%20eq%20%27SOME%25CONN%27, ?$orderby=title%20desc) to test actual double-decoding bug scenario
    • Uses existing test data to avoid breaking other tests
  • Unit Tests - 20 tests validating encoding preservation for diverse special characters
    • Tests call ExtractRawQueryParameter() directly (no reflection needed since method is internal)
    • Tests cover: &, =, +, %, #, <, >, :, /, ?
    • Tests validate edge cases: duplicate parameters, case-insensitive matching, malformed queries

Sample Request(s)

REST - Before (fails):

GET /api/Book?$filter=title eq 'SOME%CONN'
→ 400 Bad Request: "unterminated string literal" - the %25 (encoded %) gets double-decoded

GET /api/Book?$filter=title eq 'filter & test'
→ 400 Bad Request: "unterminated string literal at position 17 in 'title eq 'filter'."

GET /api/Book?$orderby=title desc
→ 400 Bad Request: similar parsing error (if column names contain special chars)

REST - After (succeeds):

GET /api/Book?$filter=title eq 'SOME%CONN'
→ 200 OK: [{"id": 16, "title": "SOME%CONN", "publisher_id": 1234}]

GET /api/Book?$filter=title eq 'filter & test'
→ 200 OK: Returns matching records with & in title

GET /api/Book?$orderby=title desc
→ 200 OK: [array of books ordered by title descending]

Works with any URL-encoded special character in both $filter and $orderby: %26 (&), %3D (=), %2B (+), %25 (%), %23 (#), %3C (<), %3E (>), %3A (:), %2F (/), %3F (?), etc.

Original prompt

This section details on the original issue you should resolve

<issue_title>[Bug]: Special characters in filter clause</issue_title>
<issue_description>### What happened?

Tried filtering on the string "filter & test"

Original URL: https://localhost:5001/api/my_entity?$filter=region eq 'filter & test'
Encoded URL: https://localhost:5001/api/my_entity?$filter=region%20eq%20%27filter%20%26%20test%27

I get this response when using the encoded URL:
{
"error": {
"code": "BadRequest",
"message": "There is an unterminated string literal at position 17 in 'region eq 'filter'.",
"status": 400
}
}

Version

1.6.84

What database are you using?

Azure SQL

What hosting model are you using?

Container Apps

Which API approach are you accessing DAB through?

REST

Relevant log output

Azure.DataApiBuilder.Service.Exceptions.DataApiBuilderException: There is an unterminated string literal at position 17 in 'region eq 'filter'.
       ---> Microsoft.OData.ODataException: There is an unterminated string literal at position 17 in 'region eq 'filter'.
         at Microsoft.OData.UriParser.UriQueryExpressionParser.ParseComparison()
         at Microsoft.OData.UriParser.UriQueryExpressionParser.ParseLogicalAnd()
         at Microsoft.OData.UriParser.UriQueryExpressionParser.ParseLogicalOr()
         at Microsoft.OData.UriParser.UriQueryExpressionParser.ParseExpression()
         at Microsoft.OData.UriParser.UriQueryExpressionParser.ParseExpressionText(String expressionText)
         at Microsoft.OData.UriParser.ODataQueryOptionParser.ParseFilterImplementation(String filter, ODataUriParserConfiguration configuration, ODataPathInfo odataPathInfo)
         at Azure.DataApiBuilder.Core.Parsers.ODataParser.GetFilterClause(String filterQueryString, String resourcePath, ODataUriResolver customResolver) in /_/src/Core/Parsers/FilterParser.cs:line 64
         --- End of inner exception stack trace ---
         at Azure.DataApiBuilder.Core.Parsers.ODataParser.GetFilterClause(String filterQueryString, String resourcePath, ODataUriResolver customResolver) in /_/src/Core/Parsers/FilterParser.cs:line 68
         at Azure.DataApiBuilder.Core.Parsers.RequestParser.ParseQueryString(RestRequestContext context, ISqlMetadataProvider sqlMetadataProvider) in /_/src/Core/Parsers/RequestParser.cs:line 99
         at Azure.DataApiBuilder.Core.Services.RestService.ExecuteAsync(String entityName, EntityActionOperation operationType, String primaryKeyRoute) in /_/src/Core/Services/RestService.cs:line 178
         at Azure.DataApiBuilder.Service.Controllers.RestController.HandleOperation(String route, EntityActionOperation operationType) in /_/src/Service/Controllers/RestController.cs:line 239

Code of Conduct

  • I agree to follow this project's Code of Conduct</issue_description>

<agent_instructions>Introduce as little new code as necessary. Ensure this and variants of this problem are resolved by creating as generic of a solution as possible. </agent_instructions>

Comments on the Issue (you are @copilot in this section)

@JerryNixon This is a DAB bug, not a user error.

What's Happening

The user correctly URL-encoded their filter value:

  • &%26
  • spaces → %20

However, DAB (or the underlying framework) is double-decoding the URL. The %26 gets decoded back to &, which then causes the query string parser to split on that & — treating everything after it as a separate query parameter.

Result: The filter string gets truncated to region eq 'filter (note the unterminated quote), hence the error.

Root Cause

This is a known class of issue with URL encoding in ASP.NET / OData scenarios. The %26 is being decoded at the framework level before the OData filter parser receives it, so the filter parser never sees the encoded value — it sees a literal & which breaks query string parsing.

Workaround

Double-encode the ampersand: %2526 (encodes the % in %26)

Fix for DAB

The OData filter parser needs to receive the raw/encoded query string, or DAB needs to handle this edge case explicitly when parsing $filter values containing special characters.</comment_new>


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI and others added 5 commits January 23, 2026 21:12
Co-authored-by: JerryNixon <1749983+JerryNixon@users.noreply.github.com>
Co-authored-by: JerryNixon <1749983+JerryNixon@users.noreply.github.com>
Co-authored-by: JerryNixon <1749983+JerryNixon@users.noreply.github.com>
…ocumentation

Co-authored-by: JerryNixon <1749983+JerryNixon@users.noreply.github.com>
Co-authored-by: JerryNixon <1749983+JerryNixon@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix special characters issue in filter clause Fix: URL-encoded special characters in $filter and $orderby break OData parsing Jan 23, 2026
Copilot AI requested a review from JerryNixon January 23, 2026 21:25
@JerryNixon JerryNixon marked this pull request as ready for review January 23, 2026 21:29
Copilot AI review requested due to automatic review settings January 23, 2026 21:29
@Aniruddh25
Copy link
Collaborator

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 6 pipeline(s).

@Aniruddh25
Copy link
Collaborator

/home/vsts/work/1/s/src/Core/Parsers/RequestParser.cs(125,25): error IDE2003: Blank line required between block and subsequent statement [/home/vsts/work/1/s/src/Core/Azure.DataApiBuilder.Core.csproj]
/home/vsts/work/1/s/src/Core/Parsers/RequestParser.cs(138,25): error IDE2003: Blank line required between block and subsequent statement [/home/vsts/work/1/s/src/Core/Azure.DataApiBuilder.Core.csproj]
/home/vsts/work/1/s/src/Core/Parsers/RequestParser.cs(327,13): error IDE2003: Blank line required between block and subsequent statement [/home/vsts/work/1/s/src/Core/Azure.DataApiBuilder.Core.csproj]
/home/vsts/work/1/s/src/Core/Parsers/RequestParser.cs(317,13): error IDE0011: Add braces to 'if' statement. [/home/vsts/work/1/s/src/Core/Azure.DataApiBuilder.Core.csproj]
/home/vsts/work/1/s/src/Core/Parsers/RequestParser.cs(325,17): error IDE0011: Add braces to 'if' statement. [/home/vsts/work/1/s/src/Core/Azure.DataApiBuilder.Core.csproj]

@copilot, fix the above dotnet format errors. And check no more failures by running:
dotnet format

…ards

Co-authored-by: Aniruddh25 <3513779+Aniruddh25@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 20, 2026

/home/vsts/work/1/s/src/Core/Parsers/RequestParser.cs(125,25): error IDE2003: Blank line required between block and subsequent statement [/home/vsts/work/1/s/src/Core/Azure.DataApiBuilder.Core.cspro...

Fixed in commit a78b348:

  • Added blank lines after if-blocks (IDE2003 errors on lines 125, 138, 327)
  • Added braces to single-line if statements (IDE0011 errors on lines 317, 325)
  • Verified with dotnet format --verify-no-changes: All formatting checks pass

@Aniruddh25
Copy link
Collaborator

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 6 pipeline(s).

Copy link
Contributor Author

Copilot AI commented Feb 21, 2026

@copilot, look at test failures in the pipeline logs and fix them:

https://dev.azure.com/sqldab/Data%20API%20builder%20Dependency%20Packages/_build/results?buildId=17357&view=logs&jobId=2d2b3007-3c...

I don't have access to the Azure DevOps pipeline logs (authentication required). Could you please paste the specific test failure messages or error output from the pipeline? I'll fix the issues once I can see what's failing.

@Aniruddh25
Copy link
Collaborator

@copilot,

Read the logs from the attached file:

ErrorLogs.txt

…g book with % character instead

Co-authored-by: Aniruddh25 <3513779+Aniruddh25@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 21, 2026

@copilot,

Read the logs from the attached file:
...

Fixed in commit 4c5ed20. Root cause: Added books 22-25 broke existing tests expecting 21 books (7 test failures).

Solution:

  • Removed test data for books 22-25 from all database schemas
  • Simplified to 2 targeted integration tests using existing test data
  • FindTestWithFilterContainingSpecialCharacters now uses book 16 (SOME%CONN) which already has %
  • FindTestWithOrderByContainingSpecialCharacters tests $orderby URL encoding
  • Unit tests still provide comprehensive coverage for all special characters: &, =, +, %, #, <, >, :, /, ?

This avoids breaking unrelated tests while still validating the fix works correctly.

@Aniruddh25
Copy link
Collaborator

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 6 pipeline(s).

@Aniruddh25
Copy link
Collaborator

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 6 pipeline(s).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: Review In Progress

Development

Successfully merging this pull request may close these issues.

[Bug]: Special characters in filter clause

4 participants