Skip to content

Spring Web ignores '+' character while encoding with HierarchicalUriComponents #23148

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
stepio opened this issue Jun 17, 2019 · 4 comments
Closed
Labels
status: invalid An issue that we don't feel is valid

Comments

@stepio
Copy link

stepio commented Jun 17, 2019

First of all, thank you very much for maintaining this great project! But let me go straight to the issue.

Here is a unit test to demonstrate the problem:

@Test
public void encode_withUrlInQueryParams() throws UnsupportedEncodingException {
    String base = "https://www.base.com";
    String first = "https://www.first.com?time_stamp=2019-06-17+03%3A48%3A56&address=25+Downing+Street";
    String second = "https://www.second.com?address=25%20Downing%20Street";
    String encoded = UriComponentsBuilder.fromHttpUrl(base)
            .queryParam("first", first)
            .queryParam("second", second)
            .build().toUriString();
    Map<String, String> values = UriComponentsBuilder.fromUriString(encoded).build().getQueryParams().toSingleValueMap();
    assertThat(URLDecoder.decode(values.get("second"), "UTF-8")).isEqualTo(second);
    assertThat(URLDecoder.decode(values.get("first"), "UTF-8")).isEqualTo(first);
}

This test works on 4.3.21.RELEASE, but fails on 5.1.7.RELEASE version:

Expected :"https://www.first.com?time_stamp=2019-06-17+03%3A48%3A56&address=25+Downing+Street"
Actual   :"https://www.first.com?time_stamp=2019-06-17 03%3A48%3A56&address=25 Downing Street"

In short: as Spring ignores + character while encoding, further decoding "breaks" parameter.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Jun 17, 2019
@stepio
Copy link
Author

stepio commented Jun 17, 2019

As I found, current behavior is a side-effect of fixing SPR-14828 (also known sa #19394) with f2e293a by @rstoyanchev.

So I'm not sure now if you consider this a bug or a feature. Maybe it's invalid like #23025.

@rstoyanchev
Copy link
Contributor

Indeed same comment as under #23025. Basically there are different (sometimes opposite) needs when it comes to encoding. For + in particular you may or may not want it to be treated as space. So we have different encoding modes. I suggest reading the URI Encoding section of the documentation. For your example you might need to add .encode() prior to building the UriComponents.

@rstoyanchev rstoyanchev added status: invalid An issue that we don't feel is valid and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Jun 19, 2019
@stepio
Copy link
Author

stepio commented Jun 19, 2019

Thanks for feedback!

For your example you might need to add .encode() prior to building the UriComponents.

Tried updating the test with the proposed suggestion:

    @Test
    public void encode_withUrlInQueryParams() throws UnsupportedEncodingException {
        String base = "https://www.base.com";
        String first = "https://www.first.com?time_stamp=2019-06-17+03%3A48%3A56&address=25+Downing+Street";
        String second = "https://www.second.com?address=25%20Downing%20Street";
        String encoded = UriComponentsBuilder.fromHttpUrl(base)
                .queryParam("first", first)
                .queryParam("second", second)
                .encode().build().toUriString();
        Map<String, String> values = UriComponentsBuilder.fromUriString(encoded).build().getQueryParams().toSingleValueMap();
        assertThat(URLDecoder.decode(values.get("second"), "UTF-8")).isEqualTo(second);
        assertThat(URLDecoder.decode(values.get("first"), "UTF-8")).isEqualTo(first);
    }

Test still fails.

Also tried applying crazy "encode everything" strategy:

    @Test
    public void encode_withUrlInQueryParams() throws UnsupportedEncodingException {
        String base = "https://www.base.com";
        String first = "https://www.first.com?time_stamp=2019-06-17+03%3A48%3A56&address=25+Downing+Street";
        String second = "https://www.second.com?address=25%20Downing%20Street";
        String encoded = UriComponentsBuilder.fromHttpUrl(base)
                .queryParam("first", first)
                .queryParam("second", second)
                .encode().build().encode().toUriString();
        Map<String, String> values = UriComponentsBuilder.fromUriString(encoded).build().getQueryParams().toSingleValueMap();
        assertThat(URLDecoder.decode(values.get("second"), "UTF-8")).isEqualTo(second);
        assertThat(URLDecoder.decode(values.get("first"), "UTF-8")).isEqualTo(first);
    }

Still failure.

So... As per my observation, Spring simply does not encode + whatever I do. I can live with this - now I simply encoded the necessary parameters myself and asked Spring to trust me (using .build(true)). But if it's still achievable with pure Spring, I'd appreciate if you could point me to the right solution using the above given test as an example.

@rstoyanchev
Copy link
Contributor

Pay attention to the documentation samples. Only URI variables are strongly encoded (i.e. all reserved characters). The literal parts of the URI template are simply checked for allowed characters per URI component.

@Test
public void encode_withUrlInQueryParams() throws UnsupportedEncodingException {
	String base = "https://www.base.com";
	String first = "https://www.first.com?time_stamp=2019-06-17+03%3A48%3A56&address=25+Downing+Street";
	String second = "https://www.second.com?address=25%20Downing%20Street";
	String encoded = UriComponentsBuilder.fromHttpUrl(base)
			.queryParam("first", "{first}")
			.queryParam("second", "{second}")
			.encode().buildAndExpand(first, second).toUriString();
	Map<String, String> values = UriComponentsBuilder.fromUriString(encoded).build().getQueryParams().toSingleValueMap();
	assertThat(URLDecoder.decode(values.get("second"), "UTF-8")).isEqualTo(second);
	assertThat(URLDecoder.decode(values.get("first"), "UTF-8")).isEqualTo(first);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: invalid An issue that we don't feel is valid
Projects
None yet
Development

No branches or pull requests

3 participants