#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 Newtonsoft.Json.Utilities; using Newtonsoft.Json.Linq; namespace Newtonsoft.Json.Bson { /// /// Represents a reader that provides fast, non-cached, forward-only access to serialized Json data. /// 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, ReferenceStart, ReferenceRef, ReferenceId, CodeWScopeStart, CodeWScopeCode, CodeWScopeScope, CodeWScopeScopeObject, CodeWScopeScopeEnd } 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 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. /// public bool JsonNet35BinaryCompatibility { get { return _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 { return _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 { return _dateTimeKindHandling; } set { _dateTimeKindHandling = value; } } /// /// Initializes a new instance of the class. /// /// The stream. public BsonReader(Stream stream) : this(stream, false, DateTimeKind.Local) { } /// /// Initializes a new instance of the class. /// /// The reader. public BsonReader(BinaryReader reader) : this(reader, false, DateTimeKind.Local) { } /// /// Initializes a new instance of the class. /// /// The stream. /// 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, "stream"); _reader = new BinaryReader(stream); _stack = new List(); _readRootValueAsArray = readRootValueAsArray; _dateTimeKindHandling = dateTimeKindHandling; } /// /// Initializes a new instance of the class. /// /// The reader. /// 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, "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 stream as a . /// /// /// A or a null reference if the next JSON token is null. This method will return null at the end of an array. /// public override byte[] ReadAsBytes() { Read(); if (IsWrappedInTypeObject()) { byte[] data = ReadAsBytes(); Read(); SetToken(JsonToken.Bytes, data); return data; } if (TokenType == JsonToken.Null) return null; if (TokenType == JsonToken.Bytes) return (byte[])Value; if (TokenType == JsonToken.EndArray) return null; throw CreateReaderException(this, "Error reading bytes. Expected bytes but got {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); } private bool IsWrappedInTypeObject() { if (TokenType == JsonToken.StartObject) { Read(); if (Value.ToString() == "$type") { Read(); if (Value != null && Value.ToString().StartsWith("System.Byte[]")) { Read(); if (Value.ToString() == "$value") { return true; } } } throw CreateReaderException(this, "Unexpected token when reading bytes: {0}.".FormatWith(CultureInfo.InvariantCulture, JsonToken.StartObject)); } return false; } /// /// Reads the next JSON token from the stream as a . /// /// A . This method will return null at the end of an array. public override decimal? ReadAsDecimal() { Read(); if (TokenType == JsonToken.Integer || TokenType == JsonToken.Float) { SetToken(JsonToken.Float, Convert.ToDecimal(Value, CultureInfo.InvariantCulture)); return (decimal)Value; } if (TokenType == JsonToken.Null) return null; decimal d; if (TokenType == JsonToken.String) { if (decimal.TryParse((string)Value, NumberStyles.Number, Culture, out d)) { SetToken(JsonToken.Float, d); return d; } else { throw CreateReaderException(this, "Could not convert string to decimal: {0}.".FormatWith(CultureInfo.InvariantCulture, Value)); } } if (TokenType == JsonToken.EndArray) return null; throw CreateReaderException(this, "Error reading decimal. Expected a number but got {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); } /// /// Reads the next JSON token from the stream as a . /// /// A . This method will return null at the end of an array. public override int? ReadAsInt32() { Read(); if (TokenType == JsonToken.Integer || TokenType == JsonToken.Float) { SetToken(JsonToken.Float, Convert.ToInt32(Value, CultureInfo.InvariantCulture)); return (int)Value; } if (TokenType == JsonToken.Null) return null; int i; if (TokenType == JsonToken.String) { if (int.TryParse((string)Value, NumberStyles.Integer, Culture, out i)) { SetToken(JsonToken.Integer, i); return i; } else { throw CreateReaderException(this, "Could not convert string to integer: {0}.".FormatWith(CultureInfo.InvariantCulture, Value)); } } if (TokenType == JsonToken.EndArray) return null; throw CreateReaderException(this, "Error reading integer. Expected a number but got {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); } #if !NET20 /// /// Reads the next JSON token from the stream as a . /// /// /// A . This method will return null at the end of an array. /// public override DateTimeOffset? ReadAsDateTimeOffset() { Read(); if (TokenType == JsonToken.Date) { SetToken(JsonToken.Date, new DateTimeOffset((DateTime) Value)); return (DateTimeOffset) Value; } if (TokenType == JsonToken.Null) return null; DateTimeOffset dt; if (TokenType == JsonToken.String) { if (DateTimeOffset.TryParse((string)Value, Culture, DateTimeStyles.None, out dt)) { SetToken(JsonToken.Date, dt); return dt; } else { throw CreateReaderException(this, "Could not convert string to DateTimeOffset: {0}.".FormatWith(CultureInfo.InvariantCulture, Value)); } } if (TokenType == JsonToken.EndArray) return null; throw CreateReaderException(this, "Error reading date. Expected date but got {0}.".FormatWith(CultureInfo.InvariantCulture, TokenType)); } #endif /// /// Reads the next JSON token from the stream. /// /// /// 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 CreateReaderException(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 to Closed. /// public override void Close() { base.Close(); if (CloseInput && _reader != null) _reader.Close(); } 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, "$ref"); _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 CreateReaderException(this, "Unexpected state when reading BSON reference: " + _bsonReaderState); } } case State.PostValue: { if (_bsonReaderState == BsonReaderState.ReferenceRef) { SetToken(JsonToken.PropertyName, "$id"); _bsonReaderState = BsonReaderState.ReferenceId; return true; } else if (_bsonReaderState == BsonReaderState.ReferenceId) { SetToken(JsonToken.EndObject); _bsonReaderState = BsonReaderState.Normal; return true; } else { throw CreateReaderException(this, "Unexpected state when reading BSON reference: " + _bsonReaderState); } } default: throw CreateReaderException(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) 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 CreateReaderException(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 CreateReaderException(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: SetToken(JsonToken.Float, ReadDouble()); 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: SetToken(JsonToken.Bytes, ReadBinary()); 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 = JsonConvert.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("type", "Unexpected BsonType value: " + type); } } private byte[] ReadBinary() { int dataLength = ReadInt32(); BsonBinaryType 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.Data && !_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; do { 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; } } } while (true); } 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); } } }