[ 
https://issues.apache.org/jira/browse/CAMEL-25306?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Serdar Gökay updated CAMEL-25306:
---------------------------------
    Description: 
{{RestOpenApiProcessor.process()}} maps the operation's path placeholders from 
{{Exchange.HTTP_PATH}}:

{code:java}
String path = exchange.getMessage().getHeader(Exchange.HTTP_PATH, String.class);
...
// map path-parameters from operation to camel headers
HttpHelper.evalPlaceholders(exchange.getMessage().getHeaders(), path, 
consumerPath);
{code}

{{HTTP_PATH}} is the raw request path ({{RoutingContext.normalizedPath()}} in 
camel-platform-http-vertx, {{getRawPath()}} in camel-http-common), so a 
contract-first operation receives its path parameters still percent-encoded. 
The Rest DSL bindings that call the same helper pass a decoded path 
({{getPathInfo()}} in camel-servlet, {{getRequestPath()}} in camel-undertow, 
{{URI.getPath()}} in camel-netty-http), and camel-platform-http-vertx uses the 
decoded {{ctx.pathParams()}}. CAMEL-24577 aligned the Spring Boot platform-http 
engine with the same behaviour.

*Reproducer* (Camel JBang 4.22.1, {{camel run openapi.json routes.camel.yaml 
application.properties}}):

{code:title=openapi.json}
{"openapi":"3.0.3","info":{"title":"t","version":"1"},"servers":[{"url":"/api"}],
 
"paths":{"/items/{id}":{"get":{"operationId":"getItem","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"ok"}}}}}}
{code}

{code:title=routes.camel.yaml}
- route:
    from:
      uri: rest-openapi:openapi.json
      steps:
        - log: "contract-first done"
- route:
    from:
      uri: direct:getItem
      steps:
        - setBody:
            simple: "id=[${header.id}]"
- rest:
    path: /dsl
    get:
      - path: /items/{id}
        to: direct:getItem
{code}

{code:title=application.properties}
camel.server.enabled=true
camel.server.port=8080
camel.rest.component=platform-http
camel.rest.bindingMode=off
camel.rest.inlineRoutes=false
{code}

Both consumers dispatch to the same route:

||Request||Response||
|{{/api/items/A%221}}|{{id=[A%221]}}|
|{{/dsl/items/A%221}}|{{id=[A"1]}}|
|{{/api/items/X%20Y}}|{{id=[X%20Y]}}|
|{{/dsl/items/X%20Y}}|{{id=[X Y]}}|
|{{/api/items/caf%C3%A9}}|{{id=[caf%C3%A9]}}|
|{{/dsl/items/caf%C3%A9}}|{{id=[café]}}|

With {{clientRequestValidation=true}}, the request validator works on the 
decoded value (a parameter whose {{pattern}} forbids spaces rejects {{X%20Y}}), 
while the route receives the encoded text.

*Proposed change*: decode each value after the path has been split, so that an 
encoded {{/}} stays inside its parameter and {{+}} is kept as is (path-segment 
decoding, not form decoding). {{RestOpenApiProcessor}} could pass a 
{{BiConsumer}} to the existing {{evalPlaceholders(BiConsumer, String, String)}} 
overload that decodes the value before setting the header. 
{{RestOpenApiProcessor}} is unchanged on main.}} parameter rejects {{X%20Y}}), 
while the route receives the encoded text.

*Proposed change*: decode each value after the path has been split, so that an 
encoded {{/}} stays inside its parameter and {{+}} is kept as is (path-segment 
decoding, not form decoding). {{RestOpenApiProcessor}} could pass a 
{{BiConsumer}} to the existing {{evalPlaceholders(BiConsumer, String, String)}} 
overload that decodes the value before setting the header. 
{{RestOpenApiProcessor}} is unchanged on main.

  was:
{{RestOpenApiProcessor.process()}} maps the operation's path placeholders from 
{{Exchange.HTTP_PATH}}:

{code:java}
String path = exchange.getMessage().getHeader(Exchange.HTTP_PATH, String.class);
...
// map path-parameters from operation to camel headers
HttpHelper.evalPlaceholders(exchange.getMessage().getHeaders(), path, 
consumerPath);
{code}

{{HTTP_PATH}} is the raw request path ({{RoutingContext.normalizedPath()}} in 
camel-platform-http-vertx, {{getRawPath()}} in camel-http-common), so a 
contract-first operation receives its path parameters still percent-encoded. 
The Rest DSL bindings that call the same helper pass a decoded path 
({{getPathInfo()}} in camel-servlet, {{getRequestPath()}} in camel-undertow, 
{{URI.getPath()}} in camel-netty-http), and camel-platform-http-vertx uses the 
decoded {{ctx.pathParams()}}. CAMEL-24577 aligned the Spring Boot platform-http 
engine with the same behaviour.

*Reproducer* (Camel JBang 4.22.1, {{camel run openapi.json routes.camel.yaml 
application.properties}}):

{code:title=openapi.json}
{"openapi":"3.0.3","info":{"title":"t","version":"1"},"servers":[{"url":"/api"}],
 
"paths":{"/items/{id}":{"get":{"operationId":"getItem","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"ok"}}}}}}
{code}

{code:title=routes.camel.yaml}
- route:
    from:
      uri: rest-openapi:openapi.json
      steps:
        - log: "contract-first done"
- route:
    from:
      uri: direct:getItem
      steps:
        - setBody:
            simple: "id=[${header.id}]"
- rest:
    path: /dsl
    get:
      - path: /items/{id}
        to: direct:getItem
{code}

{code:title=application.properties}
camel.server.enabled=true
camel.server.port=8080
camel.rest.component=platform-http
camel.rest.bindingMode=off
camel.rest.inlineRoutes=false
{code}

Both consumers dispatch to the same route:

||Request||Response||
|{{/api/items/A%221}}|{{id=[A%221]}}|
|{{/dsl/items/A%221}}|{{id=[A"1]}}|
|{{/api/items/X%20Y}}|{{id=[X%20Y]}}|
|{{/dsl/items/X%20Y}}|{{id=[X Y]}}|
|{{/api/items/caf%C3%A9}}|{{id=[caf%C3%A9]}}|
|{{/dsl/items/caf%C3%A9}}|{{id=[café]}}|

With {{clientRequestValidation=true}}, the request validator works on the 
decoded value (a {{pattern: '^[^ ]+ parameter rejects {{X%20Y}}), while the 
route receives the encoded text.

*Proposed change*: decode each value after the path has been split, so that an 
encoded {{/}} stays inside its parameter and {{+}} is kept as is (path-segment 
decoding, not form decoding). {{RestOpenApiProcessor}} could pass a 
{{BiConsumer}} to the existing {{evalPlaceholders(BiConsumer, String, String)}} 
overload that decodes the value before setting the header. 
{{RestOpenApiProcessor}} is unchanged on main.}} parameter rejects {{X%20Y}}), 
while the route receives the encoded text.

*Proposed change*: decode each value after the path has been split, so that an 
encoded {{/}} stays inside its parameter and {{+}} is kept as is (path-segment 
decoding, not form decoding). {{RestOpenApiProcessor}} could pass a 
{{BiConsumer}} to the existing {{evalPlaceholders(BiConsumer, String, String)}} 
overload that decodes the value before setting the header. 
{{RestOpenApiProcessor}} is unchanged on main.


> camel-rest-openapi - contract-first path parameter headers are not 
> percent-decoded
> ----------------------------------------------------------------------------------
>
>                 Key: CAMEL-25306
>                 URL: https://issues.apache.org/jira/browse/CAMEL-25306
>             Project: Camel
>          Issue Type: Improvement
>          Components: camel-rest-openapi
>    Affects Versions: 4.22.1
>            Reporter: Serdar Gökay
>            Priority: Minor
>
> {{RestOpenApiProcessor.process()}} maps the operation's path placeholders 
> from {{Exchange.HTTP_PATH}}:
> {code:java}
> String path = exchange.getMessage().getHeader(Exchange.HTTP_PATH, 
> String.class);
> ...
> // map path-parameters from operation to camel headers
> HttpHelper.evalPlaceholders(exchange.getMessage().getHeaders(), path, 
> consumerPath);
> {code}
> {{HTTP_PATH}} is the raw request path ({{RoutingContext.normalizedPath()}} in 
> camel-platform-http-vertx, {{getRawPath()}} in camel-http-common), so a 
> contract-first operation receives its path parameters still percent-encoded. 
> The Rest DSL bindings that call the same helper pass a decoded path 
> ({{getPathInfo()}} in camel-servlet, {{getRequestPath()}} in camel-undertow, 
> {{URI.getPath()}} in camel-netty-http), and camel-platform-http-vertx uses 
> the decoded {{ctx.pathParams()}}. CAMEL-24577 aligned the Spring Boot 
> platform-http engine with the same behaviour.
> *Reproducer* (Camel JBang 4.22.1, {{camel run openapi.json routes.camel.yaml 
> application.properties}}):
> {code:title=openapi.json}
> {"openapi":"3.0.3","info":{"title":"t","version":"1"},"servers":[{"url":"/api"}],
>  
> "paths":{"/items/{id}":{"get":{"operationId":"getItem","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"ok"}}}}}}
> {code}
> {code:title=routes.camel.yaml}
> - route:
>     from:
>       uri: rest-openapi:openapi.json
>       steps:
>         - log: "contract-first done"
> - route:
>     from:
>       uri: direct:getItem
>       steps:
>         - setBody:
>             simple: "id=[${header.id}]"
> - rest:
>     path: /dsl
>     get:
>       - path: /items/{id}
>         to: direct:getItem
> {code}
> {code:title=application.properties}
> camel.server.enabled=true
> camel.server.port=8080
> camel.rest.component=platform-http
> camel.rest.bindingMode=off
> camel.rest.inlineRoutes=false
> {code}
> Both consumers dispatch to the same route:
> ||Request||Response||
> |{{/api/items/A%221}}|{{id=[A%221]}}|
> |{{/dsl/items/A%221}}|{{id=[A"1]}}|
> |{{/api/items/X%20Y}}|{{id=[X%20Y]}}|
> |{{/dsl/items/X%20Y}}|{{id=[X Y]}}|
> |{{/api/items/caf%C3%A9}}|{{id=[caf%C3%A9]}}|
> |{{/dsl/items/caf%C3%A9}}|{{id=[café]}}|
> With {{clientRequestValidation=true}}, the request validator works on the 
> decoded value (a parameter whose {{pattern}} forbids spaces rejects 
> {{X%20Y}}), while the route receives the encoded text.
> *Proposed change*: decode each value after the path has been split, so that 
> an encoded {{/}} stays inside its parameter and {{+}} is kept as is 
> (path-segment decoding, not form decoding). {{RestOpenApiProcessor}} could 
> pass a {{BiConsumer}} to the existing {{evalPlaceholders(BiConsumer, String, 
> String)}} overload that decodes the value before setting the header. 
> {{RestOpenApiProcessor}} is unchanged on main.}} parameter rejects 
> {{X%20Y}}), while the route receives the encoded text.
> *Proposed change*: decode each value after the path has been split, so that 
> an encoded {{/}} stays inside its parameter and {{+}} is kept as is 
> (path-segment decoding, not form decoding). {{RestOpenApiProcessor}} could 
> pass a {{BiConsumer}} to the existing {{evalPlaceholders(BiConsumer, String, 
> String)}} overload that decodes the value before setting the header. 
> {{RestOpenApiProcessor}} is unchanged on main.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to