/// <summary>
        /// This method will write SASL string to given byte array using given <see cref="IEncodingInfo"/>, while escaping and transforming string as needed.
        /// See <see cref="CheckString"/> for more information about escaping and transforming.
        /// </summary>
        /// <param name="str">The string to write.</param>
        /// <param name="encodingInfo">The <see cref="IEncodingInfo"/> to use.</param>
        /// <param name="array">The byte array where to write the string.</param>
        /// <param name="offset">The offset in <paramref name="array"/> where to start writing.</param>
        /// <param name="normalizer">The optional custom normalizer callback to use.</param>
        /// <param name="checkResult">The result of <see cref="CheckString"/>, if it was used before calling this method.</param>
        /// <seealso cref="CheckString"/>
        /// <remarks>
        /// This method will not allocate any strings.
        /// </remarks>
        public static void WriteString(
            String str,
            IEncodingInfo encodingInfo,
            ResizableArray <Byte> array,
            ref Int32 offset,
            CustomStringNormalizer normalizer = null,
            Int32?checkResult = null
            )
        {
            var checkResultValue = checkResult ?? CheckString(str, normalizer);
            var encoding         = encodingInfo.Encoding;

            if (checkResultValue == -1)
            {
                // String may be written as-is
                array.CurrentMaxCapacity = offset + encoding.GetByteCount(str);
                encodingInfo.WriteString(array.Array, ref offset, str);
            }
            else
            {
                var maxCharByteCountDiff = encodingInfo.MaxCharByteCount - 1;

                String tmpStr;

                // Calculate byte size (actually, we are calculating the upper bound for byte size, but that's ok)
                var byteSize = encoding.GetByteCount(str);
                for (var i = checkResultValue; i < str.Length; ++i)
                {
                    var c = str[i];
                    if (c >= MIN_SPECIAL_CHAR)
                    {
                        // It is possible that we need to replace character
                        if (IsNonAsciiSpace(c))
                        {
                            // Mapping special space into "normal" space (0x20)
                            // Byte size doesn't grow at least.
                        }
                        else if (IsCommonlyMappedToNothing(c))
                        {
                            --byteSize;
                        }
                        else if ((tmpStr = normalizer?.Invoke(str, i)) != null)
                        {
                            byteSize += encoding.GetByteCount(tmpStr) - 1;
                        }
                    }
                }

                // Now we know that the string will take max 'byteSize' amount of bytes
                array.CurrentMaxCapacity = offset + byteSize;
                // We can get the actual array now, since we won't be resizing the ResizableArray anymore
                var buffer = array.Array;

                // Helper method to write chunks of "normal" characters
                var prev = 0;
                void WritePreviousChunk(Int32 charIdx, ref Int32 writeIdx)
                {
                    writeIdx += encoding.GetBytes(str, prev, charIdx - prev, buffer, writeIdx);
                    prev      = charIdx + 1;
                }

                for (var i = checkResultValue; i < str.Length; ++i)
                {
                    var c = str[i];
                    if (c >= MIN_SPECIAL_CHAR)
                    {
                        // It is possible that we need to replace character
                        if (IsNonAsciiSpace(c))
                        {
                            // Mapping special space into "normal" space (0x20)
                            WritePreviousChunk(i, ref offset);
                            encodingInfo.WriteASCIIByte(buffer, ref offset, 0x20);
                        }
                        else if (IsCommonlyMappedToNothing(c))
                        {
                            // Nothing additional to write
                            WritePreviousChunk(i, ref offset);
                        }
                        else if ((tmpStr = normalizer?.Invoke(str, i)) != null)
                        {
                            WritePreviousChunk(i, ref offset);
                            if (tmpStr.Length > 0)
                            {
                                encodingInfo.WriteString(buffer, ref offset, tmpStr);
                            }
                        }
                    }
                }

                if (prev < str.Length)
                {
                    // Write final chunk
                    WritePreviousChunk(str.Length - 1, ref offset);
                }
            }
        }
Beispiel #2
0
    private static async ValueTask <Int32> PerformWriteJSONTTokenAsync(
        StreamWriterWithResizableBuffer stream,
        IEncodingInfo encoding,
        JToken jsonValue
        )
    {
        ArgumentValidator.ValidateNotNull(nameof(jsonValue), jsonValue);
        Int32 bytesWritten;
        var   asciiSize = encoding.BytesPerASCIICharacter;
        Int32 max;

        (Int32 Offset, Int32 Count)range;
        switch (jsonValue)
        {
        case JArray array:
            range = stream.ReserveBufferSegment(asciiSize);
            encoding.WriteASCIIByte(stream.Buffer, ref range.Offset, (Byte)ARRAY_START);
            bytesWritten = range.Count;
            max          = array.Count;
            if (max > 0)
            {
                for (var i = 0; i < max; ++i)
                {
                    bytesWritten += await PerformWriteJSONTTokenAsync(stream, encoding, array[i]);

                    if (i < max - 1)
                    {
                        range = stream.ReserveBufferSegment(asciiSize);
                        encoding.WriteASCIIByte(stream.Buffer, ref range.Offset, (Byte)ARRAY_VALUE_DELIM);
                        bytesWritten += range.Count;
                    }
                }
            }
            range = stream.ReserveBufferSegment(asciiSize);
            encoding.WriteASCIIByte(stream.Buffer, ref range.Offset, (Byte)ARRAY_END);
            bytesWritten += await stream.FlushAsync();

            break;

        case JObject obj:
            range = stream.ReserveBufferSegment(asciiSize);
            encoding.WriteASCIIByte(stream.Buffer, ref range.Offset, (Byte)OBJ_START);
            bytesWritten = range.Count;
            max          = obj.Count;
            if (max > 0)
            {
                var j = 0;
                foreach (var kvp in obj)
                {
                    bytesWritten += WriteJSONString(stream, encoding, kvp.Key);
                    range         = stream.ReserveBufferSegment(asciiSize);
                    encoding.WriteASCIIByte(stream.Buffer, ref range.Offset, (Byte)OBJ_KEY_VALUE_DELIM);
                    bytesWritten += range.Count;
                    await stream.FlushAsync();

                    bytesWritten += await PerformWriteJSONTTokenAsync(stream, encoding, kvp.Value);

                    if (++j < max)
                    {
                        range = stream.ReserveBufferSegment(asciiSize);
                        encoding.WriteASCIIByte(stream.Buffer, ref range.Offset, (Byte)OBJ_VALUE_DELIM);
                        bytesWritten += range.Count;
                    }
                }
            }
            range = stream.ReserveBufferSegment(asciiSize);
            encoding.WriteASCIIByte(stream.Buffer, ref range.Offset, (Byte)OBJ_END);
            bytesWritten += await stream.FlushAsync();

            break;

        case JValue value:
            var val = value.Value;
            switch (val)
            {
            case String str:
                bytesWritten = WriteJSONString(stream, encoding, str);
                break;

            case Boolean boolean:
                // Write 'true' or 'false'
                range = stream.ReserveBufferSegment(boolean ? 4 : 5);
                encoding.WriteString(stream.Buffer, ref range.Offset, boolean ? "true" : "false");
                bytesWritten = range.Count;
                break;

            case Int64 i64:
                range = stream.ReserveBufferSegment(encoding.GetTextualIntegerRepresentationSize(i64));
                encoding.WriteIntegerTextual(stream.Buffer, ref range.Offset, i64);
                bytesWritten = range.Count;
                break;

            case Double dbl:
                // Have to allocate string :/
                var dblStr = dbl.ToString(System.Globalization.CultureInfo.InvariantCulture);
                range = stream.ReserveBufferSegment(encoding.Encoding.GetByteCount(dblStr));
                encoding.WriteString(stream.Buffer, ref range.Offset, dblStr);
                bytesWritten = range.Count;
                break;

            case null:
                // Write 'null'
                range = stream.ReserveBufferSegment(asciiSize * 4);
                encoding.WriteString(stream.Buffer, ref range.Offset, "null");
                bytesWritten = range.Count;
                break;

            default:
                throw new NotSupportedException($"Unsupported primitive value {val}.");
            }
            // Remember to flush stream
            await stream.FlushAsync();

            break;

        default:
            throw new NotSupportedException($"Unrecognized JToken type: {jsonValue?.GetType()}.");
        }

        return(bytesWritten);
    }