initial commit
This commit is contained in:
commit
18327f522c
11 changed files with 1145 additions and 0 deletions
11
src/client.rs
Normal file
11
src/client.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
use jid::Jid;
|
||||
|
||||
pub struct XmppClient {
|
||||
transport: SslTransport,
|
||||
}
|
||||
|
||||
impl XmppClient {
|
||||
pub fn connect(jid: Jid) -> XmppClient {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
42
src/error.rs
Normal file
42
src/error.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
use std::io;
|
||||
|
||||
use std::net::TcpStream;
|
||||
|
||||
use openssl::ssl::HandshakeError;
|
||||
use openssl::error::ErrorStack;
|
||||
|
||||
use xml::reader::Error as XmlError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
XmlError(XmlError),
|
||||
IoError(io::Error),
|
||||
HandshakeError(HandshakeError<TcpStream>),
|
||||
OpenSslErrorStack(ErrorStack),
|
||||
StreamError,
|
||||
}
|
||||
|
||||
impl From<XmlError> for Error {
|
||||
fn from(err: XmlError) -> Error {
|
||||
Error::XmlError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(err: io::Error) -> Error {
|
||||
Error::IoError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HandshakeError<TcpStream>> for Error {
|
||||
fn from(err: HandshakeError<TcpStream>) -> Error {
|
||||
Error::HandshakeError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ErrorStack> for Error {
|
||||
fn from(err: ErrorStack) -> Error {
|
||||
Error::OpenSslErrorStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
165
src/jid.rs
Normal file
165
src/jid.rs
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
use std::fmt;
|
||||
|
||||
use std::convert::Into;
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use std::string::ToString;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum JidParseError {
|
||||
NoDomain,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Jid {
|
||||
node: Option<String>,
|
||||
domain: String,
|
||||
resource: Option<String>,
|
||||
}
|
||||
|
||||
impl fmt::Display for Jid {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
// TODO: may need escaping
|
||||
if let Some(ref node) = self.node {
|
||||
write!(fmt, "{}@", node)?;
|
||||
}
|
||||
write!(fmt, "{}", self.domain)?;
|
||||
if let Some(ref resource) = self.resource {
|
||||
write!(fmt, "/{}", resource)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
enum ParserState {
|
||||
Node,
|
||||
Domain,
|
||||
Resource
|
||||
}
|
||||
|
||||
impl FromStr for Jid {
|
||||
type Err = JidParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Jid, JidParseError> {
|
||||
// TODO: very naive, may need to do it differently
|
||||
let mut iter = s.chars();
|
||||
let mut buf = String::new();
|
||||
let mut state = ParserState::Node;
|
||||
let mut node = None;
|
||||
let mut domain = None;
|
||||
let mut resource = None;
|
||||
for c in iter {
|
||||
match state {
|
||||
ParserState::Node => {
|
||||
match c {
|
||||
'@' => {
|
||||
state = ParserState::Domain;
|
||||
node = Some(buf.clone()); // TODO: performance tweaks, do not need to copy it
|
||||
buf.clear();
|
||||
},
|
||||
'/' => {
|
||||
state = ParserState::Resource;
|
||||
domain = Some(buf.clone()); // TODO: performance tweaks
|
||||
buf.clear();
|
||||
},
|
||||
c => {
|
||||
buf.push(c);
|
||||
},
|
||||
}
|
||||
},
|
||||
ParserState::Domain => {
|
||||
match c {
|
||||
'/' => {
|
||||
state = ParserState::Resource;
|
||||
domain = Some(buf.clone()); // TODO: performance tweaks
|
||||
buf.clear();
|
||||
},
|
||||
c => {
|
||||
buf.push(c);
|
||||
},
|
||||
}
|
||||
},
|
||||
ParserState::Resource => {
|
||||
buf.push(c);
|
||||
},
|
||||
}
|
||||
}
|
||||
if !buf.is_empty() {
|
||||
match state {
|
||||
ParserState::Node => {
|
||||
domain = Some(buf);
|
||||
},
|
||||
ParserState::Domain => {
|
||||
domain = Some(buf);
|
||||
},
|
||||
ParserState::Resource => {
|
||||
resource = Some(buf);
|
||||
},
|
||||
}
|
||||
}
|
||||
Ok(Jid {
|
||||
node: node,
|
||||
domain: domain.ok_or(JidParseError::NoDomain)?,
|
||||
resource: resource,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Jid {
|
||||
pub fn full<NS, DS, RS>(node: NS, domain: DS, resource: RS) -> Jid
|
||||
where NS: Into<String>
|
||||
, DS: Into<String>
|
||||
, RS: Into<String> {
|
||||
Jid {
|
||||
node: Some(node.into()),
|
||||
domain: domain.into(),
|
||||
resource: Some(resource.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bare<NS, DS>(node: NS, domain: DS) -> Jid
|
||||
where NS: Into<String>
|
||||
, DS: Into<String> {
|
||||
Jid {
|
||||
node: Some(node.into()),
|
||||
domain: domain.into(),
|
||||
resource: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn domain<DS>(domain: DS) -> Jid
|
||||
where DS: Into<String> {
|
||||
Jid {
|
||||
node: None,
|
||||
domain: domain.into(),
|
||||
resource: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn domain_with_resource<DS, RS>(domain: DS, resource: RS) -> Jid
|
||||
where DS: Into<String>
|
||||
, RS: Into<String> {
|
||||
Jid {
|
||||
node: None,
|
||||
domain: domain.into(),
|
||||
resource: Some(resource.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn can_parse_jids() {
|
||||
assert_eq!(Jid::from_str("a@b.c/d"), Ok(Jid::full("a", "b.c", "d")));
|
||||
assert_eq!(Jid::from_str("a@b.c"), Ok(Jid::bare("a", "b.c")));
|
||||
assert_eq!(Jid::from_str("b.c"), Ok(Jid::domain("b.c")));
|
||||
|
||||
assert_eq!(Jid::from_str(""), Err(JidParseError::NoDomain));
|
||||
|
||||
assert_eq!(Jid::from_str("a/b@c"), Ok(Jid::domain_with_resource("a", "b@c")));
|
||||
}
|
||||
}
|
||||
7
src/lib.rs
Normal file
7
src/lib.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
extern crate xml;
|
||||
extern crate openssl;
|
||||
|
||||
pub mod ns;
|
||||
pub mod transport;
|
||||
pub mod error;
|
||||
pub mod jid;
|
||||
3
src/ns.rs
Normal file
3
src/ns.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
pub const CLIENT: &'static str = "jabber:client";
|
||||
pub const STREAM: &'static str = "http://etherx.jabber.org/streams";
|
||||
pub const TLS: &'static str = "urn:ietf:params:xml:ns:xmpp-tls";
|
||||
49
src/transport.rs
Normal file
49
src/transport.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
use std::io::prelude::*;
|
||||
use std::io;
|
||||
|
||||
use std::net::{SocketAddr, TcpStream};
|
||||
|
||||
use xml::reader::{EventReader, XmlEvent};
|
||||
|
||||
use ns;
|
||||
|
||||
use error::Error;
|
||||
|
||||
use openssl::ssl::{SslMethod, SslConnectorBuilder, SslStream};
|
||||
|
||||
pub struct SslTransport {
|
||||
inner: SslStream<TcpStream>,
|
||||
}
|
||||
|
||||
impl SslTransport {
|
||||
pub fn connect(host: &str, port: u16) -> Result<SslTransport, Error> {
|
||||
// TODO: very quick and dirty, blame starttls
|
||||
let mut stream = TcpStream::connect((host, port))?;
|
||||
write!(stream, "<stream:stream xmlns='{}' xmlns:stream='{}' to='{}' version='1.0'>"
|
||||
, ns::CLIENT, ns::STREAM, host)?;
|
||||
write!(stream, "<starttls xmlns='{}'/>"
|
||||
, ns::TLS)?;
|
||||
let mut parser = EventReader::new(stream);
|
||||
loop { // TODO: possibly a timeout?
|
||||
match parser.next()? {
|
||||
XmlEvent::StartElement { name, namespace, .. } => {
|
||||
if let Some(ns) = name.namespace {
|
||||
if ns == ns::TLS && name.local_name == "proceed" {
|
||||
break;
|
||||
}
|
||||
else if ns == ns::STREAM && name.local_name == "error" {
|
||||
return Err(Error::StreamError);
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
let stream = parser.into_inner();
|
||||
let ssl_connector = SslConnectorBuilder::new(SslMethod::tls())?.build();
|
||||
let ssl_stream = ssl_connector.connect(host, stream)?;
|
||||
Ok(SslTransport {
|
||||
inner: ssl_stream
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue