From 21d522147841c78aefb6f57d52137d9394272bf2 Mon Sep 17 00:00:00 2001 From: Blake Li Date: Thu, 3 Feb 2022 18:12:39 -0500 Subject: [PATCH 1/2] Support delimiters(_-.~) as start or end characters for a segment if the segment does not contain complex resource names. --- .../google/api/pathtemplate/PathTemplate.java | 18 ++++- .../api/pathtemplate/PathTemplateTest.java | 77 ++++++++++++++++++- 2 files changed, 89 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/google/api/pathtemplate/PathTemplate.java b/src/main/java/com/google/api/pathtemplate/PathTemplate.java index 4e4e770e6..1f57574b7 100644 --- a/src/main/java/com/google/api/pathtemplate/PathTemplate.java +++ b/src/main/java/com/google/api/pathtemplate/PathTemplate.java @@ -887,9 +887,7 @@ private static ImmutableList parseTemplate(String template) { boolean isLastSegment = (template.indexOf(seg) + seg.length()) == template.length(); boolean isCollectionWildcard = !isLastSegment && (seg.equals("-") || seg.equals("-}")); - if (!isCollectionWildcard - && (COMPLEX_DELIMITER_PATTERN.matcher(seg.substring(0, 1)).find() - || COMPLEX_DELIMITER_PATTERN.matcher(seg.substring(seg.length() - 1)).find())) { + if (!isCollectionWildcard && isSegmentBeginOrEndInvalid(seg)) { throw new ValidationException("parse error: invalid begin or end character in '%s'", seg); } // Disallow zero or multiple delimiters between variable names. @@ -1015,6 +1013,20 @@ private static ImmutableList parseTemplate(String template) { return builder.build(); } + private static boolean isSegmentBeginOrEndInvalid(String seg) { + if (seg.length() == 1 && COMPLEX_DELIMITER_PATTERN.matcher(seg).find()) { + return true; + } + //A segment like .{well}-{known} or {well}-{known}. is invalid. + //A segment like .well-known or .well-{known} will be considered a literal hence is valid + return (COMPLEX_DELIMITER_PATTERN.matcher(seg.substring(0, 1)).find() + && seg.length() > 1 + && seg.charAt(1) == '{') + || (COMPLEX_DELIMITER_PATTERN.matcher(seg.substring(seg.length() - 1)).find() + && seg.length() > 1 + && seg.charAt(seg.length() - 2) == '}'); + } + private static List parseComplexResourceId(String seg) { List segments = new ArrayList<>(); List separatorIndices = new ArrayList<>(); diff --git a/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java b/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java index 49b64e6d4..6e4f02557 100644 --- a/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java +++ b/src/test/java/com/google/api/pathtemplate/PathTemplateTest.java @@ -487,9 +487,6 @@ public void complexResourceBasicInvalidIds() { @Test public void complexResourceMultipleDelimiters() { thrown.expect(ValidationException.class); - PathTemplate.create("projects/*/zones/.-~{zone_a}"); - thrown.expectMessage( - String.format("parse error: invalid begin or end character in '%s'", ".-~{zone_a}")); PathTemplate.create("projects/*/zones/{zone_a}~.{zone_b}"); thrown.expectMessage( @@ -717,6 +714,80 @@ public void instantiateWithCustomVerbs() { Truth.assertThat(template.matches(templateInstance)).isTrue(); } + @Test + public void instantiateWithASegmentStartsWithADelimiter() { + PathTemplate pathTemplate = + PathTemplate.create( + "v1beta1/{parent=projects/*/locations/*/clusters/*}/.well-known/openid-configuration"); + String pattern = + "v1beta1/projects/abc/locations/def/clusters/yte/.well-known/openid-configuration"; + Truth.assertThat(pathTemplate.matches(pattern)).isTrue(); + } + + @Test + public void instantiateWithASegmentContainingComplexResourceNamesAndStartsWithADelimiter() { + thrown.expect(ValidationException.class); + PathTemplate.create( + "v1beta1/{parent=projects/*/locations/*/clusters/*}/.{well}-{known}/openid-configuration"); + thrown.expectMessage( + String.format("parse error: invalid begin or end character in '%s'", ".{well}-{known}")); + } + + @Test + public void + instantiateWithASegmentContainingNoComplexResourceNamesAndStartsWithMultipleDelimiters() { + PathTemplate pathTemplate = + PathTemplate.create( + "v1beta1/{parent=projects/*/locations/*/clusters/*}/.-~well-known/openid-configuration"); + String pattern = + "v1beta1/projects/abc/locations/def/clusters/yte/.-~well-known/openid-configuration"; + Truth.assertThat(pathTemplate.matches(pattern)).isTrue(); + } + + @Test + public void instantiateWithASegmentOnlyContainingOneDelimiter() { + thrown.expect(ValidationException.class); + PathTemplate.create("v1/publishers/{publisher}/books/."); + thrown.expectMessage(String.format("parse error: invalid begin or end character in '%s'", ".")); + } + + @Test + public void instantiateWithASegmentOnlyContainingOneCharacter() { + PathTemplate pathTemplate = PathTemplate.create("v1/publishers/{publisher}/books/a"); + String pattern = "v1/publishers/o'reilly/books/a"; + Truth.assertThat(pathTemplate.matches(pattern)).isTrue(); + } + + @Test + public void instantiateWithASegmentEndsWithADelimiter() { + PathTemplate pathTemplate = + PathTemplate.create( + "v1beta1/{parent=projects/*/locations/*/clusters/*}/well-known./openid-configuration"); + String pattern = + "v1beta1/projects/abc/locations/def/clusters/yte/well-known./openid-configuration"; + Truth.assertThat(pathTemplate.matches(pattern)).isTrue(); + } + + @Test + public void instantiateWithASegmentContainingComplexResourceNamesAndEndsWithADelimiter() { + thrown.expect(ValidationException.class); + PathTemplate.create( + "v1beta1/{parent=projects/*/locations/*/clusters/*}/{well}-{known}./openid-configuration"); + thrown.expectMessage( + String.format("parse error: invalid begin or end character in '%s'", "{well}-{known}.")); + } + + @Test + public void + instantiateWithASegmentContainingNoComplexResourceNamesAndEndsWithMultipleDelimiters() { + PathTemplate pathTemplate = + PathTemplate.create( + "v1beta1/{parent=projects/*/locations/*/clusters/*}/well-known.-~/openid-configuration"); + String pattern = + "v1beta1/projects/abc/locations/def/clusters/yte/well-known.-~/openid-configuration"; + Truth.assertThat(pathTemplate.matches(pattern)).isTrue(); + } + // Other // ===== From edff9a674285a4d9013bf7e70404ae20f8b2a2a7 Mon Sep 17 00:00:00 2001 From: Blake Li Date: Thu, 3 Feb 2022 20:51:51 -0500 Subject: [PATCH 2/2] Simplify validation logic. Add more comments. --- .../com/google/api/pathtemplate/PathTemplate.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/google/api/pathtemplate/PathTemplate.java b/src/main/java/com/google/api/pathtemplate/PathTemplate.java index 1f57574b7..4458e5ed6 100644 --- a/src/main/java/com/google/api/pathtemplate/PathTemplate.java +++ b/src/main/java/com/google/api/pathtemplate/PathTemplate.java @@ -1014,16 +1014,16 @@ private static ImmutableList parseTemplate(String template) { } private static boolean isSegmentBeginOrEndInvalid(String seg) { + // A segment is invalid if it contains only one character and the character is a delimiter if (seg.length() == 1 && COMPLEX_DELIMITER_PATTERN.matcher(seg).find()) { return true; } - //A segment like .{well}-{known} or {well}-{known}. is invalid. - //A segment like .well-known or .well-{known} will be considered a literal hence is valid - return (COMPLEX_DELIMITER_PATTERN.matcher(seg.substring(0, 1)).find() - && seg.length() > 1 - && seg.charAt(1) == '{') + // A segment can start with a delimiter, as long as there is no { right after it. + // A segment can end with a delimiter, as long as there is no } right before it. + // e.g. Invalid: .{well}-{known}, {well}-{known}- + // Valid: .well-known, .well-{known}, .-~{well-known}, these segments are all considered as literals for matching + return (COMPLEX_DELIMITER_PATTERN.matcher(seg.substring(0, 1)).find() && seg.charAt(1) == '{') || (COMPLEX_DELIMITER_PATTERN.matcher(seg.substring(seg.length() - 1)).find() - && seg.length() > 1 && seg.charAt(seg.length() - 2) == '}'); }