From f26ba04b107f790e5e4a01f8b2b54075de84d1f4 Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Wed, 4 Jan 2023 15:13:53 +0800 Subject: [PATCH 1/9] fix!: allow colon and back slash in unix file name Signed-off-by: Billy Zha --- cmd/oras/internal/fileref/unix.go | 44 +++++++++++++++++++++++--- cmd/oras/internal/fileref/unix_test.go | 32 +++++++++++++++++-- 2 files changed, 69 insertions(+), 7 deletions(-) diff --git a/cmd/oras/internal/fileref/unix.go b/cmd/oras/internal/fileref/unix.go index 82494f0e9..a8127281e 100644 --- a/cmd/oras/internal/fileref/unix.go +++ b/cmd/oras/internal/fileref/unix.go @@ -24,14 +24,50 @@ import ( // Parse parses file reference on unix. func Parse(reference string, defaultMediaType string) (filePath, mediaType string, err error) { - i := strings.LastIndex(reference, ":") - if i < 0 { - filePath, mediaType = reference, defaultMediaType + i := len(reference) + for { + // found the right most colon which is not escaped + i = strings.LastIndex(reference[:i], ":") + if i < 0 || !isEscaped(reference, i) { + break + } + } + if i < 0 || isEscaped(reference, i) { + filePath, mediaType = unescape(reference), defaultMediaType } else { - filePath, mediaType = reference[:i], reference[i+1:] + filePath, mediaType = unescape(reference[:i]), reference[i+1:] } if filePath == "" { return "", "", fmt.Errorf("found empty file path in %q", reference) } return filePath, mediaType, nil } + +// isEscaped returns if the character in path with offset is escaped by '\'. +func isEscaped(path string, offset int) bool { + cnt := 0 + for i := offset - 1; i >= 0; i-- { + if path[i] != '\\' { + break + } + } + return cnt%2 != 0 +} + +func unescape(path string) string { + len := len(path) + ret := "" + i := 0 + for i < len { + if path[i] == '\\' { + if i < len-1 { + ret += string(path[i+1]) + } + i += 2 + } else { + ret += string(path[i]) + i += 1 + } + } + return ret +} diff --git a/cmd/oras/internal/fileref/unix_test.go b/cmd/oras/internal/fileref/unix_test.go index 5bf255c45..b8cfbabcf 100644 --- a/cmd/oras/internal/fileref/unix_test.go +++ b/cmd/oras/internal/fileref/unix_test.go @@ -19,7 +19,7 @@ package fileref import "testing" -func Test_ParseFileReference(t *testing.T) { +func Test_Parse_fileReference(t *testing.T) { type args struct { reference string mediaType string @@ -35,7 +35,8 @@ func Test_ParseFileReference(t *testing.T) { {"file name and default media type", args{"az", "c"}, "az", "c"}, {"file name and media type, default type ignored", args{"az:b", "c"}, "az", "b"}, {"file name and empty media type, default type ignored", args{"az:", "c"}, "az", ""}, - {"colon file name and media type", args{"az:b:c", "d"}, "az:b", "c"}, + {"colon file name and media type", args{`az\:b:c`, "d"}, "az:b", "c"}, + {"colon file name with escape char and media type", args{`az\\\:b:c`, "d"}, `az\:b`, "c"}, {"colon file name and empty media type", args{"az:b:", "c"}, "az:b", ""}, {"colon-prefix file name and media type", args{":az:b:c", "d"}, ":az:b", "c"}, @@ -55,7 +56,7 @@ func Test_ParseFileReference(t *testing.T) { } } -func TestParse(t *testing.T) { +func Test_Parse_err(t *testing.T) { type args struct { reference string mediaType string @@ -88,3 +89,28 @@ func TestParse(t *testing.T) { }) } } + + +func Test_unescape(t *testing.T) { + tests := []struct { + name string + input string + want string + }{ + {"empty string", "", ""}, + {"only escape char", "\\", ""}, + {"double escape char", `\\`, `\`}, + {"escaping t", `\t\t`, "tt"}, + {"not escaping 1st t", `\\t\t`, `\tt`}, + {"not escaping 2nd t", `\t\\t`, `t\t`}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := unescape(tt.input) + if got != tt.want { + t.Errorf("Parse() gotFilePath = %v, want %v", got, tt.want) + } + }) + } +} + From 0445e85879cadefb55d85517fceeb531d4e815a4 Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Wed, 4 Jan 2023 15:17:27 +0800 Subject: [PATCH 2/9] remove comment Signed-off-by: Billy Zha --- cmd/oras/internal/fileref/unix.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/oras/internal/fileref/unix.go b/cmd/oras/internal/fileref/unix.go index a8127281e..2c02e75da 100644 --- a/cmd/oras/internal/fileref/unix.go +++ b/cmd/oras/internal/fileref/unix.go @@ -43,7 +43,6 @@ func Parse(reference string, defaultMediaType string) (filePath, mediaType strin return filePath, mediaType, nil } -// isEscaped returns if the character in path with offset is escaped by '\'. func isEscaped(path string, offset int) bool { cnt := 0 for i := offset - 1; i >= 0; i-- { From c0a98541ce3e778c2f0655cde6f4c0e4df3b2dd5 Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Wed, 4 Jan 2023 15:26:04 +0800 Subject: [PATCH 3/9] add unit tests Signed-off-by: Billy Zha --- cmd/oras/internal/fileref/unix_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/oras/internal/fileref/unix_test.go b/cmd/oras/internal/fileref/unix_test.go index b8cfbabcf..0070f00c2 100644 --- a/cmd/oras/internal/fileref/unix_test.go +++ b/cmd/oras/internal/fileref/unix_test.go @@ -36,7 +36,7 @@ func Test_Parse_fileReference(t *testing.T) { {"file name and media type, default type ignored", args{"az:b", "c"}, "az", "b"}, {"file name and empty media type, default type ignored", args{"az:", "c"}, "az", ""}, {"colon file name and media type", args{`az\:b:c`, "d"}, "az:b", "c"}, - {"colon file name with escape char and media type", args{`az\\\:b:c`, "d"}, `az\:b`, "c"}, + {"colon file name with backslash and media type with backslash", args{`az\\\:b:c\\d`, "d"}, `az\:b`, "c\d"}, {"colon file name and empty media type", args{"az:b:", "c"}, "az:b", ""}, {"colon-prefix file name and media type", args{":az:b:c", "d"}, ":az:b", "c"}, @@ -98,8 +98,8 @@ func Test_unescape(t *testing.T) { want string }{ {"empty string", "", ""}, - {"only escape char", "\\", ""}, - {"double escape char", `\\`, `\`}, + {"only backslash", "\\", ""}, + {"double backslash", `\\`, `\`}, {"escaping t", `\t\t`, "tt"}, {"not escaping 1st t", `\\t\t`, `\tt`}, {"not escaping 2nd t", `\t\\t`, `t\t`}, From e8a3b290ce5dce54ff3b808d2c15da1dc5b70f20 Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Wed, 4 Jan 2023 15:38:48 +0800 Subject: [PATCH 4/9] fix unit test Signed-off-by: Billy Zha --- cmd/oras/internal/fileref/unix_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/oras/internal/fileref/unix_test.go b/cmd/oras/internal/fileref/unix_test.go index 0070f00c2..f2448c139 100644 --- a/cmd/oras/internal/fileref/unix_test.go +++ b/cmd/oras/internal/fileref/unix_test.go @@ -36,7 +36,7 @@ func Test_Parse_fileReference(t *testing.T) { {"file name and media type, default type ignored", args{"az:b", "c"}, "az", "b"}, {"file name and empty media type, default type ignored", args{"az:", "c"}, "az", ""}, {"colon file name and media type", args{`az\:b:c`, "d"}, "az:b", "c"}, - {"colon file name with backslash and media type with backslash", args{`az\\\:b:c\\d`, "d"}, `az\:b`, "c\d"}, + {"colon file name with backslash and media type with backslash", args{`az\\\:b:c\\d`, "d"}, `az\:b`, `c\d`}, {"colon file name and empty media type", args{"az:b:", "c"}, "az:b", ""}, {"colon-prefix file name and media type", args{":az:b:c", "d"}, ":az:b", "c"}, From a9dca4eeb0b838b9d9750031d5adaca9a57e0eee Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Wed, 4 Jan 2023 15:45:40 +0800 Subject: [PATCH 5/9] fix unit test Signed-off-by: Billy Zha --- cmd/oras/internal/fileref/unix_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/oras/internal/fileref/unix_test.go b/cmd/oras/internal/fileref/unix_test.go index f2448c139..536f54c5d 100644 --- a/cmd/oras/internal/fileref/unix_test.go +++ b/cmd/oras/internal/fileref/unix_test.go @@ -36,7 +36,7 @@ func Test_Parse_fileReference(t *testing.T) { {"file name and media type, default type ignored", args{"az:b", "c"}, "az", "b"}, {"file name and empty media type, default type ignored", args{"az:", "c"}, "az", ""}, {"colon file name and media type", args{`az\:b:c`, "d"}, "az:b", "c"}, - {"colon file name with backslash and media type with backslash", args{`az\\\:b:c\\d`, "d"}, `az\:b`, `c\d`}, + {"colon file name with backslash and media type with backslash", args{`az\\\:b:c`, "d"}, `az\:b`, `c`}, {"colon file name and empty media type", args{"az:b:", "c"}, "az:b", ""}, {"colon-prefix file name and media type", args{":az:b:c", "d"}, ":az:b", "c"}, From 739095a8f2f102cc916898f30d89cd96a68d4040 Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Wed, 4 Jan 2023 15:48:31 +0800 Subject: [PATCH 6/9] fix bug in counting escape char Signed-off-by: Billy Zha --- cmd/oras/internal/fileref/unix.go | 1 + cmd/oras/internal/fileref/unix_test.go | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/oras/internal/fileref/unix.go b/cmd/oras/internal/fileref/unix.go index 2c02e75da..ea3d9c6fd 100644 --- a/cmd/oras/internal/fileref/unix.go +++ b/cmd/oras/internal/fileref/unix.go @@ -49,6 +49,7 @@ func isEscaped(path string, offset int) bool { if path[i] != '\\' { break } + cnt++ } return cnt%2 != 0 } diff --git a/cmd/oras/internal/fileref/unix_test.go b/cmd/oras/internal/fileref/unix_test.go index 536f54c5d..72c4b48d5 100644 --- a/cmd/oras/internal/fileref/unix_test.go +++ b/cmd/oras/internal/fileref/unix_test.go @@ -36,7 +36,8 @@ func Test_Parse_fileReference(t *testing.T) { {"file name and media type, default type ignored", args{"az:b", "c"}, "az", "b"}, {"file name and empty media type, default type ignored", args{"az:", "c"}, "az", ""}, {"colon file name and media type", args{`az\:b:c`, "d"}, "az:b", "c"}, - {"colon file name with backslash and media type with backslash", args{`az\\\:b:c`, "d"}, `az\:b`, `c`}, + {"colon file name with backslash and media type", args{`az\\\:b:c`, "d"}, `az\:b`, `c`}, + {"colon file name with backslash", args{`az\\\\:b`, "c"}, `az\\`, `b`}, {"colon file name and empty media type", args{"az:b:", "c"}, "az:b", ""}, {"colon-prefix file name and media type", args{":az:b:c", "d"}, ":az:b", "c"}, From 381d2cbccc73b6aeafebc24162dbd401c44e12bf Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Wed, 4 Jan 2023 15:53:49 +0800 Subject: [PATCH 7/9] rename unix test Signed-off-by: Billy Zha --- cmd/oras/internal/fileref/unix_test.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/cmd/oras/internal/fileref/unix_test.go b/cmd/oras/internal/fileref/unix_test.go index 72c4b48d5..c4bb9544f 100644 --- a/cmd/oras/internal/fileref/unix_test.go +++ b/cmd/oras/internal/fileref/unix_test.go @@ -36,8 +36,8 @@ func Test_Parse_fileReference(t *testing.T) { {"file name and media type, default type ignored", args{"az:b", "c"}, "az", "b"}, {"file name and empty media type, default type ignored", args{"az:", "c"}, "az", ""}, {"colon file name and media type", args{`az\:b:c`, "d"}, "az:b", "c"}, - {"colon file name with backslash and media type", args{`az\\\:b:c`, "d"}, `az\:b`, `c`}, - {"colon file name with backslash", args{`az\\\\:b`, "c"}, `az\\`, `b`}, + {"colon file name with backslash and media type1", args{`az\\\:b:c`, "d"}, `az\:b`, `c`}, + {"colon file name with backslash and media type2", args{`az\\\\:b`, "c"}, `az\\`, `b`}, {"colon file name and empty media type", args{"az:b:", "c"}, "az:b", ""}, {"colon-prefix file name and media type", args{":az:b:c", "d"}, ":az:b", "c"}, @@ -91,7 +91,6 @@ func Test_Parse_err(t *testing.T) { } } - func Test_unescape(t *testing.T) { tests := []struct { name string @@ -114,4 +113,3 @@ func Test_unescape(t *testing.T) { }) } } - From 6a4fd2ce6a78be27e6962eb2612ea0c250dbb36d Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Wed, 4 Jan 2023 20:00:41 +0800 Subject: [PATCH 8/9] add unit test Signed-off-by: Billy Zha --- cmd/oras/internal/fileref/unix_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/oras/internal/fileref/unix_test.go b/cmd/oras/internal/fileref/unix_test.go index c4bb9544f..b12f5f982 100644 --- a/cmd/oras/internal/fileref/unix_test.go +++ b/cmd/oras/internal/fileref/unix_test.go @@ -36,6 +36,7 @@ func Test_Parse_fileReference(t *testing.T) { {"file name and media type, default type ignored", args{"az:b", "c"}, "az", "b"}, {"file name and empty media type, default type ignored", args{"az:", "c"}, "az", ""}, {"colon file name and media type", args{`az\:b:c`, "d"}, "az:b", "c"}, + {"colon file name and default media type", args{`az\:`, "b"}, "az:", ""}, {"colon file name with backslash and media type1", args{`az\\\:b:c`, "d"}, `az\:b`, `c`}, {"colon file name with backslash and media type2", args{`az\\\\:b`, "c"}, `az\\`, `b`}, {"colon file name and empty media type", args{"az:b:", "c"}, "az:b", ""}, @@ -108,7 +109,7 @@ func Test_unescape(t *testing.T) { t.Run(tt.name, func(t *testing.T) { got := unescape(tt.input) if got != tt.want { - t.Errorf("Parse() gotFilePath = %v, want %v", got, tt.want) + t.Errorf("unescape() gotFilePath = %v, want %v", got, tt.want) } }) } From d46fc263e720653cff956df13a903c737c3a2262 Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Wed, 4 Jan 2023 20:03:49 +0800 Subject: [PATCH 9/9] fix unit test Signed-off-by: Billy Zha --- cmd/oras/internal/fileref/unix_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/oras/internal/fileref/unix_test.go b/cmd/oras/internal/fileref/unix_test.go index b12f5f982..f0df4e534 100644 --- a/cmd/oras/internal/fileref/unix_test.go +++ b/cmd/oras/internal/fileref/unix_test.go @@ -36,7 +36,7 @@ func Test_Parse_fileReference(t *testing.T) { {"file name and media type, default type ignored", args{"az:b", "c"}, "az", "b"}, {"file name and empty media type, default type ignored", args{"az:", "c"}, "az", ""}, {"colon file name and media type", args{`az\:b:c`, "d"}, "az:b", "c"}, - {"colon file name and default media type", args{`az\:`, "b"}, "az:", ""}, + {"colon file name and default media type", args{`az\:`, "b"}, "az:", "b"}, {"colon file name with backslash and media type1", args{`az\\\:b:c`, "d"}, `az\:b`, `c`}, {"colon file name with backslash and media type2", args{`az\\\\:b`, "c"}, `az\\`, `b`}, {"colon file name and empty media type", args{"az:b:", "c"}, "az:b", ""},