Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix!: allow colon and back slash in unix file name #734

Closed
wants to merge 11 commits into from
44 changes: 40 additions & 4 deletions cmd/oras/internal/fileref/unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

func isEscaped(path string, offset int) bool {
cnt := 0
for i := offset - 1; i >= 0; i-- {
if path[i] != '\\' {
break
}
cnt++
}
return cnt%2 != 0
qweeah marked this conversation as resolved.
Show resolved Hide resolved
}

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
}
32 changes: 29 additions & 3 deletions cmd/oras/internal/fileref/unix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -35,7 +35,10 @@ 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 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", ""},
{"colon-prefix file name and media type", args{":az:b:c", "d"}, ":az:b", "c"},

Expand All @@ -55,7 +58,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
Expand Down Expand Up @@ -88,3 +91,26 @@ func TestParse(t *testing.T) {
})
}
}

func Test_unescape(t *testing.T) {
tests := []struct {
name string
input string
want string
}{
{"empty string", "", ""},
{"only backslash", "\\", ""},
{"double backslash", `\\`, `\`},
{"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("unescape() gotFilePath = %v, want %v", got, tt.want)
}
})
}
}