Skip to content

Commit

Permalink
HLRC: Fix '+' Not Correctly Encoded in GET Req. (elastic#33164)
Browse files Browse the repository at this point in the history
* HLRC: Fix '+' Not Correctly Encoded in GET Req.

* Encode `+` correctly as `%2B` in URL paths
* Keep encoding `+` as space in URL parameters
* Closes elastic#33077
  • Loading branch information
original-brownbear committed Jul 15, 2019
1 parent fc6a31e commit 648d312
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1020,6 +1020,24 @@ public void testParamsEncode() throws IOException {
}
}

public void testGetIdWithPlusSign() throws Exception {
String id = "id+id";
{
IndexRequest indexRequest = new IndexRequest("index").id(id);
indexRequest.source("field", "value");
IndexResponse indexResponse = highLevelClient().index(indexRequest, RequestOptions.DEFAULT);
assertEquals("index", indexResponse.getIndex());
assertEquals(id, indexResponse.getId());
}
{
GetRequest getRequest = new GetRequest("index").id(id);
GetResponse getResponse = highLevelClient().get(getRequest, RequestOptions.DEFAULT);
assertTrue(getResponse.isExists());
assertEquals("index", getResponse.getIndex());
assertEquals(id, getResponse.getId());
}
}

// Not entirely sure if _termvectors belongs to CRUD, and in the absence of a better place, will have it here
public void testTermvectors() throws IOException {
final String sourceIndex = "index1";
Expand Down
12 changes: 12 additions & 0 deletions docs/reference/migration/migrate_7_4.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,15 @@ breaking change was made necessary by
https://aws.amazon.com/blogs/aws/amazon-s3-path-deprecation-plan-the-rest-of-the-story/[AWS's
announcement] that the path-style access pattern is deprecated and will be
unsupported on buckets created after September 30th 2020.

[float]
[[breaking_80_http_changes]]
=== HTTP changes

[float]
==== Changes to Encoding Plus Signs in URLs

Starting in version 7.4, a `+` in a URL will be encoded as `%2B` by all REST API functionality. Prior versions handled a `+` as a single space.
If your application requires handling `+` as a single space you can return to the old behaviour by setting the system property
`es.rest.url_plus_as_space` to `true`. Note that this behaviour is deprecated and setting this system property to `true` will cease
to be supported in version 8.
56 changes: 32 additions & 24 deletions server/src/main/java/org/elasticsearch/rest/RestUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

package org.elasticsearch.rest;

import org.elasticsearch.common.Booleans;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.path.PathTrie;

Expand All @@ -30,6 +31,11 @@

public class RestUtils {

/**
* Sets whether we decode a '+' in an url as a space or not.
*/
private static final boolean DECODE_PLUS_AS_SPACE = Booleans.parseBoolean(System.getProperty("es.rest.url_plus_as_space", "false"));

public static final PathTrie.Decoder REST_DECODER = new PathTrie.Decoder() {
@Override
public String decode(String value) {
Expand All @@ -55,17 +61,17 @@ public static void decodeQueryString(String s, int fromIndex, Map<String, String
c = s.charAt(i);
if (c == '=' && name == null) {
if (pos != i) {
name = decodeComponent(s.substring(pos, i));
name = decodeQueryStringParam(s.substring(pos, i));
}
pos = i + 1;
} else if (c == '&' || c == ';') {
if (name == null && pos != i) {
// We haven't seen an `=' so far but moved forward.
// Must be a param of the form '&a&' so add it with
// an empty value.
addParam(params, decodeComponent(s.substring(pos, i)), "");
addParam(params, decodeQueryStringParam(s.substring(pos, i)), "");
} else if (name != null) {
addParam(params, name, decodeComponent(s.substring(pos, i)));
addParam(params, name, decodeQueryStringParam(s.substring(pos, i)));
name = null;
}
pos = i + 1;
Expand All @@ -74,23 +80,27 @@ public static void decodeQueryString(String s, int fromIndex, Map<String, String

if (pos != i) { // Are there characters we haven't dealt with?
if (name == null) { // Yes and we haven't seen any `='.
addParam(params, decodeComponent(s.substring(pos, i)), "");
addParam(params, decodeQueryStringParam(s.substring(pos, i)), "");
} else { // Yes and this must be the last value.
addParam(params, name, decodeComponent(s.substring(pos, i)));
addParam(params, name, decodeQueryStringParam(s.substring(pos, i)));
}
} else if (name != null) { // Have we seen a name without value?
addParam(params, name, "");
}
}

private static String decodeQueryStringParam(final String s) {
return decodeComponent(s, StandardCharsets.UTF_8, true);
}

private static void addParam(Map<String, String> params, String name, String value) {
params.put(name, value);
}

/**
* Decodes a bit of an URL encoded by a browser.
* <p>
* This is equivalent to calling {@link #decodeComponent(String, Charset)}
* This is equivalent to calling {@link #decodeComponent(String, Charset, boolean)}
* with the UTF-8 charset (recommended to comply with RFC 3986, Section 2).
*
* @param s The string to decode (can be empty).
Expand All @@ -100,7 +110,7 @@ private static void addParam(Map<String, String> params, String name, String val
* escape sequence.
*/
public static String decodeComponent(final String s) {
return decodeComponent(s, StandardCharsets.UTF_8);
return decodeComponent(s, StandardCharsets.UTF_8, DECODE_PLUS_AS_SPACE);
}

/**
Expand All @@ -119,52 +129,50 @@ public static String decodeComponent(final String s) {
* Actually this function doesn't allocate any memory if there's nothing
* to decode, the argument itself is returned.
*
* @param s The string to decode (can be empty).
* @param charset The charset to use to decode the string (should really
* be {@link StandardCharsets#UTF_8}.
* @param s The string to decode (can be empty).
* @param charset The charset to use to decode the string (should really
* be {@link StandardCharsets#UTF_8}.
* @param plusAsSpace Whether to decode a {@code '+'} to a single space {@code ' '}
* @return The decoded string, or {@code s} if there's nothing to decode.
* If the string to decode is {@code null}, returns an empty string.
* @throws IllegalArgumentException if the string contains a malformed
* escape sequence.
*/
public static String decodeComponent(final String s, final Charset charset) {
private static String decodeComponent(final String s, final Charset charset, boolean plusAsSpace) {
if (s == null) {
return "";
}
final int size = s.length();
if (!decodingNeeded(s, size)) {
if (!decodingNeeded(s, size, plusAsSpace)) {
return s;
}
final byte[] buf = new byte[size];
int pos = decode(s, size, buf);
int pos = decode(s, size, buf, plusAsSpace);
return new String(buf, 0, pos, charset);
}

@SuppressWarnings("fallthrough")
private static boolean decodingNeeded(String s, int size) {
private static boolean decodingNeeded(String s, int size, boolean plusAsSpace) {
boolean decodingNeeded = false;
for (int i = 0; i < size; i++) {
final char c = s.charAt(i);
switch (c) {
case '%':
i++; // We can skip at least one char, e.g. `%%'.
// Fall through.
case '+':
decodingNeeded = true;
break;
if (c == '%') {
i++; // We can skip at least one char, e.g. `%%'.
decodingNeeded = true;
} else if (plusAsSpace && c == '+') {
decodingNeeded = true;
}
}
return decodingNeeded;
}

@SuppressWarnings("fallthrough")
private static int decode(String s, int size, byte[] buf) {
private static int decode(String s, int size, byte[] buf, boolean plusAsSpace) {
int pos = 0; // position in `buf'.
for (int i = 0; i < size; i++) {
char c = s.charAt(i);
switch (c) {
case '+':
buf[pos++] = ' '; // "+" -> " "
buf[pos++] = (byte) (plusAsSpace ? ' ' : '+'); // "+" -> " "
break;
case '%':
if (i == size - 1) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.test.rest.ESRestTestCase;
import org.elasticsearch.xpack.ml.MachineLearning;
import org.yaml.snakeyaml.util.UriEncoder;

import java.io.IOException;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -303,7 +303,7 @@ private Response createFarequoteJob(String jobId) throws Exception {
}
xContentBuilder.endObject();

Request request = new Request("PUT", MachineLearning.BASE_PATH + "anomaly_detectors/" + URLEncoder.encode(jobId, "UTF-8"));
Request request = new Request("PUT", MachineLearning.BASE_PATH + "anomaly_detectors/" + UriEncoder.encode(jobId));
request.setJsonEntity(Strings.toString(xContentBuilder));
return client().performRequest(request);
}
Expand Down

0 comments on commit 648d312

Please sign in to comment.