< Summary - CoreWCF Coverage — PR #1766

Information
Class: CoreWCF.Runtime.UrlUtility
Assembly: CoreWCF.WebHttp
File(s): /home/runner/work/CoreWCF/CoreWCF/src/CoreWCF.WebHttp/src/CoreWCF/Runtime/UrlUtility.cs
Line coverage
26%
Covered lines: 34
Uncovered lines: 92
Coverable lines: 126
Total lines: 531
Line coverage: 26.9%
Branch coverage
21%
Covered branches: 26
Total branches: 121
Branch coverage: 21.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
ParseQueryString(...)100%11100%
ParseQueryString(...)75%8871.42%
UrlEncode(...)0%220%
UrlEncode(...)0%220%
UrlEncodeToBytes(...)0%220%
UrlEncodeUnicode(...)0%220%
UrlDecode(...)50%2266.66%
UrlEncodeUnicodeStringToStringInternal(...)0%10100%
UrlEncodeBytesToBytesInternal(...)0%18180%
UrlDecodeStringFromStringInternal(...)46.15%262632.14%
HexToInt(...)0%12120%
IntToHex(...)0%220%
IsSafe(...)0%25250%
FlushBytes()100%22100%
.ctor(...)100%11100%
AddChar(...)0%220%
AddByte(...)100%22100%
GetString()75%4480%

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        {
 116            return ParseQueryString(query, Encoding.UTF8);
 17        }
 18
 19        internal static NameValueCollection ParseQueryString(string query, Encoding encoding)
 20        {
 121            if (query == null)
 22            {
 023                throw Fx.Exception.ArgumentNull(nameof(query));
 24            }
 25
 126            if (encoding == null)
 27            {
 028                throw Fx.Exception.ArgumentNull(nameof(encoding));
 29            }
 30
 131            if (query.Length > 0 && query[0] == '?')
 32            {
 133                query = query.Substring(1);
 34            }
 35
 136            return new HttpValueCollection(query, encoding);
 37        }
 38
 39        internal static string UrlEncode(string str)
 40        {
 041            if (str == null)
 42            {
 043                return null;
 44            }
 45
 046            return UrlEncode(str, Encoding.UTF8);
 47        }
 48
 49        internal static string UrlEncode(string str, Encoding encoding)
 50        {
 051            if (str == null)
 52            {
 053                return null;
 54            }
 55
 056            return Encoding.ASCII.GetString(UrlEncodeToBytes(str, encoding));
 57        }
 58
 59        internal static byte[] UrlEncodeToBytes(string str, Encoding e)
 60        {
 061            if (str == null)
 62            {
 063                return null;
 64            }
 65
 066            byte[] bytes = e.GetBytes(str);
 067            return UrlEncodeBytesToBytesInternal(bytes, 0, bytes.Length, false);
 68        }
 69
 70        internal static string UrlEncodeUnicode(string str)
 71        {
 072            if (str == null)
 073                return null;
 074            return UrlEncodeUnicodeStringToStringInternal(str, false);
 75
 76        }
 77
 78        internal static string UrlDecode(string str, Encoding e)
 79        {
 3180            if (str == null)
 81            {
 082                return null;
 83            }
 84
 3185            return UrlDecodeStringFromStringInternal(str, e);
 86        }
 87
 88        // Private helpers for URL encoding/decoding
 89        private static string UrlEncodeUnicodeStringToStringInternal(string s, bool ignoreAscii)
 90        {
 091            int l = s.Length;
 092            StringBuilder sb = new StringBuilder(l);
 93
 094            for (int i = 0; i < l; i++)
 95            {
 096                char ch = s[i];
 97
 098                if ((ch & 0xff80) == 0)
 99                {  // 7 bit?
 0100                    if (ignoreAscii || IsSafe(ch))
 101                    {
 0102                        sb.Append(ch);
 103                    }
 0104                    else if (ch == ' ')
 105                    {
 0106                        sb.Append('+');
 107                    }
 108                    else
 109                    {
 0110                        sb.Append('%');
 0111                        sb.Append(IntToHex((ch >> 4) & 0xf));
 0112                        sb.Append(IntToHex((ch) & 0xf));
 113                    }
 114                }
 115                else
 116                { // arbitrary Unicode?
 0117                    sb.Append("%u");
 0118                    sb.Append(IntToHex((ch >> 12) & 0xf));
 0119                    sb.Append(IntToHex((ch >> 8) & 0xf));
 0120                    sb.Append(IntToHex((ch >> 4) & 0xf));
 0121                    sb.Append(IntToHex((ch) & 0xf));
 122                }
 123            }
 124
 0125            return sb.ToString();
 126        }
 127
 128        private static byte[] UrlEncodeBytesToBytesInternal(byte[] bytes, int offset, int count, bool alwaysCreateReturn
 129        {
 0130            int cSpaces = 0;
 0131            int cUnsafe = 0;
 132
 133            // count them first
 0134            for (int i = 0; i < count; i++)
 135            {
 0136                char ch = (char)bytes[offset + i];
 137
 0138                if (ch == ' ')
 139                {
 0140                    cSpaces++;
 141                }
 0142                else if (!IsSafe(ch))
 143                {
 0144                    cUnsafe++;
 145                }
 146            }
 147
 148            // nothing to expand?
 0149            if (!alwaysCreateReturnValue && cSpaces == 0 && cUnsafe == 0)
 150            {
 0151                return bytes;
 152            }
 153
 154            // expand not 'safe' characters into %XX, spaces to +s
 0155            byte[] expandedBytes = new byte[count + cUnsafe * 2];
 0156            int pos = 0;
 157
 0158            for (int i = 0; i < count; i++)
 159            {
 0160                byte b = bytes[offset + i];
 0161                char ch = (char)b;
 162
 0163                if (IsSafe(ch))
 164                {
 0165                    expandedBytes[pos++] = b;
 166                }
 0167                else if (ch == ' ')
 168                {
 0169                    expandedBytes[pos++] = (byte)'+';
 170                }
 171                else
 172                {
 0173                    expandedBytes[pos++] = (byte)'%';
 0174                    expandedBytes[pos++] = (byte)IntToHex((b >> 4) & 0xf);
 0175                    expandedBytes[pos++] = (byte)IntToHex(b & 0x0f);
 176                }
 177            }
 178
 0179            return expandedBytes;
 180        }
 181
 182        private static string UrlDecodeStringFromStringInternal(string s, Encoding e)
 183        {
 31184            int count = s.Length;
 31185            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
 350191            for (int pos = 0; pos < count; pos++)
 192            {
 144193                char ch = s[pos];
 194
 144195                if (ch == '+')
 196                {
 0197                    ch = ' ';
 198                }
 144199                else if (ch == '%' && pos < count - 2)
 200                {
 0201                    if (s[pos + 1] == 'u' && pos < count - 5)
 202                    {
 0203                        int h1 = HexToInt(s[pos + 2]);
 0204                        int h2 = HexToInt(s[pos + 3]);
 0205                        int h3 = HexToInt(s[pos + 4]);
 0206                        int h4 = HexToInt(s[pos + 5]);
 207
 0208                        if (h1 >= 0 && h2 >= 0 && h3 >= 0 && h4 >= 0)
 209                        {   // valid 4 hex chars
 0210                            ch = (char)((h1 << 12) | (h2 << 8) | (h3 << 4) | h4);
 0211                            pos += 5;
 212
 213                            // only add as char
 0214                            helper.AddChar(ch);
 0215                            continue;
 216                        }
 217                    }
 218                    else
 219                    {
 0220                        int h1 = HexToInt(s[pos + 1]);
 0221                        int h2 = HexToInt(s[pos + 2]);
 222
 0223                        if (h1 >= 0 && h2 >= 0)
 224                        {     // valid 2 hex chars
 0225                            byte b = (byte)((h1 << 4) | h2);
 0226                            pos += 2;
 227
 228                            // don't add as char
 0229                            helper.AddByte(b);
 0230                            continue;
 231                        }
 232                    }
 233                }
 234
 144235                if ((ch & 0xFF80) == 0)
 236                {
 144237                    helper.AddByte((byte)ch); // 7 bit have to go as bytes because of Unicode
 238                }
 239                else
 240                {
 0241                    helper.AddChar(ch);
 242                }
 243            }
 244
 31245            return helper.GetString();
 246        }
 247
 248        private static int HexToInt(char h)
 249        {
 0250            return (h >= '0' && h <= '9') ? h - '0' :
 0251            (h >= 'a' && h <= 'f') ? h - 'a' + 10 :
 0252            (h >= 'A' && h <= 'F') ? h - 'A' + 10 :
 0253            -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
 0261            if (n <= 9)
 262            {
 0263                return (char)(n + '0');
 264            }
 265            else
 266            {
 0267                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        {
 0274            if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '0' && ch <= '9')
 275            {
 0276                return true;
 277            }
 278
 279            switch (ch)
 280            {
 281                case '-':
 282                case '_':
 283                case '.':
 284                case '!':
 285                case '*':
 286                case '\'':
 287                case '(':
 288                case ')':
 0289                    return true;
 290            }
 291
 0292            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            {
 31313                if (_numBytes > 0)
 314                {
 31315                    _numChars += _encoding.GetChars(_byteBuffer, 0, _numBytes, _charBuffer, _numChars);
 31316                    _numBytes = 0;
 317                }
 31318            }
 319
 31320            internal UrlDecoder(int bufferSize, Encoding encoding)
 321            {
 31322                _bufferSize = bufferSize;
 31323                _encoding = encoding;
 324
 31325                _charBuffer = new char[bufferSize];
 326                // byte buffer created on demand
 31327            }
 328
 329            internal void AddChar(char ch)
 330            {
 0331                if (_numBytes > 0)
 332                {
 0333                    FlushBytes();
 334                }
 335
 0336                _charBuffer[_numChars++] = ch;
 0337            }
 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                {
 144350                    if (_byteBuffer == null)
 351                    {
 31352                        _byteBuffer = new byte[_bufferSize];
 353                    }
 354
 144355                    _byteBuffer[_numBytes++] = b;
 356                }
 144357            }
 358
 359            internal string GetString()
 360            {
 31361                if (_numBytes > 0)
 362                {
 31363                    FlushBytes();
 364                }
 365
 31366                if (_numChars > 0)
 367                {
 31368                    return new string(_charBuffer, 0, _numChars);
 369                }
 370                else
 371                {
 0372                    return string.Empty;
 373                }
 374            }
 375        }
 376    }
 377
 378    [Serializable]
 379    internal class HttpValueCollection : NameValueCollection
 380    {
 381        internal HttpValueCollection(string str, Encoding encoding)
 382            : base(StringComparer.OrdinalIgnoreCase)
 383        {
 384            if (!string.IsNullOrEmpty(str))
 385            {
 386                FillFromString(str, true, encoding);
 387            }
 388
 389            IsReadOnly = false;
 390        }
 391
 392        protected HttpValueCollection(SerializationInfo info, StreamingContext context)
 393            : base(info, context)
 394        {
 395        }
 396
 397        internal void FillFromString(string s, bool urlencoded, Encoding encoding)
 398        {
 399            int l = (s != null) ? s.Length : 0;
 400            int i = 0;
 401
 402            while (i < l)
 403            {
 404                // find next & while noting first = on the way (and if there are more)
 405
 406                int si = i;
 407                int ti = -1;
 408
 409                while (i < l)
 410                {
 411                    char ch = s[i];
 412
 413                    if (ch == '=')
 414                    {
 415                        if (ti < 0)
 416                            ti = i;
 417                    }
 418                    else if (ch == '&')
 419                    {
 420                        break;
 421                    }
 422
 423                    i++;
 424                }
 425
 426                // extract the name / value pair
 427
 428                string name = null;
 429                string value;
 430                if (ti >= 0)
 431                {
 432                    name = s.Substring(si, ti - si);
 433                    value = s.Substring(ti + 1, i - ti - 1);
 434                }
 435                else
 436                {
 437                    value = s.Substring(si, i - si);
 438                }
 439
 440                // add name / value pair to the collection
 441
 442                if (urlencoded)
 443                {
 444                    base.Add(
 445                       UrlUtility.UrlDecode(name, encoding),
 446                       UrlUtility.UrlDecode(value, encoding));
 447                }
 448                else
 449                {
 450                    base.Add(name, value);
 451                }
 452
 453                // trailing '&'
 454
 455                if (i == l - 1 && s[i] == '&')
 456                {
 457                    base.Add(null, string.Empty);
 458                }
 459
 460                i++;
 461            }
 462        }
 463
 464        public override string ToString() => ToString(true, null);
 465
 466        private string ToString(bool urlencoded, IDictionary excludeKeys)
 467        {
 468            int n = Count;
 469            if (n == 0)
 470                return string.Empty;
 471
 472            StringBuilder s = new StringBuilder();
 473            string key, keyPrefix, item;
 474
 475            for (int i = 0; i < n; i++)
 476            {
 477                key = GetKey(i);
 478
 479                if (excludeKeys != null && key != null && excludeKeys[key] != null)
 480                {
 481                    continue;
 482                }
 483                if (urlencoded)
 484                {
 485                    key = UrlUtility.UrlEncodeUnicode(key);
 486                }
 487                keyPrefix = (!string.IsNullOrEmpty(key)) ? (key + "=") : string.Empty;
 488
 489                ArrayList values = (ArrayList)BaseGet(i);
 490                int numValues = (values != null) ? values.Count : 0;
 491
 492                if (s.Length > 0)
 493                {
 494                    s.Append('&');
 495                }
 496
 497                if (numValues == 1)
 498                {
 499                    s.Append(keyPrefix);
 500                    item = (string)values[0];
 501                    if (urlencoded)
 502                        item = UrlUtility.UrlEncodeUnicode(item);
 503                    s.Append(item);
 504                }
 505                else if (numValues == 0)
 506                {
 507                    s.Append(keyPrefix);
 508                }
 509                else
 510                {
 511                    for (int j = 0; j < numValues; j++)
 512                    {
 513                        if (j > 0)
 514                        {
 515                            s.Append('&');
 516                        }
 517                        s.Append(keyPrefix);
 518                        item = (string)values[j];
 519                        if (urlencoded)
 520                        {
 521                            item = UrlUtility.UrlEncodeUnicode(item);
 522                        }
 523                        s.Append(item);
 524                    }
 525                }
 526            }
 527
 528            return s.ToString();
 529        }
 530    }
 531}