From 098f9dbbffcc75a6ad855a7803c6967ac063e387 Mon Sep 17 00:00:00 2001 From: Aaron Weiss Date: Sun, 30 Nov 2014 01:29:38 -0500 Subject: [PATCH] Added support for non-unicode encodings. --- Cargo.toml | 6 +- examples/multithreaded.rs | 1 + examples/simple.rs | 1 + examples/simple_ssl.rs | 1 + examples/tweeter.rs | 1 + mktestconfig.sh | 2 +- src/conn.rs | 133 +++++++++++++++++++++++++++----------- src/data/config.rs | 7 ++ src/lib.rs | 1 + src/server/mod.rs | 8 ++- 10 files changed, 117 insertions(+), 44 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b3e4de1..e121f94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "irc" -version = "0.2.1" +version = "0.3.0" description = "A simple, thread-safe IRC client library." authors = ["Aaron Weiss "] license = "Unlicense" @@ -14,6 +14,10 @@ readme = "README.md" ssl = ["openssl"] +[dependencies.encoding] + +encoding = "~0.2.4" + [dependencies.openssl] openssl = "~0.2.0" diff --git a/examples/multithreaded.rs b/examples/multithreaded.rs index 38f4bc2..0780895 100644 --- a/examples/multithreaded.rs +++ b/examples/multithreaded.rs @@ -18,6 +18,7 @@ fn main() { server: "irc.fyrechat.net".into_string(), port: 6667, use_ssl: false, + encoding: format!("UTF-8"), channels: vec!("#vana".into_string()), options: HashMap::new(), }; diff --git a/examples/simple.rs b/examples/simple.rs index d7efc75..12f1e33 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -17,6 +17,7 @@ fn main() { server: "irc.fyrechat.net".into_string(), port: 6667, use_ssl: false, + encoding: format!("UTF-8"), channels: vec!("#vana".into_string()), options: HashMap::new(), }; diff --git a/examples/simple_ssl.rs b/examples/simple_ssl.rs index 923f350..f65f18c 100644 --- a/examples/simple_ssl.rs +++ b/examples/simple_ssl.rs @@ -17,6 +17,7 @@ fn main() { server: "irc.fyrechat.net".into_string(), port: 6697, use_ssl: true, + encoding: format!("UTF-8"), channels: vec!("#vana".into_string()), options: HashMap::new(), }; diff --git a/examples/tweeter.rs b/examples/tweeter.rs index 0f1a345..c819572 100644 --- a/examples/tweeter.rs +++ b/examples/tweeter.rs @@ -19,6 +19,7 @@ fn main() { server: "irc.fyrechat.net".into_string(), port: 6667, use_ssl: false, + encoding: format!("UTF-8"), channels: vec!("#vana".into_string()), options: HashMap::new(), }; diff --git a/mktestconfig.sh b/mktestconfig.sh index 1a68cab..072c1ee 100755 --- a/mktestconfig.sh +++ b/mktestconfig.sh @@ -1 +1 @@ -echo "{\"owners\": [\"test\"],\"nickname\": \"test\",\"username\": \"test\",\"realname\": \"test\",\"password\": \"\",\"server\": \"irc.test.net\",\"port\": 6667,\"use_ssl\": false,\"channels\": [\"#test\", \"#test2\"],\"options\": {}}" > config.json +echo "{\"owners\": [\"test\"],\"nickname\": \"test\",\"username\": \"test\",\"realname\": \"test\",\"password\": \"\",\"server\": \"irc.test.net\",\"port\": 6667,\"use_ssl\": false,\"encoding\": \"UTF-8\",\"channels\": [\"#test\", \"#test2\"],\"options\": {}}" > config.json diff --git a/src/conn.rs b/src/conn.rs index a56bbe1..952565b 100644 --- a/src/conn.rs +++ b/src/conn.rs @@ -1,8 +1,9 @@ //! Thread-safe connections on IrcStreams. #![experimental] use std::sync::{Mutex, MutexGuard}; -use std::io::{BufferedStream, IoResult, MemWriter, TcpStream}; -#[cfg(feature = "ssl")] use std::io::{IoError, OtherIoError}; +use std::io::{BufferedStream, IoError, IoErrorKind, IoResult, MemWriter, TcpStream}; +use encoding::{DecoderTrap, EncoderTrap, Encoding}; +use encoding::label::encoding_from_whatwg_label; use data::kinds::{IrcReader, IrcStream, IrcWriter}; use data::message::Message; #[cfg(feature = "ssl")] use openssl::ssl::{SslContext, SslStream, Tlsv1}; @@ -74,6 +75,69 @@ impl Connection> { } } +impl Connection { + /// Creates a new connection from any arbitrary IrcStream. + #[experimental] + pub fn new(stream: T) -> Connection { + Connection { + stream: Mutex::new(stream), + } + } + + /// Sends a Message over this connection. + #[experimental] + pub fn send(&self, message: Message, encoding: &str) -> IoResult<()> { + let encoding = match encoding_from_whatwg_label(encoding) { + Some(enc) => enc, + None => return Err(IoError { + kind: IoErrorKind::InvalidInput, + desc: "Failed to find decoder.", + detail: Some(format!("Invalid decoder: {}", encoding)) + }) + }; + let data = match encoding.encode(message.into_string()[], EncoderTrap::Strict) { + Ok(data) => data, + Err(data) => return Err(IoError { + kind: IoErrorKind::InvalidInput, + desc: "Failed to decode message.", + detail: Some(format!("Failed to decode {} as {}.", data, encoding.name())), + }) + }; + let mut stream = self.stream.lock(); + try!(stream.write(data[])); + stream.flush() + } + + /// Receives a single line from this connection. + #[experimental] + pub fn recv(&self, encoding: &str) -> IoResult { + let encoding = match encoding_from_whatwg_label(encoding) { + Some(enc) => enc, + None => return Err(IoError { + kind: IoErrorKind::InvalidInput, + desc: "Failed to find decoder.", + detail: Some(format!("Invalid decoder: {}", encoding)) + }) + }; + self.stream.lock().read_until(b'\n').and_then(|line| + match encoding.decode(line[], DecoderTrap::Strict) { + Ok(data) => Ok(data), + Err(data) => Err(IoError { + kind: IoErrorKind::InvalidInput, + desc: "Failed to decode message.", + detail: Some(format!("Failed to decode {} as {}.", data, encoding.name())), + }) + } + ) + } + + /// Acquires the Stream lock. + #[experimental] + pub fn stream<'a>(&'a self) -> MutexGuard<'a, T> { + self.stream.lock() + } +} + /// Converts a Result into an IoResult. #[cfg(feature = "ssl")] fn ssl_to_io(res: Result) -> IoResult { @@ -118,36 +182,6 @@ impl Writer for NetStream { } } -impl Connection { - /// Creates a new connection from any arbitrary IrcStream. - #[experimental] - pub fn new(stream: T) -> Connection { - Connection { - stream: Mutex::new(stream), - } - } - - /// Sends a Message over this connection. - #[experimental] - pub fn send(&self, message: Message) -> IoResult<()> { - let mut stream = self.stream.lock(); - try!(stream.write_str(message.into_string()[])); - stream.flush() - } - - /// Receives a single line from this connection. - #[experimental] - pub fn recv(&self) -> IoResult { - self.stream.lock().read_line() - } - - /// Acquires the Stream lock. - #[experimental] - pub fn stream<'a>(&'a self) -> MutexGuard<'a, T> { - self.stream.lock() - } -} - /// An IrcStream built from an IrcWriter and an IrcReader. #[experimental] pub struct IoStream { @@ -197,22 +231,43 @@ mod test { use std::io::{MemReader, MemWriter}; use std::io::util::{NullReader, NullWriter}; use data::message::Message; + use encoding::{DecoderTrap, Encoding}; + use encoding::all::ISO_8859_15; #[test] - fn send() { + fn send_utf8() { let conn = Connection::new(IoStream::new(MemWriter::new(), NullReader)); assert!(conn.send( - Message::new(None, "PRIVMSG", Some(vec!["test"]), Some("Testing!")) + Message::new(None, "PRIVMSG", Some(vec!["test"]), Some("€ŠšŽžŒœŸ")), "l9" ).is_ok()); - let data = String::from_utf8(conn.stream().value()).unwrap(); - assert_eq!(data[], "PRIVMSG test :Testing!\r\n"); + let data = ISO_8859_15.decode(conn.stream().value()[], DecoderTrap::Strict).unwrap(); + assert_eq!(data[], "PRIVMSG test :€ŠšŽžŒœŸ\r\n"); } #[test] - fn recv() { + fn send_iso885915() { + + } + + #[test] + fn recv_utf8() { let conn = Connection::new(IoStream::new( - NullWriter, MemReader::new("PRIVMSG test :Testing!\r\n".as_bytes().to_vec()) + NullWriter, MemReader::new(b"PRIVMSG test :Testing!\r\n".to_vec()) )); - assert_eq!(conn.recv().unwrap()[], "PRIVMSG test :Testing!\r\n"); + assert_eq!(conn.recv("UTF-8").unwrap()[], "PRIVMSG test :Testing!\r\n"); + } + + #[test] + fn recv_iso885915() { + let conn = Connection::new(IoStream::new( + NullWriter, MemReader::new({ + let mut vec = Vec::new(); + vec.push_all(b"PRIVMSG test :"); + vec.push_all(&[0xA4, 0xA6, 0xA8, 0xB4, 0xB8, 0xBC, 0xBD, 0xBE]); + vec.push_all(b"\r\n"); + vec + }) + )); + assert_eq!(conn.recv("l9").unwrap()[], "PRIVMSG test :€ŠšŽžŒœŸ\r\n"); } } diff --git a/src/data/config.rs b/src/data/config.rs index 8bbdfb9..6d05a38 100644 --- a/src/data/config.rs +++ b/src/data/config.rs @@ -26,6 +26,9 @@ pub struct Config { /// Whether or not to use SSL. /// Bots will automatically panic if this is enabled without SSL support. pub use_ssl: bool, + /// The encoding type used for this connection. + /// This is typically UTF-8, but could be something else. + pub encoding: String, /// A list of channels to join on connection. pub channels: Vec, /// A map of additional options to be stored in config. @@ -81,6 +84,7 @@ mod test { server: format!("irc.test.net"), port: 6667, use_ssl: false, + encoding: format!("UTF-8"), channels: vec![format!("#test"), format!("#test2")], options: HashMap::new(), }; @@ -98,6 +102,7 @@ mod test { server: format!("irc.test.net"), port: 6667, use_ssl: false, + encoding: format!("UTF-8"), channels: vec![format!("#test"), format!("#test2")], options: HashMap::new(), }; @@ -115,6 +120,7 @@ mod test { server: format!("irc.test.net"), port: 6667, use_ssl: false, + encoding: format!("UTF-8"), channels: Vec::new(), options: HashMap::new(), }; @@ -134,6 +140,7 @@ mod test { server: format!("irc.test.net"), port: 6667, use_ssl: false, + encoding: format!("UTF-8"), channels: vec![format!("#test"), format!("#test2")], options: { let mut map = HashMap::new(); diff --git a/src/lib.rs b/src/lib.rs index 665951a..24cdc9a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ #![unstable] #![feature(if_let, slicing_syntax)] +extern crate encoding; extern crate serialize; #[cfg(feature = "ssl")] extern crate openssl; diff --git a/src/server/mod.rs b/src/server/mod.rs index bb34990..88e8afd 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -63,7 +63,8 @@ impl IrcServer> { } /// Creates a new IRC server connection from the specified configuration with the specified - /// timeout in milliseconds, connecting immediately. + /// timeout in milliseconds, connecting + /// immediately. #[experimental] pub fn from_config_with_timeout(config: Config, timeout_ms: u64) -> IoResult>> { @@ -83,7 +84,7 @@ impl<'a, T> Server<'a, T> for IrcServer where T: IrcStream { } fn send(&self, command: Command) -> IoResult<()> { - self.conn.send(command.to_message()) + self.conn.send(command.to_message(), self.config.encoding[]) } fn iter(&'a self) -> ServerIterator<'a, T> { @@ -176,7 +177,7 @@ impl<'a, T> ServerIterator<'a, T> where T: IrcStream { impl<'a, T> Iterator for ServerIterator<'a, T> where T: IrcStream { fn next(&mut self) -> Option { - let line = self.server.conn.recv(); + let line = self.server.conn.recv(self.server.config.encoding[]); match line { Err(_) => None, Ok(msg) => { @@ -209,6 +210,7 @@ mod test { server: format!("irc.test.net"), port: 6667, use_ssl: false, + encoding: format!("UTF-8"), channels: vec![format!("#test"), format!("#test2")], options: HashMap::new(), }