diff --git a/src/core/zowe/core_for_zowe_sdk/sdk_api.py b/src/core/zowe/core_for_zowe_sdk/sdk_api.py index 6761b93e..f5a49f85 100644 --- a/src/core/zowe/core_for_zowe_sdk/sdk_api.py +++ b/src/core/zowe/core_for_zowe_sdk/sdk_api.py @@ -149,9 +149,13 @@ def _encode_uri_path_for_zos(self, zos_uri_path: str) -> str: """ Encode a z/OS resource (dataset, job, or volser) path for the path component of a URI. - None of the documented z/OS resource naming special characters require encoding - to be processed successfully by z/OSMF. API-ML rejects a literal "#" with an - HTTP 400 error unless it is encoded, so it is the only character adjusted here. + Dot-segments are resolved against the service root so a caller-supplied name such as + "../../restjobs/jobs/OTHER" always resolves to a path under the intended resource. A + literal "?" is always percent-encoded so a name such as "X?fsname=Y" is parsed as a + single path value rather than a path followed by query parameters. None of the other + documented z/OS resource naming special characters require encoding to be processed + successfully by z/OSMF. API-ML rejects a literal "#" with an HTTP 400 error unless it is + encoded, so it is also adjusted here when routed through API-ML. Parameters ---------- @@ -161,11 +165,15 @@ def _encode_uri_path_for_zos(self, zos_uri_path: str) -> str: Returns ------- str - The path, with "#" encoded when the session is routed through API-ML + The normalized path, with "?" always encoded and "#" encoded when the session is + routed through API-ML """ + # Normalizing against root collapses ".." segments without escaping the service path + normalized = posixpath.normpath("/" + zos_uri_path).lstrip("/") + encoded = normalized.replace("?", "%3F") if self._is_using_apiml(): - return zos_uri_path.replace("#", "%23") - return zos_uri_path + encoded = encoded.replace("#", "%23") + return encoded def _encode_uri_path_for_uss(self, uss_uri_path: str) -> str: """ diff --git a/tests/unit/core/test_sdk_api.py b/tests/unit/core/test_sdk_api.py index e1e29b28..40c57fee 100644 --- a/tests/unit/core/test_sdk_api.py +++ b/tests/unit/core/test_sdk_api.py @@ -135,6 +135,21 @@ def test_encode_uri_path_for_zos_encodes_hash_for_apiml(self): self.assertEqual(sdk_api._encode_uri_path_for_zos("MY.DS#NAME$HERE"), "MY.DS%23NAME$HERE") + def test_encode_uri_path_for_zos_encodes_question_mark(self): + """A literal '?' must be encoded so the name is parsed as a single path value, not a query string.""" + sdk_api = SdkApi(self.basic_props, self.default_url) + + self.assertEqual(sdk_api._encode_uri_path_for_zos("X?fsname=Y"), "X%3Ffsname=Y") + + def test_encode_uri_path_for_zos_resolves_dot_segments(self): + """Dot-segments must be resolved against the service root so the request always targets the intended resource.""" + sdk_api = SdkApi(self.basic_props, self.default_url) + + self.assertEqual( + sdk_api._encode_uri_path_for_zos("../../restjobs/jobs/OTHERJOB/JOB00001"), + "restjobs/jobs/OTHERJOB/JOB00001", + ) + def test_encode_uri_path_for_uss_normalizes_path(self): """USS paths should be normalized and stripped of their leading slash.""" sdk_api = SdkApi(self.basic_props, self.default_url)