private static void EscapeString(ReadOnlySpan <byte> value, Span <byte> destination, JavaScriptEncoder encoder, ref int written)
        {
            Debug.Assert(encoder != null);

            OperationStatus result = encoder.EncodeUtf8(value, destination, out int encoderBytesConsumed, out int encoderBytesWritten);

            Debug.Assert(result != OperationStatus.DestinationTooSmall);
            Debug.Assert(result != OperationStatus.NeedMoreData);
            Debug.Assert(encoderBytesConsumed == value.Length);

            if (result != OperationStatus.Done)
            {
                ThrowHelper.ThrowArgumentException_InvalidUTF8(value.Slice(encoderBytesWritten));
            }

            written += encoderBytesWritten;
        }
        private static int EscapeNextBytes(ReadOnlySpan <byte> value, Span <byte> destination, ref int written)
        {
            SequenceValidity status = PeekFirstSequence(value, out int numBytesConsumed, out int scalar);

            if (status != SequenceValidity.WellFormed)
            {
                ThrowHelper.ThrowArgumentException_InvalidUTF8(value);
            }

            destination[written++] = (byte)'\\';
            switch (scalar)
            {
            case JsonConstants.LineFeed:
                destination[written++] = (byte)'n';
                break;

            case JsonConstants.CarriageReturn:
                destination[written++] = (byte)'r';
                break;

            case JsonConstants.Tab:
                destination[written++] = (byte)'t';
                break;

            case JsonConstants.BackSlash:
                destination[written++] = (byte)'\\';
                break;

            case JsonConstants.BackSpace:
                destination[written++] = (byte)'b';
                break;

            case JsonConstants.FormFeed:
                destination[written++] = (byte)'f';
                break;

            default:
                destination[written++] = (byte)'u';
                if (scalar < JsonConstants.UnicodePlane01StartValue)
                {
                    bool result = Utf8Formatter.TryFormat(scalar, destination.Slice(written), out int bytesWritten, format: s_hexStandardFormat);
                    Debug.Assert(result);
                    Debug.Assert(bytesWritten == 4);
                    written += bytesWritten;
                }
                else
                {
                    // Divide by 0x400 to shift right by 10 in order to find the surrogate pairs from the scalar
                    // High surrogate = ((scalar -  0x10000) / 0x400) + D800
                    // Low surrogate = ((scalar -  0x10000) % 0x400) + DC00
                    int  quotient  = Math.DivRem(scalar - JsonConstants.UnicodePlane01StartValue, JsonConstants.ShiftRightBy10, out int remainder);
                    int  firstChar = quotient + JsonConstants.HighSurrogateStartValue;
                    int  nextChar  = remainder + JsonConstants.LowSurrogateStartValue;
                    bool result    = Utf8Formatter.TryFormat(firstChar, destination.Slice(written), out int bytesWritten, format: s_hexStandardFormat);
                    Debug.Assert(result);
                    Debug.Assert(bytesWritten == 4);
                    written += bytesWritten;
                    destination[written++] = (byte)'\\';
                    destination[written++] = (byte)'u';
                    result = Utf8Formatter.TryFormat(nextChar, destination.Slice(written), out bytesWritten, format: s_hexStandardFormat);
                    Debug.Assert(result);
                    Debug.Assert(bytesWritten == 4);
                    written += bytesWritten;
                }
                break;
            }
            return(numBytesConsumed);
        }
        private static void EscapeString(ReadOnlySpan <char> value, Span <char> destination, JavaScriptEncoder encoder, ref int written)
        {
            // todo: issue #39523: add an Encode(ReadOnlySpan<char>) decode API to System.Text.Encodings.Web.TextEncoding to avoid utf16->utf8->utf16 conversion.

            Debug.Assert(encoder != null);

            // Convert char to byte.
            byte[]      utf8DestinationArray = null;
            Span <byte> utf8Destination;
            int         length = checked ((value.Length) * JsonConstants.MaxExpansionFactorWhileTranscoding);

            if (length > JsonConstants.StackallocThreshold)
            {
                utf8DestinationArray = ArrayPool <byte> .Shared.Rent(length);

                utf8Destination = utf8DestinationArray;
            }
            else
            {
                unsafe
                {
                    byte *ptr = stackalloc byte[JsonConstants.StackallocThreshold];
                    utf8Destination = new Span <byte>(ptr, JsonConstants.StackallocThreshold);
                }
            }

            ReadOnlySpan <byte> utf16Value   = MemoryMarshal.AsBytes(value);
            OperationStatus     toUtf8Status = ToUtf8(utf16Value, utf8Destination, out int bytesConsumed, out int bytesWritten);

            Debug.Assert(toUtf8Status != OperationStatus.DestinationTooSmall);
            Debug.Assert(toUtf8Status != OperationStatus.NeedMoreData);

            if (toUtf8Status != OperationStatus.Done)
            {
                if (utf8DestinationArray != null)
                {
                    utf8Destination.Slice(0, bytesWritten).Clear();
                    ArrayPool <byte> .Shared.Return(utf8DestinationArray);
                }

                ThrowHelper.ThrowArgumentException_InvalidUTF8(utf16Value.Slice(bytesWritten));
            }

            Debug.Assert(toUtf8Status == OperationStatus.Done);
            Debug.Assert(bytesConsumed == utf16Value.Length);

            // Escape the bytes.
            byte[]      utf8ConvertedDestinationArray = null;
            Span <byte> utf8ConvertedDestination;

            length = checked (bytesWritten * JsonConstants.MaxExpansionFactorWhileEscaping);
            if (length > JsonConstants.StackallocThreshold)
            {
                utf8ConvertedDestinationArray = ArrayPool <byte> .Shared.Rent(length);

                utf8ConvertedDestination = utf8ConvertedDestinationArray;
            }
            else
            {
                unsafe
                {
                    byte *ptr = stackalloc byte[JsonConstants.StackallocThreshold];
                    utf8ConvertedDestination = new Span <byte>(ptr, JsonConstants.StackallocThreshold);
                }
            }

            EscapeString(utf8Destination.Slice(0, bytesWritten), utf8ConvertedDestination, indexOfFirstByteToEscape: 0, encoder, out int convertedBytesWritten);

            if (utf8DestinationArray != null)
            {
                utf8Destination.Slice(0, bytesWritten).Clear();
                ArrayPool <byte> .Shared.Return(utf8DestinationArray);
            }

            // Convert byte to char.
#if BUILDING_INBOX_LIBRARY
            OperationStatus toUtf16Status = Utf8.ToUtf16(utf8ConvertedDestination.Slice(0, convertedBytesWritten), destination, out int bytesRead, out int charsWritten);
            Debug.Assert(toUtf16Status == OperationStatus.Done);
            Debug.Assert(bytesRead == convertedBytesWritten);
#else
            string utf16 = JsonReaderHelper.GetTextFromUtf8(utf8ConvertedDestination.Slice(0, convertedBytesWritten));
            utf16.AsSpan().CopyTo(destination);
            int charsWritten = utf16.Length;
#endif
            written += charsWritten;

            if (utf8ConvertedDestinationArray != null)
            {
                utf8ConvertedDestination.Slice(0, written).Clear();
                ArrayPool <byte> .Shared.Return(utf8ConvertedDestinationArray);
            }
        }
Example #4
0
        public static void EscapeString(ReadOnlySpan <byte> value, Span <byte> destination, int indexOfFirstByteToEscape, JavaScriptEncoder encoder, out int written)
        {
            Debug.Assert(indexOfFirstByteToEscape >= 0 && indexOfFirstByteToEscape < value.Length);

            value.Slice(0, indexOfFirstByteToEscape).CopyTo(destination);
            written = indexOfFirstByteToEscape;
            int consumed = indexOfFirstByteToEscape;

            if (encoder != null)
            {
                OperationStatus result = encoder.EncodeUtf8(
                    value.Slice(consumed), destination.Slice(written), out int encoderBytesConsumed, out int encoderBytesWritten);

                Debug.Assert(result != OperationStatus.DestinationTooSmall);
                Debug.Assert(result != OperationStatus.NeedMoreData);
                Debug.Assert(encoderBytesConsumed == value.Length - consumed);

                if (result != OperationStatus.Done)
                {
                    ThrowHelper.ThrowArgumentException_InvalidUTF8(value.Slice(encoderBytesWritten));
                }

                written += encoderBytesWritten;
            }
            else
            {
                // For performance when no encoder is specified, perform escaping here for Ascii and on the
                // first occurrence of a non-Ascii character, then call into the default encoder.
                while (consumed < value.Length)
                {
                    byte val = value[consumed];
                    if (IsAsciiValue(val))
                    {
                        if (NeedsEscapingNoBoundsCheck(val))
                        {
                            EscapeNextBytes(val, destination, ref written);
                            consumed++;
                        }
                        else
                        {
                            destination[written] = val;
                            written++;
                            consumed++;
                        }
                    }
                    else
                    {
                        // Fall back to default encoder
                        OperationStatus result = JavaScriptEncoder.Default.EncodeUtf8(
                            value.Slice(consumed), destination.Slice(written), out int encoderBytesConsumed, out int encoderBytesWritten);

                        Debug.Assert(result != OperationStatus.DestinationTooSmall);
                        Debug.Assert(result != OperationStatus.NeedMoreData);
                        Debug.Assert(encoderBytesConsumed == value.Length - consumed);

                        if (result != OperationStatus.Done)
                        {
                            ThrowHelper.ThrowArgumentException_InvalidUTF8(value.Slice(encoderBytesConsumed));
                        }

                        consumed += encoderBytesConsumed;
                        written  += encoderBytesWritten;
                    }
                }
            }
        }