Apigee removing quotes from Client Hint headers

Hello,

It seems that Apigee is not correctly getting http ClientHints Headers, such as sec-ch-ua

sec-ch-ua-platform, etc., it is actually removing quotes that should be there.
 
We use the current method to get the variables:
var val = context.getVariable(key)
 
But in debug mode we see that the quotes are not there like they should, for example:
 
Example 1
wrong value
"name": "sec-ch-ua-platform",
"value": "macOS" 
Value expected
"value": "\"macOS\""
 ------------------ ------------------ ------------------
Example 2: 
wrong vqlue
"name": "sec-ch-ua",
"value": "Google Chrome\";v=\"113,\"Chromium\";v=\"113\",\"Not-A.Brand\";v=\"24\""
},
 
expected value
"\"Google Chrome\";v=\"113\",\"Chromium\";v=\"113\",\"Not-A.Brand\";v=\"24\""

 

 

I guess a similar problem was detect previously:

https://www.googlecloudcommunity.com/gc/Apigee/Apigee-Edge-is-removing-quotes-from-the-header-value/...

Solved Solved
0 7 180
1 ACCEPTED SOLUTION

We use a ServiceCallout to send these variables to a dedicated API for Bot Protection.On our API backend both " " are missing (I guess it should be url encoded ?)

How do you construct the request to the service callout? I guess the values of those headers ... they do not get mapped to a header. They are being placed into a JSON payload I suppose? Is that right?

The issue is that the .getVariable() method tries to be helpful. In HTTP, Headers can in general appear multiple times with distinct values, and it's ok for senders (clients or servers) to concatenate the distinct multiple values of the header into a single header line, with the values separated by commas. So these options are defined to be semantically equivalent:

 

my-header: x
my-header: y

my-header: x, y

 

Find the relevant spec here.

The Apigee runtime tries to be helpful in exposing each one of those distinct values as its own variable. You can call getVariable() on request.header.my-header.1 for the first value (x), and on request.header.my-header.1 for the second (y). This is documented here. Apigee behavior is by-the-book here; it just splits the entire header value by commas and gives you the piece parts, irrespective of how any quotes or parens or anything else in the header value might imply other framing. If you call getVariable() with request.header.my-header (without the trailing index) for a header with a value that contains one or more commas, by default you get the FIRST value. Not all the values. So in your case, context.getVariable("request.header.Sec-CH-UA") will give you the text value up to the first comma.

There are some cases where you don't want Apigee to do that - you don't want Apigee to treat a header value that contains commas as a multi-valued header. To tell Apigee "just give me the string" , you can use .values.string as the suffix. Eg, context.getVariable("request.header.Sec-CH-UA.values.string").

When I use that form, I see the headers in their entirety, as expected.

View solution in original post

7 REPLIES 7

I think what you are reporting here is .... a misleading indicator from the trace system.

When I invoke an Apigee proxy with a browser, Apigee handles the client hints and passes them through to the upstream.  The upstream receives those values correctly and accurately. 

So please take care reaching conclusions based on what you see in the Trace window.  Make your evaluation based on what the upstream is seeing.

*Also, pls remember, Apigee is not designed to be a general purpose HTTP proxy for web requests.  It's for APIs. So browser hints of the type you are referring to.... are probably IRRELEVANT in the case of APIs.  I'd guess you're probably using Apigee for a non mainstream purpose if you're asking about browser hints. 

I get the feeling that it's not uncommon for people to discover Apigee and then think, "Oh, I can use it for this browser-centric thing I'm trying to do!"  and that's probably not a good idea. Apigee is good for managing APIs. Some of the people who ask questions here, I'm sure, are exploring Apigee the use of Apigee for less than honorable reasons, too.  It's hard to tell who is who.  That's all part of an online community experience I guess. 

Hello @dchiesa1 , thanks a lot for the quick reply and insight, much appreciated!

We checked our API backend (called before the upstream via ServiceCallout) and we are not receiving the expected value. Quotes \" are missing.

How it works :

- We fill variables with JS to get all headers needed from the incoming request. ex:
context.getVariable("request.header.Sec-CH-UA-Platform"), 
context.getVariable("request.header.Sec-CH-UA");

- We use a ServiceCallout to send these variables to a dedicated API for Bot Protection.On our API backend both " " are missing (I guess it should be url encoded ?)

What we don't understand is : Why we do not see the quote (") when the header value start and finish by it.
Is there anything we can do to have access to the original value on the ServiceCallout/JS level? 

Thanks and have a great week

I guess the issue is not directly linked to the headers name, but rather to how enclosing quotes are handled in values.

By sending:

"Hello World"

to Apigee, you expect the same value in the ServiceCallout, while now the result is:

Hello World

 or sending

"Hello";World="1", "Good";Bye="World"

is retrieved as below in the ServiceCallout

Hello";World="1

 It's inconsistent, and data is lost - and if expected it should be documented.

See my reply here for an explanation of what you are observing. In short: it is working as expected. 

We use a ServiceCallout to send these variables to a dedicated API for Bot Protection.On our API backend both " " are missing (I guess it should be url encoded ?)

How do you construct the request to the service callout? I guess the values of those headers ... they do not get mapped to a header. They are being placed into a JSON payload I suppose? Is that right?

The issue is that the .getVariable() method tries to be helpful. In HTTP, Headers can in general appear multiple times with distinct values, and it's ok for senders (clients or servers) to concatenate the distinct multiple values of the header into a single header line, with the values separated by commas. So these options are defined to be semantically equivalent:

 

my-header: x
my-header: y

my-header: x, y

 

Find the relevant spec here.

The Apigee runtime tries to be helpful in exposing each one of those distinct values as its own variable. You can call getVariable() on request.header.my-header.1 for the first value (x), and on request.header.my-header.1 for the second (y). This is documented here. Apigee behavior is by-the-book here; it just splits the entire header value by commas and gives you the piece parts, irrespective of how any quotes or parens or anything else in the header value might imply other framing. If you call getVariable() with request.header.my-header (without the trailing index) for a header with a value that contains one or more commas, by default you get the FIRST value. Not all the values. So in your case, context.getVariable("request.header.Sec-CH-UA") will give you the text value up to the first comma.

There are some cases where you don't want Apigee to do that - you don't want Apigee to treat a header value that contains commas as a multi-valued header. To tell Apigee "just give me the string" , you can use .values.string as the suffix. Eg, context.getVariable("request.header.Sec-CH-UA.values.string").

When I use that form, I see the headers in their entirety, as expected.

Thanks for that detailed and helpful answer !🔥

Thanks a lot for taking the time to test and share your results with us.

Really appreciate it! We were able to test the solution and it works well.