Skip to content

Commit c17e0cc

Browse files
authored
fix: ensure partially encoded path parameters are fully URL-encoded (#1762)
Previously, `encodeParam` / `encode_path_param` checked whether `decode(value) === value` before URL-encoding. If a parameter value contained both already percent-encoded sequences (such as `%20`) and unencoded reserved characters (such as `+` or `?`), the equality check evaluated to `false` and skipped encoding the remaining reserved characters. This change normalizes the input by decoding first and always URL-encoding the decoded result.
1 parent b5f4b3d commit c17e0cc

8 files changed

Lines changed: 23 additions & 16 deletions

File tree

‎csharp/rtl.Tests/SdkUtilsTests.cs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ public void EncodeParamTest()
147147
// Assert.Equal("2020-01-01T14%3A48%3A00.000Z", Transport.EncodeParam(date));
148148
Assert.Equal("foo%2Fbar", SdkUtils.EncodeParam("foo%2Fbar"));
149149
Assert.Equal("foo%2Fbar", SdkUtils.EncodeParam("foo/bar"));
150+
Assert.Equal("foo%2Fbar%3Fbaz+qux", SdkUtils.EncodeParam("foo/bar?baz%20qux"));
150151
var actual = SdkUtils.EncodeParam(true);
151152
Assert.Equal("true", actual);
152153
actual = SdkUtils.EncodeParam(2.3);

‎csharp/rtl/SdkUtils.cs‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,7 @@ public static string EncodeParam(object value)
114114
}
115115

116116
var decoded = WebUtility.UrlDecode(encoded);
117-
if (encoded == decoded)
118-
{
119-
encoded = WebUtility.UrlEncode(encoded);
120-
}
117+
encoded = WebUtility.UrlEncode(decoded);
121118

122119
return encoded;
123120
}

‎kotlin/src/main/com/looker/rtl/Transport.kt‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -225,9 +225,7 @@ fun encodeParam(value: Any?): String {
225225
}
226226
try {
227227
val decoded = URLDecoder.decode(encoded, utf8)
228-
if (encoded == decoded) {
229-
encoded = URLEncoder.encode(encoded, utf8)
230-
}
228+
encoded = URLEncoder.encode(decoded, utf8)
231229
} catch (e: IllegalArgumentException) {
232230
encoded = URLEncoder.encode(encoded, utf8)
233231
} catch (e: Exception) {

‎kotlin/src/test/TestTransport.kt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ class TestTransport {
8080
assertEquals("2020-01-01T14%3A48%3A00.000Z", encodeParam(today))
8181
assertEquals("foo%2Fbar", encodeParam("foo%2Fbar"))
8282
assertEquals("foo%2Fbar", encodeParam("foo/bar"))
83+
assertEquals("foo%2Fbar%3Fbaz+qux", encodeParam("foo/bar?baz%20qux"))
8384
assertEquals("true", encodeParam(true))
8485
assertEquals("2.3", encodeParam(2.3))
8586
}

‎packages/sdk-rtl/src/transport.spec.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,11 @@ describe('Transport', () => {
9191
expect(encodeParam(today)).toEqual('2020-01-01T14%3A48%3A00.000Z');
9292
expect(encodeParam('foo%2Fbar')).toEqual('foo%2Fbar');
9393
expect(encodeParam('foo/bar')).toEqual('foo%2Fbar');
94+
expect(encodeParam('foo/bar?baz%20qux')).toEqual('foo%2Fbar%3Fbaz%20qux');
9495
expect(encodeParam(true)).toEqual('true');
9596
expect(encodeParam(2.3)).toEqual('2.3');
9697
expect(encodeParam({ created_date: 'this year to second' })).toEqual(
97-
'{"created_date":"this year to second"}'
98+
'%7B%22created_date%22%3A%22this%20year%20to%20second%22%7D'
9899
);
99100
});
100101
});

‎packages/sdk-rtl/src/transport.ts‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -603,13 +603,11 @@ export function encodeParam(value: any) {
603603
// decodeURIComponent throws URIError if there is a % character
604604
// without it being part of an encoded
605605
try {
606-
const decoded = decodeURIComponent(value);
607-
if (value === decoded) {
608-
encoded = encodeURIComponent(value);
609-
}
606+
const decoded = decodeURIComponent(encoded);
607+
encoded = encodeURIComponent(decoded);
610608
} catch (e) {
611609
if (e instanceof URIError) {
612-
encoded = encodeURIComponent(value);
610+
encoded = encodeURIComponent(encoded);
613611
} else {
614612
throw e;
615613
}

‎python/looker_sdk/rtl/api_methods.py‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,7 @@ def _convert_query_params(
133133

134134
@staticmethod
135135
def encode_path_param(value: str) -> str:
136-
if value == urllib.parse.unquote(value):
137-
value = urllib.parse.quote(value, safe="")
138-
return value
136+
return urllib.parse.quote(urllib.parse.unquote(value), safe="")
139137

140138
def get(
141139
self,

‎python/tests/rtl/test_api_methods.py‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,3 +214,16 @@ def test_api_versioned_url_is_built_properly(
214214
api: api_methods.APIMethods, method_path: str, expected_url: str
215215
):
216216
assert api._path(method_path).endswith(expected_url)
217+
218+
219+
@pytest.mark.parametrize(
220+
"value, expected",
221+
[
222+
("foo/bar", "foo%2Fbar"),
223+
("foo%2Fbar", "foo%2Fbar"),
224+
("foo/bar?baz%20qux", "foo%2Fbar%3Fbaz%20qux"),
225+
("foo/bar%20baz", "foo%2Fbar%20baz"),
226+
],
227+
)
228+
def test_encode_path_param(api: api_methods.APIMethods, value: str, expected: str):
229+
assert api.encode_path_param(value) == expected

0 commit comments

Comments
 (0)