angular: HttpParameterCodec improperly encodes special characters like '+' and '='
I’m submitting a …
[x ] bug report => search github for a similar issue or PR before submitting
[ ] feature request
[ ] support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question
Current behavior Code exemple
let body = new URLSearchParams();
for (let i in values) {
body.set(i, values[i]);
}
let options = RequestOptions();
options.headers.append('Content-Type', 'application/x-www-form-urlencoded')
return this.http.post(uri, body, options).then();
[...]
If values[i]
has a ‘+’ character in it, then it is replaced by spaces.
Expected/desired behavior ‘+’ should be sent as any other character.
Please tell us about your environment:
- Angular version: 2.0.0-rc.5
- Browser: [all | Chrome XX | Firefox XX | IE XX ]
- Language: [Typescript]
About this issue
- Original URL
- State: open
- Created 8 years ago
- Reactions: 121
- Comments: 109 (25 by maintainers)
Links to this issue
Commits related to this issue
- UserHistoryService: Pass answers as JSON. Instead of as a query parameter. This fixes this issues: https://github.com/murraycu/angular-bigoquiz-client/issues/2 by passing the string in the body inste... — committed to murraycu/angular-bigoquiz-client by murraycu 7 years ago
- UserHistoryService: Pass answers as JSON. Instead of as a query parameter. This fixes this issues: https://github.com/murraycu/angular-bigoquiz-client/issues/2 by passing the string in the body inste... — committed to murraycu/angular-bigoquiz-client by murraycu 7 years ago
- use standard `encodeURIComponent` for uri gerneation This is needed so that a `+` is sent to the backend as it is written. See https://github.com/angular/angular/issues/11058#issuecomment-351864976 ... — committed to k-nut/unisport-frontend by k-nut 7 years ago
- Use HTML-compliant encoding for angular http parser https://github.com/angular/angular/issues/11058 — committed to opf/openproject by oliverguenther 6 years ago
- fix(http): provide alternative to broken HttpUrlEncodingCodec Currently in Angular, by default `HttpParam` instances are encoded with the `HttpUrlEncodingCodec`. This is an implementation of `HttpPar... — committed to alxhub/angular by alxhub 4 years ago
- :bug: fix(ui/calllogs): add workaround for the nasty angular/angular#11058 — committed to tierklinik-dobersberg/cis by ppacher 3 years ago
- fix(frontend): Correctly search for snippets including plus symbols This is a workaround for an Angular issue, see: https://github.com/angular/angular/issues/11058 — committed to fujaba/fulib.org by Clashsoft 2 years ago
- fix(http): encode + signs in query params as %2B (angular#11058) (#45111) Servers always decode + as a space, which is undesirable when one actually wants to query for a plus. BREAKING CHANGE: Quer... — committed to atscott/angular by Clashsoft 2 years ago
- fix(http): encode + signs in query params as %2B (#11058) — committed to Jamesweng/angular by Jamesweng 2 years ago
@jsgoupil actually, it’s same approach as the one shared right here.
Full usage is just below.
Guys, 5 years. Are you kidding?
almost 5 years, really? 😦
Standard behaviour can be easily overriden globally with this interceptor.
Then in app.module.ts
Confirmed as reported. A
+
should definitely not be encoded as itself, as this has a different meaning in URL encoded values.I believe the long-ago origin of this bug was the re-implementation of the incorrect logic from angular.js’
encodeUriSegment
. This function was meant for paths in the URL part of the URI, not query parameters. This bug was unintentionally later copied toHttpClient
, which used the same implementation of query encoding for backwards compatibility with@angular/http
.Angular’s behavior here is incorrect and should be fixed. I’m concerned that fixing it might cause issues/breaking changes if people have applied workarounds, especially in their backends. Here’s what I propose:
We introduce a new
HttpParameterCodec
with the correct encoding behavior and deprecate the old one. It’s already possible to choose an encoder when creatingHttpParams
.in v10.x, we make the default encoder configurable via DI/
HttpClientModule
options, allowing someone to opt-in globally to the fix.in v11, we swap the default, and make an opt-out possible instead.
in v12, then, we remove the opt-out and the broken codec.
How to deal with it in Angular 5.X First of all create custom encoder:
And then use this technic for setting query params
This is still an issue in angular 4.
For angular 5.1 create your own encoder:
Then in HttpParams add it as an option!
How is this simple of an issue not resolved yet after 1.5 years?
Ignoring the encoding for the
+
symbol was implemented in this commit but this creates another problem as+
becomesspace
in some PHP implementations.My use case is: I’m using an API that requires an
application/x-www-form-urlencoded
format for the payload. I want to send a string containing the character+
, but it doesn’t get encoded byURLSearchParams
So i try to encode it myself (it becomes%2B
) and when i set it asURLSearchParams
it gets double-encoded because the%
gets encoded as well.The solution in this case is to redefine the QueryEncoder used by your implementation of URLSearchParams
More than 2 years and still open…
@NKjoep Just search for how to URLSearchParams with http.get. There are many examples on stack exchange. Just pass the
new CustomQueryEncoderHelper()
into the constructor as shown above, and otherwise use like normal.I just ran into this rather obvious bug. Ten months without a one-liner fix is just sad.
This is still happening in Angular 4.0.1.
mohammadi+1
is left as it is, so is sent to the server asmohammadi 1
(space). It should bemohammadi%2B1
to be sent to the server asmohammadi+1
.Temporarily fixed this by adding a custom QueryEncoder (like
GhQueryEncoder
) that extends the parentQueryEncoder
.IMO this should be done in the default QueryEncoder.
For reference, the relevant RFC is https://www.rfc-editor.org/rfc/rfc1866#section-8.2 RFC3986 defines the allowed chars in an URL. So + is allowed, as well as %20. rfc1866 defines the meaning of the characters for example in a query string. So it states clearly that space is encoded as ‘+’ - which implies that ‘+’ must be encoded otherwise.
I’m quite puzzled that this is still open and there’s no correct implementation of this after 7 years.
the issue is open 4 years ago. shitting Angular
For V3/4 I reckon we should drop this type, and delegate to the native implementation, and require it to be polyfilled otherwise. This is currently akin to importing
Promise
and that’s something we want to avoid.Polyfill - https://github.com/WebReflection/url-search-params
Here’s how I solved encoding a dynamic set of params:
Create a custom encoder:
Create a helper function to convert params:
Usage:
Issue present in Angular 5
Error still here in Angular 4.4.4 . Hope you’ll fix that in Angular 5 !
I’d really like to have correct behavior by default, and a possible option to switch to legacy incorrect encoder just for really old projects where it’s really required because of server-side implementation based on incorrectly encoded url values… This can easily be described in upgrade instructions. “Do you rely on legacy broken url encoder? Are you in doubt? Enable this fallback option. Do you have custom params encoder which fixes old broken default one? It can be removed.”
We use alternative encoder in all our projects as a workaround already, for years, and it’s an extra code you must not forget to add to each new project, it is really bad thing.
This issue also bit me yesterday. TL;DR, I believe that the default param encoding provided by HttpClient should encode key-value pairs using
encocdeURIComponent
and nothing else so that data is sent to the server in most unambiguous manner possible.It seems to me, that if a user issues an HttpClient GET request such as
this.http.get(path, { params: data })...
, wheredata
is some object of type{ [k: string]: string }
, then the key-value pairs should be sent such that the server will receive identical key-value pairs. It’s perfectly valid to have an object or mapping with keys containing any of the URI reserved characters (RFC 3986 sec 2.2). Keys can be nearly any string. This applies to JavaScript objects, PHP arrays, and Python dictionaries. Why anyone would want to use the key “abc+def=foobar” isn’t ours to ask. But if that’s the key, the server should see that key. Same goes for values.The current default parameter encoder,
HttpUrlEncodingCodec
, isn’t implemented to be unambiguous. I don’t know the history, but I’m guessing it was to make the query string “prettier”. But it can lead to unexpected behavior, as we can plainly see from this 6-year issue.In my experimentation, I found that both PHP and Python’s Flask interpret “+” as a space in the query string. What’s more, a “=” is perfectly legal as part of the key (if unorthodox) if encoded. If multiple “=” characters appear in a query string key-value pair, then the first “=” is treated as the key-value separator, and the remaining “=” are assumed to be part of the value.
To prove out the problem, I created a simple testbed with a readily available PHP server (version 5.3.3) and Flask server (Python 3.10.4 / Flask 2.1.1 / Werkzeug 2.0.1). They are included here for completeness:
test-bed.component.ts
test-bed.component.html
test.php
test.py
As you can see, each PHP and Flask endpoint simply takes the parsed query string, which becomes the
$_GET
array in PHP, and therequest.args
dictionary in Python, and returns the JSON representation of that data. Ideally, the data passed as params tohttp.get()
should be returned exactly as we pass in the data. Since these are objects/mappings/dictionaries, order is not guaranteed. But content should be. (PHP appeared to leave the order intact, while my Flask testbed sorted the keys for caching or debug purposes.) I included' '
in the character set to demonstrate how “+” and “%20” conflict.expectedData
Default encoder (HttpUrlEncodingCodec)
The GET requests to the testbeds results in URLs with the following query string:
?a@b=c@d&a:b=c:d&a$b=c$d&a,b=c,d&a;b=c;d&a+b=c+d&a=b=c=d&a?b=c?d&a/b=c/d&a%20b=c%20d
phpData
“+” in the query string is treated as a space. (Note that PHP appears to dislike spaces in the $_GET keys, and automatically converts them to underscores. This behavior is specific to PHP, and therefore not an issue to be corrected.) Since “+” is equivalent to " ", the
"a b": "c d"
pair becomes a duplicate. The key-pair “a=b=c=d” is interpreted as"a": "b=c=d"
, which was not the intent.flaskData
Identical results, except that Flask/Python doesn’t mind a space in the key.
It should be noted here that none of the other characters seem to cause issue. So they’re “safe”, but I don’t know if that’s always guaranteed. What I do know is that by encoding the key-value pairs with
encodeURIComponent()
and any reserved characters percent-encoded, there’s zero chance that the characters are treated as reserved token characters. Testing with theURIComponentCodec
implementation above, the data sent and returned to/from the testbeds is more complete and correct:URIComponentCodec (using only
encodeURIComponent()
)The query string produced:
?a%40b=c%40d&a%3Ab=c%3Ad&a%24b=c%24d&a%2Cb=c%2Cd&a%3Bb=c%3Bd&a%2Bb=c%2Bd&a%3Db=c%3Dd&a%3Fb=c%3Fd&a%2Fb=c%2Fd&a%20b=c%20d
(Yes, it’s very ugly.)phpData
Note that we have “a+b”, “a=b”, and “a_b” keys. All values are as expected.
flaskData
Note that we again have all keys including “a+b”, “a=b”, and “a b”. All keys and values are as expected.
Conclusion
I would propose that the entire “standard encoding” be thrown out, and
encodeURIComponent()
be used as-is.encodeURIComponent()
is available specifically to encode URI components such as query string keys and values.I don’t know the reasoning behind the “standard encoding”. I suspect it was for prettier query strings. But the HttpClient is used mainly for asynchronous background data requests. The user won’t see the query string. The server will, and the server needs to see the correct query string.
At a minimum, “+” and “=” should be left percent-encoded:
%20
and%2B
are unambiguous.For those still curious about why a “+” should be a space – https://www.w3.org/Addressing/URL/uri-spec.html:
A change like this would likely need to be released in a major release. It’s possibly breaking, if users are expecting their “+” to be spaces on the server side. Or if they’re expecting
"a=b": "c"
to be"a": "b=c"
… though I think it’s safe to say that’s not clearly expected behavior.I realize that was a lot of data and opinion. I’m very interested to know if purely percent-encoded values would break some server side API. I guess I’m openly asking - what’s the advantage to decoding some of the percent encoded values, other than making it less ugly? Why not leave the query string components percent-encoded?
https://stackoverflow.com/a/17351258/7004297
Same issue here, I can’t send dates in ISO 8601 format to the API because the
+
sign is being replace by a blank space…I don’t think ‘+’ should be encoded to ‘%2B’ by default, there is an standard that accepts ‘+’ as an space for urls, there should be an easy way to do it when you need it, for example using a custom encoder, but with some interceptors this generates the double encode problem. So maybe create an easy way to encode EVERY param, using a flag option or something similar.
I see that this is still an issue, anyone knows if Angular is working on a solution?
You can try the package I built to solve it: https://github.com/tiangolo/ngx-http-client
And in your code, replace this line:
with:
…that’s it. The rest of your code is the same. Let me know if it works (or not).
I’m having this same issue. I don’t believe it is related to the server or back-end at all…
After sending the post in angular you can find it being displayed in Chrome debug tools here.
Do we know if the angular team is working on this?
This issue breaks GET request Query parameters as well.
The plus symbol must be properly encoded as %2B, it cannot be sent as unencoded + in URL query parameters - or it will be URL-decoded by servers to space character, thus any parameter with value like “A+B” will be URL-decoded to “A B”.
4 years later…
+1
I HATE THIS STUFF WE HAVE PHONE NUMBERS like +36…
xD ahem I mean… same issue here
I don’t think it’s an issue at the server side. Am using node with angular version 4.3.6 and having the same issue.
Addition to this issue: I have a param that is ISO8601 (2016-10-03T13:27:25+03:00)
UrlSearchParams.append and then toString() makes this + sign into spaces. Given that this issue here from Aug19th and complicated, is there good workaround for this?
@dylhunn Thanks for the reply.
I understand not wanting to break old code. I’ve been around enough myself to know how fragile it can be.
I think we would agree that most users don’t run into these issues. They’re sending a JSON body, or whatever else, and they simply don’t hit these edge cases. But when someone does, they inevitably google for a solution, end up here or StackOverflow, and they end up writing nearly identical work arounds.
It’s not difficult to create a custom implementation of
HttpParameterCodec
and createHttpParams
with a custom encoder. When done in a wrapped service, a user can again pass in plain ol’ JavaScript objects as a params argument. Unfortunately, everyone trying to send ISO date times with a positive time zone offset as a parameter value is going to end up going down this same path.So… What about making it easier for users to use an alternative encoder? If there were an option as simple as
this.http.defaultParamEncoder = AlternativeEncodingCodec
– it would be very nice. And an example alternative encoder could be included in params.ts, making it easy for users to toggle. Thoughts?Update: Oh, and I’d be willing to contribute to do the work. Nothing comes for free, after all. 😃
@auqfyanjun check the comment https://github.com/angular/angular/issues/11058#issuecomment-351864976 or a package developed for this purpose as mentioned in https://github.com/angular/angular/issues/11058#issuecomment-357861890
Why angular’s URLSearchParams does not behave like the standard URLSearchParams (https://url.spec.whatwg.org/#urlsearchparams) by default ?
I tested angular with this test https://github.com/w3c/web-platform-tests/blob/master/url/urlsearchparams-stringifier.html :
The result :
If you want to test in browser : http://w3c-test.org/url/urlsearchparams-stringifier.html
Seems it was fixed for Angular v15 Accordingly upgrade guide https://update.angular.io/?l=3&v=13.0-15.0
Can somebody confirm this?
Yes, we fixed + but not =. It was too risky to do them simultaneously because there’s very poor test coverage for this kind of change, since it’s at the server-client boundary. The + proved to be low-drama, so we can probably accept a fix for = in the next major.
I think that the default
HttpParameterCodec
should strive to match the behavior of URLSearchParams.That would be the least surprising (for developers) and easiest to integrate (when thinking of client-server interaction etc).
It has the additional bonus of being in a specification which can be looked up when debating what constraints are expected (such as legitimate key values).
IIUC the relevant part regarding your doubt is
As mentioned here,
Hence
=
(just like the other cases currently handled in a special way in angular) seems to be just fine in keys or values, they just need to be percent-encoded.@dylhunn are you kidding? It’s a consolidated issue from angularjs time, a lot of other issues were just closed as duplicates in favor of this issue.
Also you can look into this comment https://github.com/angular/angular/issues/11058#issuecomment-636131153 by @alxhub
As it’s a breaking change, I think with all special characters we will see original issue with wrong implementation fixed probably near Angular 21 or 22
@spanevin almost all of the discussion here is about
+
. If=
is also affected, we’d need a separate reproduction and issue, and we’d want to fix it in a separate PR, to minimize risk at one time.There are currently a couple of PRs open for this bug (#37385 and #32598). The difficulty is finding an appropriate API and ensuring a reasonable migration path for the breaking change.
@atscott there is similar issue for the new Angular as well. Seems like you copy bugs from one implementation to another 😉 https://github.com/angular/angular/issues/18261
Same issue here.
I’m using URLSearchParams to prepare the parameters to post to the auth/token service. The ‘+’ char is replaced/encoded with a space so the authentication fails.
@Adrug yes, in this thread https://github.com/angular/angular/issues/11058#issuecomment-1344111788
=
is not fixed yet.It’s unbelievable that this bug remains in angular 13 I’ve lost many hours debbuging my API and the bug was in the front end ¬¬
@dylhunn Why = was removed from the title?
Are you sure + is the only affected character? I don’t see a test for & in parameter value.
Also I don’t see a test for = in parameter name. I understand why it works without replacement in the value, but it will break name if it is not encoded properly.
Let’s say we have a parameter with name=“a=b” and value = “c&d=5” What do we have in current implementation? I think it’s …a=b=c&d=5 right? Which I, based on my knowledge, would parse as 2 parameters: “a” = “b=c” and “d” = “5” instead of “a=b” = “c&d=5” Am I wrong?
I don’t understand. If + is an ENCODED SPACE, why do you suggest to not encode +? + is +, space is space. If somebody does pre-encoding, what’s the difference between + and any other character like % which can be a part of ENCODED string? If they do pre-encoding, they MUST disable integrated encoding. If then don’t do pre-encoding - + MUST be encoded.
@LinPoLen - thank you for editing your previous message and being aware of our code of conduct when interacting on the issue tracker.
@artfulsage Yep. I ended up using the codec part, though just creating a
HttpParams
using it inline.Note that as mentioned in #33728 this issue isn’t limited to
+
. The=
character for example is left alone even for encoding query param keys, which means a key ofa=b
incorrectly gets interpreted as a key ofa
with its value prefixed byb=
, which is just totally wrong.Thanks for reporting this issue. This issue is now obsolete due to changes in the recent releases.
URLSearchParams
is no longer part of Angular. Please update to the most recent Angular version.If the problem still exists in your application, please open a new issue and follow the instructions in the issue template.
Ain’t what the PR #19711 is trying to address for us and it is just sitting there?
@dtwright I wrote a section explaining all the details I found in the package I created to solve the problem: https://github.com/tiangolo/ngx-http-client#very-technical-details
I am currently using the custom encoder solution but looking at the tags in this issue, it seems like this has been acknowledged as a bug.
Thanks for the clarification @trotyl .
I guess that both W3C/WHATWG and
encodeURIComponent
end up rendering a valid and “compatible” URL. At least, parseable by back ends without creating side effects.But the current encoder is actually reversing it, it is taking the encoded version of
+
, that results fromencodeURIComponent
(and would be the same effect using the W3C/WHATWG encoding) and converting it back to a literal+
.%2B
->+
instead of leaving
+
->%2B
(that is compatible with both versions)And the same for:
@
,:
,$
,,
,;
,+
,=
,?
,/
.I can imagine how it could generate problems having those characters being parsed by the back end literally, unencoded, in parameter keys or values. At least for:
:
,+
,=
,?
,/
.As of now, we have confirmed that it creates issues with PHP and Python back ends, from the comments above. I imagine there could be more back end languages and frameworks that would expect encoded params in a way that is compatible with at least one of W3C/WHATWG or
encodeURIComponent
.I know this project is huge, with more than 250 PRs open (including a 2 months old one from me). So I don’t know if submitting a PR is actually appreciated. Especially before knowing if the changes would acceptable.
@alxhub could you give us a hint? What was the reason for overwriting the native
encodeURIComponent
? Would a PR removing one or more of those lines be acceptable / desired?@tiangolo
->
->
encodeURIComponent
does not follow W3C/WHATWG standard as well, W3C/WHATWG Standard is:+
&+
->%2B
encodeURIComponent is:%20
&+
->%2B
There is nothing wrong to rewrite the result of
encodeURIComponent
, the problem is for how to rewrite it.This also affects Flask back ends.
Flask follows the W3C standard and converts
).
+
characters to a “space” character (I could create a PR removing that line.
…but first I would like to ask, is there a reason why you overrode the standard
encodeURIComponent
?It seems like the code was clearly written to override the default encoding and override the encoding / escaping of reserved characters (like
+
).@alxhub could you explain to us what was the idea / motivation in that code section and if you see any better approach to solving it? Git blame says you’re the person to ask…
Would it be acceptable if I create a PR removing the custom overriding and decoding for reserved characters?
Edit: I just noticed that, although the code is mostly the same and the problem is the same too, I’m talking about the
@angualr/common/http/params
module. The one used in Angular 4.3’sHttpClient
.Should I create an independent issue for that? Or should I just leave my comment here given that the problem is the same?
I still have this problem with the version of Angular “dependencies”: { “@ angular / animations”: “^ 4.4.0-RC.0”, “@ angular / common”: “^ 4.4.0-RC.0”, “@ angular / compiler”: “^ 4.4.0-RC.0”, “@ angular / core”: “^ 4.4.0-RC.0”, “@ angular / forms”: “^ 4.4.0-RC.0”, “@ angular / http”: “^ 4.4.0-RC.0”, “@ angular / platform-browser”: “^ 4.4.0-RC.0”, “@ angular / platform-browser-dynamic”: “^ 4.4.0-RC.0”, “@ angular / router”: “^ 4.4.0-RC.0”,
When I have a URL with “+” that is changed by% 2B a 404 error is thrown
http://www.condominio-mais.com/usuario/resetar/4995ffba-6e78-486e-8653-0fb8f4e6996e/CfDJ8KvZVIfag49BieUUcQkCHC0WR%2FQNYNjUtosCAOJ6V5VTJHn0phbIUB4zWt2wWX3rKBK3iwrktVIzeuuZ5bYOQYFMGSN2cFTkKx%2B7AcN0Uw7KcpPwkhU2fGDAMUssZMxwqNnoEPscVdZIQJlQ4NvbAyHSJk4I9g1wjZoTumSiT5VDDkebFvjild8RVoFtXYcsObpvpfgmxT3Cdld5h%2Fot3L5h3qkYSbfbg%2BxcE1VTxJb5saXQYWtnnDBJb4aBwH5zZg%3D%3D
@ayrad in your example you are using a
http.post
. Does it work withhttp.get
as well?The two methods have a different signature and I am afraid doesn’t work with the
http.get
.Also in Tomcat, and thus in all the servers that use it behind the scenes.
Is it the same problem when navigating to an URL? When going to for example http://localhost:5000/?q=test+test the url suddenly http://localhost:5000/?q=test%2Btest
Thats the way in 2.0.0 as well…