/// <summary> /// Parses the next <see cref="BList"/> from the stream. /// </summary> /// <param name="stream">The stream to parse from.</param> /// <returns>The parsed <see cref="BList"/>.</returns> /// <exception cref="InvalidBencodeException{BList}">Invalid bencode</exception> public override BList Parse(BencodeStream stream) { if (stream == null) { throw new ArgumentNullException(nameof(stream)); } if (stream.Length < MinimumLength) { throw InvalidBencodeException <BList> .BelowMinimumLength(MinimumLength, stream.Length, stream.Position); } // Lists must start with 'l' if (stream.ReadChar() != 'l') { throw InvalidBencodeException <BList> .UnexpectedChar('l', stream.ReadPreviousChar(), stream.Position); } var list = new BList(); // Loop until next character is the end character 'e' or end of stream while (stream.Peek() != 'e' && stream.Peek() != -1) { // Decode next object in stream var bObject = BencodeParser.Parse(stream); list.Add(bObject); } if (stream.ReadChar() != 'e') { throw InvalidBencodeException <BList> .InvalidEndChar(stream.ReadPreviousChar(), stream.Position); } return(list); }
/// <summary> /// Ensure that the previously read char is a colon (:), /// separating the string-length part and the actual string value. /// </summary> private void EnsurePreviousCharIsColon(char previousChar, long position) { if (previousChar != ':') { throw InvalidBencodeException <BString> .UnexpectedChar(':', previousChar, position - 1); } }
/// <summary> /// Parses the next <see cref="BList"/> from the reader. /// </summary> /// <param name="reader">The reader to parse from.</param> /// <param name="cancellationToken"></param> /// <returns>The parsed <see cref="BList"/>.</returns> /// <exception cref="InvalidBencodeException{BList}">Invalid bencode.</exception> public override async ValueTask <BList> ParseAsync(PipeBencodeReader reader, CancellationToken cancellationToken = default) { if (reader == null) { throw new ArgumentNullException(nameof(reader)); } var startPosition = reader.Position; // Lists must start with 'l' if (await reader.ReadCharAsync(cancellationToken).ConfigureAwait(false) != 'l') { throw InvalidBencodeException <BList> .UnexpectedChar('l', reader.PreviousChar, startPosition); } var list = new BList(); // Loop until next character is the end character 'e' or end of stream while (await reader.PeekCharAsync(cancellationToken).ConfigureAwait(false) != 'e' && await reader.PeekCharAsync(cancellationToken).ConfigureAwait(false) != default) { // Decode next object in stream var bObject = await BencodeParser.ParseAsync(reader, cancellationToken).ConfigureAwait(false); list.Add(bObject); } if (await reader.ReadCharAsync(cancellationToken).ConfigureAwait(false) != 'e') { throw InvalidBencodeException <BList> .MissingEndChar(startPosition); } return(list); }
/// <summary> /// Parses the next <see cref="BDictionary"/> and its contained keys and values from the reader. /// </summary> /// <param name="reader">The reader to parse from.</param> /// <param name="cancellationToken"></param> /// <returns>The parsed <see cref="BDictionary"/>.</returns> /// <exception cref="InvalidBencodeException{BDictionary}">Invalid bencode.</exception> public override async ValueTask <BDictionary> ParseAsync(PipeBencodeReader reader, CancellationToken cancellationToken = default) { if (reader == null) { throw new ArgumentNullException(nameof(reader)); } var startPosition = reader.Position; // Dictionaries must start with 'd' if (await reader.ReadCharAsync(cancellationToken).ConfigureAwait(false) != 'd') { throw InvalidBencodeException <BDictionary> .UnexpectedChar('d', reader.PreviousChar, startPosition); } var dictionary = new BDictionary(); // Loop until next character is the end character 'e' or end of stream while (await reader.PeekCharAsync(cancellationToken).ConfigureAwait(false) != 'e' && await reader.PeekCharAsync(cancellationToken).ConfigureAwait(false) != default) { BString key; try { // Decode next string in stream as the key key = await BencodeParser.ParseAsync <BString>(reader, cancellationToken).ConfigureAwait(false); } catch (BencodeException ex) { throw InvalidException("Could not parse dictionary key. Keys must be strings.", ex, startPosition); } IBObject value; try { // Decode next object in stream as the value value = await BencodeParser.ParseAsync(reader, cancellationToken).ConfigureAwait(false); } catch (BencodeException ex) { throw InvalidException($"Could not parse dictionary value for the key '{key}'. There needs to be a value for each key.", ex, startPosition); } if (dictionary.ContainsKey(key)) { throw InvalidException($"The dictionary already contains the key '{key}'. Duplicate keys are not supported.", startPosition); } dictionary.Add(key, value); } if (await reader.ReadCharAsync(cancellationToken).ConfigureAwait(false) != 'e') { throw InvalidBencodeException <BDictionary> .MissingEndChar(startPosition); } return(dictionary); }
/// <summary> /// Parses the next <see cref="BNumber"/> from the reader. /// </summary> /// <param name="reader">The reader 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(BencodeReader reader) { if (reader == null) { throw new ArgumentNullException(nameof(reader)); } if (reader.Length < MinimumLength) { throw InvalidBencodeException <BNumber> .BelowMinimumLength(MinimumLength, reader.Length.Value, reader.Position); } var startPosition = reader.Position; // Numbers must start with 'i' if (reader.ReadChar() != 'i') { throw InvalidBencodeException <BNumber> .UnexpectedChar('i', reader.PreviousChar, startPosition); } using var digits = MemoryPool <char> .Shared.Rent(BNumber.MaxDigits); var digitCount = 0; for (var c = reader.ReadChar(); c != default && c != 'e'; c = reader.ReadChar()) { digits.Memory.Span[digitCount++] = c; } if (digitCount == 0) { throw NoDigitsException(startPosition); } // Last read character should be 'e' if (reader.PreviousChar != 'e') { throw InvalidBencodeException <BNumber> .MissingEndChar(startPosition); } return(ParseNumber(digits.Memory.Span.Slice(0, digitCount), startPosition)); }
/// <summary> /// Parses the next <see cref="BNumber"/> from the reader. /// </summary> /// <param name="reader">The reader to parse from.</param> /// <param name="cancellationToken"></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 async ValueTask <BNumber> ParseAsync(PipeBencodeReader reader, CancellationToken cancellationToken = default) { if (reader == null) { throw new ArgumentNullException(nameof(reader)); } var startPosition = reader.Position; // Numbers must start with 'i' if (await reader.ReadCharAsync(cancellationToken).ConfigureAwait(false) != 'i') { throw InvalidBencodeException <BNumber> .UnexpectedChar('i', reader.PreviousChar, startPosition); } using var memoryOwner = MemoryPool <char> .Shared.Rent(BNumber.MaxDigits); var digits = memoryOwner.Memory; var digitCount = 0; for (var c = await reader.ReadCharAsync(cancellationToken).ConfigureAwait(false); c != default && c != 'e'; c = await reader.ReadCharAsync(cancellationToken).ConfigureAwait(false)) { digits.Span[digitCount++] = c; } if (digitCount == 0) { throw NoDigitsException(startPosition); } // Last read character should be 'e' if (reader.PreviousChar != 'e') { throw InvalidBencodeException <BNumber> .MissingEndChar(startPosition); } return(ParseNumber(digits.Span.Slice(0, digitCount), startPosition)); }
/// <summary> /// Parses the next <see cref="BList"/> from the reader. /// </summary> /// <param name="reader">The reader to parse from.</param> /// <returns>The parsed <see cref="BList"/>.</returns> /// <exception cref="InvalidBencodeException{BList}">Invalid bencode.</exception> public override BList Parse(BencodeReader reader) { if (reader == null) { throw new ArgumentNullException(nameof(reader)); } if (reader.Length < MinimumLength) { throw InvalidBencodeException <BList> .BelowMinimumLength(MinimumLength, reader.Length.Value, reader.Position); } var startPosition = reader.Position; // Lists must start with 'l' if (reader.ReadChar() != 'l') { throw InvalidBencodeException <BList> .UnexpectedChar('l', reader.PreviousChar, startPosition); } var list = new BList(); // Loop until next character is the end character 'e' or end of stream while (reader.PeekChar() != 'e' && reader.PeekChar() != default) { // Decode next object in stream var bObject = BencodeParser.Parse(reader); list.Add(bObject); } if (reader.ReadChar() != 'e') { throw InvalidBencodeException <BList> .MissingEndChar(startPosition); } return(list); }
/// <summary> /// Parses the next <see cref="BDictionary"/> from the stream and its contained keys and values. /// </summary> /// <param name="stream">The stream to parse from.</param> /// <returns>The parsed <see cref="BDictionary"/>.</returns> /// <exception cref="InvalidBencodeException{BDictionary}">Invalid bencode</exception> public override BDictionary Parse(BencodeStream stream) { if (stream == null) { throw new ArgumentNullException(nameof(stream)); } var startPosition = stream.Position; if (stream.Length < MinimumLength) { throw InvalidBencodeException <BDictionary> .BelowMinimumLength(MinimumLength, stream.Length, startPosition); } // Dictionaries must start with 'd' if (stream.ReadChar() != 'd') { throw InvalidBencodeException <BDictionary> .UnexpectedChar('d', stream.ReadPreviousChar(), startPosition); } var dictionary = new BDictionary(); // Loop until next character is the end character 'e' or end of stream while (stream.Peek() != 'e' && stream.Peek() != -1) { BString key; try { // Decode next string in stream as the key key = BencodeParser.Parse <BString>(stream); } catch (BencodeException <BString> ex) { throw InvalidException("Could not parse dictionary key. Keys must be strings.", ex, startPosition); } IBObject value; try { // Decode next object in stream as the value value = BencodeParser.Parse(stream); } catch (BencodeException ex) { throw InvalidException( $"Could not parse dictionary value for the key '{key}'. There needs to be a value for each key.", ex, startPosition); } if (dictionary.ContainsKey(key)) { throw InvalidException( $"The dictionary already contains the key '{key}'. Duplicate keys are not supported.", startPosition); } dictionary.Add(key, value); } if (stream.ReadChar() != 'e') { if (stream.EndOfStream) { throw InvalidBencodeException <BDictionary> .MissingEndChar(); } throw InvalidBencodeException <BDictionary> .InvalidEndChar(stream.ReadPreviousChar(), stream.Position); } return(dictionary); }
/// <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)); }