| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407 | #region Copyright notice and license// Copyright 2015, Google Inc.// All rights reserved.//// Redistribution and use in source and binary forms, with or without// modification, are permitted provided that the following conditions are// met:////     * Redistributions of source code must retain the above copyright// notice, this list of conditions and the following disclaimer.//     * Redistributions in binary form must reproduce the above// copyright notice, this list of conditions and the following disclaimer// in the documentation and/or other materials provided with the// distribution.//     * Neither the name of Google Inc. nor the names of its// contributors may be used to endorse or promote products derived from// this software without specific prior written permission.//// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.#endregionusing System;using System.Collections;using System.Collections.Generic;using System.Collections.Specialized;using System.Globalization;using System.Runtime.InteropServices;using System.Text;using System.Text.RegularExpressions;using Grpc.Core.Utils;namespace Grpc.Core{    /// <summary>    /// A collection of metadata entries that can be exchanged during a call.    /// gRPC supports these types of metadata:    /// <list type="bullet">    /// <item><term>Request headers</term><description>are sent by the client at the beginning of a remote call before any request messages are sent.</description></item>    /// <item><term>Response headers</term><description>are sent by the server at the beginning of a remote call handler before any response messages are sent.</description></item>    /// <item><term>Response trailers</term><description>are sent by the server at the end of a remote call along with resulting call status.</description></item>    /// </list>    /// </summary>    public sealed class Metadata : IList<Metadata.Entry>    {        /// <summary>        /// All binary headers should have this suffix.        /// </summary>        public const string BinaryHeaderSuffix = "-bin";        /// <summary>        /// An read-only instance of metadata containing no entries.        /// </summary>        public static readonly Metadata Empty = new Metadata().Freeze();        /// <summary>        /// To be used in initial metadata to request specific compression algorithm        /// for given call. Direct selection of compression algorithms is an internal        /// feature and is not part of public API.        /// </summary>        internal const string CompressionRequestAlgorithmMetadataKey = "grpc-internal-encoding-request";        readonly List<Entry> entries;        bool readOnly;        /// <summary>        /// Initializes a new instance of <c>Metadata</c>.        /// </summary>        public Metadata()        {            this.entries = new List<Entry>();        }        /// <summary>        /// Makes this object read-only.        /// </summary>        /// <returns>this object</returns>        internal Metadata Freeze()        {            this.readOnly = true;            return this;        }        // TODO: add support for access by key        #region IList members        /// <summary>        /// <see cref="T:IList`1"/>        /// </summary>        public int IndexOf(Metadata.Entry item)        {            return entries.IndexOf(item);        }        /// <summary>        /// <see cref="T:IList`1"/>        /// </summary>        public void Insert(int index, Metadata.Entry item)        {            GrpcPreconditions.CheckNotNull(item);            CheckWriteable();            entries.Insert(index, item);        }        /// <summary>        /// <see cref="T:IList`1"/>        /// </summary>        public void RemoveAt(int index)        {            CheckWriteable();            entries.RemoveAt(index);        }        /// <summary>        /// <see cref="T:IList`1"/>        /// </summary>        public Metadata.Entry this[int index]        {            get            {                return entries[index];            }            set            {                GrpcPreconditions.CheckNotNull(value);                CheckWriteable();                entries[index] = value;            }        }        /// <summary>        /// <see cref="T:IList`1"/>        /// </summary>        public void Add(Metadata.Entry item)        {            GrpcPreconditions.CheckNotNull(item);            CheckWriteable();            entries.Add(item);        }        /// <summary>        /// <see cref="T:IList`1"/>        /// </summary>        public void Add(string key, string value)        {            Add(new Entry(key, value));        }        /// <summary>        /// <see cref="T:IList`1"/>        /// </summary>        public void Add(string key, byte[] valueBytes)        {            Add(new Entry(key, valueBytes));        }        /// <summary>        /// <see cref="T:IList`1"/>        /// </summary>        public void Clear()        {            CheckWriteable();            entries.Clear();        }        /// <summary>        /// <see cref="T:IList`1"/>        /// </summary>        public bool Contains(Metadata.Entry item)        {            return entries.Contains(item);        }        /// <summary>        /// <see cref="T:IList`1"/>        /// </summary>        public void CopyTo(Metadata.Entry[] array, int arrayIndex)        {            entries.CopyTo(array, arrayIndex);        }        /// <summary>        /// <see cref="T:IList`1"/>        /// </summary>        public int Count        {            get { return entries.Count; }        }        /// <summary>        /// <see cref="T:IList`1"/>        /// </summary>        public bool IsReadOnly        {            get { return readOnly; }        }        /// <summary>        /// <see cref="T:IList`1"/>        /// </summary>        public bool Remove(Metadata.Entry item)        {            CheckWriteable();            return entries.Remove(item);        }        /// <summary>        /// <see cref="T:IList`1"/>        /// </summary>        public IEnumerator<Metadata.Entry> GetEnumerator()        {            return entries.GetEnumerator();        }        IEnumerator System.Collections.IEnumerable.GetEnumerator()        {            return entries.GetEnumerator();        }        private void CheckWriteable()        {            GrpcPreconditions.CheckState(!readOnly, "Object is read only");        }        #endregion        /// <summary>        /// Metadata entry        /// </summary>        public class Entry        {            private static readonly Encoding Encoding = Encoding.ASCII;            private static readonly Regex ValidKeyRegex = new Regex("^[a-z0-9_-]+$");            readonly string key;            readonly string value;            readonly byte[] valueBytes;            private Entry(string key, string value, byte[] valueBytes)            {                this.key = key;                this.value = value;                this.valueBytes = valueBytes;            }            /// <summary>            /// Initializes a new instance of the <see cref="Grpc.Core.Metadata.Entry"/> struct with a binary value.            /// </summary>            /// <param name="key">Metadata key, needs to have suffix indicating a binary valued metadata entry.</param>            /// <param name="valueBytes">Value bytes.</param>            public Entry(string key, byte[] valueBytes)            {                this.key = NormalizeKey(key);                GrpcPreconditions.CheckArgument(HasBinaryHeaderSuffix(this.key),                    "Key for binary valued metadata entry needs to have suffix indicating binary value.");                this.value = null;                GrpcPreconditions.CheckNotNull(valueBytes, "valueBytes");                this.valueBytes = new byte[valueBytes.Length];                Buffer.BlockCopy(valueBytes, 0, this.valueBytes, 0, valueBytes.Length);  // defensive copy to guarantee immutability            }            /// <summary>            /// Initializes a new instance of the <see cref="Grpc.Core.Metadata.Entry"/> struct holding an ASCII value.            /// </summary>            /// <param name="key">Metadata key, must not use suffix indicating a binary valued metadata entry.</param>            /// <param name="value">Value string. Only ASCII characters are allowed.</param>            public Entry(string key, string value)            {                this.key = NormalizeKey(key);                GrpcPreconditions.CheckArgument(!HasBinaryHeaderSuffix(this.key),                    "Key for ASCII valued metadata entry cannot have suffix indicating binary value.");                this.value = GrpcPreconditions.CheckNotNull(value, "value");                this.valueBytes = null;            }            /// <summary>            /// Gets the metadata entry key.            /// </summary>            public string Key            {                get                {                    return this.key;                }            }            /// <summary>            /// Gets the binary value of this metadata entry.            /// </summary>            public byte[] ValueBytes            {                get                {                    if (valueBytes == null)                    {                        return Encoding.GetBytes(value);                    }                    // defensive copy to guarantee immutability                    var bytes = new byte[valueBytes.Length];                    Buffer.BlockCopy(valueBytes, 0, bytes, 0, valueBytes.Length);                    return bytes;                }            }            /// <summary>            /// Gets the string value of this metadata entry.            /// </summary>            public string Value            {                get                {                    GrpcPreconditions.CheckState(!IsBinary, "Cannot access string value of a binary metadata entry");                    return value ?? Encoding.GetString(valueBytes);                }            }            /// <summary>            /// Returns <c>true</c> if this entry is a binary-value entry.            /// </summary>            public bool IsBinary            {                get                {                    return value == null;                }            }            /// <summary>            /// Returns a <see cref="System.String"/> that represents the current <see cref="Grpc.Core.Metadata.Entry"/>.            /// </summary>            public override string ToString()            {                if (IsBinary)                {                    return string.Format("[Entry: key={0}, valueBytes={1}]", key, valueBytes);                }                                return string.Format("[Entry: key={0}, value={1}]", key, value);            }            /// <summary>            /// Gets the serialized value for this entry. For binary metadata entries, this leaks            /// the internal <c>valueBytes</c> byte array and caller must not change contents of it.            /// </summary>            internal byte[] GetSerializedValueUnsafe()            {                return valueBytes ?? Encoding.GetBytes(value);            }            /// <summary>            /// Creates a binary value or ascii value metadata entry from data received from the native layer.            /// We trust C core to give us well-formed data, so we don't perform any checks or defensive copying.            /// </summary>            internal static Entry CreateUnsafe(string key, byte[] valueBytes)            {                if (HasBinaryHeaderSuffix(key))                {                    return new Entry(key, null, valueBytes);                }                return new Entry(key, Encoding.GetString(valueBytes), null);            }            private static string NormalizeKey(string key)            {                var normalized = GrpcPreconditions.CheckNotNull(key, "key").ToLowerInvariant();                GrpcPreconditions.CheckArgument(ValidKeyRegex.IsMatch(normalized),                     "Metadata entry key not valid. Keys can only contain lowercase alphanumeric characters, underscores and hyphens.");                return normalized;            }            /// <summary>            /// Returns <c>true</c> if the key has "-bin" binary header suffix.            /// </summary>            private static bool HasBinaryHeaderSuffix(string key)            {                // We don't use just string.EndsWith because its implementation is extremely slow                // on CoreCLR and we've seen significant differences in gRPC benchmarks caused by it.                // See https://github.com/dotnet/coreclr/issues/5612                int len = key.Length;                if (len >= 4 &&                    key[len - 4] == '-' &&                    key[len - 3] == 'b' &&                    key[len - 2] == 'i' &&                    key[len - 1] == 'n')                {                    return true;                }                return false;            }        }    }}
 |