< Summary - CoreWCF Coverage — PR #1766

Information
Class: CoreWCF.Runtime.HttpValueCollection
Assembly: CoreWCF.WebHttp
File(s): /home/runner/work/CoreWCF/CoreWCF/src/CoreWCF.WebHttp/src/CoreWCF/Runtime/UrlUtility.cs
Line coverage
43%
Covered lines: 28
Uncovered lines: 37
Coverable lines: 65
Total lines: 531
Line coverage: 43%
Branch coverage
32%
Covered branches: 17
Total branches: 52
Branch coverage: 32.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)100%22100%
.ctor(...)100%110%
FillFromString(...)75%202088.46%
ToString()100%110%
ToString(...)0%30300%

File(s)

/home/runner/work/CoreWCF/CoreWCF/src/CoreWCF.WebHttp/src/CoreWCF/Runtime/UrlUtility.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System;
 5using System.Collections;
 6using System.Collections.Specialized;
 7using System.Runtime.Serialization;
 8using System.Text;
 9
 10namespace CoreWCF.Runtime
 11{
 12    internal static class UrlUtility
 13    {
 14        internal static NameValueCollection ParseQueryString(string query)
 15        {
 16            return ParseQueryString(query, Encoding.UTF8);
 17        }
 18
 19        internal static NameValueCollection ParseQueryString(string query, Encoding encoding)
 20        {
 21            if (query == null)
 22            {
 23                throw Fx.Exception.ArgumentNull(nameof(query));
 24            }
 25
 26            if (encoding == null)
 27            {
 28                throw Fx.Exception.ArgumentNull(nameof(encoding));
 29            }
 30
 31            if (query.Length > 0 && query[0] == '?')
 32            {
 33                query = query.Substring(1);
 34            }
 35
 36            return new HttpValueCollection(query, encoding);
 37        }
 38
 39        internal static string UrlEncode(string str)
 40        {
 41            if (str == null)
 42            {
 43                return null;
 44            }
 45
 46            return UrlEncode(str, Encoding.UTF8);
 47        }
 48
 49        internal static string UrlEncode(string str, Encoding encoding)
 50        {
 51            if (str == null)
 52            {
 53                return null;
 54            }
 55
 56            return Encoding.ASCII.GetString(UrlEncodeToBytes(str, encoding));
 57        }
 58
 59        internal static byte[] UrlEncodeToBytes(string str, Encoding e)
 60        {
 61            if (str == null)
 62            {
 63                return null;
 64            }
 65
 66            byte[] bytes = e.GetBytes(str);
 67            return UrlEncodeBytesToBytesInternal(bytes, 0, bytes.Length, false);
 68        }
 69
 70        internal static string UrlEncodeUnicode(string str)
 71        {
 72            if (str == null)
 73                return null;
 74            return UrlEncodeUnicodeStringToStringInternal(str, false);
 75
 76        }
 77
 78        internal static string UrlDecode(string str, Encoding e)
 79        {
 80            if (str == null)
 81            {
 82                return null;
 83            }
 84
 85            return UrlDecodeStringFromStringInternal(str, e);
 86        }
 87
 88        // Private helpers for URL encoding/decoding
 89        private static string UrlEncodeUnicodeStringToStringInternal(string s, bool ignoreAscii)
 90        {
 91            int l = s.Length;
 92            StringBuilder sb = new StringBuilder(l);
 93
 94            for (int i = 0; i < l; i++)
 95            {
 96                char ch = s[i];
 97
 98                if ((ch & 0xff80) == 0)
 99                {  // 7 bit?
 100                    if (ignoreAscii || IsSafe(ch))
 101                    {
 102                        sb.Append(ch);
 103                    }
 104                    else if (ch == ' ')
 105                    {
 106                        sb.Append('+');
 107                    }
 108                    else
 109                    {
 110                        sb.Append('%');
 111                        sb.Append(IntToHex((ch >> 4) & 0xf));
 112                        sb.Append(IntToHex((ch) & 0xf));
 113                    }
 114                }
 115                else
 116                { // arbitrary Unicode?
 117                    sb.Append("%u");
 118                    sb.Append(IntToHex((ch >> 12) & 0xf));
 119                    sb.Append(IntToHex((ch >> 8) & 0xf));
 120                    sb.Append(IntToHex((ch >> 4) & 0xf));
 121                    sb.Append(IntToHex((ch) & 0xf));
 122                }
 123            }
 124
 125            return sb.ToString();
 126        }
 127
 128        private static byte[] UrlEncodeBytesToBytesInternal(byte[] bytes, int offset, int count, bool alwaysCreateReturn
 129        {
 130            int cSpaces = 0;
 131            int cUnsafe = 0;
 132
 133            // count them first
 134            for (int i = 0; i < count; i++)
 135            {
 136                char ch = (char)bytes[offset + i];
 137
 138                if (ch == ' ')
 139                {
 140                    cSpaces++;
 141                }
 142                else if (!IsSafe(ch))
 143                {
 144                    cUnsafe++;
 145                }
 146            }
 147
 148            // nothing to expand?
 149            if (!alwaysCreateReturnValue && cSpaces == 0 && cUnsafe == 0)
 150            {
 151                return bytes;
 152            }
 153
 154            // expand not 'safe' characters into %XX, spaces to +s
 155            byte[] expandedBytes = new byte[count + cUnsafe * 2];
 156            int pos = 0;
 157
 158            for (int i = 0; i < count; i++)
 159            {
 160                byte b = bytes[offset + i];
 161                char ch = (char)b;
 162
 163                if (IsSafe(ch))
 164                {
 165                    expandedBytes[pos++] = b;
 166                }
 167                else if (ch == ' ')
 168                {
 169                    expandedBytes[pos++] = (byte)'+';
 170                }
 171                else
 172                {
 173                    expandedBytes[pos++] = (byte)'%';
 174                    expandedBytes[pos++] = (byte)IntToHex((b >> 4) & 0xf);
 175                    expandedBytes[pos++] = (byte)IntToHex(b & 0x0f);
 176                }
 177            }
 178
 179            return expandedBytes;
 180        }
 181
 182        private static string UrlDecodeStringFromStringInternal(string s, Encoding e)
 183        {
 184            int count = s.Length;
 185            UrlDecoder helper = new UrlDecoder(count, e);
 186
 187            // go through the string's chars collapsing %XX and %uXXXX and
 188            // appending each char as char, with exception of %XX constructs
 189            // that are appended as bytes
 190
 191            for (int pos = 0; pos < count; pos++)
 192            {
 193                char ch = s[pos];
 194
 195                if (ch == '+')
 196                {
 197                    ch = ' ';
 198                }
 199                else if (ch == '%' && pos < count - 2)
 200                {
 201                    if (s[pos + 1] == 'u' && pos < count - 5)
 202                    {
 203                        int h1 = HexToInt(s[pos + 2]);
 204                        int h2 = HexToInt(s[pos + 3]);
 205                        int h3 = HexToInt(s[pos + 4]);
 206                        int h4 = HexToInt(s[pos + 5]);
 207
 208                        if (h1 >= 0 && h2 >= 0 && h3 >= 0 && h4 >= 0)
 209                        {   // valid 4 hex chars
 210                            ch = (char)((h1 << 12) | (h2 << 8) | (h3 << 4) | h4);
 211                            pos += 5;
 212
 213                            // only add as char
 214                            helper.AddChar(ch);
 215                            continue;
 216                        }
 217                    }
 218                    else
 219                    {
 220                        int h1 = HexToInt(s[pos + 1]);
 221                        int h2 = HexToInt(s[pos + 2]);
 222
 223                        if (h1 >= 0 && h2 >= 0)
 224                        {     // valid 2 hex chars
 225                            byte b = (byte)((h1 << 4) | h2);
 226                            pos += 2;
 227
 228                            // don't add as char
 229                            helper.AddByte(b);
 230                            continue;
 231                        }
 232                    }
 233                }
 234
 235                if ((ch & 0xFF80) == 0)
 236                {
 237                    helper.AddByte((byte)ch); // 7 bit have to go as bytes because of Unicode
 238                }
 239                else
 240                {
 241                    helper.AddChar(ch);
 242                }
 243            }
 244
 245            return helper.GetString();
 246        }
 247
 248        private static int HexToInt(char h)
 249        {
 250            return (h >= '0' && h <= '9') ? h - '0' :
 251            (h >= 'a' && h <= 'f') ? h - 'a' + 10 :
 252            (h >= 'A' && h <= 'F') ? h - 'A' + 10 :
 253            -1;
 254        }
 255
 256        private static char IntToHex(int n)
 257        {
 258            //WCF CHANGE: CHANGED FROM Debug.Assert() to Fx.Assert()
 259            Fx.Assert(n < 0x10, "n < 0x10");
 260
 261            if (n <= 9)
 262            {
 263                return (char)(n + '0');
 264            }
 265            else
 266            {
 267                return (char)(n - 10 + 'a');
 268            }
 269        }
 270
 271        // Set of safe chars, from RFC 1738.4 minus '+'
 272        internal static bool IsSafe(char ch)
 273        {
 274            if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '0' && ch <= '9')
 275            {
 276                return true;
 277            }
 278
 279            switch (ch)
 280            {
 281                case '-':
 282                case '_':
 283                case '.':
 284                case '!':
 285                case '*':
 286                case '\'':
 287                case '(':
 288                case ')':
 289                    return true;
 290            }
 291
 292            return false;
 293        }
 294
 295        // Internal class to facilitate URL decoding -- keeps char buffer and byte buffer, allows appending of either ch
 296        private class UrlDecoder
 297        {
 298            private readonly int _bufferSize;
 299
 300            // Accumulate characters in a special array
 301            private int _numChars;
 302            private readonly char[] _charBuffer;
 303
 304            // Accumulate bytes for decoding into characters in a special array
 305            private int _numBytes;
 306            private byte[] _byteBuffer;
 307
 308            // Encoding to convert chars to bytes
 309            private readonly Encoding _encoding;
 310
 311            private void FlushBytes()
 312            {
 313                if (_numBytes > 0)
 314                {
 315                    _numChars += _encoding.GetChars(_byteBuffer, 0, _numBytes, _charBuffer, _numChars);
 316                    _numBytes = 0;
 317                }
 318            }
 319
 320            internal UrlDecoder(int bufferSize, Encoding encoding)
 321            {
 322                _bufferSize = bufferSize;
 323                _encoding = encoding;
 324
 325                _charBuffer = new char[bufferSize];
 326                // byte buffer created on demand
 327            }
 328
 329            internal void AddChar(char ch)
 330            {
 331                if (_numBytes > 0)
 332                {
 333                    FlushBytes();
 334                }
 335
 336                _charBuffer[_numChars++] = ch;
 337            }
 338
 339            internal void AddByte(byte b)
 340            {
 341                // if there are no pending bytes treat 7 bit bytes as characters
 342                // this optimization is temp disable as it doesn't work for some encodings
 343
 344                //if (_numBytes == 0 && ((b & 0x80) == 0)) {
 345                //    AddChar((char)b);
 346                //}
 347                //else
 348
 349                {
 350                    if (_byteBuffer == null)
 351                    {
 352                        _byteBuffer = new byte[_bufferSize];
 353                    }
 354
 355                    _byteBuffer[_numBytes++] = b;
 356                }
 357            }
 358
 359            internal string GetString()
 360            {
 361                if (_numBytes > 0)
 362                {
 363                    FlushBytes();
 364                }
 365
 366                if (_numChars > 0)
 367                {
 368                    return new string(_charBuffer, 0, _numChars);
 369                }
 370                else
 371                {
 372                    return string.Empty;
 373                }
 374            }
 375        }
 376    }
 377
 378    [Serializable]
 379    internal class HttpValueCollection : NameValueCollection
 380    {
 381        internal HttpValueCollection(string str, Encoding encoding)
 1382            : base(StringComparer.OrdinalIgnoreCase)
 383        {
 1384            if (!string.IsNullOrEmpty(str))
 385            {
 1386                FillFromString(str, true, encoding);
 387            }
 388
 1389            IsReadOnly = false;
 1390        }
 391
 392        protected HttpValueCollection(SerializationInfo info, StreamingContext context)
 0393            : base(info, context)
 394        {
 0395        }
 396
 397        internal void FillFromString(string s, bool urlencoded, Encoding encoding)
 398        {
 1399            int l = (s != null) ? s.Length : 0;
 1400            int i = 0;
 401
 2402            while (i < l)
 403            {
 404                // find next & while noting first = on the way (and if there are more)
 405
 1406                int si = i;
 1407                int ti = -1;
 408
 11409                while (i < l)
 410                {
 10411                    char ch = s[i];
 412
 10413                    if (ch == '=')
 414                    {
 1415                        if (ti < 0)
 1416                            ti = i;
 417                    }
 9418                    else if (ch == '&')
 419                    {
 420                        break;
 421                    }
 422
 10423                    i++;
 424                }
 425
 426                // extract the name / value pair
 427
 1428                string name = null;
 429                string value;
 1430                if (ti >= 0)
 431                {
 1432                    name = s.Substring(si, ti - si);
 1433                    value = s.Substring(ti + 1, i - ti - 1);
 434                }
 435                else
 436                {
 0437                    value = s.Substring(si, i - si);
 438                }
 439
 440                // add name / value pair to the collection
 441
 1442                if (urlencoded)
 443                {
 1444                    base.Add(
 1445                       UrlUtility.UrlDecode(name, encoding),
 1446                       UrlUtility.UrlDecode(value, encoding));
 447                }
 448                else
 449                {
 0450                    base.Add(name, value);
 451                }
 452
 453                // trailing '&'
 454
 1455                if (i == l - 1 && s[i] == '&')
 456                {
 0457                    base.Add(null, string.Empty);
 458                }
 459
 1460                i++;
 461            }
 1462        }
 463
 0464        public override string ToString() => ToString(true, null);
 465
 466        private string ToString(bool urlencoded, IDictionary excludeKeys)
 467        {
 0468            int n = Count;
 0469            if (n == 0)
 0470                return string.Empty;
 471
 0472            StringBuilder s = new StringBuilder();
 473            string key, keyPrefix, item;
 474
 0475            for (int i = 0; i < n; i++)
 476            {
 0477                key = GetKey(i);
 478
 0479                if (excludeKeys != null && key != null && excludeKeys[key] != null)
 480                {
 481                    continue;
 482                }
 0483                if (urlencoded)
 484                {
 0485                    key = UrlUtility.UrlEncodeUnicode(key);
 486                }
 0487                keyPrefix = (!string.IsNullOrEmpty(key)) ? (key + "=") : string.Empty;
 488
 0489                ArrayList values = (ArrayList)BaseGet(i);
 0490                int numValues = (values != null) ? values.Count : 0;
 491
 0492                if (s.Length > 0)
 493                {
 0494                    s.Append('&');
 495                }
 496
 0497                if (numValues == 1)
 498                {
 0499                    s.Append(keyPrefix);
 0500                    item = (string)values[0];
 0501                    if (urlencoded)
 0502                        item = UrlUtility.UrlEncodeUnicode(item);
 0503                    s.Append(item);
 504                }
 0505                else if (numValues == 0)
 506                {
 0507                    s.Append(keyPrefix);
 508                }
 509                else
 510                {
 0511                    for (int j = 0; j < numValues; j++)
 512                    {
 0513                        if (j > 0)
 514                        {
 0515                            s.Append('&');
 516                        }
 0517                        s.Append(keyPrefix);
 0518                        item = (string)values[j];
 0519                        if (urlencoded)
 520                        {
 0521                            item = UrlUtility.UrlEncodeUnicode(item);
 522                        }
 0523                        s.Append(item);
 524                    }
 525                }
 526            }
 527
 0528            return s.ToString();
 529        }
 530    }
 531}