[PATCH 1/2] erofs-utils: lib: oci: fix a corner-case in `ocierofs_parse_ref()`

Yifan Zhao zhaoyifan28 at huawei.com
Sun Nov 30 21:42:56 AEDT 2025


Currently, `ocierofs_parse_ref()` fails to correctly parse OCI
reference strings of the form "localhost:5000/myapp:latest", as it
assumes a valid registry name must contain '.', which is not the case.

Let's also treat `ref_str` with a colon before slash (i.e., containing a
port number) as valid registry names.

This patch also adds unit tests for `ocierofs_parse_ref()`.

This patch also removes repeated codes in `ocierofs_parse_ref()`.

Signed-off-by: Yifan Zhao <zhaoyifan28 at huawei.com>
---
 lib/Makefile.am   |   9 +-
 lib/remotes/oci.c | 220 +++++++++++++++++++++++++++++++++++++++-------
 2 files changed, 194 insertions(+), 35 deletions(-)

diff --git a/lib/Makefile.am b/lib/Makefile.am
index 4d31f6a..1721039 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -95,8 +95,15 @@ liberofs_la_LDFLAGS += ${json_c_LIBS}
 liberofs_la_SOURCES += gzran.c
 
 if ENABLE_S3
-noinst_PROGRAMS  = s3erofs_test
+noinst_PROGRAMS = s3erofs_test
 s3erofs_test_SOURCES = remotes/s3.c
 s3erofs_test_CFLAGS = -Wall -I$(top_srcdir)/include ${libxml2_CFLAGS} ${openssl_CFLAGS} -DTEST
 s3erofs_test_LDADD = liberofs.la
 endif
+
+if ENABLE_OCI
+noinst_PROGRAMS = ocierofs_test
+ocierofs_test_SOURCES = remotes/oci.c
+ocierofs_test_CFLAGS = -Wall -I$(top_srcdir)/include ${json_c_CFLAGS} -DTEST
+ocierofs_test_LDADD = liberofs.la
+endif
diff --git a/lib/remotes/oci.c b/lib/remotes/oci.c
index ac8d495..c1d6cae 100644
--- a/lib/remotes/oci.c
+++ b/lib/remotes/oci.c
@@ -1038,7 +1038,9 @@ static int ocierofs_parse_ref(struct ocierofs_ctx *ctx, const char *ref_str)
 	slash = strchr(ref_str, '/');
 	if (slash) {
 		dot = strchr(ref_str, '.');
-		if (dot && dot < slash) {
+		colon = strchr(ref_str, ':');
+		/* a dot or colon before the slash indicating a registry */
+		if ((dot && dot < slash) || (colon && colon < slash)) {
 			len = slash - ref_str;
 			tmp = strndup(ref_str, len);
 			if (!tmp)
@@ -1057,48 +1059,32 @@ static int ocierofs_parse_ref(struct ocierofs_ctx *ctx, const char *ref_str)
 	if (colon) {
 		len = colon - repo_part;
 		tmp = strndup(repo_part, len);
-		if (!tmp)
-			return -ENOMEM;
+	} else {
+		tmp = strdup(repo_part);
+	}
+	if (!tmp)
+		return -ENOMEM;
 
-		if (!strchr(tmp, '/') &&
-		    (!strcmp(ctx->registry, DOCKER_API_REGISTRY) ||
-		     !strcmp(ctx->registry, DOCKER_REGISTRY))) {
-			char *full_repo;
+	if (!strchr(tmp, '/') &&
+	    (!strcmp(ctx->registry, DOCKER_API_REGISTRY) ||
+	     !strcmp(ctx->registry, DOCKER_REGISTRY))) {
+		char *full_repo;
 
-			if (asprintf(&full_repo, "library/%s", tmp) == -1) {
-				free(tmp);
-				return -ENOMEM;
-			}
+		if (asprintf(&full_repo, "library/%s", tmp) == -1) {
 			free(tmp);
-			tmp = full_repo;
+			return -ENOMEM;
 		}
-		free(ctx->repository);
-		ctx->repository = tmp;
+		free(tmp);
+		tmp = full_repo;
+	}
+	free(ctx->repository);
+	ctx->repository = tmp;
 
+	if (colon) {
 		free(ctx->tag);
 		ctx->tag = strdup(colon + 1);
 		if (!ctx->tag)
 			return -ENOMEM;
-	} else {
-		tmp = strdup(repo_part);
-		if (!tmp)
-			return -ENOMEM;
-
-		if (!strchr(tmp, '/') &&
-		    (!strcmp(ctx->registry, DOCKER_API_REGISTRY) ||
-		     !strcmp(ctx->registry, DOCKER_REGISTRY))) {
-
-			char *full_repo;
-
-			if (asprintf(&full_repo, "library/%s", tmp) == -1) {
-				free(tmp);
-				return -ENOMEM;
-			}
-			free(tmp);
-			tmp = full_repo;
-		}
-		free(ctx->repository);
-		ctx->repository = tmp;
 	}
 	return 0;
 }
@@ -1575,3 +1561,169 @@ int ocierofs_io_open(struct erofs_vfile *vfile, const struct ocierofs_config *cf
 	return -EOPNOTSUPP;
 }
 #endif
+
+#if defined(OCIEROFS_ENABLED) && defined(TEST)
+struct ocierofs_parse_ref_testcase {
+	const char *name;
+	const char *ref_str;
+	const char *expected_registry;
+	const char *expected_repository;
+	const char *expected_tag;
+};
+
+static bool run_ocierofs_parse_ref_test(const struct ocierofs_parse_ref_testcase *tc)
+{
+	struct ocierofs_ctx ctx = {};
+	int ret;
+
+	printf("Running test: %s\n", tc->name);
+
+	/* Initialize with default values */
+	ctx.registry = strdup(DOCKER_API_REGISTRY);
+	ctx.tag = strdup("latest");
+	if (!ctx.registry || !ctx.tag) {
+		printf("  FAILED: memory allocation error during setup\n");
+		free(ctx.registry);
+		free(ctx.tag);
+		return false;
+	}
+
+	ret = ocierofs_parse_ref(&ctx, tc->ref_str);
+	if (ret < 0) {
+		printf("  FAILED: ocierofs_parse_ref returned %d\n", ret);
+		goto cleanup;
+	}
+
+	if (tc->expected_registry && strcmp(ctx.registry, tc->expected_registry) != 0) {
+		printf("  FAILED: registry mismatch\n");
+		printf("    Expected: %s\n", tc->expected_registry);
+		printf("    Got:      %s\n", ctx.registry);
+		ret = -EINVAL;
+		goto cleanup;
+	}
+
+	if (tc->expected_repository && strcmp(ctx.repository, tc->expected_repository) != 0) {
+		printf("  FAILED: repository mismatch\n");
+		printf("    Expected: %s\n", tc->expected_repository);
+		printf("    Got:      %s\n", ctx.repository);
+		ret = -EINVAL;
+		goto cleanup;
+	}
+
+	if (tc->expected_tag && strcmp(ctx.tag, tc->expected_tag) != 0) {
+		printf("  FAILED: tag mismatch\n");
+		printf("    Expected: %s\n", tc->expected_tag);
+		printf("    Got:      %s\n", ctx.tag);
+		ret = -EINVAL;
+		goto cleanup;
+	}
+
+	printf("  PASSED\n");
+	printf("    Registry:   %s\n", ctx.registry);
+	printf("    Repository: %s\n", ctx.repository);
+	printf("    Tag:        %s\n", ctx.tag);
+
+cleanup:
+	free(ctx.registry);
+	free(ctx.repository);
+	free(ctx.tag);
+	return ret == 0;
+}
+
+static int test_ocierofs_parse_ref(void)
+{
+	struct ocierofs_parse_ref_testcase tests[] = {
+		{
+			.name = "Simple image name (Docker Hub library)",
+			.ref_str = "nginx",
+			.expected_registry = DOCKER_API_REGISTRY,
+			.expected_repository = "library/nginx",
+			.expected_tag = "latest",
+		},
+		{
+			.name = "Image with tag (Docker Hub library)",
+			.ref_str = "nginx:1.21",
+			.expected_registry = DOCKER_API_REGISTRY,
+			.expected_repository = "library/nginx",
+			.expected_tag = "1.21",
+		},
+		{
+			.name = "User repository without tag",
+			.ref_str = "user/myapp",
+			.expected_registry = DOCKER_API_REGISTRY,
+			.expected_repository = "user/myapp",
+			.expected_tag = "latest",
+		},
+		{
+			.name = "User repository with tag",
+			.ref_str = "user/myapp:v2.0",
+			.expected_registry = DOCKER_API_REGISTRY,
+			.expected_repository = "user/myapp",
+			.expected_tag = "v2.0",
+		},
+		{
+			.name = "Custom registry without tag",
+			.ref_str = "registry.example.com/myapp",
+			.expected_registry = "registry.example.com",
+			.expected_repository = "myapp",
+			.expected_tag = "latest",
+		},
+		{
+			.name = "Custom registry with tag",
+			.ref_str = "registry.example.com/myapp:v1.0",
+			.expected_registry = "registry.example.com",
+			.expected_repository = "myapp",
+			.expected_tag = "v1.0",
+		},
+		{
+			.name = "Custom registry with port",
+			.ref_str = "localhost:5000/myapp:latest",
+			.expected_registry = "localhost:5000",
+			.expected_repository = "myapp",
+			.expected_tag = "latest",
+		},
+		{
+			.name = "Custom registry with ip & port",
+			.ref_str = "127.0.0.1:5000/myapp:latest",
+			.expected_registry = "127.0.0.1:5000",
+			.expected_repository = "myapp",
+			.expected_tag = "latest",
+		},
+		{
+			.name = "Custom registry with nested repository",
+			.ref_str = "registry.example.com/org/project/app:dev",
+			.expected_registry = "registry.example.com",
+			.expected_repository = "org/project/app",
+			.expected_tag = "dev",
+		},
+		{
+			.name = "Tag with digest-like format",
+			.ref_str = "myapp:sha256-abc123",
+			.expected_registry = DOCKER_API_REGISTRY,
+			.expected_repository = "library/myapp",
+			.expected_tag = "sha256-abc123",
+		},
+		{
+			.name = "Multi-level path without registry",
+			.ref_str = "org/team/app:v1",
+			.expected_registry = DOCKER_API_REGISTRY,
+			.expected_repository = "org/team/app",
+			.expected_tag = "v1",
+		},
+	};
+	int i, pass = 0;
+
+	for (i = 0; i < ARRAY_SIZE(tests); ++i) {
+		pass += run_ocierofs_parse_ref_test(&tests[i]);
+		putc('\n', stdout);
+	}
+
+	printf("Run all %d tests with %d PASSED\n", i, pass);
+	return ARRAY_SIZE(tests) == pass;
+}
+
+int main(int argc, char *argv[])
+{
+	exit(test_ocierofs_parse_ref() ? EXIT_SUCCESS : EXIT_FAILURE);
+}
+#endif
\ No newline at end of file
-- 
2.43.0



More information about the Linux-erofs mailing list