Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -131,6 +131,13 @@ public class CloudRequestEngineDefault
private final CloudEvidenceKeyFilter evidenceKeyFilter =
new CloudEvidenceKeyFilter();

/**
* The prefixes after query, header and cookie, highest precedence
* first, as evidence.md lists them.
*/
private static final String[] OTHER_PREFIX_PRECEDENCE =
{ "server", "fiftyone", "location" };

/**
* Held while the discovery requests are made, so that concurrent first
* uses make them once.
Expand Down Expand Up @@ -469,9 +476,11 @@ private byte[] getContent(FlowData data) throws UnsupportedEncodingException {

/**
* The Evidence to send, keyed by the name the cloud service expects.
* Only Evidence that {@link #isSent(String)} allows is included, and
* where two prefixes give the same name the precedence order decides,
* query first, then header, then cookie, then any other prefix.
* Only Evidence that {@link #isSent(String)} allows is included. Keys
* ignore letter case, so one name under two prefixes is one field,
* sent with the name and value of the key that wins by the precedence
* in evidence.md, being query, header, cookie, server, fiftyone and
* location, then any other prefix alphabetically.
* Evidence with no prefix ranks with the other prefixes, unless the
* whole key is query, header or cookie, as {@link #keyPrefix(String)}
* gives the whole key as its prefix.
Expand All @@ -486,9 +495,12 @@ Map<String, Object> getFormData(FlowData flowData) {
evidence.put(entry.getKey(), entry.getValue());
}
}
Map<String, Object> formData = new HashMap<>();
Map<String, Object> formData =
new TreeMap<>(String.CASE_INSENSITIVE_ORDER);

// Add evidence in reverse alphabetical order, excluding special keys.
// Add the other evidence lowest precedence first, being other
// prefixes in reverse alphabetical order, then location, fiftyone
// and server.
addFormData(formData, evidence, getSelectedEvidence(evidence, "other"));
// Add cookie evidence.
addFormData(formData, evidence, getSelectedEvidence(evidence, "cookie"));
Expand Down Expand Up @@ -552,8 +564,16 @@ private void addFormData(
Map<String, Object> allEvidence,
Map<String, Object> evidence) {

List<String> evidenceKeys = Arrays.asList(evidence.keySet().toArray(new String[0]));
Collections.sort(evidenceKeys, Collections.reverseOrder());
// Lowest precedence first, so a later key replaces an earlier one.
// Other prefixes are compared as prefixes, in lower case, then the
// whole key, so that the order is the same whatever the map gives.
List<String> evidenceKeys = new ArrayList<>(evidence.keySet());
evidenceKeys.sort(Comparator
.comparingInt((String key) -> otherPrefixRank(keyPrefix(key)))
.thenComparing((String key) ->
keyPrefix(key).toLowerCase(Locale.ROOT))
.thenComparing((String key) -> key.toLowerCase(Locale.ROOT))
.reversed());

for (String evidenceKey : evidenceKeys) {
String prefix = keyPrefix(evidenceKey);
Expand All @@ -572,7 +592,9 @@ private void addFormData(
if (prefix.equalsIgnoreCase("query") == false) {
Map<String, Object> conflicts = new HashMap<>();
for (String key : allEvidence.keySet()) {
if (key.equals(evidenceKey) == false && key.contains(suffix)) {
if (key.equals(evidenceKey) == false &&
key.toLowerCase(Locale.ROOT).contains(
suffix.toLowerCase(Locale.ROOT))) {
conflicts.put(key, allEvidence.get(key));
}
}
Expand All @@ -592,12 +614,30 @@ private void addFormData(
conflictStr.toString());
logger.warn(warningMessage);
}
// Overwrite the existing queryParameter value.
// Replace the existing field, name and value, so the name
// sent is the winning key's.
formData.remove(suffix);
formData.put(suffix, evidence.get(evidenceKey));
}
}
}

/**
* The precedence of a prefix that is not query, header or cookie,
* lower first, being server, fiftyone and location as evidence.md
* lists them, then any other prefix.
* @param prefix the prefix
* @return the rank, where a lower rank wins
*/
private static int otherPrefixRank(String prefix) {
for (int i = 0; i < OTHER_PREFIX_PRECEDENCE.length; i++) {
if (OTHER_PREFIX_PRECEDENCE[i].equalsIgnoreCase(prefix)) {
return i;
}
}
return OTHER_PREFIX_PRECEDENCE.length;
}

/**
* Get evidence with specified prefix.
* @param evidence the Evidence being sent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
Expand Down Expand Up @@ -539,6 +540,62 @@ public void getFormData(String name, Map<String, Object> evidence, String expect
}
}

private static Stream<Arguments> oneNameInTwoCasesArgs() {
return Stream.of(
Arguments.of("header.User-Agent", "query.user-agent", "user-agent", "B"),
Arguments.of("query.User-Agent", "header.user-agent", "User-Agent", "A"),
Arguments.of("cookie.X-Thing", "header.x-thing", "x-thing", "B"),
Arguments.of("location.Value", "server.value", "value", "B"),
Arguments.of("location.Value", "fiftyone.value", "value", "B"),
Arguments.of("fiftyone.Value", "server.value", "value", "B"),
Arguments.of("server.Value", "a.value", "Value", "A"),
Arguments.of("b.Value", "a.value", "value", "B"),
Arguments.of("ab-c.Value", "ab.value", "value", "B"),
Arguments.of("a.Value", "_x.value", "value", "B"));
}

/**
* Evidence keys ignore letter case, so one name under two prefixes in
* two letter cases is one field. It is sent once, with the name and
* value of the key that wins by the precedence in evidence.md, being
* query, header, cookie, server, fiftyone and location, then any other
* prefix alphabetically. Two other prefixes are compared as prefixes,
* in lower case, so ab ranks before ab-c and _x before a.
*/
@ParameterizedTest(name = "{0} and {1}")
@MethodSource("oneNameInTwoCasesArgs")
public void getFormData_OneNameInTwoCases_SentOnceByPrecedence(
String first,
String second,
String expectedName,
String expectedValue) throws Exception {
acceptEvidence(Arrays.asList(first, second));
configureMockedClient();
CloudRequestEngineDefault engine = (CloudRequestEngineDefault)
new CloudRequestEngineBuilder(loggerFactory, httpClient)
.setResourceKey("resourcekey")
.build();

try (Pipeline pipeline = new PipelineBuilder(loggerFactory)
.addFlowElement(engine)
.build();
FlowData data = pipeline.createFlowData()) {
data.addEvidence(first, "A");
data.addEvidence(second, "B");

Map<String, Object> result = engine.getFormData(data);
List<String> names = new ArrayList<>();
for (String key : result.keySet()) {
if (key.equalsIgnoreCase(expectedName)) {
names.add(key);
}
}
assertEquals(Collections.singletonList(expectedName), names,
"The name must be sent once: " + result);
assertEquals(expectedValue, result.get(expectedName));
}
}

/**
* The web integration adds the whole web request as server.http-request
* for usage sharing to read. It holds every header and cookie, so it is
Expand Down
Loading