Skip to content
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

Validate Accept and Content-Type header for compatible API #54103

Merged
merged 61 commits into from
Apr 20, 2020
Merged
Show file tree
Hide file tree
Changes from 55 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
a4f80ca
rest index action - 283 nofix
pgomulka Mar 5, 2020
c99b818
Index and Get and Infra
pgomulka Mar 6, 2020
c9c0e55
minor tests
pgomulka Mar 6, 2020
258542e
compile fixees
pgomulka Mar 6, 2020
548fbd9
Merge branch 'compat_rest_api' into compat/type-index-get
pgomulka Mar 9, 2020
0dbea8a
disable testing conventions
pgomulka Mar 9, 2020
4cf6bc1
assertions and todo for header fix
pgomulka Mar 10, 2020
3ac22b1
more tests and cleanup
pgomulka Mar 10, 2020
38721fe
Merge remote-tracking branch 'upstream/compat_rest_api' into compat/t…
jakelandis Mar 11, 2020
f2db19f
introduce a module to house the REST code
jakelandis Mar 11, 2020
b83b4ce
fix preCommit
jakelandis Mar 11, 2020
cf1d340
Merge pull request #18 from jakelandis/introduce_module
pgomulka Mar 11, 2020
5c4a02d
move restindex compatible handlers to rest-compatibility module. 228 …
pgomulka Mar 11, 2020
0ef7e18
moving test classes and compat related code to separate v7 module
pgomulka Mar 11, 2020
835ce56
test class rename and return 400 when compatible header not present
pgomulka Mar 13, 2020
f440231
clean up deprecation warnings and remove use of consumers
pgomulka Mar 13, 2020
84f1dde
v7 compatible actions warnings tests
pgomulka Mar 13, 2020
d106d1b
rename tests and enable them
pgomulka Mar 16, 2020
cf61bbd
rename isV7Compatible method
pgomulka Mar 16, 2020
f00f438
checkstyle
pgomulka Mar 16, 2020
ae1799f
Merge branch 'master' into compat/type-index-get
pgomulka Mar 16, 2020
a0fce89
Revert "Merge branch 'master' into compat/type-index-get"
pgomulka Mar 16, 2020
7e55744
Merge branch 'compat_rest_api' into compat/type-index-get
pgomulka Mar 16, 2020
00fe62d
Revert "Revert "Merge branch 'master' into compat/type-index-get""
pgomulka Mar 16, 2020
a6f0b9a
use locale with toLowerCase
pgomulka Mar 17, 2020
4eff534
spotless
pgomulka Mar 17, 2020
2d4161c
imports and disable integ tests as there are none
pgomulka Mar 17, 2020
6830f97
fix tests
pgomulka Mar 17, 2020
587f84d
Use content-type header with compatible api
pgomulka Mar 24, 2020
7b092f5
imports
pgomulka Mar 24, 2020
ef25920
Merge branch 'compat_rest_api' into compat/headers
pgomulka Mar 25, 2020
1c2fece
Merge branch 'compat_rest_api' into compat/headers
pgomulka Mar 30, 2020
2374c20
spotless
pgomulka Apr 1, 2020
eeae41f
fix compile test
pgomulka Apr 1, 2020
e7d9b29
checkstyle
pgomulka Apr 1, 2020
268bc1c
rename of mimetype header
pgomulka Apr 2, 2020
15f4be5
additional header test
pgomulka Apr 2, 2020
017c804
Merge branch 'compat_rest_api' into compat/headers
pgomulka Apr 3, 2020
3d975f3
initial headers validation
pgomulka Apr 6, 2020
09ef281
test cases
pgomulka Apr 6, 2020
bf09243
remove unused testcase
pgomulka Apr 6, 2020
30f1c40
precommit
pgomulka Apr 6, 2020
a013a03
headers validation
pgomulka Apr 7, 2020
6c28434
273 failing
pgomulka Apr 8, 2020
37d6426
*.*
pgomulka Apr 8, 2020
1d57fe7
precommit
pgomulka Apr 9, 2020
0662ef3
cleanup
pgomulka Apr 9, 2020
86f8bc0
cleanup
pgomulka Apr 9, 2020
b292080
Merge branch 'compat_rest_api' into compat/headers
pgomulka Apr 9, 2020
a4f472d
fix docs
pgomulka Apr 9, 2020
bb827bc
fix different mimetypes
pgomulka Apr 9, 2020
e9a4fbd
support text formats
pgomulka Apr 10, 2020
f876d46
do not validate media type correctness
pgomulka Apr 10, 2020
47fd5cb
do not allow empty accept header
pgomulka Apr 10, 2020
bcb11fd
allow empty accept header
pgomulka Apr 10, 2020
225a00b
exception propagation to user
pgomulka Apr 14, 2020
ebaa7c9
check which tests fail
pgomulka Apr 14, 2020
c96bd86
fix netty test
pgomulka Apr 15, 2020
10e8d1b
refactor test to be using hamcrest style
pgomulka Apr 15, 2020
3181daf
cleanup
pgomulka Apr 16, 2020
9bd396a
Merge branch 'compat_rest_api' into compat/headers
pgomulka Apr 16, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ public boolean wait(int durationInMs) throws GeneralSecurityException, Interrupt

protected void checkResource(SSLContext ssl) throws IOException {
final HttpURLConnection connection = buildConnection(ssl);
connection.setRequestProperty("Accept", "application/json");

connection.connect();
final Integer response = connection.getResponseCode();
if (validResponseCodes.contains(response)) {
Expand All @@ -144,6 +146,7 @@ HttpURLConnection buildConnection(SSLContext ssl) throws IOException {
final HttpURLConnection connection = (HttpURLConnection) this.url.openConnection();
configureSslContext(connection, ssl);
configureBasicAuth(connection);

connection.setRequestMethod("GET");
return connection;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ public XContent xContent() {
};

private static final Pattern COMPATIBLE_API_HEADER_PATTERN = Pattern.compile(
"application/(vnd.elasticsearch\\+)?([^;]+)(\\s*;\\s*compatible-with=(\\d+))?",
"(application|text)/(vnd.elasticsearch\\+)?([^;]+)(\\s*;\\s*compatible-with=(\\d+))?",
Pattern.CASE_INSENSITIVE);

/**
Expand All @@ -126,7 +126,9 @@ public XContent xContent() {
* also supports a wildcard accept for {@code application/*}. This method can be used to parse the {@code Accept} HTTP header or a
* format query string parameter. This method will return {@code null} if no match is found
*/
public static XContentType fromMediaTypeOrFormat(String mediaType) {
public static XContentType fromMediaTypeOrFormat(String mediaTypeHeaderValue) {
String mediaType = parseMediaType(mediaTypeHeaderValue);

if (mediaType == null) {
return null;
}
Expand All @@ -136,7 +138,7 @@ public static XContentType fromMediaTypeOrFormat(String mediaType) {
}
}
final String lowercaseMediaType = mediaType.toLowerCase(Locale.ROOT);
if (lowercaseMediaType.startsWith("application/*")) {
if (lowercaseMediaType.startsWith("application/*") || lowercaseMediaType.equals("*/*")) {
return JSON;
}

Expand Down Expand Up @@ -165,11 +167,12 @@ public static XContentType fromMediaType(String mediaTypeHeaderValue) {
return null;
}

//public scope needed for text formats hack
public static String parseMediaType(String mediaType) {
if (mediaType != null) {
Matcher matcher = COMPATIBLE_API_HEADER_PATTERN.matcher(mediaType);
if (matcher.find()) {
return "application/" + matcher.group(2).toLowerCase(Locale.ROOT);
return matcher.group(1) + "/" + matcher.group(3).toLowerCase(Locale.ROOT);
}
}

Expand All @@ -179,9 +182,9 @@ public static String parseMediaType(String mediaType) {
public static String parseVersion(String mediaType){
if(mediaType != null){
Matcher matcher = COMPATIBLE_API_HEADER_PATTERN.matcher(mediaType);
if (matcher.find() && "vnd.elasticsearch+".equalsIgnoreCase(matcher.group(1))) {
if (matcher.find() && "vnd.elasticsearch+".equalsIgnoreCase(matcher.group(2))) {

return matcher.group(4);
return matcher.group(5);
}
}
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,12 @@ public class RestGetActionV7 extends RestGetAction {
private static final DeprecationLogger deprecationLogger = new DeprecationLogger(LogManager.getLogger(RestGetAction.class));
static final String TYPES_DEPRECATION_MESSAGE = "[types removal] Specifying types in "
+ "document get requests is deprecated, use the /{index}/_doc/{id} endpoint instead.";

@Override
public String getName() {
return "document_get_action_v7";
}


@Override
public List<Route> routes() {
assert Version.CURRENT.major == 8 : "REST API compatibility for version 7 is only supported on version 8";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ public boolean compatibilityRequired() {
}

public static class CompatibleCreateHandler extends RestIndexAction.CreateHandler {

@Override
public String getName() {
return "document_create_action_v7";
Expand All @@ -100,15 +101,16 @@ public boolean compatibilityRequired() {
}

public static final class CompatibleAutoIdHandler extends RestIndexAction.AutoIdHandler {
@Override
public String getName() {
return "document_create_action_auto_id_v7";
}

public CompatibleAutoIdHandler(Supplier<DiscoveryNodes> nodesInCluster) {
super(nodesInCluster);
}

@Override
public String getName() {
return "document_create_action_auto_id_v7";
}

@Override
public List<Route> routes() {
return singletonList(new Route(POST, "/{index}/{type}"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.StreamSupport;

/**
Expand All @@ -43,7 +44,6 @@ protected AbstractCompatRestTest(@Name("yaml") ClientYamlTestCandidate testCandi
super(testCandidate);
}


private static final Logger staticLogger = LogManager.getLogger(AbstractCompatRestTest.class);

public static final String COMPAT_TESTS_PATH = "/rest-api-spec/test-compat";
Expand All @@ -66,38 +66,48 @@ public static Iterable<Object[]> createParameters() throws Exception {
});
finalTestCandidates.add(testCandidates.toArray());
}
localCandidates.keySet().forEach(lc -> finalTestCandidates.add(new Object[]{lc}));
localCandidates.keySet().forEach(lc -> finalTestCandidates.add(new Object[] { lc }));
return finalTestCandidates;
}


private static void mutateTestCandidate(ClientYamlTestCandidate testCandidate) {
testCandidate.getTestSection().getExecutableSections().stream().filter(s -> s instanceof DoSection).forEach(ds -> {
testCandidate.getSetupSection().getExecutableSections().stream().filter(s -> s instanceof DoSection).forEach(updateDoSection());
testCandidate.getTestSection().getExecutableSections().stream().filter(s -> s instanceof DoSection).forEach(updateDoSection());
}

private static Consumer<? super ExecutableSection> updateDoSection() {
return ds -> {
DoSection doSection = (DoSection) ds;
//TODO: be more selective here
// TODO: be more selective here
doSection.setIgnoreWarnings(true);

String compatibleHeader = createCompatibleHeader();
//TODO decide which one to use - Accept or Content-Type
doSection.getApiCallSection()
.addHeaders(Map.of(
CompatibleConstants.COMPATIBLE_HEADER, compatibleHeader,
"Content-Type", compatibleHeader
));
});
// for cat apis accept headers would break tests which expect txt response
if (doSection.getApiCallSection().getApi().startsWith("cat") == false) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have tests that need Accept header to be empty - expecting text response
maybe we should extend XContentType to text
but then again, we don't expect content-type to be text, and adding text there would allow this

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can punt on the cat api's for now, and this is sufficient for now. this code is temporary and i can address this later.

doSection.getApiCallSection()
.addHeaders(
Map.of(
CompatibleConstants.COMPATIBLE_ACCEPT_HEADER,
compatibleHeader,
CompatibleConstants.COMPATIBLE_CONTENT_TYPE_HEADER,
compatibleHeader
)
);
}

};
}

private static String createCompatibleHeader() {
return "application/vnd.elasticsearch+json;compatible-with=" + CompatibleConstants.COMPATIBLE_VERSION;
}


private static Map<ClientYamlTestCandidate, ClientYamlTestCandidate> getLocalCompatibilityTests() throws Exception {
Iterable<Object[]> candidates =
ESClientYamlSuiteTestCase.createParameters(ExecutableSection.XCONTENT_REGISTRY, COMPAT_TESTS_PATH);
Iterable<Object[]> candidates = ESClientYamlSuiteTestCase.createParameters(ExecutableSection.XCONTENT_REGISTRY, COMPAT_TESTS_PATH);
Map<ClientYamlTestCandidate, ClientYamlTestCandidate> localCompatibilityTests = new HashMap<>();
StreamSupport.stream(candidates.spliterator(), false)
.flatMap(Arrays::stream).forEach(o -> localCompatibilityTests.put((ClientYamlTestCandidate) o, (ClientYamlTestCandidate) o));
.flatMap(Arrays::stream)
.forEach(o -> localCompatibilityTests.put((ClientYamlTestCandidate) o, (ClientYamlTestCandidate) o));
return localCompatibilityTests;
}
}
4 changes: 3 additions & 1 deletion server/src/main/java/org/elasticsearch/Version.java
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ public class Version implements Comparable<Version>, ToXContentFragment {
public static final Version V_7_8_0 = new Version(7080099, org.apache.lucene.util.Version.LUCENE_8_5_0);
public static final Version V_8_0_0 = new Version(8000099, org.apache.lucene.util.Version.LUCENE_8_5_0);
public static final Version CURRENT = V_8_0_0;
public static final Version PREVIOUS = V_7_0_0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit ambiguous, and probably not a necessary as a static constant.

Can we add in a method like minimumRestCompatibilityVersion ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In doing so, can you please make the method static? It should only apply to the CURRENT version. The fact the other compatibility methods are on Version instances makes testing and modifying the rules difficult, even though in practice the CURRENT version's compatibility is all we need, and the others are just artificial for testing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree, the method should be static


private static final ImmutableOpenIntMap<Version> idToVersion;

Expand All @@ -89,7 +90,7 @@ public class Version implements Comparable<Version>, ToXContentFragment {
for (final Field declaredField : Version.class.getFields()) {
if (declaredField.getType().equals(Version.class)) {
final String fieldName = declaredField.getName();
if (fieldName.equals("CURRENT") || fieldName.equals("V_EMPTY")) {
if (fieldName.equals("CURRENT") || fieldName.equals("V_EMPTY") || fieldName.equals("PREVIOUS")) {
continue;
}
assert fieldName.matches("V_\\d+_\\d+_\\d+")
Expand Down Expand Up @@ -402,6 +403,7 @@ public static List<Version> getDeclaredVersions(final Class<?> versionClass) {
}
switch (field.getName()) {
case "CURRENT":
case "PREVIOUS":
case "V_EMPTY":
continue;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,11 @@ private void handleIncomingRequest(final HttpRequest httpRequest, final HttpChan
} catch (final RestRequest.BadParameterException e) {
badRequestCause = ExceptionsHelper.useOrSuppress(badRequestCause, e);
innerRestRequest = RestRequest.requestWithoutParameters(xContentRegistry, httpRequest, httpChannel);
} catch (final RestRequest.CompatibleApiHeadersCombinationException e){
badRequestCause = ExceptionsHelper.useOrSuppress(badRequestCause, e);
//todo // tempting to just rethrow. removing content type is just making this less obvious
throw e;
// innerRestRequest = requestWithoutContentTypeHeader(httpRequest, httpChannel, badRequestCause);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you clean up the this todo abit ? Not sure I follow.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just throwing an exception was helping checking the logs, but indeed was breaking the channel creation and there was no response sent back..
will fix

}
restRequest = innerRestRequest;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ public class CompatibleConstants {
/**
* TODO revisit when https://github.com/elastic/elasticsearch/issues/52370 is resolved
*/
public static final String COMPATIBLE_HEADER = "Accept";
public static final String COMPATIBLE_ACCEPT_HEADER = "Accept";
public static final String COMPATIBLE_CONTENT_TYPE_HEADER = "Content-Type";
public static final String COMPATIBLE_PARAMS_KEY = "Compatible-With";
public static final String COMPATIBLE_VERSION = "" + (Version.CURRENT.major - 1);
public static final String COMPATIBLE_VERSION = String.valueOf(Version.CURRENT.major - 1);

}
96 changes: 84 additions & 12 deletions server/src/main/java/org/elasticsearch/rest/RestRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import org.apache.lucene.util.SetOnce;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.Version;
import org.elasticsearch.common.Booleans;
import org.elasticsearch.common.CheckedConsumer;
import org.elasticsearch.common.Nullable;
Expand All @@ -45,6 +46,7 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
Expand Down Expand Up @@ -132,29 +134,91 @@ void ensureSafeBuffers() {
public static RestRequest request(NamedXContentRegistry xContentRegistry, HttpRequest httpRequest, HttpChannel httpChannel) {
Map<String, String> params = params(httpRequest.uri());
String path = path(httpRequest.uri());
RestRequest restRequest = new RestRequest(xContentRegistry, params, path, httpRequest.getHeaders(), httpRequest, httpChannel,
return new RestRequest(xContentRegistry, params, path, httpRequest.getHeaders(), httpRequest, httpChannel,
requestIdGenerator.incrementAndGet());
return restRequest;
}

private void addCompatibleParameter() {
if (isRequestCompatible()) {
String compatibleVersion = XContentType.parseVersion(header(CompatibleConstants.COMPATIBLE_HEADER));
params().put(CompatibleConstants.COMPATIBLE_PARAMS_KEY, compatibleVersion);
Version compatible = compatibleWithVersion();
if (Version.PREVIOUS.equals(compatible)) {
params().put(CompatibleConstants.COMPATIBLE_PARAMS_KEY, String.valueOf(compatible.major));
//use it so it won't fail request validation with unused parameter
param(CompatibleConstants.COMPATIBLE_PARAMS_KEY);
}
}

private boolean isRequestCompatible() {
return isHeaderCompatible(header(CompatibleConstants.COMPATIBLE_HEADER));
}
private Version compatibleWithVersion() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be greatly simplified and read easier if this returns a boolean and only concerns itself with the version (not the supported media types (that can happen later if need be)).

I think something like this below the same level of validation.

    private void addCompatibleParameter() {
        if (isRequestingCompatibility()) {
            params().put(CompatibleConstants.COMPATIBLE_PARAMS_KEY, String.valueOf(Version.CURRENT.major - 1));
            param(CompatibleConstants.COMPATIBLE_PARAMS_KEY);
        }
    }

    private boolean isRequestingCompatibility() {
        String acceptHeader = header(CompatibleConstants.COMPATIBLE_ACCEPT_HEADER);
        String aVersion = XContentType.parseVersion(acceptHeader);
        byte acceptVersion = aVersion == null ? Version.CURRENT.major : Integer.valueOf(aVersion).byteValue();
        String contentTypeHeader = header(CompatibleConstants.COMPATIBLE_CONTENT_TYPE_HEADER);
        String cVersion = XContentType.parseVersion(contentTypeHeader);
        byte contentTypeVersion = cVersion == null ? Version.CURRENT.major : Integer.valueOf(cVersion).byteValue();

        if(Version.CURRENT.major < acceptVersion || Version.CURRENT.major - acceptVersion > 1 ){
            throw new IllegalStateException("something about only 1 major version support");
        }

        if (hasContent()) {
            if(Version.CURRENT.major < contentTypeVersion || Version.CURRENT.major - contentTypeVersion > 1 ){
                throw new IllegalStateException("something about only 1 major version support");
            }

            if (contentTypeVersion != acceptVersion) {
                throw new IllegalStateException("something about needing to match");
            }
        }

        return contentTypeVersion < Version.CURRENT.major || acceptVersion < Version.CURRENT.major;
    }

String currentVersion = String.valueOf(Version.CURRENT.major);
String previousVersion = String.valueOf(Version.CURRENT.major - 1);

private boolean isHeaderCompatible(String headerValue) {
String version = XContentType.parseVersion(headerValue);
return CompatibleConstants.COMPATIBLE_VERSION.equals(version);
}
String acceptHeader = header(CompatibleConstants.COMPATIBLE_ACCEPT_HEADER);
String acceptVersion = XContentType.parseVersion(acceptHeader);
String contentTypeHeader = header(CompatibleConstants.COMPATIBLE_CONTENT_TYPE_HEADER);
String contentTypeVersion = XContentType.parseVersion(contentTypeHeader);

//TODO not sure about this one as this does not cover text formats
boolean isSupportedMediaTypeAccept = acceptHeader == null || XContentType.parseMediaType(acceptHeader) != null;

boolean isSupportedMediaTypeContentType = contentTypeHeader == null || XContentType.parseMediaType(contentTypeHeader) != null;

if (hasContent()) {
//both headers versioned
if (acceptVersion != null && contentTypeVersion != null) {
// both Accept and Content-Type are versioned and set to a previous version
if (previousVersion.equals(acceptVersion) && previousVersion.equals(contentTypeVersion)) {
return Version.PREVIOUS;
}
// both Accept and Content-Type are versioned to a current version
if (currentVersion.equals(acceptVersion) && currentVersion.equals(contentTypeVersion)) {
return Version.CURRENT;
}
// both headers are versioned but set to incorrect version
throw new CompatibleApiHeadersCombinationException(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Throwing the exception here results in an abrupt termination of the connection (no error code or proper response). Not sure the fix, but we should add a REST test to ensure the error code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that we allow RestRequest to be in an incorrect state. When creating a request and it fails due to Content-Type validation https://github.com/elastic/elasticsearch/blob/master/server/src/main/java/org/elasticsearch/rest/RestRequest.java#L93 or parameter validation https://github.com/elastic/elasticsearch/blob/master/server/src/main/java/org/elasticsearch/rest/RestRequest.java#L145
we try create again without either content-type or without parameters.

This makes it harder to create a validation of headers.. we would need to allow to have an incorrect state that would be used for channel creation in order to response back..

String.format(Locale.ROOT, "Request with a body and incompatible Accept and Content-Type header values. " +
"Accept=%s Content-Type=%s hasContent=%b %s %s %s", acceptHeader,
contentTypeHeader, hasContent(), path(), params.toString(), method().toString()));
}
// Content-Type is versioned but accept is not present
if(previousVersion.equals(contentTypeVersion)
&& (acceptHeader == null || acceptHeader.equals("*/*") )) {
return Version.PREVIOUS;
}
// Content type is set (not verioned) but accept is not present. It will be defaulted to JSON
if(isSupportedMediaTypeContentType &&
(acceptHeader == null || acceptHeader.equals("*/*") )){//TODO when do we default this?
return Version.CURRENT;
}
// both Accept and Content-Type are not a compatible format, but are supported and not empty
if (isSupportedMediaTypeContentType && isSupportedMediaTypeAccept &&
acceptHeader != null && contentTypeHeader != null) {
return Version.CURRENT;
}
} else {
if (acceptVersion != null) {
//Accept header is versioned and set to previous
if (previousVersion.equals(acceptVersion)) {
return Version.PREVIOUS;
}
if (currentVersion.equals(acceptVersion)) {
return Version.CURRENT;
}
// Accept header is versioned but set to incorrect version
throw new CompatibleApiHeadersCombinationException(
String.format(Locale.ROOT, "Request with a body and incompatible Accept and Content-Type header values. " +
"Accept=%s Content-Type=%s hasContent=%b %s %s %s", acceptHeader,
contentTypeHeader, hasContent(), path(), params.toString(), method().toString()));
}
//Accept header is not versioned but is supported
if (isSupportedMediaTypeAccept) {
return Version.CURRENT;
}
}

throw new CompatibleApiHeadersCombinationException(
String.format(Locale.ROOT, "Request with a body and incompatible Accept and Content-Type header values. " +
"Accept=%s Content-Type=%s hasContent=%b %s %s %s", acceptHeader,
contentTypeHeader, hasContent(), path(), params.toString(), method().toString()));
}

private static Map<String, String> params(final String uri) {
final Map<String, String> params = new HashMap<>();
Expand Down Expand Up @@ -559,4 +623,12 @@ public static class BadParameterException extends RuntimeException {

}

public static class CompatibleApiHeadersCombinationException extends RuntimeException {

CompatibleApiHeadersCombinationException(String cause) {
super(cause);
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,21 @@ public void testVersionParsing() {
assertThat(XContentType.parseVersion("APPLICATION/JSON"),
nullValue());
}

public void testVersionParsingOnText() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as per my comment https://github.com/elastic/elasticsearch/pull/54103/files#r409392721
maybe we should also extend parsing media types that are text? Should we unify this with SQL?
see RestSqlQueryAction at the moment sql has to use format parameter
cc @astefan

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we postpone that decision for now and work and in future work ensure that text based APIs are good ?

String version = String.valueOf(Math.abs(randomByte()));
assertThat(XContentType.parseVersion("text/vnd.elasticsearch+csv;compatible-with=" + version),
equalTo(version));
assertThat(XContentType.parseVersion("text/vnd.elasticsearch+text;compatible-with=" + version),
equalTo(version));
assertThat(XContentType.parseVersion("text/vnd.elasticsearch+tab-separated-values;compatible-with=" + version),
equalTo(version));
assertThat(XContentType.parseVersion("text/csv"),
nullValue());

assertThat(XContentType.parseVersion("TEXT/VND.ELASTICSEARCH+CSV;COMPATIBLE-WITH=" + version),
equalTo(version));
assertThat(XContentType.parseVersion("TEXT/csv"),
nullValue());
}
}
Loading