-
Notifications
You must be signed in to change notification settings - Fork 4.4k
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
metadata: validate metadata keys and values #4886
Merged
Merged
Changes from all commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
6399d6e
add metadata validate function
Patrick0308 aec9260
add metadata validate unit test and fix key checker
Patrick0308 b6f263e
add validate add server stream and grpc.Invoke
Patrick0308 2520260
make TestValidate a table driven test
Patrick0308 8625b25
optimize for
Patrick0308 b848e9b
move Validate to internal.metadata
Patrick0308 99522c3
update log and unittest
Patrick0308 4b88571
not use pairs
Patrick0308 0ac1e02
move validate to newClientStream and update comment
Patrick0308 88ea32f
fix unit test
Patrick0308 84bbd64
format code
Patrick0308 bc980fb
ignore presudo header and add tests
Patrick0308 60aca70
fix problems
Patrick0308 82240b2
fix test problem
Patrick0308 4bee015
format code
Patrick0308 f04f60c
fix review problem
Patrick0308 a5d18dd
fix format
Patrick0308 cb062d0
delete useless import and fix tests
Patrick0308 fad092e
fix review problem
Patrick0308 9e3a801
fix tests problem
Patrick0308 4084375
fix problem
Patrick0308 145bef2
format code
Patrick0308 881ab45
update tests
Patrick0308 47c552d
fix review problem
Patrick0308 30b92a3
format
Patrick0308 6674adc
expect error log
Patrick0308 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,6 +22,9 @@ | |
package metadata | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
|
||
"google.golang.org/grpc/metadata" | ||
"google.golang.org/grpc/resolver" | ||
) | ||
|
@@ -72,3 +75,46 @@ func Set(addr resolver.Address, md metadata.MD) resolver.Address { | |
addr.Attributes = addr.Attributes.WithValue(mdKey, mdValue(md)) | ||
return addr | ||
} | ||
|
||
// Validate returns an error if the input md contains invalid keys or values. | ||
// | ||
// If the header is not a pseudo-header, the following items are checked: | ||
// - header names must contain one or more characters from this set [0-9 a-z _ - .]. | ||
// - if the header-name ends with a "-bin" suffix, no validation of the header value is performed. | ||
// - otherwise, the header value must contain one or more characters from the set [%x20-%x7E]. | ||
func Validate(md metadata.MD) error { | ||
for k, vals := range md { | ||
// pseudo-header will be ignored | ||
if k[0] == ':' { | ||
continue | ||
} | ||
// check key, for i that saving a conversion if not using for range | ||
for i := 0; i < len(k); i++ { | ||
r := k[i] | ||
if !(r >= 'a' && r <= 'z') && !(r >= '0' && r <= '9') && r != '.' && r != '-' && r != '_' { | ||
return fmt.Errorf("header key %q contains illegal characters not in [0-9a-z-_.]", k) | ||
} | ||
} | ||
if strings.HasSuffix(k, "-bin") { | ||
continue | ||
} | ||
// check value | ||
for _, val := range vals { | ||
if hasNotPrintable(val) { | ||
return fmt.Errorf("header key %q contains value with non-printable ASCII characters", k) | ||
} | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// hasNotPrintable return true if msg contains any characters which are not in %x20-%x7E | ||
func hasNotPrintable(msg string) bool { | ||
// for i that saving a conversion if not using for range | ||
for i := 0; i < len(msg); i++ { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same question re: |
||
if msg[i] < 0x20 || msg[i] > 0x7E { | ||
return true | ||
} | ||
} | ||
return false | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
/* | ||
* | ||
* Copyright 2022 gRPC 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 test | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"io" | ||
"reflect" | ||
"testing" | ||
"time" | ||
|
||
"google.golang.org/grpc/codes" | ||
"google.golang.org/grpc/internal/grpctest" | ||
"google.golang.org/grpc/internal/stubserver" | ||
"google.golang.org/grpc/metadata" | ||
"google.golang.org/grpc/status" | ||
testpb "google.golang.org/grpc/test/grpc_testing" | ||
) | ||
|
||
func (s) TestInvalidMetadata(t *testing.T) { | ||
Patrick0308 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
grpctest.TLogger.ExpectErrorN("stream: failed to validate md when setting trailer", 2) | ||
|
||
tests := []struct { | ||
md metadata.MD | ||
want error | ||
recv error | ||
}{ | ||
{ | ||
md: map[string][]string{string(rune(0x19)): {"testVal"}}, | ||
want: status.Error(codes.Internal, "header key \"\\x19\" contains illegal characters not in [0-9a-z-_.]"), | ||
recv: status.Error(codes.Internal, "invalid header field name \"\\x19\""), | ||
}, | ||
{ | ||
md: map[string][]string{"test": {string(rune(0x19))}}, | ||
want: status.Error(codes.Internal, "header key \"test\" contains value with non-printable ASCII characters"), | ||
recv: status.Error(codes.Internal, "invalid header field value \"\\x19\""), | ||
}, | ||
{ | ||
md: map[string][]string{"test-bin": {string(rune(0x19))}}, | ||
want: nil, | ||
recv: io.EOF, | ||
}, | ||
{ | ||
md: map[string][]string{"test": {"value"}}, | ||
want: nil, | ||
recv: io.EOF, | ||
}, | ||
} | ||
|
||
testNum := 0 | ||
ss := &stubserver.StubServer{ | ||
EmptyCallF: func(ctx context.Context, in *testpb.Empty) (*testpb.Empty, error) { | ||
return &testpb.Empty{}, nil | ||
}, | ||
FullDuplexCallF: func(stream testpb.TestService_FullDuplexCallServer) error { | ||
_, err := stream.Recv() | ||
if err != nil { | ||
return err | ||
} | ||
test := tests[testNum] | ||
testNum++ | ||
if err := stream.SetHeader(test.md); !reflect.DeepEqual(test.want, err) { | ||
return fmt.Errorf("call stream.SendHeader(md) validate metadata which is %v got err :%v, want err :%v", test.md, err, test.want) | ||
} | ||
if err := stream.SendHeader(test.md); !reflect.DeepEqual(test.want, err) { | ||
return fmt.Errorf("call stream.SendHeader(md) validate metadata which is %v got err :%v, want err :%v", test.md, err, test.want) | ||
} | ||
stream.SetTrailer(test.md) | ||
return nil | ||
}, | ||
} | ||
if err := ss.Start(nil); err != nil { | ||
t.Fatalf("Error starting ss endpoint server: %v", err) | ||
} | ||
defer ss.Stop() | ||
|
||
for _, test := range tests { | ||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) | ||
defer cancel() | ||
|
||
ctx = metadata.NewOutgoingContext(ctx, test.md) | ||
if _, err := ss.Client.EmptyCall(ctx, &testpb.Empty{}); !reflect.DeepEqual(test.want, err) { | ||
Patrick0308 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
t.Errorf("call ss.Client.EmptyCall() validate metadata which is %v got err :%v, want err :%v", test.md, err, test.want) | ||
} | ||
} | ||
|
||
// call the stream server's api to drive the server-side unit testing | ||
for _, test := range tests { | ||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) | ||
stream, err := ss.Client.FullDuplexCall(ctx) | ||
defer cancel() | ||
if err != nil { | ||
t.Errorf("call ss.Client.FullDuplexCall(context.Background()) will success but got err :%v", err) | ||
Patrick0308 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
continue | ||
} | ||
if err := stream.Send(&testpb.StreamingOutputCallRequest{}); err != nil { | ||
t.Errorf("call ss.Client stream Send(nil) will success but got err :%v", err) | ||
} | ||
if _, err := stream.Recv(); !reflect.DeepEqual(test.recv, err) { | ||
t.Errorf("stream.Recv() = _, get err :%v, want err :%v", err, test.recv) | ||
} | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand this. Why would
for _, r := range(k) {
be worse?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Range is different for strings (runes vs bytes etc): http://go.dev/play/p/2B-0HiWektw
#4886 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIUC all of these will work correctly anyway since we don't allow anything besides low ASCII, right?