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 @@ -36,9 +36,9 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.net.PercentEscaper;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
Expand Down Expand Up @@ -888,12 +888,14 @@ private String instantiate(Map<String, String> values, boolean allowPartial) {
|| nextNext.kind() != SegmentKind.END_BINDING;
restore(iterator, iterator.nextIndex() - 2);
if (!pathEscape) {
validatePathSegment(var, value);
result.append(encodeUrl(value));
} else {
// For a path wildcard or path of length greater 1, split the value and escape
// every sub-segment.
boolean first = true;
for (String subSeg : SLASH_SPLITTER.split(value)) {
validatePathSegment(var, subSeg);
if (!first) {
result.append('/');
}
Expand Down Expand Up @@ -1208,13 +1210,18 @@ private static List<Segment> parseComplexResourceId(String seg) {
// Helpers
// =======

private static final PercentEscaper URL_ESCAPER = new PercentEscaper("-_.~", false);

private static void validatePathSegment(String var, String segment) {
if (".".equals(segment) || "..".equals(segment)) {
throw new ValidationException(
"Value for %s must not contain segments that are exactly . or ..", var);
}
}

private String encodeUrl(String text) {
if (urlEncoding) {
try {
return URLEncoder.encode(text, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new ValidationException("UTF-8 encoding is not supported on this platform");
}
return URL_ESCAPER.escape(text);
} else {
// When encoding is disabled, we accept any character except '/'
final String INVALID_CHAR = "/";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,120 @@ void instantiateEscapeUnsafeChar() {
.isEqualTo("buckets/f%2Fo%2Fo/objects/b%2Fa%2Fr");
}

@Test
void instantiateRejectsDotInSingleWildcard() {
PathTemplate template =
PathTemplate.create("/compute/v1/projects/{project}/regions/{region}/addresses");
ValidationException dotException =
Assertions.assertThrows(
ValidationException.class,
() -> template.instantiate("project", "my-project", "region", "."));
Truth.assertThat(dotException.getMessage())
.isEqualTo("Value for region must not contain segments that are exactly . or ..");
}

@Test
void instantiateRejectsDoubleDotInSingleWildcard() {
PathTemplate template =
PathTemplate.create("/compute/v1/projects/{project}/regions/{region}/addresses");
ValidationException dotDotException =
Assertions.assertThrows(
ValidationException.class,
() -> template.instantiate("project", "my-project", "region", ".."));
Truth.assertThat(dotDotException.getMessage())
.isEqualTo("Value for region must not contain segments that are exactly . or ..");
}

@Test
void instantiateRejectsDotInMultiSegmentWildcard() {
PathTemplate template =
PathTemplate.create("/v2/{parent=projects/*/locations/*}/content:inspect");
ValidationException dotException =
Assertions.assertThrows(
ValidationException.class,
() -> template.instantiate("parent", "projects/my-project/locations/."));
Truth.assertThat(dotException.getMessage())
.isEqualTo("Value for parent must not contain segments that are exactly . or ..");
}

@Test
void instantiateRejectsDoubleDotInMultiSegmentWildcard() {
PathTemplate template =
PathTemplate.create("/v2/{parent=projects/*/locations/*}/content:inspect");
ValidationException dotDotException =
Assertions.assertThrows(
ValidationException.class,
() -> template.instantiate("parent", "projects/my-project/locations/.."));
Truth.assertThat(dotDotException.getMessage())
.isEqualTo("Value for parent must not contain segments that are exactly . or ..");
}

@Test
void instantiateRejectsDotInPathWildcard() {
PathTemplate template =
PathTemplate.create("/v3/{name=projects/*/monitoredResourceDescriptors/**}");
for (String invalidName :
new String[] {
"projects/my-project/monitoredResourceDescriptors/instance/my-instance/.",
"projects/my-project/monitoredResourceDescriptors/.",
"projects/my-project/monitoredResourceDescriptors/instance/./my-instance",
"projects/my-project/monitoredResourceDescriptors/ . "
}) {
ValidationException exception =
Assertions.assertThrows(
ValidationException.class, () -> template.instantiate("name", invalidName));
Truth.assertThat(exception.getMessage())
.isEqualTo("Value for name must not contain segments that are exactly . or ..");
}
}

@Test
void instantiateRejectsDoubleDotInPathWildcard() {
PathTemplate template =
PathTemplate.create("/v3/{name=projects/*/monitoredResourceDescriptors/**}");
for (String invalidName :
new String[] {
"projects/my-project/monitoredResourceDescriptors/instance/my-instance/..",
"projects/my-project/monitoredResourceDescriptors/..",
"projects/my-project/monitoredResourceDescriptors/instance/./my-instance/..",
"projects/my-project/monitoredResourceDescriptors/instance/../..",
"projects/my-project/monitoredResourceDescriptors/ .. "
}) {
ValidationException exception =
Assertions.assertThrows(
ValidationException.class, () -> template.instantiate("name", invalidName));
Truth.assertThat(exception.getMessage())
.isEqualTo("Value for name must not contain segments that are exactly . or ..");
}
}

@Test
void instantiateAllowsSafeDotsAndPercentEncodesReservedChars() {
PathTemplate template = PathTemplate.create("/v1/{x=projects/*/databases/*/documents/**}");
Truth.assertThat(
template.instantiate("x", "projects/sys-prod-123/databases/default/documents/my.txt"))
.isEqualTo("v1/projects/sys-prod-123/databases/default/documents/my.txt");
Truth.assertThat(
template.instantiate(
"x", "projects/sys-prod-123/databases/default/documents/my..file.txt"))
.isEqualTo("v1/projects/sys-prod-123/databases/default/documents/my..file.txt");
Truth.assertThat(
template.instantiate(
"x", "projects/sys-prod-123/databases/default/documents/doc?key=val"))
.isEqualTo("v1/projects/sys-prod-123/databases/default/documents/doc%3Fkey%3Dval");
Truth.assertThat(
template.instantiate("x", "projects/sys-prod-123/databases/default/documents/doc#frag"))
.isEqualTo("v1/projects/sys-prod-123/databases/default/documents/doc%23frag");
Truth.assertThat(
template.instantiate(
"x", "projects/sys-prod-123/databases/default/documents/doc with space"))
.isEqualTo("v1/projects/sys-prod-123/databases/default/documents/doc%20with%20space");
Truth.assertThat(
template.instantiate(
"x", "projects/sys-prod-123/databases/default/documents/%2e%2e/escape-db"))
.isEqualTo("v1/projects/sys-prod-123/databases/default/documents/%252e%252e/escape-db");
}

@Test
void instantiateNotEscapeForUnboundedWildcard() {
PathTemplate template = PathTemplate.create("buckets/*/objects/**");
Expand Down
Loading