|
@@ -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>
|