< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 287
Coverable lines: 287
Total lines: 914
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 194
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.cctor()100%110%
.ctor()100%110%
.ctor(...)100%110%
MakeTables()0%10100%
SetDefaultFallbacks()100%110%
Equals(...)0%660%
GetHashCode()100%110%
GetByteCount(...)0%880%
GetByteCount(...)0%440%
GetByteCount(...)100%110%
GetBytes(...)0%880%
GetBytes(...)0%12120%
GetBytes(...)100%110%
GetCharCount(...)0%880%
GetCharCount(...)100%110%
GetChars(...)0%12120%
GetChars(...)100%110%
GetString(...)0%880%
GetByteCount(...)100%110%
GetBytes(...)0%54540%
GetCharCount(...)100%110%
GetChars(...)0%42420%
GetDecoder()100%110%
GetEncoder()100%110%
GetMaxByteCount(...)0%220%
GetMaxCharCount(...)0%220%
.ctor(...)100%110%
Reset()0%220%
.ctor(...)100%110%
Reset()0%220%
CreateFallbackBuffer()100%110%
Equals(...)100%110%
GetHashCode()100%110%
.ctor()100%110%
Fallback(...)0%220%
GetNextChar()0%220%
MovePrevious()0%440%
Reset()100%110%
InternalFallback(...)0%220%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Private.CoreLib/src/System/Text/UTF7Encoding.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
 4//
 5// Don't override IsAlwaysNormalized because it is just a Unicode Transformation and could be confused.
 6//
 7
 8using System.Diagnostics;
 9using System.Diagnostics.CodeAnalysis;
 10using System.Runtime.InteropServices;
 11
 12namespace System.Text
 13{
 14    public class UTF7Encoding : Encoding
 15    {
 16#pragma warning disable SYSLIB0001
 17        // Used by Encoding.UTF7 for lazy initialization
 18        // The initialization code will not be run until a static member of the class is referenced
 019        internal static readonly UTF7Encoding s_default = new UTF7Encoding();
 20#pragma warning restore SYSLIB0001
 21
 22        // The set of base 64 characters.
 23        private byte[] _base64Bytes;
 24        // The decoded bits for every base64 values. This array has a size of 128 elements.
 25        // The index is the code point value of the base 64 characters.  The value is -1 if
 26        // the code point is not a valid base 64 character.  Otherwise, the value is a value
 27        // from 0 ~ 63.
 28        private sbyte[] _base64Values;
 29        // The array to decide if a Unicode code point below 0x80 can be directly encoded in UTF7.
 30        // This array has a size of 128.
 31        private bool[] _directEncode;
 32
 33        private readonly bool _allowOptionals;
 34
 35        private const int UTF7_CODEPAGE = 65000;
 36
 37        [Obsolete(Obsoletions.SystemTextEncodingUTF7Message, DiagnosticId = Obsoletions.SystemTextEncodingUTF7DiagId, Ur
 38        public UTF7Encoding()
 039            : this(false)
 40        {
 041        }
 42
 43        [Obsolete(Obsoletions.SystemTextEncodingUTF7Message, DiagnosticId = Obsoletions.SystemTextEncodingUTF7DiagId, Ur
 44        public UTF7Encoding(bool allowOptionals)
 045            : base(UTF7_CODEPAGE) // Set the data item.
 46        {
 47            // Allowing optionals?
 048            _allowOptionals = allowOptionals;
 49
 50            // Make our tables
 051            MakeTables();
 052        }
 53
 54        [MemberNotNull(nameof(_base64Bytes))]
 55        [MemberNotNull(nameof(_base64Values))]
 56        [MemberNotNull(nameof(_directEncode))]
 57        private void MakeTables()
 58        {
 59            // Build our tables
 60
 061            _base64Bytes = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"u8.ToArray();
 062            Debug.Assert(_base64Bytes.Length == 64);
 63
 064            _base64Values = new sbyte[128];
 065            for (int i = 0; i < 128; i++) _base64Values[i] = -1;
 066            for (int i = 0; i < 64; i++) _base64Values[_base64Bytes[i]] = (sbyte)i;
 67
 68            // These are the characters that can be directly encoded in UTF7.
 069            ReadOnlySpan<byte> directChars = "\t\n\r '(),-./0123456789:?ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuv
 070            _directEncode = new bool[128];
 071            foreach (byte c in directChars)
 72            {
 073                _directEncode[c] = true;
 74            }
 75
 076            if (_allowOptionals)
 77            {
 78                // These are the characters that can be optionally directly encoded in UTF7.
 079                ReadOnlySpan<byte> optionalChars = "!\"#$%&*;<=>@[]^_`{|}"u8;
 80
 081                foreach (byte c in optionalChars)
 82                {
 083                    _directEncode[c] = true;
 84                }
 85            }
 086        }
 87
 88        // We go ahead and set this because Encoding expects it, however nothing can fall back in UTF7.
 89        internal sealed override void SetDefaultFallbacks()
 90        {
 91            // UTF7 had an odd decoderFallback behavior, and the Encoder fallback
 92            // is irrelevant because we encode surrogates individually and never check for unmatched ones
 93            // (so nothing can fallback during encoding)
 094            this.encoderFallback = new EncoderReplacementFallback(string.Empty);
 095            this.decoderFallback = new DecoderUTF7Fallback();
 096        }
 97
 98        public override bool Equals([NotNullWhen(true)] object? value)
 99        {
 0100            if (value is UTF7Encoding that)
 101            {
 0102                return (_allowOptionals == that._allowOptionals) &&
 0103                       (EncoderFallback.Equals(that.EncoderFallback)) &&
 0104                       (DecoderFallback.Equals(that.DecoderFallback));
 105            }
 0106            return false;
 107        }
 108
 109        // Compared to all the other encodings, variations of UTF7 are unlikely
 110
 111        public override int GetHashCode()
 112        {
 0113            return this.CodePage + this.EncoderFallback.GetHashCode() + this.DecoderFallback.GetHashCode();
 114        }
 115
 116        // The following methods are copied from EncodingNLS.cs.
 117        // Unfortunately EncodingNLS.cs is internal and we're public, so we have to re-implement them here.
 118        // These should be kept in sync for the following classes:
 119        // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
 120
 121        // Returns the number of bytes required to encode a range of characters in
 122        // a character array.
 123        //
 124        // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
 125        // So if you fix this, fix the others.  Currently those include:
 126        // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
 127        // parent method is safe
 128
 129        public override unsafe int GetByteCount(char[] chars, int index, int count)
 130        {
 0131            ArgumentNullException.ThrowIfNull(chars);
 132
 0133            ArgumentOutOfRangeException.ThrowIfNegative(index);
 0134            ArgumentOutOfRangeException.ThrowIfNegative(count);
 135
 0136            if (chars.Length - index < count)
 0137                throw new ArgumentOutOfRangeException(nameof(chars), SR.ArgumentOutOfRange_IndexCountBuffer);
 138
 139            // If no input, return 0, avoid fixed empty array problem
 0140            if (count == 0)
 0141                return 0;
 142
 143            // Just call the pointer version
 0144            fixed (char* pChars = chars)
 0145                return GetByteCount(pChars + index, count, null);
 146        }
 147
 148        // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
 149        // So if you fix this, fix the others.  Currently those include:
 150        // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
 151        // parent method is safe
 152
 153        public override unsafe int GetByteCount(string s)
 154        {
 0155            if (s is null)
 156            {
 0157                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
 158            }
 159
 0160            fixed (char* pChars = s)
 0161                return GetByteCount(pChars, s.Length, null);
 162        }
 163
 164        // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
 165        // So if you fix this, fix the others.  Currently those include:
 166        // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
 167
 168        [CLSCompliant(false)]
 169        public override unsafe int GetByteCount(char* chars, int count)
 170        {
 0171            ArgumentNullException.ThrowIfNull(chars);
 172
 0173            ArgumentOutOfRangeException.ThrowIfNegative(count);
 174
 175            // Call it with empty encoder
 0176            return GetByteCount(chars, count, null);
 177        }
 178
 179        // Parent method is safe.
 180        // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
 181        // So if you fix this, fix the others.  Currently those include:
 182        // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
 183
 184        public override unsafe int GetBytes(string s, int charIndex, int charCount,
 185                                            byte[] bytes, int byteIndex)
 186        {
 0187            ArgumentNullException.ThrowIfNull(s);
 0188            ArgumentNullException.ThrowIfNull(bytes);
 189
 0190            ArgumentOutOfRangeException.ThrowIfNegative(charIndex);
 0191            ArgumentOutOfRangeException.ThrowIfNegative(charCount);
 192
 0193            if (s.Length - charIndex < charCount)
 0194                throw new ArgumentOutOfRangeException(nameof(s), SR.ArgumentOutOfRange_IndexCount);
 195
 0196            if (byteIndex < 0 || byteIndex > bytes.Length)
 0197                throw new ArgumentOutOfRangeException(nameof(byteIndex), SR.ArgumentOutOfRange_IndexMustBeLessOrEqual);
 198
 0199            int byteCount = bytes.Length - byteIndex;
 200
 0201            fixed (char* pChars = s)
 0202            fixed (byte* pBytes = &MemoryMarshal.GetArrayDataReference(bytes))
 203            {
 0204                return GetBytes(pChars + charIndex, charCount, pBytes + byteIndex, byteCount, null);
 205            }
 206        }
 207
 208        // Encodes a range of characters in a character array into a range of bytes
 209        // in a byte array. An exception occurs if the byte array is not large
 210        // enough to hold the complete encoding of the characters. The
 211        // GetByteCount method can be used to determine the exact number of
 212        // bytes that will be produced for a given range of characters.
 213        // Alternatively, the GetMaxByteCount method can be used to
 214        // determine the maximum number of bytes that will be produced for a given
 215        // number of characters, regardless of the actual character values.
 216        //
 217        // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
 218        // So if you fix this, fix the others.  Currently those include:
 219        // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
 220        // parent method is safe
 221
 222        public override unsafe int GetBytes(char[] chars, int charIndex, int charCount,
 223                                            byte[] bytes, int byteIndex)
 224        {
 0225            ArgumentNullException.ThrowIfNull(chars);
 0226            ArgumentNullException.ThrowIfNull(bytes);
 227
 0228            ArgumentOutOfRangeException.ThrowIfNegative(charIndex);
 0229            ArgumentOutOfRangeException.ThrowIfNegative(charCount);
 230
 0231            if (chars.Length - charIndex < charCount)
 0232                throw new ArgumentOutOfRangeException(nameof(chars), SR.ArgumentOutOfRange_IndexCountBuffer);
 233
 0234            if (byteIndex < 0 || byteIndex > bytes.Length)
 0235                throw new ArgumentOutOfRangeException(nameof(byteIndex), SR.ArgumentOutOfRange_IndexMustBeLessOrEqual);
 236
 237            // If nothing to encode return 0, avoid fixed problem
 0238            if (charCount == 0)
 0239                return 0;
 240
 241            // Just call pointer version
 0242            int byteCount = bytes.Length - byteIndex;
 243
 0244            fixed (char* pChars = chars)
 0245            fixed (byte* pBytes = &MemoryMarshal.GetArrayDataReference(bytes))
 246            {
 247                // Remember that byteCount is # to decode, not size of array.
 0248                return GetBytes(pChars + charIndex, charCount, pBytes + byteIndex, byteCount, null);
 249            }
 250        }
 251
 252        // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
 253        // So if you fix this, fix the others.  Currently those include:
 254        // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
 255
 256        [CLSCompliant(false)]
 257        public override unsafe int GetBytes(char* chars, int charCount, byte* bytes, int byteCount)
 258        {
 0259            ArgumentNullException.ThrowIfNull(chars);
 0260            ArgumentNullException.ThrowIfNull(bytes);
 261
 0262            ArgumentOutOfRangeException.ThrowIfNegative(charCount);
 0263            ArgumentOutOfRangeException.ThrowIfNegative(byteCount);
 264
 0265            return GetBytes(chars, charCount, bytes, byteCount, null);
 266        }
 267
 268        // Returns the number of characters produced by decoding a range of bytes
 269        // in a byte array.
 270        //
 271        // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
 272        // So if you fix this, fix the others.  Currently those include:
 273        // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
 274        // parent method is safe
 275
 276        public override unsafe int GetCharCount(byte[] bytes, int index, int count)
 277        {
 0278            ArgumentNullException.ThrowIfNull(bytes);
 279
 0280            ArgumentOutOfRangeException.ThrowIfNegative(index);
 0281            ArgumentOutOfRangeException.ThrowIfNegative(count);
 282
 0283            if (bytes.Length - index < count)
 0284                throw new ArgumentOutOfRangeException(nameof(bytes), SR.ArgumentOutOfRange_IndexCountBuffer);
 285
 286            // If no input just return 0, fixed doesn't like 0 length arrays.
 0287            if (count == 0)
 0288                return 0;
 289
 290            // Just call pointer version
 0291            fixed (byte* pBytes = bytes)
 0292                return GetCharCount(pBytes + index, count, null);
 293        }
 294
 295        // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
 296        // So if you fix this, fix the others.  Currently those include:
 297        // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
 298
 299        [CLSCompliant(false)]
 300        public override unsafe int GetCharCount(byte* bytes, int count)
 301        {
 0302            ArgumentNullException.ThrowIfNull(bytes);
 303
 0304            ArgumentOutOfRangeException.ThrowIfNegative(count);
 305
 0306            return GetCharCount(bytes, count, null);
 307        }
 308
 309        // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
 310        // So if you fix this, fix the others.  Currently those include:
 311        // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
 312        // parent method is safe
 313
 314        public override unsafe int GetChars(byte[] bytes, int byteIndex, int byteCount,
 315                                            char[] chars, int charIndex)
 316        {
 0317            ArgumentNullException.ThrowIfNull(bytes);
 0318            ArgumentNullException.ThrowIfNull(chars);
 319
 0320            ArgumentOutOfRangeException.ThrowIfNegative(byteIndex);
 0321            ArgumentOutOfRangeException.ThrowIfNegative(byteCount);
 322
 0323            if (bytes.Length - byteIndex < byteCount)
 0324                throw new ArgumentOutOfRangeException(nameof(bytes), SR.ArgumentOutOfRange_IndexCountBuffer);
 325
 0326            if (charIndex < 0 || charIndex > chars.Length)
 0327                throw new ArgumentOutOfRangeException(nameof(charIndex), SR.ArgumentOutOfRange_IndexMustBeLessOrEqual);
 328
 329            // If no input, return 0 & avoid fixed problem
 0330            if (byteCount == 0)
 0331                return 0;
 332
 333            // Just call pointer version
 0334            int charCount = chars.Length - charIndex;
 335
 0336            fixed (byte* pBytes = bytes)
 0337            fixed (char* pChars = &MemoryMarshal.GetArrayDataReference(chars))
 338            {
 339                // Remember that charCount is # to decode, not size of array
 0340                return GetChars(pBytes + byteIndex, byteCount, pChars + charIndex, charCount, null);
 341            }
 342        }
 343
 344        // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
 345        // So if you fix this, fix the others.  Currently those include:
 346        // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
 347
 348        [CLSCompliant(false)]
 349        public override unsafe int GetChars(byte* bytes, int byteCount, char* chars, int charCount)
 350        {
 0351            ArgumentNullException.ThrowIfNull(bytes);
 0352            ArgumentNullException.ThrowIfNull(chars);
 353
 0354            ArgumentOutOfRangeException.ThrowIfNegative(charCount);
 0355            ArgumentOutOfRangeException.ThrowIfNegative(byteCount);
 356
 0357            return GetChars(bytes, byteCount, chars, charCount, null);
 358        }
 359
 360        // Returns a string containing the decoded representation of a range of
 361        // bytes in a byte array.
 362        //
 363        // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS)
 364        // So if you fix this, fix the others.  Currently those include:
 365        // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding
 366        // parent method is safe
 367
 368        public override unsafe string GetString(byte[] bytes, int index, int count)
 369        {
 0370            ArgumentNullException.ThrowIfNull(bytes);
 371
 0372            ArgumentOutOfRangeException.ThrowIfNegative(index);
 0373            ArgumentOutOfRangeException.ThrowIfNegative(count);
 374
 0375            if (bytes.Length - index < count)
 0376                throw new ArgumentOutOfRangeException(nameof(bytes), SR.ArgumentOutOfRange_IndexCountBuffer);
 377
 378            // Avoid problems with empty input buffer
 0379            if (count == 0) return string.Empty;
 380
 0381            fixed (byte* pBytes = bytes)
 0382                return string.CreateStringFromEncoding(
 0383                    pBytes + index, count, this);
 384        }
 385
 386        //
 387        // End of standard methods copied from EncodingNLS.cs
 388        //
 389        internal sealed override unsafe int GetByteCount(char* chars, int count, EncoderNLS? baseEncoder)
 390        {
 0391            Debug.Assert(chars is not null, "[UTF7Encoding.GetByteCount]chars!=null");
 0392            Debug.Assert(count >= 0, "[UTF7Encoding.GetByteCount]count >=0");
 393
 394            // Just call GetBytes with bytes is null
 0395            return GetBytes(chars, count, null, 0, baseEncoder);
 396        }
 397
 398        internal sealed override unsafe int GetBytes(
 399            char* chars, int charCount, byte* bytes, int byteCount, EncoderNLS? baseEncoder)
 400        {
 0401            Debug.Assert(byteCount >= 0, "[UTF7Encoding.GetBytes]byteCount >=0");
 0402            Debug.Assert(chars is not null, "[UTF7Encoding.GetBytes]chars!=null");
 0403            Debug.Assert(charCount >= 0, "[UTF7Encoding.GetBytes]charCount >=0");
 404
 405            // Get encoder info
 0406            Encoder? encoder = (Encoder?)baseEncoder;
 407
 408            // Default bits & count
 0409            int bits = 0;
 0410            int bitCount = -1;
 411
 412            // prepare our helpers
 0413            EncodingByteBuffer buffer = new EncodingByteBuffer(
 0414                this, encoder, bytes, byteCount, chars, charCount);
 415
 0416            if (encoder is not null)
 417            {
 0418                bits = encoder.bits;
 0419                bitCount = encoder.bitCount;
 420
 421                // May have had too many left over
 0422                while (bitCount >= 6)
 423                {
 0424                    bitCount -= 6;
 425                    // If we fail we'll never really have enough room
 0426                    if (!buffer.AddByte(_base64Bytes[(bits >> bitCount) & 0x3F]))
 0427                        ThrowBytesOverflow(encoder, buffer.Count == 0);
 428                }
 429            }
 430
 0431            while (buffer.MoreData)
 432            {
 0433                char currentChar = buffer.GetNextChar();
 434
 0435                if (currentChar < 0x80 && _directEncode[currentChar])
 436                {
 0437                    if (bitCount >= 0)
 438                    {
 0439                        if (bitCount > 0)
 440                        {
 441                            // Try to add the next byte
 0442                            if (!buffer.AddByte(_base64Bytes[bits << 6 - bitCount & 0x3F]))
 443                                break;                                          // Stop here, didn't throw
 444
 0445                            bitCount = 0;
 446                        }
 447
 448                        // Need to get emit '-' and our char, 2 bytes total
 0449                        if (!buffer.AddByte((byte)'-'))
 450                            break;                                          // Stop here, didn't throw
 451
 0452                        bitCount = -1;
 453                    }
 454
 455                    // Need to emit our char
 0456                    if (!buffer.AddByte((byte)currentChar))
 0457                        break;                                          // Stop here, didn't throw
 458                }
 0459                else if (bitCount < 0 && currentChar == '+')
 460                {
 0461                    if (!buffer.AddByte((byte)'+', (byte)'-'))
 0462                        break;                                          // Stop here, didn't throw
 463                }
 464                else
 465                {
 0466                    if (bitCount < 0)
 467                    {
 468                        // Need to emit a + and 12 bits (3 bytes)
 469                        // Only 12 of the 16 bits will be emitted this time, the other 4 wait 'til next time
 0470                        if (!buffer.AddByte((byte)'+'))
 471                            break;                                          // Stop here, didn't throw
 472
 473                        // We're now in bit mode, but haven't stored data yet
 0474                        bitCount = 0;
 475                    }
 476
 477                    // Add our bits
 0478                    bits = bits << 16 | currentChar;
 0479                    bitCount += 16;
 480
 0481                    while (bitCount >= 6)
 482                    {
 0483                        bitCount -= 6;
 0484                        if (!buffer.AddByte(_base64Bytes[(bits >> bitCount) & 0x3F]))
 485                        {
 0486                            bitCount += 6;                              // We didn't use these bits
 0487                            buffer.GetNextChar();                       // We're processing this char still, but AddByte
 488                                                                        // --'d it when we ran out of space
 0489                            break;                                      // Stop here, not enough room for bytes
 490                        }
 491                    }
 492
 0493                    if (bitCount >= 6)
 494                        break;                  // Didn't have room to encode enough bits
 495                }
 496            }
 497
 498            // Now if we have bits left over we have to encode them.
 499            // MustFlush may have been cleared by encoding.ThrowBytesOverflow earlier if converting
 0500            if (bitCount >= 0 && (encoder is null || encoder.MustFlush))
 501            {
 502                // Do we have bits we have to stick in?
 0503                if (bitCount > 0)
 504                {
 0505                    if (buffer.AddByte(_base64Bytes[(bits << (6 - bitCount)) & 0x3F]))
 506                    {
 507                        // Emitted spare bits, 0 bits left
 0508                        bitCount = 0;
 509                    }
 510                }
 511
 512                // If converting and failed bitCount above, then we'll fail this too
 0513                if (buffer.AddByte((byte)'-'))
 514                {
 515                    // turned off bit mode';
 0516                    bits = 0;
 0517                    bitCount = -1;
 518                }
 519                else
 520                    // If not successful, convert will maintain state for next time, also
 521                    // AddByte will have decremented our char count, however we need it to remain the same
 0522                    buffer.GetNextChar();
 523            }
 524
 525            // Do we have an encoder we're allowed to use?
 526            // bytes is null if counting, so don't use encoder then
 0527            if (bytes is not null && encoder is not null)
 528            {
 529                // We already cleared bits & bitcount for mustflush case
 0530                encoder.bits = bits;
 0531                encoder.bitCount = bitCount;
 0532                encoder._charsUsed = buffer.CharsUsed;
 533            }
 534
 0535            return buffer.Count;
 536        }
 537
 538        internal sealed override unsafe int GetCharCount(byte* bytes, int count, DecoderNLS? baseDecoder)
 539        {
 0540            Debug.Assert(count >= 0, "[UTF7Encoding.GetCharCount]count >=0");
 0541            Debug.Assert(bytes is not null, "[UTF7Encoding.GetCharCount]bytes!=null");
 542
 543            // Just call GetChars with null char* to do counting
 0544            return GetChars(bytes, count, null, 0, baseDecoder);
 545        }
 546
 547        internal sealed override unsafe int GetChars(
 548            byte* bytes, int byteCount, char* chars, int charCount, DecoderNLS? baseDecoder)
 549        {
 0550            Debug.Assert(byteCount >= 0, "[UTF7Encoding.GetChars]byteCount >=0");
 0551            Debug.Assert(bytes is not null, "[UTF7Encoding.GetChars]bytes!=null");
 0552            Debug.Assert(charCount >= 0, "[UTF7Encoding.GetChars]charCount >=0");
 553
 554            // Might use a decoder
 0555            Decoder? decoder = (Decoder?)baseDecoder;
 556
 557            // Get our output buffer info.
 0558            EncodingCharBuffer buffer = new EncodingCharBuffer(
 0559                this, decoder, chars, charCount, bytes, byteCount);
 560
 561            // Get decoder info
 0562            int bits = 0;
 0563            int bitCount = -1;
 0564            bool firstByte = false;
 0565            if (decoder is not null)
 566            {
 0567                bits = decoder.bits;
 0568                bitCount = decoder.bitCount;
 0569                firstByte = decoder.firstByte;
 570
 0571                Debug.Assert(!firstByte || decoder.bitCount <= 0,
 0572                    "[UTF7Encoding.GetChars]If remembered bits, then first byte flag shouldn't be set");
 573            }
 574
 575            // We may have had bits in the decoder that we couldn't output last time, so do so now
 0576            if (bitCount >= 16)
 577            {
 578                // Check our decoder buffer
 0579                if (!buffer.AddChar((char)((bits >> (bitCount - 16)) & 0xFFFF)))
 0580                    ThrowCharsOverflow(decoder, true);  // Always throw, they need at least 1 char even in Convert
 581
 582                // Used this one, clean up extra bits
 0583                bitCount -= 16;
 584            }
 585
 586            // Loop through the input
 0587            while (buffer.MoreData)
 588            {
 0589                byte currentByte = buffer.GetNextByte();
 590                int c;
 591
 0592                if (bitCount >= 0)
 593                {
 594                    //
 595                    // Modified base 64 encoding.
 596                    //
 597                    sbyte v;
 0598                    if (currentByte < 0x80 && ((v = _base64Values[currentByte]) >= 0))
 599                    {
 0600                        firstByte = false;
 0601                        bits = (bits << 6) | ((byte)v);
 0602                        bitCount += 6;
 0603                        if (bitCount >= 16)
 604                        {
 0605                            c = (bits >> (bitCount - 16)) & 0xFFFF;
 0606                            bitCount -= 16;
 607                        }
 608                        // If not enough bits just continue
 609                        else continue;
 610                    }
 611                    else
 612                    {
 613                        // If it wasn't a base 64 byte, everything's going to turn off base 64 mode
 0614                        bitCount = -1;
 615
 0616                        if (currentByte != '-')
 617                        {
 618                            // >= 0x80 (because of 1st if statemtn)
 619                            // We need this check since the _base64Values[b] check below need b <= 0x7f.
 620                            // This is not a valid base 64 byte.  Terminate the shifted-sequence and
 621                            // emit this byte.
 622
 623                            // not in base 64 table
 624                            // According to the RFC 1642 and the example code of UTF-7
 625                            // in Unicode 2.0, we should just zero-extend the invalid UTF7 byte
 626
 627                            // Chars won't be updated unless this works, try to fallback
 0628                            if (!buffer.Fallback(currentByte))
 0629                                break;                                          // Stop here, didn't throw
 630
 631                            // Used that byte, we're done with it
 632                            continue;
 633                        }
 634
 635                        //
 636                        // The encoding for '+' is "+-".
 637                        //
 0638                        if (firstByte) c = '+';
 639                        // We just turn it off if not emitting a +, so we're done.
 640                        else continue;
 641                    }
 642                    //
 643                    // End of modified base 64 encoding block.
 644                    //
 645                }
 0646                else if (currentByte == '+')
 647                {
 648                    //
 649                    // Found the start of a modified base 64 encoding block or a plus sign.
 650                    //
 0651                    bitCount = 0;
 0652                    firstByte = true;
 0653                    continue;
 654                }
 655                else
 656                {
 657                    // Normal character
 0658                    if (currentByte >= 0x80)
 659                    {
 660                        // Try to fallback
 0661                        if (!buffer.Fallback(currentByte))
 0662                            break;                                          // Stop here, didn't throw
 663
 664                        // Done falling back
 665                        continue;
 666                    }
 667
 668                    // Use the normal character
 0669                    c = currentByte;
 670                }
 671
 0672                if (c >= 0)
 673                {
 674                    // Check our buffer
 0675                    if (!buffer.AddChar((char)c))
 676                    {
 677                        // No room.  If it was a plain char we'll try again later.
 678                        // Note, we'll consume this byte and stick it in decoder, even if we can't output it
 0679                        if (bitCount >= 0)                                  // Can we rememmber this byte (char)
 680                        {
 0681                            buffer.AdjustBytes(+1);                         // Need to readd the byte that AddChar subtr
 0682                            bitCount += 16;                                 // We'll still need that char we have in our
 683                        }
 0684                        break;                                              // didn't throw, stop
 685                    }
 686                }
 687            }
 688
 689            // Stick stuff in the decoder if we can (chars is null if counting, so don't store decoder)
 0690            if (chars is not null && decoder is not null)
 691            {
 692                // MustFlush?  (Could've been cleared by ThrowCharsOverflow if Convert & didn't reach end of buffer)
 0693                if (decoder.MustFlush)
 694                {
 695                    // RFC doesn't specify what would happen if we have non-0 leftover bits, we just drop them
 0696                    decoder.bits = 0;
 0697                    decoder.bitCount = -1;
 0698                    decoder.firstByte = false;
 699                }
 700                else
 701                {
 0702                    decoder.bits = bits;
 0703                    decoder.bitCount = bitCount;
 0704                    decoder.firstByte = firstByte;
 705                }
 0706                decoder._bytesUsed = buffer.BytesUsed;
 707            }
 708            // else ignore any hanging bits.
 709
 710            // Return our count
 0711            return buffer.Count;
 712        }
 713
 714        public override Text.Decoder GetDecoder()
 715        {
 0716            return new Decoder(this);
 717        }
 718
 719        public override Text.Encoder GetEncoder()
 720        {
 0721            return new Encoder(this);
 722        }
 723
 724        public override int GetMaxByteCount(int charCount)
 725        {
 0726            ArgumentOutOfRangeException.ThrowIfNegative(charCount);
 727
 728            // Suppose that every char can not be direct-encoded, we know that
 729            // a byte can encode 6 bits of the Unicode character.  And we will
 730            // also need two extra bytes for the shift-in ('+') and shift-out ('-') mark.
 731            // Therefore, the max byte should be:
 732            // byteCount = 2 + Math.Ceiling((double)charCount * 16 / 6);
 733            // That is always <= 2 + 3 * charCount;
 734            // Longest case is alternating encoded, direct, encoded data for 5 + 1 + 5... bytes per char.
 735            // UTF7 doesn't have left over surrogates, but if no input we may need an output - to turn off
 736            // encoding if MustFlush is true.
 737
 738            // Its easiest to think of this as 2 bytes to turn on/off the base64 mode, then 3 bytes per char.
 739            // 3 bytes is 18 bits of encoding, which is more than we need, but if its direct encoded then 3
 740            // bytes allows us to turn off and then back on base64 mode if necessary.
 741
 742            // Note that UTF7 encoded surrogates individually and isn't worried about mismatches, so all
 743            // code points are encodable int UTF7.
 0744            long byteCount = (long)charCount * 3 + 2;
 745
 746            // check for overflow
 0747            if (byteCount > 0x7fffffff)
 0748                throw new ArgumentOutOfRangeException(nameof(charCount), SR.ArgumentOutOfRange_GetByteCountOverflow);
 749
 0750            return (int)byteCount;
 751        }
 752
 753        public override int GetMaxCharCount(int byteCount)
 754        {
 0755            ArgumentOutOfRangeException.ThrowIfNegative(byteCount);
 756
 757            // Worst case is 1 char per byte.  Minimum 1 for left over bits in case decoder is being flushed
 758            // Also note that we ignore extra bits (per spec), so UTF7 doesn't have unknown in this direction.
 0759            int charCount = byteCount;
 0760            if (charCount == 0) charCount = 1;
 761
 0762            return charCount;
 763        }
 764
 765        // Of all the amazing things... This MUST be Decoder so that our com name
 766        // for System.Text.Decoder doesn't change
 767        private sealed class Decoder : DecoderNLS
 768        {
 769            /*private*/
 770            internal int bits;
 771            /*private*/
 772            internal int bitCount;
 773            /*private*/
 774            internal bool firstByte;
 775
 0776            public Decoder(UTF7Encoding encoding) : base(encoding)
 777            {
 778                // base calls reset
 0779            }
 780
 781            public override void Reset()
 782            {
 0783                this.bits = 0;
 0784                this.bitCount = -1;
 0785                this.firstByte = false;
 0786                _fallbackBuffer?.Reset();
 0787            }
 788
 789            // Anything left in our encoder?
 790            internal override bool HasState =>
 791                // NOTE: This forces the last -, which some encoder might not encode.  If we
 792                // don't see it we don't think we're done reading.
 0793                this.bitCount != -1;
 794        }
 795
 796        // Of all the amazing things... This MUST be Encoder so that our com name
 797        // for System.Text.Encoder doesn't change
 798        private sealed class Encoder : EncoderNLS
 799        {
 800            /*private*/
 801            internal int bits;
 802            /*private*/
 803            internal int bitCount;
 804
 0805            public Encoder(UTF7Encoding encoding) : base(encoding)
 806            {
 807                // base calls reset
 0808            }
 809
 810            public override void Reset()
 811            {
 0812                this.bitCount = -1;
 0813                this.bits = 0;
 0814                _fallbackBuffer?.Reset();
 0815            }
 816
 817            // Anything left in our encoder?
 0818            internal override bool HasState => this.bits != 0 || this.bitCount != -1;
 819        }
 820
 821        // Preexisting UTF7 behavior for bad bytes was just to spit out the byte as the next char
 822        // and turn off base64 mode if it was in that mode.  We still exit the mode, but now we fallback.
 823        private sealed class DecoderUTF7Fallback : DecoderFallback
 824        {
 825            // Default replacement fallback uses no best fit and ? replacement string
 826
 827            public override DecoderFallbackBuffer CreateFallbackBuffer() =>
 0828                new DecoderUTF7FallbackBuffer();
 829
 830            // Maximum number of characters that this instance of this fallback could return
 0831            public override int MaxCharCount => 1; // returns 1 char per bad byte
 832
 0833            public override bool Equals([NotNullWhen(true)] object? value) => value is DecoderUTF7Fallback;
 834
 0835            public override int GetHashCode() => 984;
 836        }
 837
 838        private sealed class DecoderUTF7FallbackBuffer : DecoderFallbackBuffer
 839        {
 840            // Store our default string
 841            private char cFallback;
 0842            private int iCount = -1;
 843            private int iSize;
 844
 845            // Fallback Methods
 846            public override bool Fallback(byte[] bytesUnknown, int index)
 847            {
 848                // We expect no previous fallback in our buffer
 0849                Debug.Assert(iCount < 0, "[DecoderUTF7FallbackBuffer.Fallback] Can't have recursive fallbacks");
 0850                Debug.Assert(bytesUnknown.Length == 1, "[DecoderUTF7FallbackBuffer.Fallback] Only possible fallback case
 851
 852                // Go ahead and get our fallback
 0853                cFallback = (char)bytesUnknown[0];
 854
 855                // Any of the fallback characters can be handled except for 0
 0856                if (cFallback == 0)
 857                {
 0858                    return false;
 859                }
 860
 0861                iCount = iSize = 1;
 862
 0863                return true;
 864            }
 865
 866            public override char GetNextChar()
 867            {
 0868                if (iCount-- > 0)
 0869                    return cFallback;
 870
 871                // Note: this means that 0 in UTF7 stream will never be emitted.
 0872                return (char)0;
 873            }
 874
 875            public override bool MovePrevious()
 876            {
 0877                if (iCount >= 0)
 878                {
 0879                    iCount++;
 880                }
 881
 882                // return true if we were allowed to do this
 0883                return iCount >= 0 && iCount <= iSize;
 884            }
 885
 886            // Return # of chars left in this fallback
 0887            public override int Remaining => (iCount > 0) ? iCount : 0;
 888
 889            // Clear the buffer
 890            public override unsafe void Reset()
 891            {
 0892                iCount = -1;
 0893                byteStart = null;
 0894            }
 895
 896            // This version just counts the fallback and doesn't actually copy anything.
 897            internal override unsafe int InternalFallback(byte[] bytes, byte* pBytes)
 898            // Right now this has both bytes and bytes[], since we might have extra bytes, hence the
 899            // array, and we might need the index, hence the byte*
 900            {
 901                // We expect no previous fallback in our buffer
 0902                Debug.Assert(iCount < 0, "[DecoderUTF7FallbackBuffer.InternalFallback] Can't have recursive fallbacks");
 0903                if (bytes.Length != 1)
 904                {
 0905                    throw new ArgumentException(SR.Argument_InvalidCharSequenceNoIndex);
 906                }
 907
 908                // Can't fallback a byte 0, so return for that case, 1 otherwise.
 0909                return bytes[0] == 0 ? 0 : 1;
 910            }
 911        }
 912    }
 913}
 914