@@ -4,8 +4,11 @@ import (
44 "context"
55 "fmt"
66 io "io"
7+ "net/url"
78 "os"
9+ "strconv"
810 "strings"
11+ "unicode"
912
1013 "github.com/moby/buildkit/session"
1114 "github.com/pkg/errors"
@@ -24,6 +27,7 @@ const (
2427 keyFollowPaths = "followpaths"
2528 keyDirName = "dir-name"
2629 keyExporterMetaPrefix = "exporter-md-"
30+ keyOptsEncoded = "opts-encoded"
2731)
2832
2933type fsSyncProvider struct {
@@ -83,6 +87,17 @@ func (sp *fsSyncProvider) handle(method string, stream grpc.ServerStream) (retEr
8387
8488 opts , _ := metadata .FromIncomingContext (stream .Context ()) // if no metadata continue with empty object
8589
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+
86101 dirName := ""
87102 name , ok := opts [keyDirName ]
88103 if ok && len (name ) > 0 {
@@ -209,6 +224,11 @@ func FSSync(ctx context.Context, c session.Caller, opt FSSendRequestOpt) error {
209224
210225 var stream grpc.ClientStream
211226
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+
212232 ctx = metadata .NewOutgoingContext (ctx , opts )
213233
214234 switch pr .name {
@@ -337,3 +357,44 @@ func (e InvalidSessionError) Error() string {
337357func (e InvalidSessionError ) Unwrap () error {
338358 return e .err
339359}
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