@@ -4,8 +4,11 @@ import (
4
4
"context"
5
5
"fmt"
6
6
io "io"
7
+ "net/url"
7
8
"os"
9
+ "strconv"
8
10
"strings"
11
+ "unicode"
9
12
10
13
"github.com/moby/buildkit/session"
11
14
"github.com/pkg/errors"
@@ -24,6 +27,7 @@ const (
24
27
keyFollowPaths = "followpaths"
25
28
keyDirName = "dir-name"
26
29
keyExporterMetaPrefix = "exporter-md-"
30
+ keyOptsEncoded = "opts-encoded"
27
31
)
28
32
29
33
type fsSyncProvider struct {
@@ -83,6 +87,17 @@ func (sp *fsSyncProvider) handle(method string, stream grpc.ServerStream) (retEr
83
87
84
88
opts , _ := metadata .FromIncomingContext (stream .Context ()) // if no metadata continue with empty object
85
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
+ }
100
+
86
101
dirName := ""
87
102
name , ok := opts [keyDirName ]
88
103
if ok && len (name ) > 0 {
@@ -209,6 +224,11 @@ func FSSync(ctx context.Context, c session.Caller, opt FSSendRequestOpt) error {
209
224
210
225
var stream grpc.ClientStream
211
226
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
+
230
+ opts = encodeOpts (opts )
231
+
212
232
ctx = metadata .NewOutgoingContext (ctx , opts )
213
233
214
234
switch pr .name {
@@ -337,3 +357,44 @@ func (e InvalidSessionError) Error() string {
337
357
func (e InvalidSessionError ) Unwrap () error {
338
358
return e .err
339
359
}
360
+
361
+ func encodeOpts (opts map [string ][]string ) map [string ][]string {
362
+ md := make (map [string ][]string , len (opts ))
363
+ for k , v := range opts {
364
+ out := make ([]string , len (v ))
365
+ for i , s := range v {
366
+ out [i ] = encodeStringForHeader (s )
367
+ }
368
+ md [k ] = out
369
+ }
370
+ return md
371
+ }
372
+
373
+ func decodeOpts (opts map [string ][]string ) map [string ][]string {
374
+ md := make (map [string ][]string , len (opts ))
375
+ for k , v := range opts {
376
+ out := make ([]string , len (v ))
377
+ for i , s := range v {
378
+ out [i ], _ = url .QueryUnescape (s )
379
+ }
380
+ md [k ] = out
381
+ }
382
+ return md
383
+ }
384
+
385
+ // encodeStringForHeader encodes a string value so it can be used in grpc header. This encoding
386
+ // 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 )
397
+ }
398
+ }
399
+ return output .String ()
400
+ }
0 commit comments