/*
* Copyright (C) 2004-2005 Jonathan Bindel
* Copyright (C) 2006-2007 Eskil Bylund
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
using System;
using System.Diagnostics;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using DCSharp.Backend.Connections;
using DCSharp.Backend.Objects;
using DCSharp.Extras;
namespace DCSharp.Backend.Protocols.Nmdc{
public class HubProtocol : UdpProtocol, IHubProtocol
{
private Incoming incoming;
public HubProtocol(IHubProtocolHelper helper) :
base(helper, helper)
{
if (helper == null)
{
throw new ArgumentNullException("helper");
}
this.helper = helper;
incoming = new Incoming(this);
#region Handlers
// Handshake
commands.Add("$lock", new StringEventHandler(incoming.Lock));
commands.Add("$hubname", new StringEventHandler(incoming.HubName));
commands.Add("$validatedenide", new StringEventHandler(incoming.ValidateDenide));
commands.Add("$getpass", new BlankEventHandler(incoming.GetPass));
commands.Add("$badpass", new BlankEventHandler(incoming.BadPass));
commands.Add("$logedin", new StringEventHandler(incoming.LogedIn));
commands.Add("$hello", new StringEventHandler(incoming.Hello));
// General
commands.Add("$to:", new StringEventHandler(incoming.To));
commands.Add("$myinfo", new StringEventHandler(incoming.MyInfo));
commands.Add("$getnetinfo", new BlankEventHandler(incoming.GetNetInfo));
commands.Add("$quit", new StringEventHandler(incoming.Quit));
// Connect
commands.Add("$revconnecttome", new StringEventHandler(incoming.RevConnectToMe));
commands.Add("$connecttome", new StringEventHandler(incoming.ConnectToMe));
commands.Add("$forcemove", new StringEventHandler(incoming.ForceMove));
// User
commands.Add("$nicklist", new StringEventHandler(incoming.NickList));
commands.Add("$oplist", new StringEventHandler(incoming.OpList));
// Search
commands.Add("$search", new StringEventHandler(incoming.Search));
#endregion
}
#region Classes
internal class Incoming
{
private HubProtocol protocol;
public Incoming(HubProtocol protocol)
{
this.protocol = protocol;
}
#region Handshake
public void Lock(string argument)
{
protocol.Key(CalculateKey(argument, protocol.Encoding));
protocol.ValidateNick();
}
public void HubName(string argument)
{
protocol.Connection.Name = argument;
}
public void ValidateDenide(string argument)
{
//protocol.Connection.Display("Username taken");
protocol.Connection.Disconnect();
}
public void GetPass()
{
protocol.MyPass();
}
public void BadPass()
{
//protocol.Connection.Display("*** Bad Password");
protocol.Connection.Disconnect();
}
public void LogedIn(string nick)
{
// This command is sent to ops after a successful $MyPass.
if(nick == protocol.Connection.LocalIdentity.Nick)
{
protocol.Connection.LocalIdentity.Op = true;
}
}
public void Hello(string argument)
{
if(argument == protocol.Connection.LocalIdentity.Nick)
{
protocol.Connection.Send("$Version 1,0091|");
protocol.GetNickList();
protocol.Connection.Send("$MyINFO $ALL " + GetIdentity() +
"|");
}
}
#endregion
#region General
public void To(string argument)
{
//<othernick> From: <nick> $<<nick>> <message>
int end = argument.IndexOf('$');
if(end < 0)
{
return;
}
int beg = argument.IndexOf("From: ") + 6;
if(beg < 6)
{
return;
}
string nick = argument.Substring(beg, end - beg).Trim();
Identity user = protocol.GetUser(nick);
if(user == null)
{
user = protocol.CreateUser(nick);
}
string message = argument.Substring(end + 1);
message = message.Substring(message.IndexOf('>') + 1);
message = message.Trim();
message = message.Replace("$", "$");
message = message.Replace("|", "|");
protocol.Connection.EmitPrivateMessage(user,
protocol.Connection.LocalIdentity, message);
}
public void MyInfo(string argument)
{
if(argument.StartsWith("$ALL") && argument.Length > 5)
{
argument = argument.Substring(5);
}
string nick = Regex.Match(argument, @"([^\s^<])*").Value;
Identity user = protocol.GetUser(nick);
if (user == null)
{
user = protocol.CreateUser(nick);
ParseMyInfo(user, argument);
protocol.Connection.Users.Add(user);
}
else
{
ParseMyInfo(user, argument);
protocol.Connection.Users.EmitUserChanged(user);
}
}
public void GetNetInfo()
{
StringBuilder sb = new StringBuilder();
sb.Append("$NetInfo ");
sb.Append(protocol.Helper.Slots);
sb.Append('$');
sb.Append("1");
sb.Append(protocol.Helper.ConnectionSettings.SupportsIncoming ? 'A' : 'P');
sb.Append('|');
protocol.Connection.Send(sb.ToString());
}
public void Quit(string argument)
{
Identity user = protocol.GetUser(argument);
if(user != null)
{
//user.Online = false;
protocol.Connection.Users.Remove(user);
}
}
#endregion
#region Connect
public void RevConnectToMe(string argument)
{
if (protocol.Helper.ConnectionSettings.SupportsIncoming)
{
string[] strings = argument.Split(' ');
if(strings.Length > 0)
{
string nick = strings[0];
protocol.ConnectToMe(nick);
}
}
}
public void ConnectToMe(string argument)
{
string[] strings = argument.Split(' ');
if(strings.Length == 2)
{
string hostname = strings[1];
string address = Util.AddressPart(hostname);
int port = Util.PortPart(hostname);
IPAddress ipAddress = IPAddress.Parse(address);
IPEndPoint endPoint = new IPEndPoint(ipAddress, port);
UserProtocol userProtocol = new UserProtocol(protocol.Helper.UserProtocolHelper);
UserConnection userConnection = protocol.Helper.CreateUserConnection(endPoint,
userProtocol);
userProtocol.Connection = userConnection;
userConnection.LocalIdentity = protocol.Connection.LocalIdentity;
userConnection.Hub = protocol.Connection;
userConnection.Connect();
}
}
public void ForceMove(string argument)
{
protocol.Connection.Disconnect();
protocol.Helper.RedirectTo(argument);
}
#endregion
#region User
public void NickList(string argument)
{
string[] nicks = Regex.Split(argument, @"\${2}");
foreach(string nick in nicks)
{
if(nick.Length > 0)
{
if(protocol.GetUser(nick) == null)
{
Identity user = protocol.CreateUser(nick);
//user.Online = true;
protocol.Connection.Users.Add(user);
}
//TODO: (possibly) get sending encoder to work correctly with tm's.
protocol.GetInfo(nick);
}
}
}
public void OpList(string argument)
{
string[] nicks = Regex.Split(argument, @"\${2}");
foreach(string nick in nicks)
{
if(nick.Length > 0)
{
Identity user = protocol.GetUser(nick);
if(user == null)
{
user = protocol.CreateUser(nick);
user.Op = true;
protocol.Connection.Users.Add(user);
}
else if(!user.Op)
{
user.Op = true;
protocol.Connection.Users.EmitUserChanged(user);
}
}
}
}
#endregion
#region Search
public void Search(string argument)
{
// $Search <ip>:<port> <searchstring>
// $Search Hub:<requestornick> <searchstring>
string[] parts = argument.Split(' ');
if(parts.Length != 2)
{
return;
}
string[] from = parts[0].Split(':');
if(from.Length != 2)
{
return;
}
string nick = null;
IPEndPoint endPoint = null;
if(from[0].Equals("Hub"))
{
nick = from[1];
Identity identity = protocol.GetUser(nick);
if (identity == null || identity == protocol.Connection.LocalIdentity ||
(protocol.Helper.ConnectionSettings.SupportsIncoming == false &&
identity.Active == false))
{
return;
}
}
else
{
if(from[0] == protocol.Helper.ConnectionSettings.Address &&
from[1] == protocol.Helper.ConnectionSettings.Port.ToString())
{
return;
}
string ip = Util.AddressPart(parts[0]);
int port = Util.PortPart(parts[0]);
endPoint = new IPEndPoint(IPAddress.Parse(ip), port);
}
if (!protocol.Helper.CanSearch(parts[0]))
return;
// <sizerestricted>?<ismaxsize>?<size>?<datatype>?<searchpattern>
string[] searchString = parts[1].Split('?');
if(searchString.Length < 5)
{
return;
}
long? maxSize = null;
long? minSize = null;
bool sizeRestrict = searchString[0].Equals("T");
if(sizeRestrict)
{
bool isMaxSize = searchString[1].Equals("T");
long? size = long.Parse(searchString[2]);
maxSize = isMaxSize ? size : null;
minSize = !isMaxSize ? size : null;
}
SearchFileType type = (SearchFileType)Enum.Parse(typeof(SearchFileType),
searchString[3]);
SearchInfo searchInfo;
if ((int)type == 9)
{
// It's a TTH search
string tth = searchString[4].Substring(4);
searchInfo = new SearchInfo(tth);
}
else
{
string pattern = searchString[4];
string[] keywords = pattern.Split('$');
searchInfo = new SearchInfo(keywords, type, maxSize,
minSize);
}
// Search
int? max = endPoint != null ? 10 : 5;
protocol.Helper.Search(searchInfo, max);
// Respond
foreach (SearchResult result in searchInfo.SearchResults)
{
string message = CreateResultString(result, nick);
if (endPoint != null)
{
UdpServer.Send(endPoint, message, Protocol.DefaultEncoding);
}
else
{
protocol.Connection.Send(message);
}
}
}
private string CreateResultString(SearchResult result, string nick)
{
// $SR <source_nick> <result> <free_slots>/<total_slots><0x05><hub_name> (<hub_ip:listening_port>)[<0x05><target_nick>]|
// <result> is one of the following:
// * <file_name><0x05><file_size> for file results
// * <directory> for directory results
// For files containing TTH, the <hub_name> parameter is replaced with TTH:<base32_encoded_tth_hash>
System.Text.StringBuilder message = new System.Text.StringBuilder();
message.Append("$SR ");
message.Append(protocol.Connection.LocalIdentity.Nick);
message.Append(" ");
message.Append(result.Path);
if(result.Type == ResultType.File)
{
message.Append((char)5);
message.Append(result.Size);
}
message.Append(" ");
message.Append(protocol.Helper.FreeSlots);
message.Append("/");
message.Append(protocol.Helper.Slots);
message.Append((char)5);
if(result.TTH != null)
{
message.Append("TTH:");
message.Append(result.TTH);
}
else
{
message.Append(protocol.Connection.Name);
}
message.Append(" (");
message.Append(protocol.Connection.Address);
message.Append(")");
if(nick != null)
{
message.Append((char)5);
message.Append(nick);
}
message.Append("|");
return message.ToString();
}
#endregion
#region MyInfo
private string GetIdentity()
{
Identity user = protocol.Connection.LocalIdentity;
string mode = protocol.Helper.ConnectionSettings.SupportsIncoming ? "A" : "P";
int hubsNotRegistered = 1;
int hubsRegistered = 0;
int hubsOp = 0;
//int maxToOpen = 40;
StringBuilder tag = new StringBuilder(40);
tag.Append("<++ V:0.674,M:");
tag.Append(mode);
tag.Append(",H:");
tag.Append(hubsNotRegistered);
tag.Append("/");
tag.Append(hubsRegistered);
tag.Append("/");
tag.Append(hubsOp);
tag.Append(",S:");
tag.Append(protocol.Helper.Slots);
//tag.Append(",O:");
//tag.Append(maxToOpen);
tag.Append(">");
StringBuilder info = new StringBuilder();
info.Append(user.Nick);
info.Append(" ");
info.Append(user.Description);
info.Append(tag.ToString());
info.Append("$ $");
info.Append(user.Connection);
//info.Append(Encoding.Default.GetString(new byte[] {(byte)user.Flag}));
info.Append((char)1);
info.Append("$");
info.Append(user.Email);
info.Append("$");
//info.Append(user.ShareSize);
info.Append(protocol.Helper.ShareTotal);
info.Append("$");
return info.ToString();
}
public void ParseMyInfo(Identity user, string info)
{
// <nick> <description>$<active>$<connection><flag>$<e-mail>$<sharesize>$|
string[] elements = Regex.Split(info, @"\$");
//[0] = <nick> <description><tag>
//[1] = " " or <active>
//[2] = <connection><flag>
//[3] = <email>
//[4] = <sharesize>
//[5] = " "
if(elements.Length > 0)
{
string descriptionAndTag = Regex.Match(elements[0], @"\s([^\$])*").Value;
int cutoff = descriptionAndTag.LastIndexOf("<");
if(cutoff > 0)
{
user.Description = descriptionAndTag.Substring(0, cutoff).Trim();
string tag = descriptionAndTag.Substring(cutoff).Trim();
if (tag.Length > 0)
{
user.SetParameter("tag", tag);
user.Active = tag.IndexOf("M:A") > 0;
}
}
else
{
user.Description = descriptionAndTag.Trim();
// TODO: Check if this is still correct.
user.Active = elements.Length > 1 && elements[1] == "A";
}
if(elements.Length > 2)
{
if(elements[2].Length > 1)
{
user.Connection = elements[2].Substring(0, elements[2].Length - 1);
//user.Flag = (int)Encoding.Default.GetBytes(elements[2].Substring(elements[2].Length - 1))[0];
}
if(elements.Length > 3)
{
user.Email = elements[3];
if(elements.Length > 4 && elements[4].Length > 0 &&
Util.WholeNumber(elements[4]))
{
user.ShareSize = long.Parse(elements[4]);
}
}
}
}
}
#endregion
}
#endregion
#region Properties
public HubConnection Connection
{
get { return connection; }
set { connection = value; }
}
private HubConnection connection;
public IHubProtocolHelper Helper
{
get { return helper; }
}
private IHubProtocolHelper helper;
#endregion
#region Methods
public override void Handle(string message)
{
if (message.StartsWith("$"))
{
base.Handle(message);
}
else
{
Identity user = null;
int beg = message.IndexOf('<');
int end = message.IndexOf('>');
if (beg == 0 && end > 0)
{
string nick = message.Substring(1, end - 1);
user = GetUser(nick);
if (user == null)
{
user = CreateUser(nick);
}
message = message.Substring(end + 2);
}
message = message.Replace("$", "$");
message = message.Replace("|", "|");
connection.EmitMessage(user, message);
}
}
protected override void Invoke(string command, string argument)
{
try
{
base.Invoke(command, argument);
}
catch (Exception e)
{
Debug.WriteLine(e);
}
}
protected override Identity GetIdentity(string nick, string hubName,
string hubAddress)
{
return GetUser(nick);
}
#region Outgoing Commands
#region Handshake
protected void Key(string argument)
{
connection.ConnectionSend("$Key " + argument + "|");
}
protected void ValidateNick()
{
connection.Send("$ValidateNick " + connection.LocalIdentity.Nick + "|");
}
protected void MyPass()
{
connection.Send("$MyPass " + connection.Password + "|");
}
#endregion
#region General
public void GetInfo(string nick)
{
connection.Send("$GetINFO " + nick + " " + connection.LocalIdentity.Nick + "|");
}
public void GetNickList()
{
connection.Send("$GetNickList|");
}
public void Kick(Identity user, string reason)
{
SendPrivateMessage(user, "You are being kicked because: " + reason);
connection.Send("$Kick " + user.Nick + "|");
}
public void Redirect(Identity user, string address, string reason)
{
SendPrivateMessage(user, "You are being redirected to " + address + " because: " + reason);
connection.Send("$OpForceMove $Who:" + user.Nick + "$Where:" + address + "$Msg:" + reason + "|");
}
#endregion
#region Connect
public void RequestConnection(Identity user)
{
if (Helper.ConnectionSettings.SupportsIncoming)
{
ConnectToMe(user.Nick);
}
else
{
RevConnectToMe(user.Nick);
}
}
protected void ConnectToMe(string nick)
{
int port = Helper.ConnectionSettings.Port;
connection.Send("$ConnectToMe " + nick + " " + Helper.ConnectionSettings.Address + ":" + port.ToString() + "|");
}
protected void RevConnectToMe(string nick)
{
connection.Send("$RevConnectToMe " + connection.LocalIdentity.Nick + " " + nick + "|");
}
#endregion
#region Message
public void SendMessage(string message)
{
if (message == null)
{
throw new ArgumentNullException("message");
}
message = message.Replace("$", "$");
message = message.Replace("|", "|");
connection.Send("<" + connection.LocalIdentity.Nick + "> " + message +
"|");
}
public void SendPrivateMessage(Identity user, string message)
{
if (user == null)
{
throw new ArgumentNullException("user");
}
if (message == null)
{
throw new ArgumentNullException("message");
}
message = message.Replace("$", "$");
message = message.Replace("|", "|");
connection.Send(String.Format("$To: {0} From: {1} $<{1}> {2}|",
user.Nick, connection.LocalIdentity.Nick, message));
// Emitting event since private messages aren't echoed back
connection.EmitPrivateMessage(connection.LocalIdentity, user, message);
}
#endregion
#region Search
public void Search(SearchInfo searchInfo)
{
if (searchInfo == null)
{
throw new ArgumentNullException("searchInfo");
}
StringBuilder message = new StringBuilder();
message.Append("$Search ");
if (Helper.ConnectionSettings.SupportsIncoming)
{
message.Append(Helper.ConnectionSettings.Address);
message.Append(":");
message.Append(Helper.ConnectionSettings.Port);
}
else
{
message.Append("Hub:");
message.Append(connection.LocalIdentity.Nick);
}
message.Append(" ");
message.Append(GetSearchString(searchInfo));
connection.Send(message.ToString());
}
protected string GetSearchString(SearchInfo searchInfo)
{
StringBuilder search = new StringBuilder();
// <sizerestricted>?<ismaximumsize>?<size>?<datatype>?<searchpattern>
// <sizerestricted> is 'T' if the search should be restricted to files
// of a minimum or maximum size, otherwise 'F'.
// <ismaximumsize> is 'F' if <sizerestricted> is 'F' or if the size
// restriction places a lower limit on file size, otherwise 'T'.
// <size> is the minimum or maximum size of the file to report
// (according to <ismaximumsize>) if <sizerestricted> is 'T', otherwise 0.
// <searchpattern> is used other users to determine if any files match.
// Non-alphanumeric characters (including spaces and periods) are replaced by '$'.
// The server must forward this message unmodified to all the other users. Every other
// user with one or more matching files must send a UDP packet to <ip>:<port>
long size = searchInfo.MaxSize ?? (searchInfo.MinSize ?? 0);
bool isMaximumSize = searchInfo.MaxSize != null;
search.Append(size > 0 ? "T" : "F");
search.Append("?");
search.Append(isMaximumSize ? "T" : "F");
search.Append("?");
search.Append(size);
search.Append("?");
if(searchInfo.TTH == null)
{
string keywords = String.Join(" ", searchInfo.Keywords);
search.Append((int)searchInfo.Type);
search.Append("?");
search.Append(Regex.Replace(keywords, @"\W", "$"));
}
else
{
// DC++ uses 9 as the datatype for TTH
search.Append(9);
//search.Append((int)SearchFileType.TTH);
search.Append("?");
search.Append("TTH:");
search.Append(searchInfo.TTH);
}
search.Append("|");
return search.ToString();
}
#endregion
#endregion
public Identity GetUser(string nick)
{
return GetUser(connection, nick);
}
public Identity CreateUser(string nick)
{
if (connection.LocalIdentity.Nick == nick)
{
return connection.LocalIdentity;
}
Uid cid = MakeCid(nick, connection.Address);
return new Identity(User.Create(cid, nick), nick);
}
#endregion
}
}
|