mirror of
https://github.com/honeymoose/OpenSearch.git
synced 2025-02-10 06:55:32 +00:00
Properly encode location header
Today when trying to encode the location header to ASCII, we rely on the Java URI API. This API requires a proper URI which blows up whenever the URI contains, for example, a space (which can happen if the type, ID, or routing contain a space). This commit addresses this issue by properly encoding the URI. Additionally, we remove the need to create a URI simplifying the code flow. Relates #23133
This commit is contained in:
parent
072748cd67
commit
9dff5e2af7
@ -38,8 +38,11 @@ import org.elasticsearch.index.shard.ShardId;
|
|||||||
import org.elasticsearch.rest.RestStatus;
|
import org.elasticsearch.rest.RestStatus;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
|
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
|
||||||
@ -201,31 +204,43 @@ public abstract class DocWriteResponse extends ReplicationResponse implements Wr
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the location of the written document as a string suitable for a {@code Location} header.
|
* Return the relative URI for the location of the document suitable for use in the {@code Location} header. The use of relative URIs is
|
||||||
* @param routing any routing used in the request. If null the location doesn't include routing information.
|
* permitted as of HTTP/1.1 (cf. https://tools.ietf.org/html/rfc7231#section-7.1.2).
|
||||||
*
|
*
|
||||||
|
* @param routing custom routing or {@code null} if custom routing is not used
|
||||||
|
* @return the relative URI for the location of the document
|
||||||
*/
|
*/
|
||||||
public String getLocation(@Nullable String routing) throws URISyntaxException {
|
public String getLocation(@Nullable String routing) {
|
||||||
// Absolute path for the location of the document. This should be allowed as of HTTP/1.1:
|
final String encodedIndex;
|
||||||
// https://tools.ietf.org/html/rfc7231#section-7.1.2
|
final String encodedType;
|
||||||
String index = getIndex();
|
final String encodedId;
|
||||||
String type = getType();
|
final String encodedRouting;
|
||||||
String id = getId();
|
try {
|
||||||
String routingStart = "?routing=";
|
// encode the path components separately otherwise the path separators will be encoded
|
||||||
int bufferSize = 3 + index.length() + type.length() + id.length();
|
encodedIndex = URLEncoder.encode(getIndex(), "UTF-8");
|
||||||
if (routing != null) {
|
encodedType = URLEncoder.encode(getType(), "UTF-8");
|
||||||
bufferSize += routingStart.length() + routing.length();
|
encodedId = URLEncoder.encode(getId(), "UTF-8");
|
||||||
|
encodedRouting = routing == null ? null : URLEncoder.encode(routing, "UTF-8");
|
||||||
|
} catch (final UnsupportedEncodingException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
}
|
}
|
||||||
StringBuilder location = new StringBuilder(bufferSize);
|
final String routingStart = "?routing=";
|
||||||
location.append('/').append(index);
|
final int bufferSizeExcludingRouting = 3 + encodedIndex.length() + encodedType.length() + encodedId.length();
|
||||||
location.append('/').append(type);
|
final int bufferSize;
|
||||||
location.append('/').append(id);
|
if (encodedRouting == null) {
|
||||||
if (routing != null) {
|
bufferSize = bufferSizeExcludingRouting;
|
||||||
location.append(routingStart).append(routing);
|
} else {
|
||||||
|
bufferSize = bufferSizeExcludingRouting + routingStart.length() + encodedRouting.length();
|
||||||
|
}
|
||||||
|
final StringBuilder location = new StringBuilder(bufferSize);
|
||||||
|
location.append('/').append(encodedIndex);
|
||||||
|
location.append('/').append(encodedType);
|
||||||
|
location.append('/').append(encodedId);
|
||||||
|
if (encodedRouting != null) {
|
||||||
|
location.append(routingStart).append(encodedRouting);
|
||||||
}
|
}
|
||||||
|
|
||||||
URI uri = new URI(location.toString());
|
return location.toString();
|
||||||
return uri.toASCIIString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -57,7 +57,7 @@ public class RestStatusToXContentListener<Response extends StatusToXContentObjec
|
|||||||
response.toXContent(builder, channel.request());
|
response.toXContent(builder, channel.request());
|
||||||
RestResponse restResponse = new BytesRestResponse(response.status(), builder);
|
RestResponse restResponse = new BytesRestResponse(response.status(), builder);
|
||||||
if (RestStatus.CREATED == restResponse.status()) {
|
if (RestStatus.CREATED == restResponse.status()) {
|
||||||
String location = extractLocation.apply(response);
|
final String location = extractLocation.apply(response);
|
||||||
if (location != null) {
|
if (location != null) {
|
||||||
restResponse.addHeader("Location", location);
|
restResponse.addHeader("Location", location);
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,6 @@ import org.elasticsearch.rest.action.RestActions;
|
|||||||
import org.elasticsearch.rest.action.RestStatusToXContentListener;
|
import org.elasticsearch.rest.action.RestStatusToXContentListener;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URISyntaxException;
|
|
||||||
|
|
||||||
import static org.elasticsearch.rest.RestRequest.Method.POST;
|
import static org.elasticsearch.rest.RestRequest.Method.POST;
|
||||||
import static org.elasticsearch.rest.RestRequest.Method.PUT;
|
import static org.elasticsearch.rest.RestRequest.Method.PUT;
|
||||||
@ -80,14 +79,7 @@ public class RestIndexAction extends BaseRestHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return channel ->
|
return channel ->
|
||||||
client.index(indexRequest, new RestStatusToXContentListener<>(channel, r -> {
|
client.index(indexRequest, new RestStatusToXContentListener<>(channel, r -> r.getLocation(indexRequest.routing())));
|
||||||
try {
|
|
||||||
return r.getLocation(indexRequest.routing());
|
|
||||||
} catch (URISyntaxException ex) {
|
|
||||||
logger.warn("Location string is not a valid URI.", ex);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,6 @@ import org.elasticsearch.rest.action.RestStatusToXContentListener;
|
|||||||
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
|
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URISyntaxException;
|
|
||||||
|
|
||||||
import static org.elasticsearch.rest.RestRequest.Method.POST;
|
import static org.elasticsearch.rest.RestRequest.Method.POST;
|
||||||
|
|
||||||
@ -98,13 +97,7 @@ public class RestUpdateAction extends BaseRestHandler {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return channel ->
|
return channel ->
|
||||||
client.update(updateRequest, new RestStatusToXContentListener<>(channel, r -> {
|
client.update(updateRequest, new RestStatusToXContentListener<>(channel, r -> r.getLocation(updateRequest.routing())));
|
||||||
try {
|
|
||||||
return r.getLocation(updateRequest.routing());
|
|
||||||
} catch (URISyntaxException ex) {
|
|
||||||
logger.warn("Location string is not a valid URI.", ex);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -30,55 +30,49 @@ import org.elasticsearch.index.shard.ShardId;
|
|||||||
import org.elasticsearch.test.ESTestCase;
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URISyntaxException;
|
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.hasEntry;
|
import static org.hamcrest.Matchers.hasEntry;
|
||||||
import static org.hamcrest.Matchers.hasKey;
|
import static org.hamcrest.Matchers.hasKey;
|
||||||
import static org.hamcrest.Matchers.not;
|
import static org.hamcrest.Matchers.not;
|
||||||
|
|
||||||
public class DocWriteResponseTests extends ESTestCase {
|
public class DocWriteResponseTests extends ESTestCase {
|
||||||
public void testGetLocation() throws URISyntaxException {
|
public void testGetLocation() {
|
||||||
DocWriteResponse response =
|
final DocWriteResponse response =
|
||||||
new DocWriteResponse(
|
new DocWriteResponse(
|
||||||
new ShardId("index", "uuid", 0),
|
new ShardId("index", "uuid", 0),
|
||||||
"type",
|
"type",
|
||||||
"id",
|
"id",
|
||||||
SequenceNumbersService.UNASSIGNED_SEQ_NO,
|
SequenceNumbersService.UNASSIGNED_SEQ_NO,
|
||||||
0,
|
0,
|
||||||
Result.CREATED) {
|
Result.CREATED) {};
|
||||||
// DocWriteResponse is abstract so we have to sneak a subclass in here to test it.
|
|
||||||
};
|
|
||||||
assertEquals("/index/type/id", response.getLocation(null));
|
assertEquals("/index/type/id", response.getLocation(null));
|
||||||
assertEquals("/index/type/id?routing=test_routing", response.getLocation("test_routing"));
|
assertEquals("/index/type/id?routing=test_routing", response.getLocation("test_routing"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testGetLocationNonAscii() throws URISyntaxException {
|
public void testGetLocationNonAscii() {
|
||||||
DocWriteResponse response =
|
final DocWriteResponse response =
|
||||||
new DocWriteResponse(
|
new DocWriteResponse(
|
||||||
new ShardId("index", "uuid", 0),
|
new ShardId("index", "uuid", 0),
|
||||||
"type",
|
"type",
|
||||||
"❤",
|
"❤",
|
||||||
SequenceNumbersService.UNASSIGNED_SEQ_NO,
|
SequenceNumbersService.UNASSIGNED_SEQ_NO,
|
||||||
0,
|
0,
|
||||||
Result.CREATED) {
|
Result.CREATED) {};
|
||||||
};
|
|
||||||
assertEquals("/index/type/%E2%9D%A4", response.getLocation(null));
|
assertEquals("/index/type/%E2%9D%A4", response.getLocation(null));
|
||||||
assertEquals("/index/type/%E2%9D%A4?routing=%C3%A4", response.getLocation("%C3%A4"));
|
assertEquals("/index/type/%E2%9D%A4?routing=%C3%A4", response.getLocation("ä"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testInvalidGetLocation() {
|
public void testGetLocationWithSpaces() {
|
||||||
String invalidPath = "!^*$(@!^!#@";
|
final DocWriteResponse response =
|
||||||
DocWriteResponse invalid =
|
new DocWriteResponse(
|
||||||
new DocWriteResponse(
|
new ShardId("index", "uuid", 0),
|
||||||
new ShardId("index", "uuid", 0),
|
"type",
|
||||||
"type",
|
"a b",
|
||||||
invalidPath,
|
SequenceNumbersService.UNASSIGNED_SEQ_NO,
|
||||||
SequenceNumbersService.UNASSIGNED_SEQ_NO,
|
0,
|
||||||
0,
|
Result.CREATED) {};
|
||||||
Result.CREATED) {
|
assertEquals("/index/type/a+b", response.getLocation(null));
|
||||||
};
|
assertEquals("/index/type/a+b?routing=c+d", response.getLocation("c d"));
|
||||||
Throwable exception = expectThrows(URISyntaxException.class, () -> invalid.getLocation(null));
|
|
||||||
assertTrue(exception.getMessage().contains(invalidPath));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
x
Reference in New Issue
Block a user