You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
556 lines
22 KiB
556 lines
22 KiB
/* Copyright 2010-2011 10gen Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
|
|
namespace MongoDB.Bson.IO {
|
|
/// <summary>
|
|
/// Represents a BSON reader for a binary BSON byte array.
|
|
/// </summary>
|
|
public class BsonBinaryReader : BsonReader {
|
|
#region private fields
|
|
private BsonBuffer buffer; // if reading from a stream Create will have loaded the buffer
|
|
private bool disposeBuffer;
|
|
private new BsonBinaryReaderSettings settings; // same value as in base class just declared as derived class
|
|
private BsonBinaryReaderContext context;
|
|
#endregion
|
|
|
|
#region constructors
|
|
/// <summary>
|
|
/// Initializes a new instance of the BsonBinaryReader class.
|
|
/// <param name="buffer">A BsonBuffer.</param>
|
|
/// <param name="settings">A BsonBinaryReaderSettings.</param>
|
|
/// </summary>
|
|
public BsonBinaryReader(
|
|
BsonBuffer buffer,
|
|
BsonBinaryReaderSettings settings
|
|
)
|
|
: base(settings) {
|
|
if (buffer == null) {
|
|
this.buffer = new BsonBuffer();
|
|
this.disposeBuffer = true; // only call Dispose if we allocated the buffer
|
|
} else {
|
|
this.buffer = buffer;
|
|
this.disposeBuffer = false;
|
|
}
|
|
this.settings = settings; // already frozen by base class
|
|
context = new BsonBinaryReaderContext(null, ContextType.TopLevel, 0, 0);
|
|
}
|
|
#endregion
|
|
|
|
#region public properties
|
|
/// <summary>
|
|
/// Gets the reader's buffer.
|
|
/// </summary>
|
|
public BsonBuffer Buffer {
|
|
get { return buffer; }
|
|
}
|
|
#endregion
|
|
|
|
#region public methods
|
|
/// <summary>
|
|
/// Closes the reader.
|
|
/// </summary>
|
|
public override void Close() {
|
|
// Close can be called on Disposed objects
|
|
if (state != BsonReaderState.Closed) {
|
|
state = BsonReaderState.Closed;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a bookmark to the reader's current position and state.
|
|
/// </summary>
|
|
/// <returns>A bookmark.</returns>
|
|
public override BsonReaderBookmark GetBookmark() {
|
|
return new BsonBinaryReaderBookmark(state, currentBsonType, currentName, context, buffer.Position);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads BSON binary data from the reader.
|
|
/// </summary>
|
|
/// <param name="bytes">The binary data.</param>
|
|
/// <param name="subType">The binary data subtype.</param>
|
|
#pragma warning disable 618 // about obsolete BsonBinarySubType.OldBinary
|
|
public override void ReadBinaryData(
|
|
out byte[] bytes,
|
|
out BsonBinarySubType subType
|
|
) {
|
|
if (disposed) { ThrowObjectDisposedException(); }
|
|
VerifyBsonType("ReadBinaryData", BsonType.Binary);
|
|
|
|
int size = ReadSize();
|
|
subType = (BsonBinarySubType) buffer.ReadByte();
|
|
if (subType == BsonBinarySubType.OldBinary) {
|
|
// sub type OldBinary has two sizes (for historical reasons)
|
|
int size2 = ReadSize();
|
|
if (size2 != size - 4) {
|
|
throw new FileFormatException("Binary sub type OldBinary has inconsistent sizes");
|
|
}
|
|
size = size2;
|
|
|
|
if (settings.FixOldBinarySubTypeOnInput) {
|
|
subType = BsonBinarySubType.Binary; // replace obsolete OldBinary with new Binary sub type
|
|
}
|
|
}
|
|
bytes = buffer.ReadBytes(size);
|
|
|
|
state = GetNextState();
|
|
}
|
|
#pragma warning restore 618
|
|
|
|
/// <summary>
|
|
/// Reads a BSON boolean from the reader.
|
|
/// </summary>
|
|
/// <returns>A Boolean.</returns>
|
|
public override bool ReadBoolean() {
|
|
if (disposed) { ThrowObjectDisposedException(); }
|
|
VerifyBsonType("ReadBoolean", BsonType.Boolean);
|
|
state = GetNextState();
|
|
return buffer.ReadBoolean();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a BsonType from the reader.
|
|
/// </summary>
|
|
/// <returns>A BsonType.</returns>
|
|
public override BsonType ReadBsonType() {
|
|
if (disposed) { ThrowObjectDisposedException(); }
|
|
if (state == BsonReaderState.Initial || state == BsonReaderState.Done || state == BsonReaderState.ScopeDocument) {
|
|
// there is an implied type of Document for the top level and for scope documents
|
|
currentBsonType = BsonType.Document;
|
|
state = BsonReaderState.Value;
|
|
return currentBsonType;
|
|
}
|
|
if (state != BsonReaderState.Type) {
|
|
ThrowInvalidState("ReadBsonType", BsonReaderState.Type);
|
|
}
|
|
|
|
currentBsonType = buffer.ReadBsonType();
|
|
|
|
if (currentBsonType == BsonType.EndOfDocument) {
|
|
switch (context.ContextType) {
|
|
case ContextType.Array:
|
|
state = BsonReaderState.EndOfArray;
|
|
return BsonType.EndOfDocument;
|
|
case ContextType.Document:
|
|
case ContextType.ScopeDocument:
|
|
state = BsonReaderState.EndOfDocument;
|
|
return BsonType.EndOfDocument;
|
|
default:
|
|
var message = string.Format("BsonType EndOfDocument is not valid when ContextType is {0}.", context.ContextType);
|
|
throw new FileFormatException(message);
|
|
}
|
|
} else {
|
|
switch (context.ContextType) {
|
|
case ContextType.Array:
|
|
buffer.SkipCString(); // ignore array element names
|
|
state = BsonReaderState.Value;
|
|
break;
|
|
case ContextType.Document:
|
|
case ContextType.ScopeDocument:
|
|
currentName = buffer.ReadCString();
|
|
state = BsonReaderState.Name;
|
|
break;
|
|
default:
|
|
throw new BsonInternalException("Unexpected ContextType.");
|
|
}
|
|
|
|
return currentBsonType;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a BSON DateTime from the reader.
|
|
/// </summary>
|
|
/// <returns>The number of milliseconds since the Unix epoch.</returns>
|
|
public override long ReadDateTime() {
|
|
if (disposed) { ThrowObjectDisposedException(); }
|
|
VerifyBsonType("ReadDateTime", BsonType.DateTime);
|
|
state = GetNextState();
|
|
var value = buffer.ReadInt64();
|
|
if (value == BsonConstants.DateTimeMaxValueMillisecondsSinceEpoch + 1) {
|
|
if (settings.FixOldDateTimeMaxValueOnInput) {
|
|
value = BsonConstants.DateTimeMaxValueMillisecondsSinceEpoch;
|
|
}
|
|
}
|
|
return value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a BSON Double from the reader.
|
|
/// </summary>
|
|
/// <returns>A Double.</returns>
|
|
public override double ReadDouble() {
|
|
if (disposed) { ThrowObjectDisposedException(); }
|
|
VerifyBsonType("ReadDouble", BsonType.Double);
|
|
state = GetNextState();
|
|
return buffer.ReadDouble();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the end of a BSON array from the reader.
|
|
/// </summary>
|
|
public override void ReadEndArray() {
|
|
if (disposed) { ThrowObjectDisposedException(); }
|
|
if (context.ContextType != ContextType.Array) {
|
|
ThrowInvalidContextType("ReadEndArray", context.ContextType, ContextType.Array);
|
|
}
|
|
if (state == BsonReaderState.Type) {
|
|
ReadBsonType(); // will set state to EndOfArray if at end of array
|
|
}
|
|
if (state != BsonReaderState.EndOfArray) {
|
|
ThrowInvalidState("ReadEndArray", BsonReaderState.EndOfArray);
|
|
}
|
|
|
|
context = context.PopContext(buffer.Position);
|
|
switch (context.ContextType) {
|
|
case ContextType.Array: state = BsonReaderState.Type; break;
|
|
case ContextType.Document: state = BsonReaderState.Type; break;
|
|
case ContextType.TopLevel: state = BsonReaderState.Done; break;
|
|
default: throw new BsonInternalException("Unexpected ContextType.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the end of a BSON document from the reader.
|
|
/// </summary>
|
|
public override void ReadEndDocument() {
|
|
if (disposed) { ThrowObjectDisposedException(); }
|
|
if (
|
|
context.ContextType != ContextType.Document &&
|
|
context.ContextType != ContextType.ScopeDocument
|
|
) {
|
|
ThrowInvalidContextType("ReadEndDocument", context.ContextType, ContextType.Document, ContextType.ScopeDocument);
|
|
}
|
|
if (state == BsonReaderState.Type) {
|
|
ReadBsonType(); // will set state to EndOfDocument if at end of document
|
|
}
|
|
if (state != BsonReaderState.EndOfDocument) {
|
|
ThrowInvalidState("ReadEndDocument", BsonReaderState.EndOfDocument);
|
|
}
|
|
|
|
context = context.PopContext(buffer.Position);
|
|
if (context != null && context.ContextType == ContextType.JavaScriptWithScope) {
|
|
context = context.PopContext(buffer.Position); // JavaScriptWithScope
|
|
}
|
|
switch (context.ContextType) {
|
|
case ContextType.Array: state = BsonReaderState.Type; break;
|
|
case ContextType.Document: state = BsonReaderState.Type; break;
|
|
case ContextType.TopLevel: state = BsonReaderState.Done; break;
|
|
default: throw new BsonInternalException("Unexpected ContextType.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a BSON Int32 from the reader.
|
|
/// </summary>
|
|
/// <returns>An Int32.</returns>
|
|
public override int ReadInt32() {
|
|
if (disposed) { ThrowObjectDisposedException(); }
|
|
VerifyBsonType("ReadInt32", BsonType.Int32);
|
|
state = GetNextState();
|
|
return buffer.ReadInt32();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a BSON Int64 from the reader.
|
|
/// </summary>
|
|
/// <returns>An Int64.</returns>
|
|
public override long ReadInt64() {
|
|
if (disposed) { ThrowObjectDisposedException(); }
|
|
VerifyBsonType("ReadInt64", BsonType.Int64);
|
|
state = GetNextState();
|
|
return buffer.ReadInt64();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a BSON JavaScript from the reader.
|
|
/// </summary>
|
|
/// <returns>A string.</returns>
|
|
public override string ReadJavaScript() {
|
|
if (disposed) { ThrowObjectDisposedException(); }
|
|
VerifyBsonType("ReadJavaScript", BsonType.JavaScript);
|
|
state = GetNextState();
|
|
return buffer.ReadString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a BSON JavaScript with scope from the reader (call ReadStartDocument next to read the scope).
|
|
/// </summary>
|
|
/// <returns>A string.</returns>
|
|
public override string ReadJavaScriptWithScope() {
|
|
if (disposed) { ThrowObjectDisposedException(); }
|
|
VerifyBsonType("ReadJavaScriptWithScope", BsonType.JavaScriptWithScope);
|
|
|
|
var startPosition = buffer.Position; // position of size field
|
|
var size = ReadSize();
|
|
context = new BsonBinaryReaderContext(context, ContextType.JavaScriptWithScope, startPosition, size);
|
|
var code = buffer.ReadString();
|
|
|
|
state = BsonReaderState.ScopeDocument;
|
|
return code;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a BSON MaxKey from the reader.
|
|
/// </summary>
|
|
public override void ReadMaxKey() {
|
|
if (disposed) { ThrowObjectDisposedException(); }
|
|
VerifyBsonType("ReadMaxKey", BsonType.MaxKey);
|
|
state = GetNextState();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a BSON MinKey from the reader.
|
|
/// </summary>
|
|
public override void ReadMinKey() {
|
|
if (disposed) { ThrowObjectDisposedException(); }
|
|
VerifyBsonType("ReadMinKey", BsonType.MinKey);
|
|
state = GetNextState();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a BSON null from the reader.
|
|
/// </summary>
|
|
public override void ReadNull() {
|
|
if (disposed) { ThrowObjectDisposedException(); }
|
|
VerifyBsonType("ReadNull", BsonType.Null);
|
|
state = GetNextState();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a BSON ObjectId from the reader.
|
|
/// </summary>
|
|
/// <param name="timestamp">The timestamp.</param>
|
|
/// <param name="machine">The machine hash.</param>
|
|
/// <param name="pid">The PID.</param>
|
|
/// <param name="increment">The increment.</param>
|
|
public override void ReadObjectId(
|
|
out int timestamp,
|
|
out int machine,
|
|
out short pid,
|
|
out int increment
|
|
) {
|
|
if (disposed) { ThrowObjectDisposedException(); }
|
|
VerifyBsonType("ReadObjectId", BsonType.ObjectId);
|
|
buffer.ReadObjectId(out timestamp, out machine, out pid, out increment);
|
|
state = GetNextState();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a BSON regular expression from the reader.
|
|
/// </summary>
|
|
/// <param name="pattern">A regular expression pattern.</param>
|
|
/// <param name="options">A regular expression options.</param>
|
|
public override void ReadRegularExpression(
|
|
out string pattern,
|
|
out string options
|
|
) {
|
|
if (disposed) { ThrowObjectDisposedException(); }
|
|
VerifyBsonType("ReadRegularExpression", BsonType.RegularExpression);
|
|
pattern = buffer.ReadCString();
|
|
options = buffer.ReadCString();
|
|
state = GetNextState();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the start of a BSON array.
|
|
/// </summary>
|
|
public override void ReadStartArray() {
|
|
if (disposed) { ThrowObjectDisposedException(); }
|
|
VerifyBsonType("ReadStartArray", BsonType.Array);
|
|
|
|
var startPosition = buffer.Position; // position of size field
|
|
var size = ReadSize();
|
|
context = new BsonBinaryReaderContext(context, ContextType.Array, startPosition, size);
|
|
state = BsonReaderState.Type;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the start of a BSON document.
|
|
/// </summary>
|
|
public override void ReadStartDocument() {
|
|
if (disposed) { ThrowObjectDisposedException(); }
|
|
VerifyBsonType("ReadStartDocument", BsonType.Document);
|
|
|
|
var contextType = (state == BsonReaderState.ScopeDocument) ? ContextType.ScopeDocument : ContextType.Document;
|
|
var startPosition = buffer.Position; // position of size field
|
|
var size = ReadSize();
|
|
context = new BsonBinaryReaderContext(context, contextType, startPosition, size);
|
|
state = BsonReaderState.Type;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a BSON string from the reader.
|
|
/// </summary>
|
|
/// <returns>A String.</returns>
|
|
public override string ReadString() {
|
|
if (disposed) { ThrowObjectDisposedException(); }
|
|
VerifyBsonType("ReadString", BsonType.String);
|
|
state = GetNextState();
|
|
return buffer.ReadString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a BSON symbol from the reader.
|
|
/// </summary>
|
|
/// <returns>A string.</returns>
|
|
public override string ReadSymbol() {
|
|
if (disposed) { ThrowObjectDisposedException(); }
|
|
VerifyBsonType("ReadSymbol", BsonType.Symbol);
|
|
state = GetNextState();
|
|
return buffer.ReadString();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a BSON timestamp from the reader.
|
|
/// </summary>
|
|
/// <returns>The combined timestamp/increment.</returns>
|
|
public override long ReadTimestamp() {
|
|
if (disposed) { ThrowObjectDisposedException(); }
|
|
VerifyBsonType("ReadTimestamp", BsonType.Timestamp);
|
|
state = GetNextState();
|
|
return buffer.ReadInt64();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a BSON undefined from the reader.
|
|
/// </summary>
|
|
public override void ReadUndefined() {
|
|
if (disposed) { ThrowObjectDisposedException(); }
|
|
VerifyBsonType("ReadUndefined", BsonType.Undefined);
|
|
state = GetNextState();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the reader to previously bookmarked position and state.
|
|
/// </summary>
|
|
/// <param name="bookmark">The bookmark.</param>
|
|
public override void ReturnToBookmark(
|
|
BsonReaderBookmark bookmark
|
|
) {
|
|
var binaryReaderBookmark = (BsonBinaryReaderBookmark) bookmark;
|
|
state = binaryReaderBookmark.State;
|
|
currentBsonType = binaryReaderBookmark.CurrentBsonType;
|
|
currentName = binaryReaderBookmark.CurrentName;
|
|
context = binaryReaderBookmark.CloneContext();
|
|
buffer.Position = binaryReaderBookmark.Position;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Skips the name (reader must be positioned on a name).
|
|
/// </summary>
|
|
public override void SkipName() {
|
|
if (disposed) { ThrowObjectDisposedException(); }
|
|
if (state != BsonReaderState.Name) {
|
|
ThrowInvalidState("SkipName", BsonReaderState.Name);
|
|
}
|
|
|
|
state = BsonReaderState.Value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Skips the value (reader must be positioned on a value).
|
|
/// </summary>
|
|
public override void SkipValue() {
|
|
if (disposed) { ThrowObjectDisposedException(); }
|
|
if (state != BsonReaderState.Value) {
|
|
ThrowInvalidState("SkipValue", BsonReaderState.Value);
|
|
}
|
|
|
|
int skip;
|
|
switch (currentBsonType) {
|
|
case BsonType.Array: skip = ReadSize() - 4; break;
|
|
case BsonType.Binary: skip = ReadSize() + 1; break;
|
|
case BsonType.Boolean: skip = 1; break;
|
|
case BsonType.DateTime: skip = 8; break;
|
|
case BsonType.Document: skip = ReadSize() - 4; break;
|
|
case BsonType.Double: skip = 8; break;
|
|
case BsonType.Int32: skip = 4; break;
|
|
case BsonType.Int64: skip = 8; break;
|
|
case BsonType.JavaScript: skip = ReadSize(); break;
|
|
case BsonType.JavaScriptWithScope: skip = ReadSize() - 4; break;
|
|
case BsonType.MaxKey: skip = 0; break;
|
|
case BsonType.MinKey: skip = 0; break;
|
|
case BsonType.Null: skip = 0; break;
|
|
case BsonType.ObjectId: skip = 12; break;
|
|
case BsonType.RegularExpression: buffer.SkipCString(); buffer.SkipCString(); skip = 0; break;
|
|
case BsonType.String: skip = ReadSize(); break;
|
|
case BsonType.Symbol: skip = ReadSize(); break;
|
|
case BsonType.Timestamp: skip = 8; break;
|
|
case BsonType.Undefined: skip = 0; break;
|
|
default: throw new BsonInternalException("Unexpected BsonType.");
|
|
}
|
|
buffer.Skip(skip);
|
|
|
|
state = BsonReaderState.Type;
|
|
}
|
|
#endregion
|
|
|
|
#region protected methods
|
|
/// <summary>
|
|
/// Disposes of any resources used by the reader.
|
|
/// </summary>
|
|
/// <param name="disposing">True if called from Dispose.</param>
|
|
protected override void Dispose(
|
|
bool disposing
|
|
) {
|
|
if (disposing) {
|
|
try {
|
|
Close();
|
|
if (disposeBuffer) {
|
|
buffer.Dispose();
|
|
buffer = null;
|
|
}
|
|
} catch { } // ignore exceptions
|
|
}
|
|
base.Dispose(disposing);
|
|
}
|
|
#endregion
|
|
|
|
#region private methods
|
|
private BsonReaderState GetNextState() {
|
|
switch (context.ContextType) {
|
|
case ContextType.Array:
|
|
case ContextType.Document:
|
|
case ContextType.ScopeDocument:
|
|
return BsonReaderState.Type;
|
|
case ContextType.TopLevel:
|
|
return BsonReaderState.Done;
|
|
default:
|
|
throw new BsonInternalException("Unexpected ContextType.");
|
|
}
|
|
}
|
|
|
|
private int ReadSize() {
|
|
int size = buffer.ReadInt32();
|
|
if (size < 0) {
|
|
var message = string.Format("Size {0} is not valid because it is negative.", size);
|
|
throw new FileFormatException(message);
|
|
}
|
|
if (size > settings.MaxDocumentSize) {
|
|
var message = string.Format("Size {0} is not valid because it is larger than MaxDocumentSize {0}.", size, settings.MaxDocumentSize);
|
|
throw new FileFormatException(message);
|
|
}
|
|
return size;
|
|
}
|
|
#endregion
|
|
}
|
|
}
|