/// <summary> /// Parses the next <see cref="BString"/> from the stream. /// </summary> /// <param name="stream">The stream to parse from.</param> /// <returns>The parsed <see cref="BString"/>.</returns> /// <exception cref="InvalidBencodeException{BString}">Invalid bencode</exception> /// <exception cref="UnsupportedBencodeException{BString}">The bencode is unsupported by this library</exception> public override BString Parse(BencodeStream stream) { if (stream == null) { throw new ArgumentNullException(nameof(stream)); } // Minimum valid bencode string is '0:' meaning an empty string if (stream.Length < MinimumLength) { throw InvalidBencodeException <BString> .BelowMinimumLength(MinimumLength, stream.Length, stream.Position); } var startPosition = stream.Position; var lengthString = new StringBuilder(); for (var c = stream.ReadChar(); c != ':' && c != default(char); c = stream.ReadChar()) { // Because of memory limitations (~1-2 GB) we know for certain we cannot handle more than 10 digits (10GB) if (lengthString.Length >= BString.LengthMaxDigits) { throw UnsupportedException( $"Length of string is more than {BString.LengthMaxDigits} digits (>10GB) and is not supported (max is ~1-2GB).", startPosition); } lengthString.Append(c); } long stringLength; if (!ParseUtil.TryParseLongFast(lengthString.ToString(), out stringLength)) { throw InvalidException($"Invalid length '{lengthString}' of string.", startPosition); } // Int32.MaxValue is ~2GB and is the absolute maximum that can be handled in memory if (stringLength > int.MaxValue) { throw UnsupportedException( $"Length of string is {stringLength:N0} but maximum supported length is {int.MaxValue:N0}.", startPosition); } var bytes = stream.Read((int)stringLength); // If the two don't match we've reached the end of the stream before reading the expected number of chars if (bytes.Length != stringLength) { throw InvalidException( $"Expected string to be {stringLength:N0} bytes long but could only read {bytes.Length:N0} bytes.", startPosition); } return(new BString(bytes, Encoding)); }
/// <summary> /// Parses the string-length <see cref="string"/> into a <see cref="long"/>. /// </summary> private long ParseStringLength(Span <char> lengthString, int lengthStringCount, long startPosition) { lengthString = lengthString.Slice(0, lengthStringCount); if (!ParseUtil.TryParseLongFast(lengthString, out var stringLength)) { throw InvalidException($"Invalid length '{lengthString.AsString()}' of string.", startPosition); } // Int32.MaxValue is ~2GB and is the absolute maximum that can be handled in memory if (stringLength > int.MaxValue) { throw UnsupportedException( $"Length of string is {stringLength:N0} but maximum supported length is {int.MaxValue:N0}.", startPosition); } return(stringLength); }
/// <summary> /// Parses the next <see cref="BNumber"/> from the stream. /// </summary> /// <param name="stream">The stream to parse from.</param> /// <returns>The parsed <see cref="BNumber"/>.</returns> /// <exception cref="InvalidBencodeException{BNumber}">Invalid bencode</exception> /// <exception cref="UnsupportedBencodeException{BNumber}">The bencode is unsupported by this library</exception> public override BNumber Parse(BencodeStream stream) { if (stream == null) { throw new ArgumentNullException(nameof(stream)); } if (stream.Length < MinimumLength) { throw InvalidBencodeException <BNumber> .BelowMinimumLength(MinimumLength, stream.Length, stream.Position); } var startPosition = stream.Position; // Numbers must start with 'i' if (stream.ReadChar() != 'i') { throw InvalidBencodeException <BNumber> .UnexpectedChar('i', stream.ReadPreviousChar(), stream.Position); } var digits = new StringBuilder(); char c; for (c = stream.ReadChar(); c != 'e' && c != default(char); c = stream.ReadChar()) { digits.Append(c); } // Last read character should be 'e' if (c != 'e') { throw InvalidBencodeException <BNumber> .InvalidEndChar(c, stream.Position); } var isNegative = digits[0] == '-'; var numberOfDigits = isNegative ? digits.Length - 1 : digits.Length; // We do not support numbers that cannot be stored as a long (Int64) if (numberOfDigits > BNumber.MaxDigits) { throw UnsupportedException( $"The number '{digits}' has more than 19 digits and cannot be stored as a long (Int64) and therefore is not supported.", startPosition); } // We need at least one digit if (numberOfDigits < 1) { throw InvalidException("It contains no digits.", startPosition); } var firstDigit = isNegative ? digits[1] : digits[0]; // Leading zeros are not valid if (firstDigit == '0' && numberOfDigits > 1) { throw InvalidException($"Leading '0's are not valid. Found value '{digits}'.", startPosition); } // '-0' is not valid either if (firstDigit == '0' && numberOfDigits == 1 && isNegative) { throw InvalidException("'-0' is not a valid number.", startPosition); } long number; if (!ParseUtil.TryParseLongFast(digits.ToString(), out number)) { var nonSignChars = isNegative ? digits.ToString(1, digits.Length - 1) : digits.ToString(); if (nonSignChars.Any(x => !x.IsDigit())) { throw InvalidException($"The value '{digits}' is not a valid number.", startPosition); } throw UnsupportedException( $"The value '{digits}' is not a valid long (Int64). Supported values range from '{long.MinValue:N0}' to '{long.MaxValue:N0}'.", startPosition); } return(new BNumber(number)); }