|
|
|
@ -53,16 +53,13 @@ namespace Google.Protobuf |
|
|
|
|
/// </remarks> |
|
|
|
|
public sealed class CodedInputStream |
|
|
|
|
{ |
|
|
|
|
// TODO(jonskeet): Consider whether recursion and size limits shouldn't be readonly, |
|
|
|
|
// set at construction time. |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Buffer of data read from the stream or provided at construction time. |
|
|
|
|
/// </summary> |
|
|
|
|
private readonly byte[] buffer; |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// The number of valid bytes in the buffer. |
|
|
|
|
/// The index of the buffer at which we need to refill from the stream (if there is one). |
|
|
|
|
/// </summary> |
|
|
|
|
private int bufferSize; |
|
|
|
|
|
|
|
|
@ -106,61 +103,103 @@ namespace Google.Protobuf |
|
|
|
|
/// </summary> |
|
|
|
|
private int currentLimit = int.MaxValue; |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// <see cref="SetRecursionLimit"/> |
|
|
|
|
/// </summary> |
|
|
|
|
private int recursionDepth = 0; |
|
|
|
|
|
|
|
|
|
private int recursionLimit = DefaultRecursionLimit; |
|
|
|
|
private readonly int recursionLimit; |
|
|
|
|
private readonly int sizeLimit; |
|
|
|
|
|
|
|
|
|
#region Construction |
|
|
|
|
// Note that the checks are performed such that we don't end up checking obviously-valid things |
|
|
|
|
// like non-null references for arrays we've just created. |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// <see cref="SetSizeLimit"/> |
|
|
|
|
/// Creates a new CodedInputStream reading data from the given byte array. |
|
|
|
|
/// </summary> |
|
|
|
|
private int sizeLimit = DefaultSizeLimit; |
|
|
|
|
public CodedInputStream(byte[] buffer) : this(null, Preconditions.CheckNotNull(buffer, "buffer"), 0, buffer.Length) |
|
|
|
|
{ |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#region Construction |
|
|
|
|
/// <summary> |
|
|
|
|
/// Creates a new CodedInputStream reading data from the given |
|
|
|
|
/// byte array. |
|
|
|
|
/// Creates a new CodedInputStream that reads from the given byte array slice. |
|
|
|
|
/// </summary> |
|
|
|
|
public CodedInputStream(byte[] buf) : this(buf, 0, buf.Length) |
|
|
|
|
{ |
|
|
|
|
public CodedInputStream(byte[] buffer, int offset, int length) |
|
|
|
|
: this(null, Preconditions.CheckNotNull(buffer, "buffer"), offset, offset + length) |
|
|
|
|
{ |
|
|
|
|
if (offset < 0 || offset > buffer.Length) |
|
|
|
|
{ |
|
|
|
|
throw new ArgumentOutOfRangeException("offset", "Offset must be within the buffer"); |
|
|
|
|
} |
|
|
|
|
if (length < 0 || offset + length > buffer.Length) |
|
|
|
|
{ |
|
|
|
|
throw new ArgumentOutOfRangeException("length", "Length must be non-negative and within the buffer"); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Creates a new CodedInputStream that reads from the given |
|
|
|
|
/// byte array slice. |
|
|
|
|
/// Creates a new CodedInputStream reading data from the given stream. |
|
|
|
|
/// </summary> |
|
|
|
|
public CodedInputStream(byte[] buffer, int offset, int length) |
|
|
|
|
public CodedInputStream(Stream input) : this(input, new byte[BufferSize], 0, 0) |
|
|
|
|
{ |
|
|
|
|
this.buffer = buffer; |
|
|
|
|
this.bufferPos = offset; |
|
|
|
|
this.bufferSize = offset + length; |
|
|
|
|
this.input = null; |
|
|
|
|
Preconditions.CheckNotNull(input, "input"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Creates a new CodedInputStream reading data from the given stream. |
|
|
|
|
/// Creates a new CodedInputStream reading data from the given |
|
|
|
|
/// stream and buffer, using the default limits. |
|
|
|
|
/// </summary> |
|
|
|
|
public CodedInputStream(Stream input) |
|
|
|
|
internal CodedInputStream(Stream input, byte[] buffer, int bufferPos, int bufferSize) |
|
|
|
|
{ |
|
|
|
|
this.buffer = new byte[BufferSize]; |
|
|
|
|
this.bufferSize = 0; |
|
|
|
|
this.input = input; |
|
|
|
|
this.buffer = buffer; |
|
|
|
|
this.bufferPos = bufferPos; |
|
|
|
|
this.bufferSize = bufferSize; |
|
|
|
|
this.sizeLimit = DefaultSizeLimit; |
|
|
|
|
this.recursionLimit = DefaultRecursionLimit; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Creates a new CodedInputStream reading data from the given |
|
|
|
|
/// stream, with a pre-allocated buffer. |
|
|
|
|
/// stream and buffer, using the specified limits. |
|
|
|
|
/// </summary> |
|
|
|
|
internal CodedInputStream(Stream input, byte[] buffer) |
|
|
|
|
/// <remarks> |
|
|
|
|
/// This chains to the version with the default limits instead of vice versa to avoid |
|
|
|
|
/// having to check that the default values are valid every time. |
|
|
|
|
/// </remarks> |
|
|
|
|
internal CodedInputStream(Stream input, byte[] buffer, int bufferPos, int bufferSize, int sizeLimit, int recursionLimit) |
|
|
|
|
: this(input, buffer, bufferPos, bufferSize) |
|
|
|
|
{ |
|
|
|
|
this.buffer = buffer; |
|
|
|
|
this.bufferSize = 0; |
|
|
|
|
this.input = input; |
|
|
|
|
if (sizeLimit <= 0) |
|
|
|
|
{ |
|
|
|
|
throw new ArgumentOutOfRangeException("sizeLimit", "Size limit must be positive"); |
|
|
|
|
} |
|
|
|
|
if (recursionLimit <= 0) |
|
|
|
|
{ |
|
|
|
|
throw new ArgumentOutOfRangeException("recursionLimit!", "Recursion limit must be positive"); |
|
|
|
|
} |
|
|
|
|
this.sizeLimit = sizeLimit; |
|
|
|
|
this.recursionLimit = recursionLimit; |
|
|
|
|
} |
|
|
|
|
#endregion |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Creates a <see cref="CodedInputStream"/> with the specified size and recursion limits, reading |
|
|
|
|
/// from an input stream. |
|
|
|
|
/// </summary> |
|
|
|
|
/// <remarks> |
|
|
|
|
/// This method exists separately from the constructor to reduce the number of constructor overloads. |
|
|
|
|
/// It is likely to be used considerably less frequently than the constructors, as the default limits |
|
|
|
|
/// are suitable for most use cases. |
|
|
|
|
/// </remarks> |
|
|
|
|
/// <param name="input">The input stream to read from</param> |
|
|
|
|
/// <param name="sizeLimit">The total limit of data to read from the stream.</param> |
|
|
|
|
/// <param name="recursionLimit">The maximum recursion depth to allow while reading.</param> |
|
|
|
|
/// <returns>A <c>CodedInputStream</c> reading from <paramref name="input"/> with the specified size |
|
|
|
|
/// and recursion limits.</returns> |
|
|
|
|
public static CodedInputStream CreateWithLimits(Stream input, int sizeLimit, int recursionLimit) |
|
|
|
|
{ |
|
|
|
|
return new CodedInputStream(input, new byte[BufferSize], 0, 0, sizeLimit, recursionLimit); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Returns the current position in the input stream, or the position in the input buffer |
|
|
|
|
/// </summary> |
|
|
|
@ -182,59 +221,30 @@ namespace Google.Protobuf |
|
|
|
|
/// </summary> |
|
|
|
|
internal uint LastTag { get { return lastTag; } } |
|
|
|
|
|
|
|
|
|
#region Limits for recursion and length |
|
|
|
|
/// <summary> |
|
|
|
|
/// Set the maximum message recursion depth. |
|
|
|
|
/// Returns the size limit for this stream. |
|
|
|
|
/// </summary> |
|
|
|
|
/// <remarks> |
|
|
|
|
/// In order to prevent malicious |
|
|
|
|
/// messages from causing stack overflows, CodedInputStream limits |
|
|
|
|
/// how deeply messages may be nested. The default limit is 64. |
|
|
|
|
/// This limit is applied when reading from the underlying stream, as a sanity check. It is |
|
|
|
|
/// not applied when reading from a byte array data source without an underlying stream. |
|
|
|
|
/// The default value is 64MB. |
|
|
|
|
/// </remarks> |
|
|
|
|
public int SetRecursionLimit(int limit) |
|
|
|
|
{ |
|
|
|
|
if (limit < 0) |
|
|
|
|
{ |
|
|
|
|
throw new ArgumentOutOfRangeException("Recursion limit cannot be negative: " + limit); |
|
|
|
|
} |
|
|
|
|
int oldLimit = recursionLimit; |
|
|
|
|
recursionLimit = limit; |
|
|
|
|
return oldLimit; |
|
|
|
|
} |
|
|
|
|
/// <value> |
|
|
|
|
/// The size limit. |
|
|
|
|
/// </value> |
|
|
|
|
public int SizeLimit { get { return sizeLimit; } } |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Set the maximum message size. |
|
|
|
|
/// Returns the recursion limit for this stream. This limit is applied whilst reading messages, |
|
|
|
|
/// to avoid maliciously-recursive data. |
|
|
|
|
/// </summary> |
|
|
|
|
/// <remarks> |
|
|
|
|
/// In order to prevent malicious messages from exhausting memory or |
|
|
|
|
/// causing integer overflows, CodedInputStream limits how large a message may be. |
|
|
|
|
/// The default limit is 64MB. You should set this limit as small |
|
|
|
|
/// as you can without harming your app's functionality. Note that |
|
|
|
|
/// size limits only apply when reading from an InputStream, not |
|
|
|
|
/// when constructed around a raw byte array (nor with ByteString.NewCodedInput). |
|
|
|
|
/// If you want to read several messages from a single CodedInputStream, you |
|
|
|
|
/// can call ResetSizeCounter() after each message to avoid hitting the |
|
|
|
|
/// size limit. |
|
|
|
|
/// The default limit is 64. |
|
|
|
|
/// </remarks> |
|
|
|
|
public int SetSizeLimit(int limit) |
|
|
|
|
{ |
|
|
|
|
if (limit < 0) |
|
|
|
|
{ |
|
|
|
|
throw new ArgumentOutOfRangeException("Size limit cannot be negative: " + limit); |
|
|
|
|
} |
|
|
|
|
int oldLimit = sizeLimit; |
|
|
|
|
sizeLimit = limit; |
|
|
|
|
return oldLimit; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// <summary> |
|
|
|
|
/// Resets the current size counter to zero (see <see cref="SetSizeLimit"/>). |
|
|
|
|
/// </summary> |
|
|
|
|
public void ResetSizeCounter() |
|
|
|
|
{ |
|
|
|
|
totalBytesRetired = 0; |
|
|
|
|
} |
|
|
|
|
#endregion |
|
|
|
|
/// <value> |
|
|
|
|
/// The recursion limit for this stream. |
|
|
|
|
/// </value> |
|
|
|
|
public int RecursionLimit { get { return recursionLimit; } } |
|
|
|
|
|
|
|
|
|
#region Validation |
|
|
|
|
/// <summary> |
|
|
|
|