Radarr/src/MonoTorrent/InfoHash.cs

171 lines
5.5 KiB
C#

using System;
using System.Collections.Generic;
using System.Text;
namespace MonoTorrent
{
public class InfoHash : IEquatable<InfoHash>
{
static Dictionary<char, byte> base32DecodeTable;
static InfoHash()
{
base32DecodeTable = new Dictionary<char, byte>();
string table = "abcdefghijklmnopqrstuvwxyz234567";
for (int i = 0; i < table.Length; i++)
base32DecodeTable[table[i]] = (byte)i;
}
byte[] hash;
internal byte[] Hash
{
get { return hash; }
}
public InfoHash(byte[] infoHash)
{
Check.InfoHash(infoHash);
if (infoHash.Length != 20)
throw new ArgumentException("Infohash must be exactly 20 bytes long");
hash = (byte[])infoHash.Clone();
}
public override bool Equals(object obj)
{
return Equals(obj as InfoHash);
}
public bool Equals(byte[] other)
{
return other == null || other.Length != 20 ? false : Toolbox.ByteMatch(Hash, other);
}
public bool Equals(InfoHash other)
{
return this == other;
}
public override int GetHashCode()
{
// Equality is based generally on checking 20 positions, checking 4 should be enough
// for the hashcode as infohashes are randomly distributed.
return Hash[0] | (Hash[1] << 8) | (Hash[2] << 16) | (Hash[3] << 24);
}
public byte[] ToArray()
{
return (byte[])hash.Clone();
}
public string ToHex()
{
StringBuilder sb = new StringBuilder(40);
for (int i = 0; i < hash.Length; i++)
{
string hex = hash[i].ToString("X");
if (hex.Length != 2)
sb.Append("0");
sb.Append(hex);
}
return sb.ToString();
}
public override string ToString()
{
return BitConverter.ToString(hash);
}
public string UrlEncode()
{
return UriHelper.UrlEncode(Hash);
}
public static bool operator ==(InfoHash left, InfoHash right)
{
if ((object)left == null)
return (object)right == null;
if ((object)right == null)
return false;
return Toolbox.ByteMatch(left.Hash, right.Hash);
}
public static bool operator !=(InfoHash left, InfoHash right)
{
return !(left == right);
}
public static InfoHash FromBase32(string infoHash)
{
Check.InfoHash(infoHash);
if (infoHash.Length != 32)
throw new ArgumentException("Infohash must be a base32 encoded 32 character string");
infoHash = infoHash.ToLower();
int infohashOffset = 0;
byte[] hash = new byte[20];
var temp = new byte[8];
for (int i = 0; i < hash.Length;)
{
for (int j = 0; j < 8; j++)
if (!base32DecodeTable.TryGetValue(infoHash[infohashOffset++], out temp[j]))
throw new ArgumentException("infoHash", "Value is not a valid base32 encoded string");
//8 * 5bits = 40 bits = 5 bytes
hash[i++] = (byte)((temp[0] << 3) | (temp[1] >> 2));
hash[i++] = (byte)((temp[1] << 6) | (temp[2] << 1) | (temp[3] >> 4));
hash[i++] = (byte)((temp[3] << 4) | (temp[4] >> 1));
hash[i++] = (byte)((temp[4] << 7) | (temp[5] << 2) | (temp[6] >> 3));
hash[i++] = (byte)((temp[6] << 5) | temp[7]);
}
return new InfoHash(hash);
}
public static InfoHash FromHex(string infoHash)
{
Check.InfoHash(infoHash);
if (infoHash.Length != 40)
throw new ArgumentException("Infohash must be 40 characters long");
byte[] hash = new byte[20];
for (int i = 0; i < hash.Length; i++)
hash[i] = byte.Parse(infoHash.Substring(i * 2, 2), System.Globalization.NumberStyles.HexNumber);
return new InfoHash(hash);
}
public static InfoHash FromMagnetLink(string magnetLink)
{
Check.MagnetLink(magnetLink);
if (!magnetLink.StartsWith("magnet:?"))
throw new ArgumentException("Invalid magnet link format");
magnetLink = magnetLink.Substring("magnet:?".Length);
int hashStart = magnetLink.IndexOf("xt=urn:btih:");
if (hashStart == -1)
throw new ArgumentException("Magnet link does not contain an infohash");
hashStart += "xt=urn:btih:".Length;
int hashEnd = magnetLink.IndexOf('&', hashStart);
if (hashEnd == -1)
hashEnd = magnetLink.Length;
switch (hashEnd - hashStart)
{
case 32:
return FromBase32(magnetLink.Substring(hashStart, 32));
case 40:
return FromHex(magnetLink.Substring(hashStart, 40));
default:
throw new ArgumentException("Infohash must be base32 or hex encoded.");
}
}
public static InfoHash UrlDecode(string infoHash)
{
Check.InfoHash(infoHash);
return new InfoHash(UriHelper.UrlDecode(infoHash));
}
}
}