mirror of
https://github.com/Readarr/Readarr.git
synced 2026-04-23 22:25:09 -04:00
New: Implemented Torrent Download Clients: uTorrent, Transmission and Deluge. And several public and private Torrent Indexers.
This commit is contained in:
@@ -0,0 +1,321 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace MonoTorrent.BEncoding
|
||||
{
|
||||
/// <summary>
|
||||
/// Class representing a BEncoded Dictionary
|
||||
/// </summary>
|
||||
public class BEncodedDictionary : BEncodedValue, IDictionary<BEncodedString, BEncodedValue>
|
||||
{
|
||||
#region Member Variables
|
||||
|
||||
private SortedDictionary<BEncodedString, BEncodedValue> dictionary;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Create a new BEncodedDictionary
|
||||
/// </summary>
|
||||
public BEncodedDictionary()
|
||||
{
|
||||
this.dictionary = new SortedDictionary<BEncodedString, BEncodedValue>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Encode/Decode Methods
|
||||
|
||||
/// <summary>
|
||||
/// Encodes the dictionary to a byte[]
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer to encode the data to</param>
|
||||
/// <param name="offset">The offset to start writing the data to</param>
|
||||
/// <returns></returns>
|
||||
public override int Encode(byte[] buffer, int offset)
|
||||
{
|
||||
int written = 0;
|
||||
|
||||
//Dictionaries start with 'd'
|
||||
buffer[offset] = (byte)'d';
|
||||
written++;
|
||||
|
||||
foreach (KeyValuePair<BEncodedString, BEncodedValue> keypair in this)
|
||||
{
|
||||
written += keypair.Key.Encode(buffer, offset + written);
|
||||
written += keypair.Value.Encode(buffer, offset + written);
|
||||
}
|
||||
|
||||
// Dictionaries end with 'e'
|
||||
buffer[offset + written] = (byte)'e';
|
||||
written++;
|
||||
return written;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
internal override void DecodeInternal(RawReader reader)
|
||||
{
|
||||
DecodeInternal(reader, reader.StrictDecoding);
|
||||
}
|
||||
|
||||
private void DecodeInternal(RawReader reader, bool strictDecoding)
|
||||
{
|
||||
BEncodedString key = null;
|
||||
BEncodedValue value = null;
|
||||
BEncodedString oldkey = null;
|
||||
|
||||
if (reader.ReadByte() != 'd')
|
||||
throw new BEncodingException("Invalid data found. Aborting"); // Remove the leading 'd'
|
||||
|
||||
while ((reader.PeekByte() != -1) && (reader.PeekByte() != 'e'))
|
||||
{
|
||||
key = (BEncodedString)BEncodedValue.Decode(reader); // keys have to be BEncoded strings
|
||||
|
||||
if (oldkey != null && oldkey.CompareTo(key) > 0)
|
||||
if (strictDecoding)
|
||||
throw new BEncodingException(String.Format(
|
||||
"Illegal BEncodedDictionary. The attributes are not ordered correctly. Old key: {0}, New key: {1}",
|
||||
oldkey, key));
|
||||
|
||||
oldkey = key;
|
||||
value = BEncodedValue.Decode(reader); // the value is a BEncoded value
|
||||
dictionary.Add(key, value);
|
||||
}
|
||||
|
||||
if (reader.ReadByte() != 'e') // remove the trailing 'e'
|
||||
throw new BEncodingException("Invalid data found. Aborting");
|
||||
}
|
||||
|
||||
public static BEncodedDictionary DecodeTorrent(byte[] bytes)
|
||||
{
|
||||
return DecodeTorrent(new MemoryStream(bytes));
|
||||
}
|
||||
|
||||
public static BEncodedDictionary DecodeTorrent(Stream s)
|
||||
{
|
||||
return DecodeTorrent(new RawReader(s));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Special decoding method for torrent files - allows dictionary attributes to be out of order for the
|
||||
/// overall torrent file, but imposes strict rules on the info dictionary.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static BEncodedDictionary DecodeTorrent(RawReader reader)
|
||||
{
|
||||
BEncodedString key = null;
|
||||
BEncodedValue value = null;
|
||||
BEncodedDictionary torrent = new BEncodedDictionary();
|
||||
if (reader.ReadByte() != 'd')
|
||||
throw new BEncodingException("Invalid data found. Aborting"); // Remove the leading 'd'
|
||||
|
||||
while ((reader.PeekByte() != -1) && (reader.PeekByte() != 'e'))
|
||||
{
|
||||
key = (BEncodedString)BEncodedValue.Decode(reader); // keys have to be BEncoded strings
|
||||
|
||||
if (reader.PeekByte() == 'd')
|
||||
{
|
||||
value = new BEncodedDictionary();
|
||||
if (key.Text.ToLower().Equals("info"))
|
||||
((BEncodedDictionary)value).DecodeInternal(reader, true);
|
||||
else
|
||||
((BEncodedDictionary)value).DecodeInternal(reader, false);
|
||||
}
|
||||
else
|
||||
value = BEncodedValue.Decode(reader); // the value is a BEncoded value
|
||||
|
||||
torrent.dictionary.Add(key, value);
|
||||
}
|
||||
|
||||
if (reader.ReadByte() != 'e') // remove the trailing 'e'
|
||||
throw new BEncodingException("Invalid data found. Aborting");
|
||||
|
||||
return torrent;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
/// <summary>
|
||||
/// Returns the size of the dictionary in bytes using UTF8 encoding
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override int LengthInBytes()
|
||||
{
|
||||
int length = 0;
|
||||
length += 1; // Dictionaries start with 'd'
|
||||
|
||||
foreach (KeyValuePair<BEncodedString, BEncodedValue> keypair in this.dictionary)
|
||||
{
|
||||
length += keypair.Key.LengthInBytes();
|
||||
length += keypair.Value.LengthInBytes();
|
||||
}
|
||||
length += 1; // Dictionaries end with 'e'
|
||||
return length;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Overridden Methods
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
BEncodedValue val;
|
||||
BEncodedDictionary other = obj as BEncodedDictionary;
|
||||
if (other == null)
|
||||
return false;
|
||||
|
||||
if (this.dictionary.Count != other.dictionary.Count)
|
||||
return false;
|
||||
|
||||
foreach (KeyValuePair<BEncodedString, BEncodedValue> keypair in this.dictionary)
|
||||
{
|
||||
if (!other.TryGetValue(keypair.Key, out val))
|
||||
return false;
|
||||
|
||||
if (!keypair.Value.Equals(val))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
int result = 0;
|
||||
foreach (KeyValuePair<BEncodedString, BEncodedValue> keypair in dictionary)
|
||||
{
|
||||
result ^= keypair.Key.GetHashCode();
|
||||
result ^= keypair.Value.GetHashCode();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return System.Text.Encoding.UTF8.GetString(Encode());
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region IDictionary and IList methods
|
||||
public void Add(BEncodedString key, BEncodedValue value)
|
||||
{
|
||||
this.dictionary.Add(key, value);
|
||||
}
|
||||
|
||||
public void Add(KeyValuePair<BEncodedString, BEncodedValue> item)
|
||||
{
|
||||
this.dictionary.Add(item.Key, item.Value);
|
||||
}
|
||||
public void Clear()
|
||||
{
|
||||
this.dictionary.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(KeyValuePair<BEncodedString, BEncodedValue> item)
|
||||
{
|
||||
if (!this.dictionary.ContainsKey(item.Key))
|
||||
return false;
|
||||
|
||||
return this.dictionary[item.Key].Equals(item.Value);
|
||||
}
|
||||
|
||||
public bool ContainsKey(BEncodedString key)
|
||||
{
|
||||
return this.dictionary.ContainsKey(key);
|
||||
}
|
||||
|
||||
public void CopyTo(KeyValuePair<BEncodedString, BEncodedValue>[] array, int arrayIndex)
|
||||
{
|
||||
this.dictionary.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get { return this.dictionary.Count; }
|
||||
}
|
||||
|
||||
//public int IndexOf(KeyValuePair<BEncodedString, IBEncodedValue> item)
|
||||
//{
|
||||
// return this.dictionary.IndexOf(item);
|
||||
//}
|
||||
|
||||
//public void Insert(int index, KeyValuePair<BEncodedString, IBEncodedValue> item)
|
||||
//{
|
||||
// this.dictionary.Insert(index, item);
|
||||
//}
|
||||
|
||||
public bool IsReadOnly
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public bool Remove(BEncodedString key)
|
||||
{
|
||||
return this.dictionary.Remove(key);
|
||||
}
|
||||
|
||||
public bool Remove(KeyValuePair<BEncodedString, BEncodedValue> item)
|
||||
{
|
||||
return this.dictionary.Remove(item.Key);
|
||||
}
|
||||
|
||||
//public void RemoveAt(int index)
|
||||
//{
|
||||
// this.dictionary.RemoveAt(index);
|
||||
//}
|
||||
|
||||
public bool TryGetValue(BEncodedString key, out BEncodedValue value)
|
||||
{
|
||||
return this.dictionary.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
public BEncodedValue this[BEncodedString key]
|
||||
{
|
||||
get { return this.dictionary[key]; }
|
||||
set { this.dictionary[key] = value; }
|
||||
}
|
||||
|
||||
//public KeyValuePair<BEncodedString, IBEncodedValue> this[int index]
|
||||
//{
|
||||
// get { return this.dictionary[index]; }
|
||||
// set { this.dictionary[index] = value; }
|
||||
//}
|
||||
|
||||
public ICollection<BEncodedString> Keys
|
||||
{
|
||||
get { return this.dictionary.Keys; }
|
||||
}
|
||||
|
||||
public ICollection<BEncodedValue> Values
|
||||
{
|
||||
get { return this.dictionary.Values; }
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<BEncodedString, BEncodedValue>> GetEnumerator()
|
||||
{
|
||||
return this.dictionary.GetEnumerator();
|
||||
}
|
||||
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
|
||||
{
|
||||
return this.dictionary.GetEnumerator();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
|
||||
namespace MonoTorrent.BEncoding
|
||||
{
|
||||
/// <summary>
|
||||
/// Class representing a BEncoded list
|
||||
/// </summary>
|
||||
public class BEncodedList : BEncodedValue, IList<BEncodedValue>
|
||||
{
|
||||
#region Member Variables
|
||||
|
||||
private List<BEncodedValue> list;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
/// <summary>
|
||||
/// Create a new BEncoded List with default capacity
|
||||
/// </summary>
|
||||
public BEncodedList()
|
||||
: this(new List<BEncodedValue>())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new BEncoded List with the supplied capacity
|
||||
/// </summary>
|
||||
/// <param name="capacity">The initial capacity</param>
|
||||
public BEncodedList(int capacity)
|
||||
: this(new List<BEncodedValue>(capacity))
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public BEncodedList(IEnumerable<BEncodedValue> list)
|
||||
{
|
||||
if (list == null)
|
||||
throw new ArgumentNullException("list");
|
||||
|
||||
this.list = new List<BEncodedValue>(list);
|
||||
}
|
||||
|
||||
private BEncodedList(List<BEncodedValue> value)
|
||||
{
|
||||
this.list = value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Encode/Decode Methods
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Encodes the list to a byte[]
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer to encode the list to</param>
|
||||
/// <param name="offset">The offset to start writing the data at</param>
|
||||
/// <returns></returns>
|
||||
public override int Encode(byte[] buffer, int offset)
|
||||
{
|
||||
int written = 0;
|
||||
buffer[offset] = (byte)'l';
|
||||
written++;
|
||||
for (int i = 0; i < this.list.Count; i++)
|
||||
written += this.list[i].Encode(buffer, offset + written);
|
||||
buffer[offset + written] = (byte)'e';
|
||||
written++;
|
||||
return written;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decodes a BEncodedList from the given StreamReader
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
internal override void DecodeInternal(RawReader reader)
|
||||
{
|
||||
if (reader.ReadByte() != 'l') // Remove the leading 'l'
|
||||
throw new BEncodingException("Invalid data found. Aborting");
|
||||
|
||||
while ((reader.PeekByte() != -1) && (reader.PeekByte() != 'e'))
|
||||
list.Add(BEncodedValue.Decode(reader));
|
||||
|
||||
if (reader.ReadByte() != 'e') // Remove the trailing 'e'
|
||||
throw new BEncodingException("Invalid data found. Aborting");
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Helper Methods
|
||||
/// <summary>
|
||||
/// Returns the size of the list in bytes
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override int LengthInBytes()
|
||||
{
|
||||
int length = 0;
|
||||
|
||||
length += 1; // Lists start with 'l'
|
||||
for (int i=0; i < this.list.Count; i++)
|
||||
length += this.list[i].LengthInBytes();
|
||||
|
||||
length += 1; // Lists end with 'e'
|
||||
return length;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Overridden Methods
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
BEncodedList other = obj as BEncodedList;
|
||||
|
||||
if (other == null)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < this.list.Count; i++)
|
||||
if (!this.list[i].Equals(other.list[i]))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
int result = 0;
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
result ^= list[i].GetHashCode();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return System.Text.Encoding.UTF8.GetString(Encode());
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region IList methods
|
||||
public void Add(BEncodedValue item)
|
||||
{
|
||||
this.list.Add(item);
|
||||
}
|
||||
|
||||
public void AddRange (IEnumerable<BEncodedValue> collection)
|
||||
{
|
||||
list.AddRange (collection);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
this.list.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(BEncodedValue item)
|
||||
{
|
||||
return this.list.Contains(item);
|
||||
}
|
||||
|
||||
public void CopyTo(BEncodedValue[] array, int arrayIndex)
|
||||
{
|
||||
this.list.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get { return this.list.Count; }
|
||||
}
|
||||
|
||||
public int IndexOf(BEncodedValue item)
|
||||
{
|
||||
return this.list.IndexOf(item);
|
||||
}
|
||||
|
||||
public void Insert(int index, BEncodedValue item)
|
||||
{
|
||||
this.list.Insert(index, item);
|
||||
}
|
||||
|
||||
public bool IsReadOnly
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public bool Remove(BEncodedValue item)
|
||||
{
|
||||
return this.list.Remove(item);
|
||||
}
|
||||
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
this.list.RemoveAt(index);
|
||||
}
|
||||
|
||||
public BEncodedValue this[int index]
|
||||
{
|
||||
get { return this.list[index]; }
|
||||
set { this.list[index] = value; }
|
||||
}
|
||||
|
||||
public IEnumerator<BEncodedValue> GetEnumerator()
|
||||
{
|
||||
return this.list.GetEnumerator();
|
||||
}
|
||||
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
|
||||
{
|
||||
return this.GetEnumerator();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MonoTorrent.BEncoding
|
||||
{
|
||||
/// <summary>
|
||||
/// Class representing a BEncoded number
|
||||
/// </summary>
|
||||
public class BEncodedNumber : BEncodedValue, IComparable<BEncodedNumber>
|
||||
{
|
||||
#region Member Variables
|
||||
/// <summary>
|
||||
/// The value of the BEncodedNumber
|
||||
/// </summary>
|
||||
public long Number
|
||||
{
|
||||
get { return number; }
|
||||
set { number = value; }
|
||||
}
|
||||
internal long number;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
public BEncodedNumber()
|
||||
: this(0)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new BEncoded number with the given value
|
||||
/// </summary>
|
||||
/// <param name="initialValue">The inital value of the BEncodedNumber</param>
|
||||
public BEncodedNumber(long value)
|
||||
{
|
||||
this.number = value;
|
||||
}
|
||||
|
||||
public static implicit operator BEncodedNumber(long value)
|
||||
{
|
||||
return new BEncodedNumber(value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Encode/Decode Methods
|
||||
|
||||
/// <summary>
|
||||
/// Encodes this number to the supplied byte[] starting at the supplied offset
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer to write the data to</param>
|
||||
/// <param name="offset">The offset to start writing the data at</param>
|
||||
/// <returns></returns>
|
||||
public override int Encode(byte[] buffer, int offset)
|
||||
{
|
||||
long number = this.number;
|
||||
|
||||
int written = offset;
|
||||
buffer[written++] = (byte)'i';
|
||||
|
||||
if (number < 0)
|
||||
{
|
||||
buffer[written++] = (byte)'-';
|
||||
number = -number;
|
||||
}
|
||||
// Reverse the number '12345' to get '54321'
|
||||
long reversed = 0;
|
||||
for (long i = number; i != 0; i /= 10)
|
||||
reversed = reversed * 10 + i % 10;
|
||||
|
||||
// Write each digit of the reversed number to the array. We write '1'
|
||||
// first, then '2', etc
|
||||
for (long i = reversed; i != 0; i /= 10)
|
||||
buffer[written++] = (byte)(i % 10 + '0');
|
||||
|
||||
if (number == 0)
|
||||
buffer[written++] = (byte)'0';
|
||||
|
||||
// If the original number ends in one or more zeros, they are lost
|
||||
// when we reverse the number. We add them back in here.
|
||||
for (long i = number; i % 10 == 0 && number != 0; i /= 10)
|
||||
buffer[written++] = (byte)'0';
|
||||
|
||||
buffer[written++] = (byte)'e';
|
||||
return written - offset;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Decodes a BEncoded number from the supplied RawReader
|
||||
/// </summary>
|
||||
/// <param name="reader">RawReader containing a BEncoded Number</param>
|
||||
internal override void DecodeInternal(RawReader reader)
|
||||
{
|
||||
int sign = 1;
|
||||
if (reader == null)
|
||||
throw new ArgumentNullException("reader");
|
||||
|
||||
if (reader.ReadByte() != 'i') // remove the leading 'i'
|
||||
throw new BEncodingException("Invalid data found. Aborting.");
|
||||
|
||||
if (reader.PeekByte() == '-')
|
||||
{
|
||||
sign = -1;
|
||||
reader.ReadByte ();
|
||||
}
|
||||
|
||||
int letter;
|
||||
while (((letter = reader.PeekByte()) != -1) && letter != 'e')
|
||||
{
|
||||
if(letter < '0' || letter > '9')
|
||||
throw new BEncodingException("Invalid number found.");
|
||||
number = number * 10 + (letter - '0');
|
||||
reader.ReadByte ();
|
||||
}
|
||||
if (reader.ReadByte() != 'e') //remove the trailing 'e'
|
||||
throw new BEncodingException("Invalid data found. Aborting.");
|
||||
|
||||
number *= sign;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Helper Methods
|
||||
/// <summary>
|
||||
/// Returns the length of the encoded string in bytes
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override int LengthInBytes()
|
||||
{
|
||||
long number = this.number;
|
||||
int count = 2; // account for the 'i' and 'e'
|
||||
|
||||
if (number == 0)
|
||||
return count + 1;
|
||||
|
||||
if (number < 0)
|
||||
{
|
||||
number = -number;
|
||||
count++;
|
||||
}
|
||||
for (long i = number; i != 0; i /= 10)
|
||||
count++;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
public int CompareTo(object other)
|
||||
{
|
||||
if (other is BEncodedNumber || other is long || other is int)
|
||||
return CompareTo((BEncodedNumber)other);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int CompareTo(BEncodedNumber other)
|
||||
{
|
||||
if (other == null)
|
||||
throw new ArgumentNullException("other");
|
||||
|
||||
return this.number.CompareTo(other.number);
|
||||
}
|
||||
|
||||
|
||||
public int CompareTo(long other)
|
||||
{
|
||||
return this.number.CompareTo(other);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Overridden Methods
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
BEncodedNumber obj2 = obj as BEncodedNumber;
|
||||
if (obj2 == null)
|
||||
return false;
|
||||
|
||||
return (this.number == obj2.number);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return this.number.GetHashCode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return (this.number.ToString());
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections;
|
||||
using System.Text;
|
||||
using MonoTorrent.Common;
|
||||
using MonoTorrent.Messages;
|
||||
|
||||
namespace MonoTorrent.BEncoding
|
||||
{
|
||||
/// <summary>
|
||||
/// Class representing a BEncoded string
|
||||
/// </summary>
|
||||
public class BEncodedString : BEncodedValue, IComparable<BEncodedString>
|
||||
{
|
||||
#region Member Variables
|
||||
|
||||
/// <summary>
|
||||
/// The value of the BEncodedString
|
||||
/// </summary>
|
||||
public string Text
|
||||
{
|
||||
get { return Encoding.UTF8.GetString(textBytes); }
|
||||
set { textBytes = Encoding.UTF8.GetBytes(value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The underlying byte[] associated with this BEncodedString
|
||||
/// </summary>
|
||||
public byte[] TextBytes
|
||||
{
|
||||
get { return this.textBytes; }
|
||||
}
|
||||
private byte[] textBytes;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
/// <summary>
|
||||
/// Create a new BEncodedString using UTF8 encoding
|
||||
/// </summary>
|
||||
public BEncodedString()
|
||||
: this(new byte[0])
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new BEncodedString using UTF8 encoding
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
public BEncodedString(char[] value)
|
||||
: this(System.Text.Encoding.UTF8.GetBytes(value))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new BEncodedString using UTF8 encoding
|
||||
/// </summary>
|
||||
/// <param name="value">Initial value for the string</param>
|
||||
public BEncodedString(string value)
|
||||
: this(System.Text.Encoding.UTF8.GetBytes(value))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create a new BEncodedString using UTF8 encoding
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
public BEncodedString(byte[] value)
|
||||
{
|
||||
this.textBytes = value;
|
||||
}
|
||||
|
||||
|
||||
public static implicit operator BEncodedString(string value)
|
||||
{
|
||||
return new BEncodedString(value);
|
||||
}
|
||||
public static implicit operator BEncodedString(char[] value)
|
||||
{
|
||||
return new BEncodedString(value);
|
||||
}
|
||||
public static implicit operator BEncodedString(byte[] value)
|
||||
{
|
||||
return new BEncodedString(value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Encode/Decode Methods
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Encodes the BEncodedString to a byte[] using the supplied Encoding
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer to encode the string to</param>
|
||||
/// <param name="offset">The offset at which to save the data to</param>
|
||||
/// <param name="e">The encoding to use</param>
|
||||
/// <returns>The number of bytes encoded</returns>
|
||||
public override int Encode(byte[] buffer, int offset)
|
||||
{
|
||||
int written = offset;
|
||||
written += Message.WriteAscii(buffer, written, textBytes.Length.ToString ());
|
||||
written += Message.WriteAscii(buffer, written, ":");
|
||||
written += Message.Write(buffer, written, textBytes);
|
||||
return written - offset;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Decodes a BEncodedString from the supplied StreamReader
|
||||
/// </summary>
|
||||
/// <param name="reader">The StreamReader containing the BEncodedString</param>
|
||||
internal override void DecodeInternal(RawReader reader)
|
||||
{
|
||||
if (reader == null)
|
||||
throw new ArgumentNullException("reader");
|
||||
|
||||
int letterCount;
|
||||
string length = string.Empty;
|
||||
|
||||
while ((reader.PeekByte() != -1) && (reader.PeekByte() != ':')) // read in how many characters
|
||||
length += (char)reader.ReadByte(); // the string is
|
||||
|
||||
if (reader.ReadByte() != ':') // remove the ':'
|
||||
throw new BEncodingException("Invalid data found. Aborting");
|
||||
|
||||
if (!int.TryParse(length, out letterCount))
|
||||
throw new BEncodingException(string.Format("Invalid BEncodedString. Length was '{0}' instead of a number", length));
|
||||
|
||||
this.textBytes = new byte[letterCount];
|
||||
if (reader.Read(textBytes, 0, letterCount) != letterCount)
|
||||
throw new BEncodingException("Couldn't decode string");
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Helper Methods
|
||||
public string Hex
|
||||
{
|
||||
get { return BitConverter.ToString(TextBytes); }
|
||||
}
|
||||
|
||||
public override int LengthInBytes()
|
||||
{
|
||||
// The length is equal to the length-prefix + ':' + length of data
|
||||
int prefix = 1; // Account for ':'
|
||||
|
||||
// Count the number of characters needed for the length prefix
|
||||
for (int i = textBytes.Length; i != 0; i = i/10)
|
||||
prefix += 1;
|
||||
|
||||
if (textBytes.Length == 0)
|
||||
prefix++;
|
||||
|
||||
return prefix + textBytes.Length;
|
||||
}
|
||||
|
||||
public int CompareTo(object other)
|
||||
{
|
||||
return CompareTo(other as BEncodedString);
|
||||
}
|
||||
|
||||
|
||||
public int CompareTo(BEncodedString other)
|
||||
{
|
||||
if (other == null)
|
||||
return 1;
|
||||
|
||||
int difference=0;
|
||||
int length = this.textBytes.Length > other.textBytes.Length ? other.textBytes.Length : this.textBytes.Length;
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
if ((difference = this.textBytes[i].CompareTo(other.textBytes[i])) != 0)
|
||||
return difference;
|
||||
|
||||
if (this.textBytes.Length == other.textBytes.Length)
|
||||
return 0;
|
||||
|
||||
return this.textBytes.Length > other.textBytes.Length ? 1 : -1;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Overridden Methods
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
return false;
|
||||
|
||||
BEncodedString other;
|
||||
if (obj is string)
|
||||
other = new BEncodedString((string)obj);
|
||||
else if (obj is BEncodedString)
|
||||
other = (BEncodedString)obj;
|
||||
else
|
||||
return false;
|
||||
|
||||
return Toolbox.ByteMatch(this.textBytes, other.textBytes);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
int hash = 0;
|
||||
for (int i = 0; i < this.textBytes.Length; i++)
|
||||
hash += this.textBytes[i];
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return System.Text.Encoding.UTF8.GetString(textBytes);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace MonoTorrent.BEncoding
|
||||
{
|
||||
[Serializable]
|
||||
public class BEncodingException : Exception
|
||||
{
|
||||
public BEncodingException()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
public BEncodingException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public BEncodingException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
protected BEncodingException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace MonoTorrent.BEncoding
|
||||
{
|
||||
/// <summary>
|
||||
/// Base interface for all BEncoded values.
|
||||
/// </summary>
|
||||
public abstract class BEncodedValue
|
||||
{
|
||||
internal abstract void DecodeInternal(RawReader reader);
|
||||
|
||||
/// <summary>
|
||||
/// Encodes the BEncodedValue into a byte array
|
||||
/// </summary>
|
||||
/// <returns>Byte array containing the BEncoded Data</returns>
|
||||
public byte[] Encode()
|
||||
{
|
||||
byte[] buffer = new byte[LengthInBytes()];
|
||||
if (Encode(buffer, 0) != buffer.Length)
|
||||
throw new BEncodingException("Error encoding the data");
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Encodes the BEncodedValue into the supplied buffer
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer to encode the information to</param>
|
||||
/// <param name="offset">The offset in the buffer to start writing the data</param>
|
||||
/// <returns></returns>
|
||||
public abstract int Encode(byte[] buffer, int offset);
|
||||
|
||||
public static T Clone <T> (T value)
|
||||
where T : BEncodedValue
|
||||
{
|
||||
Check.Value (value);
|
||||
return (T) BEncodedValue.Decode (value.Encode ());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface for all BEncoded values
|
||||
/// </summary>
|
||||
/// <param name="data">The byte array containing the BEncoded data</param>
|
||||
/// <returns></returns>
|
||||
public static BEncodedValue Decode(byte[] data)
|
||||
{
|
||||
if (data == null)
|
||||
throw new ArgumentNullException("data");
|
||||
|
||||
using (RawReader stream = new RawReader(new MemoryStream(data)))
|
||||
return (Decode(stream));
|
||||
}
|
||||
|
||||
internal static BEncodedValue Decode(byte[] buffer, bool strictDecoding)
|
||||
{
|
||||
return Decode(buffer, 0, buffer.Length, strictDecoding);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode BEncoded data in the given byte array
|
||||
/// </summary>
|
||||
/// <param name="buffer">The byte array containing the BEncoded data</param>
|
||||
/// <param name="offset">The offset at which the data starts at</param>
|
||||
/// <param name="length">The number of bytes to be decoded</param>
|
||||
/// <returns>BEncodedValue containing the data that was in the byte[]</returns>
|
||||
public static BEncodedValue Decode(byte[] buffer, int offset, int length)
|
||||
{
|
||||
return Decode(buffer, offset, length, true);
|
||||
}
|
||||
|
||||
public static BEncodedValue Decode(byte[] buffer, int offset, int length, bool strictDecoding)
|
||||
{
|
||||
if (buffer == null)
|
||||
throw new ArgumentNullException("buffer");
|
||||
|
||||
if (offset < 0 || length < 0)
|
||||
throw new IndexOutOfRangeException("Neither offset or length can be less than zero");
|
||||
|
||||
if (offset > buffer.Length - length)
|
||||
throw new ArgumentOutOfRangeException("length");
|
||||
|
||||
using (RawReader reader = new RawReader(new MemoryStream(buffer, offset, length), strictDecoding))
|
||||
return (BEncodedValue.Decode(reader));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Decode BEncoded data in the given stream
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream containing the BEncoded data</param>
|
||||
/// <returns>BEncodedValue containing the data that was in the stream</returns>
|
||||
public static BEncodedValue Decode(Stream stream)
|
||||
{
|
||||
if (stream == null)
|
||||
throw new ArgumentNullException("stream");
|
||||
|
||||
return Decode(new RawReader(stream));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Decode BEncoded data in the given RawReader
|
||||
/// </summary>
|
||||
/// <param name="reader">The RawReader containing the BEncoded data</param>
|
||||
/// <returns>BEncodedValue containing the data that was in the stream</returns>
|
||||
public static BEncodedValue Decode(RawReader reader)
|
||||
{
|
||||
if (reader == null)
|
||||
throw new ArgumentNullException("reader");
|
||||
|
||||
BEncodedValue data;
|
||||
switch (reader.PeekByte())
|
||||
{
|
||||
case ('i'): // Integer
|
||||
data = new BEncodedNumber();
|
||||
break;
|
||||
|
||||
case ('d'): // Dictionary
|
||||
data = new BEncodedDictionary();
|
||||
break;
|
||||
|
||||
case ('l'): // List
|
||||
data = new BEncodedList();
|
||||
break;
|
||||
|
||||
case ('1'): // String
|
||||
case ('2'):
|
||||
case ('3'):
|
||||
case ('4'):
|
||||
case ('5'):
|
||||
case ('6'):
|
||||
case ('7'):
|
||||
case ('8'):
|
||||
case ('9'):
|
||||
case ('0'):
|
||||
data = new BEncodedString();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new BEncodingException("Could not find what value to decode");
|
||||
}
|
||||
|
||||
data.DecodeInternal(reader);
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Interface for all BEncoded values
|
||||
/// </summary>
|
||||
/// <param name="data">The byte array containing the BEncoded data</param>
|
||||
/// <returns></returns>
|
||||
public static T Decode<T>(byte[] data) where T : BEncodedValue
|
||||
{
|
||||
return (T)BEncodedValue.Decode(data);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Decode BEncoded data in the given byte array
|
||||
/// </summary>
|
||||
/// <param name="buffer">The byte array containing the BEncoded data</param>
|
||||
/// <param name="offset">The offset at which the data starts at</param>
|
||||
/// <param name="length">The number of bytes to be decoded</param>
|
||||
/// <returns>BEncodedValue containing the data that was in the byte[]</returns>
|
||||
public static T Decode<T>(byte[] buffer, int offset, int length) where T : BEncodedValue
|
||||
{
|
||||
return BEncodedValue.Decode<T>(buffer, offset, length, true);
|
||||
}
|
||||
|
||||
public static T Decode<T>(byte[] buffer, int offset, int length, bool strictDecoding) where T : BEncodedValue
|
||||
{
|
||||
return (T)BEncodedValue.Decode(buffer, offset, length, strictDecoding);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Decode BEncoded data in the given stream
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream containing the BEncoded data</param>
|
||||
/// <returns>BEncodedValue containing the data that was in the stream</returns>
|
||||
public static T Decode<T>(Stream stream) where T : BEncodedValue
|
||||
{
|
||||
return (T)BEncodedValue.Decode(stream);
|
||||
}
|
||||
|
||||
|
||||
public static T Decode<T>(RawReader reader) where T : BEncodedValue
|
||||
{
|
||||
return (T)BEncodedValue.Decode(reader);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns the size of the byte[] needed to encode this BEncodedValue
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public abstract int LengthInBytes();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
|
||||
namespace MonoTorrent.BEncoding
|
||||
{
|
||||
public class RawReader : Stream
|
||||
{
|
||||
bool hasPeek;
|
||||
Stream input;
|
||||
byte[] peeked;
|
||||
bool strictDecoding;
|
||||
|
||||
public bool StrictDecoding
|
||||
{
|
||||
get { return strictDecoding; }
|
||||
}
|
||||
|
||||
public RawReader(Stream input)
|
||||
: this(input, true)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public RawReader(Stream input, bool strictDecoding)
|
||||
{
|
||||
this.input = input;
|
||||
this.peeked = new byte[1];
|
||||
this.strictDecoding = strictDecoding;
|
||||
}
|
||||
|
||||
public override bool CanRead
|
||||
{
|
||||
get { return input.CanRead; }
|
||||
}
|
||||
|
||||
public override bool CanSeek
|
||||
{
|
||||
get { return input.CanSeek; }
|
||||
}
|
||||
|
||||
public override bool CanWrite
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override long Length
|
||||
{
|
||||
get { return input.Length; }
|
||||
}
|
||||
|
||||
public int PeekByte()
|
||||
{
|
||||
if (!hasPeek)
|
||||
hasPeek = Read(peeked, 0, 1) == 1;
|
||||
return hasPeek ? peeked[0] : -1;
|
||||
}
|
||||
|
||||
public override int ReadByte()
|
||||
{
|
||||
if (hasPeek)
|
||||
{
|
||||
hasPeek = false;
|
||||
return peeked[0];
|
||||
}
|
||||
return base.ReadByte();
|
||||
}
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get
|
||||
{
|
||||
if (hasPeek)
|
||||
return input.Position - 1;
|
||||
return input.Position;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value != Position)
|
||||
{
|
||||
hasPeek = false;
|
||||
input.Position = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
int read = 0;
|
||||
if (hasPeek && count > 0)
|
||||
{
|
||||
hasPeek = false;
|
||||
buffer[offset] = peeked[0];
|
||||
offset++;
|
||||
count--;
|
||||
read++;
|
||||
}
|
||||
read += input.Read(buffer, offset, count);
|
||||
return read;
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
long val;
|
||||
if (hasPeek && origin == SeekOrigin.Current)
|
||||
val = input.Seek(offset - 1, origin);
|
||||
else
|
||||
val = input.Seek(offset, origin);
|
||||
hasPeek = false;
|
||||
return val;
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user