#region License
// Copyright (c) 2007 James Newton-King
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
#endregion
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.IO;
using LC.Newtonsoft.Json.Serialization;
using LC.Newtonsoft.Json.Utilities;
using LC.Newtonsoft.Json.Linq;
#nullable disable
namespace LC.Newtonsoft.Json.Bson
{
///
/// Represents a reader that provides fast, non-cached, forward-only access to serialized BSON data.
///
[Obsolete("BSON reading and writing has been moved to its own package. See https://www.nuget.org/packages/Newtonsoft.Json.Bson for more details.")]
public class BsonReader : JsonReader
{
private const int MaxCharBytesSize = 128;
private static readonly byte[] SeqRange1 = new byte[] { 0, 127 }; // range of 1-byte sequence
private static readonly byte[] SeqRange2 = new byte[] { 194, 223 }; // range of 2-byte sequence
private static readonly byte[] SeqRange3 = new byte[] { 224, 239 }; // range of 3-byte sequence
private static readonly byte[] SeqRange4 = new byte[] { 240, 244 }; // range of 4-byte sequence
private readonly BinaryReader _reader;
private readonly List _stack;
private byte[] _byteBuffer;
private char[] _charBuffer;
private BsonType _currentElementType;
private BsonReaderState _bsonReaderState;
private ContainerContext _currentContext;
private bool _readRootValueAsArray;
private bool _jsonNet35BinaryCompatibility;
private DateTimeKind _dateTimeKindHandling;
private enum BsonReaderState
{
Normal = 0,
ReferenceStart = 1,
ReferenceRef = 2,
ReferenceId = 3,
CodeWScopeStart = 4,
CodeWScopeCode = 5,
CodeWScopeScope = 6,
CodeWScopeScopeObject = 7,
CodeWScopeScopeEnd = 8
}
private class ContainerContext
{
public readonly BsonType Type;
public int Length;
public int Position;
public ContainerContext(BsonType type)
{
Type = type;
}
}
///
/// Gets or sets a value indicating whether binary data reading should be compatible with incorrect Json.NET 3.5 written binary.
///
///
/// true if binary data reading will be compatible with incorrect Json.NET 3.5 written binary; otherwise, false.
///
[Obsolete("JsonNet35BinaryCompatibility will be removed in a future version of Json.NET.")]
public bool JsonNet35BinaryCompatibility
{
get => _jsonNet35BinaryCompatibility;
set => _jsonNet35BinaryCompatibility = value;
}
///
/// Gets or sets a value indicating whether the root object will be read as a JSON array.
///
///
/// true if the root object will be read as a JSON array; otherwise, false.
///
public bool ReadRootValueAsArray
{
get => _readRootValueAsArray;
set => _readRootValueAsArray = value;
}
///
/// Gets or sets the used when reading values from BSON.
///
/// The used when reading values from BSON.
public DateTimeKind DateTimeKindHandling
{
get => _dateTimeKindHandling;
set => _dateTimeKindHandling = value;
}
///
/// Initializes a new instance of the class.
///
/// The containing the BSON data to read.
public BsonReader(Stream stream)
: this(stream, false, DateTimeKind.Local)
{
}
///
/// Initializes a new instance of the class.
///
/// The containing the BSON data to read.
public BsonReader(BinaryReader reader)
: this(reader, false, DateTimeKind.Local)
{
}
///
/// Initializes a new instance of the class.
///
/// The containing the BSON data to read.
/// if set to true the root object will be read as a JSON array.
/// The used when reading values from BSON.
public BsonReader(Stream stream, bool readRootValueAsArray, DateTimeKind dateTimeKindHandling)
{
ValidationUtils.ArgumentNotNull(stream, nameof(stream));
_reader = new BinaryReader(stream);
_stack = new List();
_readRootValueAsArray = readRootValueAsArray;
_dateTimeKindHandling = dateTimeKindHandling;
}
///
/// Initializes a new instance of the class.
///
/// The containing the BSON data to read.
/// if set to true the root object will be read as a JSON array.
/// The used when reading values from BSON.
public BsonReader(BinaryReader reader, bool readRootValueAsArray, DateTimeKind dateTimeKindHandling)
{
ValidationUtils.ArgumentNotNull(reader, nameof(reader));
_reader = reader;
_stack = new List();
_readRootValueAsArray = readRootValueAsArray;
_dateTimeKindHandling = dateTimeKindHandling;
}
private string ReadElement()
{
_currentElementType = ReadType();
string elementName = ReadString();
return elementName;
}
///
/// Reads the next JSON token from the underlying .
///
///
/// true if the next token was read successfully; false if there are no more tokens to read.
///
public override bool Read()
{
try
{
bool success;
switch (_bsonReaderState)
{
case BsonReaderState.Normal:
success = ReadNormal();
break;
case BsonReaderState.ReferenceStart:
case BsonReaderState.ReferenceRef:
case BsonReaderState.ReferenceId:
success = ReadReference();
break;
case BsonReaderState.CodeWScopeStart:
case BsonReaderState.CodeWScopeCode:
case BsonReaderState.CodeWScopeScope:
case BsonReaderState.CodeWScopeScopeObject:
case BsonReaderState.CodeWScopeScopeEnd:
success = ReadCodeWScope();
break;
default:
throw JsonReaderException.Create(this, "Unexpected state: {0}".FormatWith(CultureInfo.InvariantCulture, _bsonReaderState));
}
if (!success)
{
SetToken(JsonToken.None);
return false;
}
return true;
}
catch (EndOfStreamException)
{
SetToken(JsonToken.None);
return false;
}
}
///
/// Changes the reader's state to .
/// If is set to true, the underlying is also closed.
///
public override void Close()
{
base.Close();
if (CloseInput)
{
#if HAVE_STREAM_READER_WRITER_CLOSE
_reader?.Close();
#else
_reader?.Dispose();
#endif
}
}
private bool ReadCodeWScope()
{
switch (_bsonReaderState)
{
case BsonReaderState.CodeWScopeStart:
SetToken(JsonToken.PropertyName, "$code");
_bsonReaderState = BsonReaderState.CodeWScopeCode;
return true;
case BsonReaderState.CodeWScopeCode:
// total CodeWScope size - not used
ReadInt32();
SetToken(JsonToken.String, ReadLengthString());
_bsonReaderState = BsonReaderState.CodeWScopeScope;
return true;
case BsonReaderState.CodeWScopeScope:
if (CurrentState == State.PostValue)
{
SetToken(JsonToken.PropertyName, "$scope");
return true;
}
else
{
SetToken(JsonToken.StartObject);
_bsonReaderState = BsonReaderState.CodeWScopeScopeObject;
ContainerContext newContext = new ContainerContext(BsonType.Object);
PushContext(newContext);
newContext.Length = ReadInt32();
return true;
}
case BsonReaderState.CodeWScopeScopeObject:
bool result = ReadNormal();
if (result && TokenType == JsonToken.EndObject)
{
_bsonReaderState = BsonReaderState.CodeWScopeScopeEnd;
}
return result;
case BsonReaderState.CodeWScopeScopeEnd:
SetToken(JsonToken.EndObject);
_bsonReaderState = BsonReaderState.Normal;
return true;
default:
throw new ArgumentOutOfRangeException();
}
}
private bool ReadReference()
{
switch (CurrentState)
{
case State.ObjectStart:
{
SetToken(JsonToken.PropertyName, JsonTypeReflector.RefPropertyName);
_bsonReaderState = BsonReaderState.ReferenceRef;
return true;
}
case State.Property:
{
if (_bsonReaderState == BsonReaderState.ReferenceRef)
{
SetToken(JsonToken.String, ReadLengthString());
return true;
}
else if (_bsonReaderState == BsonReaderState.ReferenceId)
{
SetToken(JsonToken.Bytes, ReadBytes(12));
return true;
}
else
{
throw JsonReaderException.Create(this, "Unexpected state when reading BSON reference: " + _bsonReaderState);
}
}
case State.PostValue:
{
if (_bsonReaderState == BsonReaderState.ReferenceRef)
{
SetToken(JsonToken.PropertyName, JsonTypeReflector.IdPropertyName);
_bsonReaderState = BsonReaderState.ReferenceId;
return true;
}
else if (_bsonReaderState == BsonReaderState.ReferenceId)
{
SetToken(JsonToken.EndObject);
_bsonReaderState = BsonReaderState.Normal;
return true;
}
else
{
throw JsonReaderException.Create(this, "Unexpected state when reading BSON reference: " + _bsonReaderState);
}
}
default:
throw JsonReaderException.Create(this, "Unexpected state when reading BSON reference: " + CurrentState);
}
}
private bool ReadNormal()
{
switch (CurrentState)
{
case State.Start:
{
JsonToken token = (!_readRootValueAsArray) ? JsonToken.StartObject : JsonToken.StartArray;
BsonType type = (!_readRootValueAsArray) ? BsonType.Object : BsonType.Array;
SetToken(token);
ContainerContext newContext = new ContainerContext(type);
PushContext(newContext);
newContext.Length = ReadInt32();
return true;
}
case State.Complete:
case State.Closed:
return false;
case State.Property:
{
ReadType(_currentElementType);
return true;
}
case State.ObjectStart:
case State.ArrayStart:
case State.PostValue:
ContainerContext context = _currentContext;
if (context == null)
{
if (SupportMultipleContent)
{
goto case State.Start;
}
return false;
}
int lengthMinusEnd = context.Length - 1;
if (context.Position < lengthMinusEnd)
{
if (context.Type == BsonType.Array)
{
ReadElement();
ReadType(_currentElementType);
return true;
}
else
{
SetToken(JsonToken.PropertyName, ReadElement());
return true;
}
}
else if (context.Position == lengthMinusEnd)
{
if (ReadByte() != 0)
{
throw JsonReaderException.Create(this, "Unexpected end of object byte value.");
}
PopContext();
if (_currentContext != null)
{
MovePosition(context.Length);
}
JsonToken endToken = (context.Type == BsonType.Object) ? JsonToken.EndObject : JsonToken.EndArray;
SetToken(endToken);
return true;
}
else
{
throw JsonReaderException.Create(this, "Read past end of current container context.");
}
case State.ConstructorStart:
break;
case State.Constructor:
break;
case State.Error:
break;
case State.Finished:
break;
default:
throw new ArgumentOutOfRangeException();
}
return false;
}
private void PopContext()
{
_stack.RemoveAt(_stack.Count - 1);
if (_stack.Count == 0)
{
_currentContext = null;
}
else
{
_currentContext = _stack[_stack.Count - 1];
}
}
private void PushContext(ContainerContext newContext)
{
_stack.Add(newContext);
_currentContext = newContext;
}
private byte ReadByte()
{
MovePosition(1);
return _reader.ReadByte();
}
private void ReadType(BsonType type)
{
switch (type)
{
case BsonType.Number:
double d = ReadDouble();
if (_floatParseHandling == FloatParseHandling.Decimal)
{
SetToken(JsonToken.Float, Convert.ToDecimal(d, CultureInfo.InvariantCulture));
}
else
{
SetToken(JsonToken.Float, d);
}
break;
case BsonType.String:
case BsonType.Symbol:
SetToken(JsonToken.String, ReadLengthString());
break;
case BsonType.Object:
{
SetToken(JsonToken.StartObject);
ContainerContext newContext = new ContainerContext(BsonType.Object);
PushContext(newContext);
newContext.Length = ReadInt32();
break;
}
case BsonType.Array:
{
SetToken(JsonToken.StartArray);
ContainerContext newContext = new ContainerContext(BsonType.Array);
PushContext(newContext);
newContext.Length = ReadInt32();
break;
}
case BsonType.Binary:
BsonBinaryType binaryType;
byte[] data = ReadBinary(out binaryType);
object value = (binaryType != BsonBinaryType.Uuid)
? data
: (object)new Guid(data);
SetToken(JsonToken.Bytes, value);
break;
case BsonType.Undefined:
SetToken(JsonToken.Undefined);
break;
case BsonType.Oid:
byte[] oid = ReadBytes(12);
SetToken(JsonToken.Bytes, oid);
break;
case BsonType.Boolean:
bool b = Convert.ToBoolean(ReadByte());
SetToken(JsonToken.Boolean, b);
break;
case BsonType.Date:
long ticks = ReadInt64();
DateTime utcDateTime = DateTimeUtils.ConvertJavaScriptTicksToDateTime(ticks);
DateTime dateTime;
switch (DateTimeKindHandling)
{
case DateTimeKind.Unspecified:
dateTime = DateTime.SpecifyKind(utcDateTime, DateTimeKind.Unspecified);
break;
case DateTimeKind.Local:
dateTime = utcDateTime.ToLocalTime();
break;
default:
dateTime = utcDateTime;
break;
}
SetToken(JsonToken.Date, dateTime);
break;
case BsonType.Null:
SetToken(JsonToken.Null);
break;
case BsonType.Regex:
string expression = ReadString();
string modifiers = ReadString();
string regex = @"/" + expression + @"/" + modifiers;
SetToken(JsonToken.String, regex);
break;
case BsonType.Reference:
SetToken(JsonToken.StartObject);
_bsonReaderState = BsonReaderState.ReferenceStart;
break;
case BsonType.Code:
SetToken(JsonToken.String, ReadLengthString());
break;
case BsonType.CodeWScope:
SetToken(JsonToken.StartObject);
_bsonReaderState = BsonReaderState.CodeWScopeStart;
break;
case BsonType.Integer:
SetToken(JsonToken.Integer, (long)ReadInt32());
break;
case BsonType.TimeStamp:
case BsonType.Long:
SetToken(JsonToken.Integer, ReadInt64());
break;
default:
throw new ArgumentOutOfRangeException(nameof(type), "Unexpected BsonType value: " + type);
}
}
private byte[] ReadBinary(out BsonBinaryType binaryType)
{
int dataLength = ReadInt32();
binaryType = (BsonBinaryType)ReadByte();
#pragma warning disable 612,618
// the old binary type has the data length repeated in the data for some reason
if (binaryType == BsonBinaryType.BinaryOld && !_jsonNet35BinaryCompatibility)
{
dataLength = ReadInt32();
}
#pragma warning restore 612,618
return ReadBytes(dataLength);
}
private string ReadString()
{
EnsureBuffers();
StringBuilder builder = null;
int totalBytesRead = 0;
// used in case of left over multibyte characters in the buffer
int offset = 0;
while (true)
{
int count = offset;
byte b;
while (count < MaxCharBytesSize && (b = _reader.ReadByte()) > 0)
{
_byteBuffer[count++] = b;
}
int byteCount = count - offset;
totalBytesRead += byteCount;
if (count < MaxCharBytesSize && builder == null)
{
// pref optimization to avoid reading into a string builder
// if string is smaller than the buffer then return it directly
int length = Encoding.UTF8.GetChars(_byteBuffer, 0, byteCount, _charBuffer, 0);
MovePosition(totalBytesRead + 1);
return new string(_charBuffer, 0, length);
}
else
{
// calculate the index of the end of the last full character in the buffer
int lastFullCharStop = GetLastFullCharStop(count - 1);
int charCount = Encoding.UTF8.GetChars(_byteBuffer, 0, lastFullCharStop + 1, _charBuffer, 0);
if (builder == null)
{
builder = new StringBuilder(MaxCharBytesSize * 2);
}
builder.Append(_charBuffer, 0, charCount);
if (lastFullCharStop < byteCount - 1)
{
offset = byteCount - lastFullCharStop - 1;
// copy left over multi byte characters to beginning of buffer for next iteration
Array.Copy(_byteBuffer, lastFullCharStop + 1, _byteBuffer, 0, offset);
}
else
{
// reached end of string
if (count < MaxCharBytesSize)
{
MovePosition(totalBytesRead + 1);
return builder.ToString();
}
offset = 0;
}
}
}
}
private string ReadLengthString()
{
int length = ReadInt32();
MovePosition(length);
string s = GetString(length - 1);
_reader.ReadByte();
return s;
}
private string GetString(int length)
{
if (length == 0)
{
return string.Empty;
}
EnsureBuffers();
StringBuilder builder = null;
int totalBytesRead = 0;
// used in case of left over multibyte characters in the buffer
int offset = 0;
do
{
int count = ((length - totalBytesRead) > MaxCharBytesSize - offset)
? MaxCharBytesSize - offset
: length - totalBytesRead;
int byteCount = _reader.Read(_byteBuffer, offset, count);
if (byteCount == 0)
{
throw new EndOfStreamException("Unable to read beyond the end of the stream.");
}
totalBytesRead += byteCount;
// Above, byteCount is how many bytes we read this time.
// Below, byteCount is how many bytes are in the _byteBuffer.
byteCount += offset;
if (byteCount == length)
{
// pref optimization to avoid reading into a string builder
// first iteration and all bytes read then return string directly
int charCount = Encoding.UTF8.GetChars(_byteBuffer, 0, byteCount, _charBuffer, 0);
return new string(_charBuffer, 0, charCount);
}
else
{
int lastFullCharStop = GetLastFullCharStop(byteCount - 1);
if (builder == null)
{
builder = new StringBuilder(length);
}
int charCount = Encoding.UTF8.GetChars(_byteBuffer, 0, lastFullCharStop + 1, _charBuffer, 0);
builder.Append(_charBuffer, 0, charCount);
if (lastFullCharStop < byteCount - 1)
{
offset = byteCount - lastFullCharStop - 1;
// copy left over multi byte characters to beginning of buffer for next iteration
Array.Copy(_byteBuffer, lastFullCharStop + 1, _byteBuffer, 0, offset);
}
else
{
offset = 0;
}
}
} while (totalBytesRead < length);
return builder.ToString();
}
private int GetLastFullCharStop(int start)
{
int lookbackPos = start;
int bis = 0;
while (lookbackPos >= 0)
{
bis = BytesInSequence(_byteBuffer[lookbackPos]);
if (bis == 0)
{
lookbackPos--;
continue;
}
else if (bis == 1)
{
break;
}
else
{
lookbackPos--;
break;
}
}
if (bis == start - lookbackPos)
{
//Full character.
return start;
}
else
{
return lookbackPos;
}
}
private int BytesInSequence(byte b)
{
if (b <= SeqRange1[1])
{
return 1;
}
if (b >= SeqRange2[0] && b <= SeqRange2[1])
{
return 2;
}
if (b >= SeqRange3[0] && b <= SeqRange3[1])
{
return 3;
}
if (b >= SeqRange4[0] && b <= SeqRange4[1])
{
return 4;
}
return 0;
}
private void EnsureBuffers()
{
if (_byteBuffer == null)
{
_byteBuffer = new byte[MaxCharBytesSize];
}
if (_charBuffer == null)
{
int charBufferSize = Encoding.UTF8.GetMaxCharCount(MaxCharBytesSize);
_charBuffer = new char[charBufferSize];
}
}
private double ReadDouble()
{
MovePosition(8);
return _reader.ReadDouble();
}
private int ReadInt32()
{
MovePosition(4);
return _reader.ReadInt32();
}
private long ReadInt64()
{
MovePosition(8);
return _reader.ReadInt64();
}
private BsonType ReadType()
{
MovePosition(1);
return (BsonType)_reader.ReadSByte();
}
private void MovePosition(int count)
{
_currentContext.Position += count;
}
private byte[] ReadBytes(int count)
{
MovePosition(count);
return _reader.ReadBytes(count);
}
}
}