diff options
Diffstat (limited to 'src/csharp')
-rw-r--r-- | src/csharp/Grpc.Core.Tests/MetadataTest.cs | 17 | ||||
-rw-r--r-- | src/csharp/Grpc.Core/ClientBase.cs | 20 | ||||
-rw-r--r-- | src/csharp/Grpc.Core/Internal/MarshalUtils.cs | 7 | ||||
-rw-r--r-- | src/csharp/Grpc.Core/Metadata.cs | 40 |
4 files changed, 75 insertions, 9 deletions
diff --git a/src/csharp/Grpc.Core.Tests/MetadataTest.cs b/src/csharp/Grpc.Core.Tests/MetadataTest.cs index 171c5c470e..d85d7572a6 100644 --- a/src/csharp/Grpc.Core.Tests/MetadataTest.cs +++ b/src/csharp/Grpc.Core.Tests/MetadataTest.cs @@ -73,6 +73,23 @@ namespace Grpc.Core.Tests } [Test] + public void KeysAreNormalized_UppercaseKey() + { + var uppercaseKey = "ABC"; + var entry = new Metadata.Entry(uppercaseKey, "XYZ"); + Assert.AreEqual("abc", entry.Key); + } + + [Test] + public void KeysAreNormalized_LowercaseKey() + { + var lowercaseKey = "abc"; + var entry = new Metadata.Entry(lowercaseKey, "XYZ"); + // no allocation if key already lowercase + Assert.AreSame(lowercaseKey, entry.Key); + } + + [Test] public void Entry_ConstructionPreconditions() { Assert.Throws(typeof(ArgumentNullException), () => new Metadata.Entry(null, "xyz")); diff --git a/src/csharp/Grpc.Core/ClientBase.cs b/src/csharp/Grpc.Core/ClientBase.cs index fac34071be..05edce7467 100644 --- a/src/csharp/Grpc.Core/ClientBase.cs +++ b/src/csharp/Grpc.Core/ClientBase.cs @@ -151,12 +151,12 @@ namespace Grpc.Core { private class ClientBaseConfigurationInterceptor : Interceptor { - readonly Func<IMethod, string, CallOptions, Tuple<string, CallOptions>> interceptor; + readonly Func<IMethod, string, CallOptions, ClientBaseConfigurationInfo> interceptor; /// <summary> /// Creates a new instance of ClientBaseConfigurationInterceptor given the specified header and host interceptor function. /// </summary> - public ClientBaseConfigurationInterceptor(Func<IMethod, string, CallOptions, Tuple<string, CallOptions>> interceptor) + public ClientBaseConfigurationInterceptor(Func<IMethod, string, CallOptions, ClientBaseConfigurationInfo> interceptor) { this.interceptor = GrpcPreconditions.CheckNotNull(interceptor, nameof(interceptor)); } @@ -166,7 +166,7 @@ namespace Grpc.Core where TResponse : class { var newHostAndCallOptions = interceptor(context.Method, context.Host, context.Options); - return new ClientInterceptorContext<TRequest, TResponse>(context.Method, newHostAndCallOptions.Item1, newHostAndCallOptions.Item2); + return new ClientInterceptorContext<TRequest, TResponse>(context.Method, newHostAndCallOptions.Host, newHostAndCallOptions.CallOptions); } public override TResponse BlockingUnaryCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, BlockingUnaryCallContinuation<TRequest, TResponse> continuation) @@ -195,6 +195,18 @@ namespace Grpc.Core } } + internal struct ClientBaseConfigurationInfo + { + internal readonly string Host; + internal readonly CallOptions CallOptions; + + internal ClientBaseConfigurationInfo(string host, CallOptions callOptions) + { + Host = host; + CallOptions = callOptions; + } + } + readonly CallInvoker undecoratedCallInvoker; readonly string host; @@ -206,7 +218,7 @@ namespace Grpc.Core internal CallInvoker CreateDecoratedCallInvoker() { - return undecoratedCallInvoker.Intercept(new ClientBaseConfigurationInterceptor((method, host, options) => Tuple.Create(this.host, options))); + return undecoratedCallInvoker.Intercept(new ClientBaseConfigurationInterceptor((method, host, options) => new ClientBaseConfigurationInfo(this.host, options))); } internal ClientBaseConfiguration WithHost(string host) diff --git a/src/csharp/Grpc.Core/Internal/MarshalUtils.cs b/src/csharp/Grpc.Core/Internal/MarshalUtils.cs index 3f9605bfdd..09ded1a036 100644 --- a/src/csharp/Grpc.Core/Internal/MarshalUtils.cs +++ b/src/csharp/Grpc.Core/Internal/MarshalUtils.cs @@ -35,6 +35,13 @@ namespace Grpc.Core.Internal /// </summary> public static string PtrToStringUTF8(IntPtr ptr, int len) { + if (len == 0) + { + return ""; + } + + // TODO(jtattermusch): once Span dependency is added, + // use Span-based API to decode the string without copying the buffer. var bytes = new byte[len]; Marshal.Copy(ptr, bytes, 0, len); return EncodingUTF8.GetString(bytes); diff --git a/src/csharp/Grpc.Core/Metadata.cs b/src/csharp/Grpc.Core/Metadata.cs index 281952d6d4..a9aa2cbbfc 100644 --- a/src/csharp/Grpc.Core/Metadata.cs +++ b/src/csharp/Grpc.Core/Metadata.cs @@ -225,8 +225,6 @@ namespace Grpc.Core /// </summary> public class Entry { - private static readonly Regex ValidKeyRegex = new Regex("^[.a-z0-9_-]+$"); - readonly string key; readonly string value; readonly byte[] valueBytes; @@ -358,10 +356,42 @@ namespace Grpc.Core private static string NormalizeKey(string key) { - var normalized = GrpcPreconditions.CheckNotNull(key, "key").ToLowerInvariant(); - GrpcPreconditions.CheckArgument(ValidKeyRegex.IsMatch(normalized), + GrpcPreconditions.CheckNotNull(key, "key"); + + GrpcPreconditions.CheckArgument(IsValidKey(key, out bool isLowercase), "Metadata entry key not valid. Keys can only contain lowercase alphanumeric characters, underscores, hyphens and dots."); - return normalized; + if (isLowercase) + { + // save allocation of a new string if already lowercase + return key; + } + + return key.ToLowerInvariant(); + } + + private static bool IsValidKey(string input, out bool isLowercase) + { + isLowercase = true; + for (int i = 0; i < input.Length; i++) + { + char c = input[i]; + if ('a' <= c && c <= 'z' || + '0' <= c && c <= '9' || + c == '.' || + c == '_' || + c == '-' ) + continue; + + if ('A' <= c && c <= 'Z') + { + isLowercase = false; + continue; + } + + return false; + } + + return true; } /// <summary> |