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!: fix colon in media type for windows #698

Merged
merged 39 commits into from
Dec 23, 2022
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
fb94ff5
fix: forbidden colon in media type
qweeah Nov 21, 2022
e2934c3
add unit tests
qweeah Nov 21, 2022
975cbda
fix pull
qweeah Nov 21, 2022
a78f0ff
add GOOS
qweeah Nov 21, 2022
b8d759d
fix failure
qweeah Nov 21, 2022
b2731a0
fix windows test
qweeah Nov 21, 2022
9fff66b
remove CI change
qweeah Nov 21, 2022
afdd10e
uncomment tests
qweeah Nov 21, 2022
6f779d8
update unit tests
qweeah Nov 23, 2022
98bb765
unit test
qweeah Nov 23, 2022
7420372
add tests
qweeah Nov 23, 2022
6302404
add tests
qweeah Nov 23, 2022
50afb43
more tests
qweeah Nov 23, 2022
0e19580
rename & fix windows
qweeah Nov 25, 2022
adb80a4
fix tests
qweeah Nov 25, 2022
4b3e428
correct func name
qweeah Nov 25, 2022
1b93831
add path validation
qweeah Nov 25, 2022
35cb221
remove path validation
qweeah Nov 27, 2022
9b285f8
check reserved char on windows
qweeah Nov 28, 2022
cb225e7
fix linux build
qweeah Nov 28, 2022
6179f57
fix unit test
qweeah Nov 28, 2022
70f05aa
fix typo
qweeah Nov 28, 2022
f860501
Merge remote-tracking branch 'origin_src/main' into fix-windows
qweeah Dec 14, 2022
959ea67
resolve comments
qweeah Dec 22, 2022
697b526
Merge remote-tracking branch 'origin_src' into fix-windows
qweeah Dec 22, 2022
3931be8
disallow related file path with disk prefix
qweeah Dec 23, 2022
a97c3bf
resolve comments
qweeah Dec 23, 2022
82b754d
add unit test for unix
qweeah Dec 23, 2022
52b333b
fix compile
qweeah Dec 23, 2022
a8be80b
fix test
qweeah Dec 23, 2022
f18a93b
remove obsolete tests
qweeah Dec 23, 2022
a7bbac7
Merge remote-tracking branch 'origin_src' into fix-windows
qweeah Dec 23, 2022
a32eb08
add test
qweeah Dec 23, 2022
6f1abcb
change test
qweeah Dec 23, 2022
219e3cc
fix test
qweeah Dec 23, 2022
380b8f3
enhance unit tests
qweeah Dec 23, 2022
3804485
update unix
qweeah Dec 23, 2022
9e37975
change tests
qweeah Dec 23, 2022
9d5e2be
resolve comment
qweeah Dec 23, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion cmd/oras/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,16 @@ import (

ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"oras.land/oras-go/v2/content/file"
"oras.land/oras/cmd/oras/internal/fileref"
)

func loadFiles(ctx context.Context, store *file.Store, annotations map[string]map[string]string, fileRefs []string, verbose bool) ([]ocispec.Descriptor, error) {
var files []ocispec.Descriptor
for _, fileRef := range fileRefs {
filename, mediaType := parseFileReference(fileRef, "")
filename, mediaType, err := fileref.Parse(fileRef, "")
if err != nil {
return nil, err
}

// get shortest absolute path as unique name
name := filepath.Clean(filename)
Expand Down
40 changes: 0 additions & 40 deletions cmd/oras/file_windows.go

This file was deleted.

21 changes: 13 additions & 8 deletions cmd/oras/file_unix.go → cmd/oras/internal/fileref/unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,21 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

package main
package fileref

import "strings"

// parseFileReference parses file reference on unix.
func parseFileReference(reference string, mediaType string) (filePath, mediatype string) {
import (
"strings"
"fmt"
)
// Parse parses file reference on unix.
func Parse(reference string, mediaType string) (filePath, mediatype string, err error) {
i := strings.LastIndex(reference, ":")
if i < 0 {
return reference, mediaType
return reference, mediaType, nil
qweeah marked this conversation as resolved.
Show resolved Hide resolved
}
return reference[:i], reference[i+1:]

filePath, mediatype = reference[:i], reference[i+1:]
if filePath == "" {
return "", "", fmt.Errorf("found empty file path in %q", reference)
}
return filePath, mediatype, nil
}
88 changes: 88 additions & 0 deletions cmd/oras/internal/fileref/unix_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//go:build !windows

/*
Copyright The ORAS Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package fileref

import "testing"

func Test_ParseFileReference(t *testing.T) {
type args struct {
reference string
mediaType string
}
tests := []struct {
name string
args args
wantFilePath string
wantMediatype string
}{
{"file name and media type", args{"az:b", ""}, "az", "b"},
{"file name and empty media type", args{"az:", ""}, "az", ""},
{"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 empty media type", args{"az:b:", "c"}, "az:b", ""},
{"colon-prefix file name and media type", args{":az:b:c", "d"}, ":az:b", "c"},

{"pure colon file name and media type", args{"::a", "b"}, ":", "a"},
{"pure colon file name and empty media type", args{"::", "a"}, ":", ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotFilePath, gotMediatype, _ := Parse(tt.args.reference, tt.args.mediaType)
if gotFilePath != tt.wantFilePath {
t.Errorf("Parse() gotFilePath = %v, want %v", gotFilePath, tt.wantFilePath)
}
if gotMediatype != tt.wantMediatype {
t.Errorf("Parse() gotMediatype = %v, want %v", gotMediatype, tt.wantMediatype)
}
})
}
}

func TestParse(t *testing.T) {
type args struct {
reference string
mediaType string
}
tests := []struct {
name string
args args
wantFilePath string
wantMediatype string
wantErr bool
}{
{"empty file name", args{":", ""}, "", "", true},
{"empty file name, with media type", args{":a", "b"}, "", "", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotFilePath, gotMediatype, err := Parse(tt.args.reference, tt.args.mediaType)
if (err != nil) != tt.wantErr {
t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr)
return
}
if gotFilePath != tt.wantFilePath {
t.Errorf("Parse() gotFilePath = %v, want %v", gotFilePath, tt.wantFilePath)
}
if gotMediatype != tt.wantMediatype {
t.Errorf("Parse() gotMediatype = %v, want %v", gotMediatype, tt.wantMediatype)
}
})
}
}
46 changes: 46 additions & 0 deletions cmd/oras/internal/fileref/windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//go:build windows

/*
Copyright The ORAS Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package fileref

import (
"fmt"
"strings"
"unicode"
)

// Parse parses file reference on windows.
func Parse(reference string, defaultMediaType string) (filePath, mediaType string, err error) {
filePath, mediaType = doParse(reference, defaultMediaType)
if filePath == "" {
return "", "", fmt.Errorf("found empty file path in %q", reference)
}
if strings.ContainsAny(filePath, `<>:"|?*`) {
// Reference: https://learn.microsoft.com/windows/win32/fileio/naming-a-file#naming-conventions
return "", "", fmt.Errorf("reserved characters found in the file path: %s", filePath)
}
return filePath, mediaType, nil
}

func doParse(reference string, mediaType string) (filePath, mediatype string) {
i := strings.LastIndex(reference, ":")
if i < 0 || (i == 1 && len(reference) > 2 && unicode.IsLetter(rune(reference[0])) && reference[2] == '\\') {
// Relative file path with disk prefix is NOT supported, e.g. `c:file1`
return reference, mediaType
}
return reference[:i], reference[i+1:]
}
116 changes: 116 additions & 0 deletions cmd/oras/internal/fileref/windows_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
//go:build windows

/*
Copyright The ORAS Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package fileref

import (
"testing"
)

func Test_doParse(t *testing.T) {
type args struct {
reference string
mediaType string
}
tests := []struct {
name string
args args
wantFilePath string
wantMediatype string
}{
{"file name and media type", args{"az:b", ""}, "az", "b"},
{"file name and empty media type", args{"az:", ""}, "az", ""},
{"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", ""},

{"empty file name and media type", args{":a", "b"}, "", "a"},
qweeah marked this conversation as resolved.
Show resolved Hide resolved
{"empty file name and empty media type", args{":", "a"}, "", ""},
{"empty name and default media type", args{"", "a"}, "", "a"},

{"colon file name 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"},

{"pure colon file name and media type", args{"::a", "b"}, ":", "a"},
{"pure colon file name and empty media type", args{"::", "a"}, ":", ""},

{"windows file name1 and default type", args{`a:\b`, "c"}, `a:\b`, "c"},
{"windows file name2 and default type", args{`z:b`, "c"}, `z`, "b"},
{"windows file name and media type", args{`a:\b:c`, "d"}, `a:\b`, "c"},
{"windows file name and empty media type", args{`a:\b:`, "c"}, `a:\b`, ""},
{"numeric file name and media type", args{`1:\a`, "b"}, `1`, `\a`},
{"non-windows file name and media type", args{`ab:\c`, ""}, `ab`, `\c`},
{"non-windows file name and media type, default type ignored", args{`1:\a`, "b"}, `1`, `\a`},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotFilePath, gotMediatype := doParse(tt.args.reference, tt.args.mediaType)
if gotFilePath != tt.wantFilePath {
t.Errorf("doParse() gotFilePath = %v, want %v", gotFilePath, tt.wantFilePath)
}
if gotMediatype != tt.wantMediatype {
t.Errorf("doParse() gotMediatype = %v, want %v", gotMediatype, tt.wantMediatype)
}
})
}
}

func TestParse(t *testing.T) {
type args struct {
reference string
mediaType string
}
tests := []struct {
name string
args args
wantFilePath string
wantMediatype string
wantErr bool
}{
{"empty file name", args{":", ""}, "", "", true},
{"reserved character1 in file name", args{"<", "a"}, "", "", true},
{"reserved character2 in file name", args{">", "a"}, "", "", true},
{"reserved character3 in file name", args{"*", "a"}, "", "", true},
{"reserved character4 in file name", args{`"`, "a"}, "", "", true},
{"reserved character5 in file name", args{"|", "a"}, "", "", true},
{"reserved character6 in file name", args{"?", "a"}, "", "", true},
{"empty file name, with media type", args{":", "a"}, "", "", true},
{"reserved character1 in file name, with media type", args{"<:", "a"}, "", "", true},
{"reserved character2 in file name, with media type", args{">:", "a"}, "", "", true},
{"reserved character3 in file name, with media type", args{"*:", "a"}, "", "", true},
{"reserved character4 in file name, with media type", args{`":`, "a"}, "", "", true},
{"reserved character5 in file name, with media type", args{"|:", "a"}, "", "", true},
{"reserved character6 in file name, with media type", args{"?:", "a"}, "", "", true},
{"reserved character7 in file name, with media type", args{"::", "a"}, "", "", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotFilePath, gotMediatype, err := Parse(tt.args.reference, tt.args.mediaType)
if (err != nil) != tt.wantErr {
t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr)
return
}
if gotFilePath != tt.wantFilePath {
t.Errorf("Parse() gotFilePath = %v, want %v", gotFilePath, tt.wantFilePath)
}
if gotMediatype != tt.wantMediatype {
t.Errorf("Parse() gotMediatype = %v, want %v", gotMediatype, tt.wantMediatype)
}
})
}
}
6 changes: 5 additions & 1 deletion cmd/oras/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"oras.land/oras-go/v2/content/file"
"oras.land/oras/cmd/oras/internal/display"
"oras.land/oras/cmd/oras/internal/errors"
"oras.land/oras/cmd/oras/internal/fileref"
"oras.land/oras/cmd/oras/internal/option"
"oras.land/oras/internal/graph"
)
Expand Down Expand Up @@ -117,7 +118,10 @@ func runPull(opts pullOptions) error {
// Copy Options
copyOptions := oras.DefaultCopyOptions
copyOptions.Concurrency = opts.concurrency
configPath, configMediaType := parseFileReference(opts.ManifestConfigRef, "")
configPath, configMediaType, err := fileref.Parse(opts.ManifestConfigRef, "")
if err != nil {
return err
}
if targetPlatform != nil {
copyOptions.WithTargetPlatform(targetPlatform)
}
Expand Down
6 changes: 5 additions & 1 deletion cmd/oras/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"oras.land/oras-go/v2/registry/remote"
"oras.land/oras-go/v2/registry/remote/errcode"
"oras.land/oras/cmd/oras/internal/display"
"oras.land/oras/cmd/oras/internal/fileref"
"oras.land/oras/cmd/oras/internal/option"
)

Expand Down Expand Up @@ -129,7 +130,10 @@ func runPush(opts pushOptions) error {
defer store.Close()
store.AllowPathTraversalOnWrite = opts.PathValidationDisabled
if opts.manifestConfigRef != "" {
path, cfgMediaType := parseFileReference(opts.manifestConfigRef, oras.MediaTypeUnknownConfig)
path, cfgMediaType, err := fileref.Parse(opts.manifestConfigRef, oras.MediaTypeUnknownConfig)
if err != nil {
return err
}
desc, err := store.Add(ctx, option.AnnotationConfig, cfgMediaType, path)
if err != nil {
return err
Expand Down