using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Net.Sockets;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Xml;
using System.Globalization;
using FastReport.Export;
using FastReport.Cloud;
using FastReport.Cloud.StorageClient.FastCloud;
using FastReport.Messaging.Authentication.Sasl;
using FastReport.Utils;
namespace FastReport.Messaging.Xmpp
{
///
/// Represents the XMPP messenger.
///
public class XmppMessenger : MessengerBase, IDisposable
{
#region Constants
private const int DEFAULT_PORT_NUMBER = 5222;
private const int DEFAULT_CHUNK_SIZE = 4096;
private const string DEFAULT_RESOURCE = "frnet";
private const string DEFAULT_SESSION_ID = "frnet_session_0";
#endregion // Constants
#region Fields
private string username;
private string password;
private string hostname;
private int port;
private string sendToUsername;
private TcpClient client;
private Stream stream;
private StreamParser parser;
private bool connected;
private bool authenticated;
private bool disposed;
private string sessionId;
private string jidFrom;
private string jidTo;
private int chunkSize;
#endregion // Fields
#region Properties
///
/// Gets or sets the username.
///
public string Username
{
get { return username; }
set { username = value; }
}
///
/// Gets or sets the user's password.
///
public string Password
{
get { return password; }
set { password = value; }
}
///
/// Gets or sets the hostname of XMPP server.
///
public string Hostname
{
get { return hostname; }
set { hostname = value; }
}
///
/// Gets or sets the port number of the XMPP service of the server.
///
public int Port
{
get { return port; }
set { port = value; }
}
///
/// Gets or sets the username to send file to.
///
public string SendToUsername
{
get { return sendToUsername; }
set { sendToUsername = value; }
}
///
/// Gets or sets the JID to send from.
///
public string JidFrom
{
get { return jidFrom; }
set
{
jidFrom = value;
if (!String.IsNullOrEmpty(jidFrom))
{
int jidFromAtPosition = jidFrom.IndexOf("@");
this.username = jidFrom.Substring(0, jidFromAtPosition);
this.hostname = jidFrom.Substring(jidFromAtPosition + 1, jidFrom.Length - jidFromAtPosition - 1);
}
}
}
///
/// Gets or set the JID to send to.
///
public string JidTo
{
get { return jidTo; }
set
{
jidTo = value;
if (!String.IsNullOrEmpty(jidTo))
{
int jidToAtPosition = jidTo.IndexOf("@");
this.sendToUsername = jidTo.Substring(0, jidToAtPosition);
}
}
}
#endregion // Properties
#region Constructors
///
/// Initializes a new instance of the class.
///
public XmppMessenger()
{
username = "";
password = "";
hostname = "";
port = DEFAULT_PORT_NUMBER;
sendToUsername = "";
client = null;
stream = null;
parser = null;
connected = false;
authenticated = false;
disposed = false;
sessionId = DEFAULT_SESSION_ID;
jidFrom = "";
jidTo = "";
chunkSize = DEFAULT_CHUNK_SIZE;
}
///
/// Initializes a new instance of the class with specified parameters.
///
/// Username.
/// Password.
/// Hostname.
/// Port.
/// Username to send file to.
/// Send to user's resource.
public XmppMessenger(string username, string password, string hostname, int port, string sendToUsername, string sendToResource)
{
this.username = username;
this.password = password;
this.hostname = hostname;
this.port = port;
this.sendToUsername = sendToUsername;
client = null;
stream = null;
parser = null;
connected = false;
authenticated = false;
disposed = false;
sessionId = DEFAULT_SESSION_ID;
jidFrom = username + "@" + hostname + "/" + DEFAULT_RESOURCE;
jidTo = sendToUsername + "@" + hostname + "/" + sendToResource;
chunkSize = DEFAULT_CHUNK_SIZE;
}
///
/// Initializes a new instance of the class with specified parameters.
///
/// User's JID without resource.
/// User's password.
/// JID to send to with resource.
public XmppMessenger(string jidFrom, string password, string jidTo)
{
this.port = DEFAULT_PORT_NUMBER;
client = null;
stream = null;
parser = null;
connected = false;
authenticated = false;
disposed = false;
sessionId = DEFAULT_SESSION_ID;
if (!String.IsNullOrEmpty(jidFrom))
{
jidFrom += "/" + DEFAULT_RESOURCE;
}
JidFrom = jidFrom;
this.password = password;
JidTo = jidTo;
chunkSize = DEFAULT_CHUNK_SIZE;
}
#endregion // Constructors
#region Private Methods
///
/// Sends the specified string to the server.
///
/// The string to send.
private void SendString(string str)
{
byte[] buffer = Encoding.UTF8.GetBytes(str);
try
{
stream.Write(buffer, 0, buffer.Length);
}
catch
{
connected = false;
}
}
///
/// Initiates the stream to the server.
///
/// The hostname.
/// The features response of the server.
private XmlElement InitiateStream(string hostname)
{
XmlElement xml = Xml.CreateElement("stream:stream", "jabber:client");
Xml.AddAttribute(xml, "from", jidFrom);
Xml.AddAttribute(xml, "to", hostname);
Xml.AddAttribute(xml, "version", "1.0");
Xml.AddAttribute(xml, "xml:lang", "en");
Xml.AddAttribute(xml, "xmlns", "jabber:client");
Xml.AddAttribute(xml, "xmlns:stream", "http://etherx.jabber.org/streams");
string str = Xml.ToXmlString(xml, true, true);
SendString(str);
if (parser != null)
{
parser.Close();
}
parser = new StreamParser(stream, true);
return parser.ReadNextElement(new List(new string[] { "stream:features" }));
}
///
/// Validates the server certificate.
///
/// The sender object.
/// X509 certificate.
/// The X509 chain.
/// The SSL policy errors.
/// True if successfull.
public static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors != SslPolicyErrors.None)
{
return false;
}
return true;
}
///
/// Secures the stream by TLS.
///
/// The hostname.
/// The features response of the server.
private XmlElement StartTls(string hostname)
{
XmlElement xml = Xml.CreateElement("starttls", "urn:ietf:params:xml:ns:xmpp-tls");
SendString("");
XmlElement proceed = parser.ReadNextElement(new List(new string[] { "proceed" }));
SslStream sslStream = new SslStream(stream, false, new RemoteCertificateValidationCallback(ValidateServerCertificate), null);
sslStream.AuthenticateAsClient(hostname);
stream = sslStream;
return InitiateStream(hostname);
}
///
/// Selects the SASL authentication mechanism.
///
/// List of mechanisms.
/// The string containing mechanism name.
private string SelectMechanism(List mechanisms)
{
bool hasPlain = false;
bool hasDigestMd5 = false;
foreach (string mechanism in mechanisms)
{
if (!hasPlain)
{
hasPlain = mechanism.ToUpper().Contains(PlainMechanism.MECHANISM_NAME);
}
if (!hasDigestMd5)
{
hasDigestMd5 = mechanism.ToUpper().Contains(DigestMd5Mechanism.MECHANISM_NAME);
}
}
if (hasDigestMd5)
{
return DigestMd5Mechanism.MECHANISM_NAME;
}
else if (hasPlain)
{
return PlainMechanism.MECHANISM_NAME;
}
return null;
}
///
/// Authenticates the user on the server using Plain mechanism.
///
private void AuthenticateUsingPlainMechanism()
{
XmlElement xml = Xml.CreateElement("auth", "urn:ietf:params:xml:ns:xmpp-sasl");
Xml.AddAttribute(xml, "mechanism", PlainMechanism.MECHANISM_NAME);
Xml.AddText(xml, new PlainMechanism(username, password).GetResponse(""));
SendString(Xml.ToXmlString(xml, false, true));
XmlElement element = parser.ReadNextElement(new List(new string[] { "success", "failure" }));
if (element.Name == "success")
{
authenticated = true;
}
}
///
/// Authenticates the user on the server using Digest-MD5 mechanism.
///
private void AuthenticateUsingDigestMd5Mechanism()
{
XmlElement xml = Xml.CreateElement("auth", "urn:ietf:params:xml:ns:xmpp-sasl");
Xml.AddAttribute(xml, "mechanism", DigestMd5Mechanism.MECHANISM_NAME);
SendString(Xml.ToXmlString(xml, false, true));
XmlElement element = parser.ReadNextElement(new List(new string[] { "challenge", "success", "failure" }));
if (element.Name == "challenge")
{
//!!
authenticated = true;
}
}
///
/// Authenticates the user on the server.
///
/// The SASL mechanisms list.
private void Authenticate(List mechanisms)
{
if (mechanisms.Count > 0)
{
//string mechanismName = SelectMechanism(mechanisms);
string mechanismName = PlainMechanism.MECHANISM_NAME;
switch (mechanismName)
{
case DigestMd5Mechanism.MECHANISM_NAME:
AuthenticateUsingDigestMd5Mechanism();
break;
default:
AuthenticateUsingPlainMechanism();
break;
}
}
}
///
/// Setups the connection with the server.
///
private void SetupConnection()
{
XmlElement features = InitiateStream(hostname);
//if (features.GetElementsByTagName("starttls").Count > 0)
//{
// features = StartTls(hostname);
//}
XmlNodeList mechanismsNodes = features.GetElementsByTagName("mechanism");
List mechanisms = new List();
foreach (XmlNode node in mechanismsNodes)
{
mechanisms.Add(node.InnerText);
}
Authenticate(mechanisms);
if (!authenticated && !Config.WebMode)
{
FRMessageBox.Error(Res.Get("Messaging,Xmpp,Errors,InvalidJidOrPassword"));
}
}
///
/// Binds resource and gets the full JID that will be associated with current session.
///
/// The full session JID.
private string BindResource()
{
string jid = null;
XmlElement xml = Xml.CreateElement("iq", "");
Xml.AddAttribute(xml, "type", "set");
Xml.AddAttribute(xml, "id", "bind-0");
XmlElement bind = Xml.CreateElement("bind", "urn:ietf:params:xml:ns:xmpp-bind");
XmlElement resource = Xml.CreateElement("resource", "");
Xml.AddText(resource, DEFAULT_RESOURCE);
Xml.AddChild(bind, resource);
Xml.AddChild(xml, bind);
SendString(Xml.ToXmlString(xml, false, true));
XmlElement res = parser.ReadNextElement(new List(new string[] { "iq" }));
XmlNodeList jidNode = res.GetElementsByTagName("jid");
if (jidNode.Count > 0)
{
jid = jidNode[0].InnerText;
}
return jid;
}
///
/// Opens session between client and server.
///
/// The id of the opened session.
private string OpenSession()
{
SendString("");
XmlElement result = parser.ReadNextElement(new List(new string[] { "iq" }));
return result.GetAttribute("id"); ;
}
///
/// Connects to the server.
///
private void Connect()
{
client = new TcpClient(hostname, port);
stream = client.GetStream();
SetupConnection();
if (authenticated)
{
XmlElement features = InitiateStream(hostname);
XmlNodeList bind = features.GetElementsByTagName("bind");
if (bind.Count > 0)
{
jidFrom = BindResource();
sessionId = OpenSession();
if (!String.IsNullOrEmpty(jidFrom) && !String.IsNullOrEmpty(sessionId))
{
connected = true;
}
}
}
}
///
/// Sends the message.
///
/// The text of the message.
/// True if message has been successfully sent.
private bool SendMessage(string text)
{
XmlElement data = Xml.CreateElement("body", null);
Xml.AddText(data, text);
List list = new List();
list.Add(data);
Message message = new Message(null, "chat", jidFrom, jidTo, sessionId, new CultureInfo("en"), list);
SendString(message.ToString());
return true;
}
///
/// Sends the presence.
///
/// The text of the presence.
/// True if presence has been successfully sent.
private bool SendPresence(string text)
{
SendString("");
while (true)
{
XmlElement resp = parser.ReadNextElement(new List(new string[] { "iq", "presence" }));
if (resp == null) break;
}
return true;
}
///
/// Initiates the In Band Bytestream for sending the file (XEP-0047).
///
/// True if bytestream has been successfully initiated.
private bool InitiateInBandBytestream()
{
XmlElement open = Xml.CreateElement("open", "http://jabber.org/protocol/ibb");
Xml.AddAttribute(open, "block-size", chunkSize.ToString());
Xml.AddAttribute(open, "sid", sessionId);
Xml.AddAttribute(open, "stanza", "message");
List list = new List();
list.Add(open);
Iq iq = new Iq(null, "set", jidFrom, jidTo, "ibb1", null, list);
//Message iq = new Message(null, "set", fullJid, jidTo + "/Miranda", sessionId, null, list);
string str = iq.ToString();
//string str2 = "";
SendString(iq.ToString());
XmlElement resp = parser.ReadNextElement(new List(new string[] { "iq" }));
//resp = parser.ReadNextElement(new List(new string[] { "iq" }));
//resp = parser.ReadNextElement(new List(new string[] { "iq" }));
resp = parser.ReadNextElement(new List(new string[] { "iq" }));
resp = parser.ReadNextElement(new List(new string[] { "iq" }));
while (resp == null)
{
resp = parser.ReadNextElement(new List(new string[] { "iq" }));
resp = parser.ReadNextElement(new List(new string[] { "iq" }));
}
if (resp.GetElementsByTagName("service-unavailable").Count > 0 || resp.GetElementsByTagName("feature-not-implemented").Count > 0)
{
return false;
}
return true;
}
///
/// Sends the chunk to the XMPP server.
///
/// The data of the chunk.
/// The number of the chunk.
private void SendChunk(byte[] chunk, int number)
{
XmlElement data = Xml.CreateElement("data", "http://jabber.org/protocol/ibb");
Xml.AddAttribute(data, "seq", number.ToString());
Xml.AddAttribute(data, "sid", sessionId);
Xml.AddText(data, Convert.ToBase64String(chunk));
List list = new List();
list.Add(data);
//Iq iq = new Iq(null, "set", fullJid, jidTo, sessionId, new CultureInfo("en"), list);
Message iq = new Message(null, null, jidFrom, jidTo, sessionId, null, list);
SendString(iq.ToString());
//XmlElement resp = parser.ReadNextElement(new List(new string[] { "iq" }));
}
///
/// Sends the file using In Band Bytestream.
///
/// The memory stream containing data of the file.
/// True if file has been successfully sent.
private bool SendFileUsingIbb(MemoryStream ms)
{
InitiateInBandBytestream();
try
{
byte[] chunk = new byte[chunkSize];
int chunksCount = (int)Math.Ceiling((double)ms.Length / chunkSize);
ms.Position = 0;
for (int i = 0; i < chunksCount - 1; i++)
{
ms.Read(chunk, 0, chunk.Length);
SendChunk(chunk, i);
}
byte[] lastChunk = new byte[ms.Length - ((chunksCount - 1) * chunkSize)];
ms.Read(lastChunk, 0, lastChunk.Length);
SendChunk(lastChunk, chunksCount - 1);
}
catch
{
return false;
}
return true;
}
///
/// Sends the file using FastReport Cloud as a proxy server.
///
/// The report template.
/// The export filter to export report before sending.
/// True if file has been successfully sent.
private bool SendFileUsingFastReportCloud(Report report, ExportBase export)
{
FastCloudStorageClient client = new FastCloudStorageClient();
if (ProxySettings != null)
{
client.ProxySettings = new CloudProxySettings(FastReport.Cloud.ProxyType.Http, ProxySettings.Server, ProxySettings.Port, ProxySettings.Username, ProxySettings.Password);
switch (ProxySettings.ProxyType)
{
case ProxyType.Socks4:
client.ProxySettings.ProxyType = FastReport.Cloud.ProxyType.Socks4;
break;
case ProxyType.Socks5:
client.ProxySettings.ProxyType = FastReport.Cloud.ProxyType.Socks5;
break;
default:
client.ProxySettings.ProxyType = FastReport.Cloud.ProxyType.Http;
break;
}
}
client.AccessToken = "sozTWBzEm9toiTB5J2MA";
bool saved = false;
try
{
// заменить null на export когда появится поддержка других форматов в облаке
client.SaveReport(report, null);
saved = true;
}
catch { }
if (saved)
{
SendMessage(client.ReportUrl);
}
else
{
if (!Config.WebMode)
{
FRMessageBox.Error(Res.Get("Messaging,Xmpp,Errors,UnableToUploadFile"));
}
return false;
}
return true;
}
///
/// Disconnects from the server.
///
private void Disconnect()
{
if (connected)
{
SendString("");
connected = false;
authenticated = false;
}
}
#endregion // Private Methods
#region Protected Methods
///
protected override bool Authorize()
{
Connect();
return connected;
}
#endregion // Protected Methods
#region Public Methods
///
public override bool SendReport(Report report, ExportBase export)
{
bool result = false;
Authorize();
if (connected)
{
//using (MemoryStream ms = PrepareToSave(report, export))
//{
// result = SendFileUsingIbb(ms);
//}
result = SendFileUsingFastReportCloud(report, export);
Disconnect();
}
return result;
}
///
/// Closes the connection.
///
public void Close()
{
if (connected)
{
Disconnect();
}
if (!disposed)
{
Dispose();
}
}
///
/// Releases all the resources used by the XMPP messenger.
///
public void Dispose()
{
if (!disposed)
{
if (parser != null)
{
parser.Close();
parser = null;
}
if (client != null)
{
client.Close();
client = null;
}
disposed = true;
}
}
#endregion // Public Methods
}
}