Skip to content

Commit 6c3bdd4

Browse files
authored
Merge pull request moby#4009 from jedevc/filesync-ascii-special-chars
filesync: escape special query characters
2 parents b623322 + 48b6240 commit 6c3bdd4

File tree

2 files changed

+48
-34
lines changed

2 files changed

+48
-34
lines changed

frontend/dockerfile/dockerfile_test.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6766,12 +6766,16 @@ func testCopyUnicodePath(t *testing.T, sb integration.Sandbox) {
67666766
dockerfile := []byte(`
67676767
FROM alpine
67686768
COPY test-äöü.txt /
6769+
COPY test-%C3%A4%C3%B6%C3%BC.txt /
6770+
COPY test+aou.txt /
67696771
`)
67706772

67716773
dir, err := integration.Tmpdir(
67726774
t,
67736775
fstest.CreateFile("Dockerfile", dockerfile, 0600),
6774-
fstest.CreateFile("test-äöü.txt", []byte("test"), 0644),
6776+
fstest.CreateFile("test-äöü.txt", []byte("foo"), 0644),
6777+
fstest.CreateFile("test-%C3%A4%C3%B6%C3%BC.txt", []byte("bar"), 0644),
6778+
fstest.CreateFile("test+aou.txt", []byte("baz"), 0644),
67756779
)
67766780
require.NoError(t, err)
67776781

@@ -6794,7 +6798,15 @@ COPY test-äöü.txt /
67946798

67956799
dt, err := os.ReadFile(filepath.Join(destDir, "test-äöü.txt"))
67966800
require.NoError(t, err)
6797-
require.Equal(t, "test", string(dt))
6801+
require.Equal(t, "foo", string(dt))
6802+
6803+
dt, err = os.ReadFile(filepath.Join(destDir, "test-%C3%A4%C3%B6%C3%BC.txt"))
6804+
require.NoError(t, err)
6805+
require.Equal(t, "bar", string(dt))
6806+
6807+
dt, err = os.ReadFile(filepath.Join(destDir, "test+aou.txt"))
6808+
require.NoError(t, err)
6809+
require.Equal(t, "baz", string(dt))
67986810
}
67996811

68006812
func runShell(dir string, cmds ...string) error {

session/filesync/filesync.go

Lines changed: 34 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ const (
2727
keyFollowPaths = "followpaths"
2828
keyDirName = "dir-name"
2929
keyExporterMetaPrefix = "exporter-md-"
30-
keyOptsEncoded = "opts-encoded"
3130
)
3231

3332
type fsSyncProvider struct {
@@ -86,17 +85,7 @@ func (sp *fsSyncProvider) handle(method string, stream grpc.ServerStream) (retEr
8685
}
8786

8887
opts, _ := metadata.FromIncomingContext(stream.Context()) // if no metadata continue with empty object
89-
90-
isDecoded := false
91-
if v, ok := opts[keyOptsEncoded]; ok && len(v) > 0 {
92-
if b, _ := strconv.ParseBool(v[0]); b {
93-
isDecoded = true
94-
}
95-
}
96-
97-
if isDecoded {
98-
opts = decodeOpts(opts)
99-
}
88+
opts = decodeOpts(opts)
10089

10190
dirName := ""
10291
name, ok := opts[keyDirName]
@@ -224,9 +213,6 @@ func FSSync(ctx context.Context, c session.Caller, opt FSSendRequestOpt) error {
224213

225214
var stream grpc.ClientStream
226215

227-
// mark that we have encoded options so older versions with raw values can be detected on client side
228-
opts[keyOptsEncoded] = []string{"1"}
229-
230216
opts = encodeOpts(opts)
231217

232218
ctx = metadata.NewOutgoingContext(ctx, opts)
@@ -361,11 +347,11 @@ func (e InvalidSessionError) Unwrap() error {
361347
func encodeOpts(opts map[string][]string) map[string][]string {
362348
md := make(map[string][]string, len(opts))
363349
for k, v := range opts {
364-
out := make([]string, len(v))
365-
for i, s := range v {
366-
out[i] = encodeStringForHeader(s)
367-
}
350+
out, encoded := encodeStringForHeader(v)
368351
md[k] = out
352+
if encoded {
353+
md[k+"-encoded"] = []string{"1"}
354+
}
369355
}
370356
return md
371357
}
@@ -374,8 +360,18 @@ func decodeOpts(opts map[string][]string) map[string][]string {
374360
md := make(map[string][]string, len(opts))
375361
for k, v := range opts {
376362
out := make([]string, len(v))
377-
for i, s := range v {
378-
out[i], _ = url.QueryUnescape(s)
363+
var isDecoded bool
364+
if v, ok := opts[k+"-encoded"]; ok && len(v) > 0 {
365+
if b, _ := strconv.ParseBool(v[0]); b {
366+
isDecoded = true
367+
}
368+
}
369+
if isDecoded {
370+
for i, s := range v {
371+
out[i], _ = url.QueryUnescape(s)
372+
}
373+
} else {
374+
copy(out, v)
379375
}
380376
md[k] = out
381377
}
@@ -384,17 +380,23 @@ func decodeOpts(opts map[string][]string) map[string][]string {
384380

385381
// encodeStringForHeader encodes a string value so it can be used in grpc header. This encoding
386382
// is backwards compatible and avoids encoding ASCII characters.
387-
func encodeStringForHeader(input string) string {
388-
var output strings.Builder
389-
for _, runeVal := range input {
390-
// Only encode non-ASCII characters.
391-
if runeVal > unicode.MaxASCII {
392-
// Encode each non-ASCII character individually.
393-
output.WriteString(url.QueryEscape(string(runeVal)))
394-
} else {
395-
// Directly append ASCII characters and '*' to the output.
396-
output.WriteRune(runeVal)
383+
func encodeStringForHeader(inputs []string) ([]string, bool) {
384+
var encode bool
385+
for _, input := range inputs {
386+
for _, runeVal := range input {
387+
// Only encode non-ASCII characters, and characters that have special
388+
// meaning during decoding.
389+
if runeVal > unicode.MaxASCII {
390+
encode = true
391+
break
392+
}
397393
}
398394
}
399-
return output.String()
395+
if !encode {
396+
return inputs, false
397+
}
398+
for i, input := range inputs {
399+
inputs[i] = url.QueryEscape(input)
400+
}
401+
return inputs, true
400402
}

0 commit comments

Comments
 (0)