Added support for non-unicode encodings.

This commit is contained in:
Aaron Weiss 2014-11-30 01:29:38 -05:00
parent 77a324b0e5
commit 098f9dbbff
10 changed files with 117 additions and 44 deletions

View file

@ -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 <aaronweiss74@gmail.com>"]
license = "Unlicense"
@ -14,6 +14,10 @@ readme = "README.md"
ssl = ["openssl"]
[dependencies.encoding]
encoding = "~0.2.4"
[dependencies.openssl]
openssl = "~0.2.0"

View file

@ -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(),
};

View file

@ -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(),
};

View file

@ -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(),
};

View file

@ -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(),
};

View file

@ -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

View file

@ -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<BufferedStream<TcpStream>> {
}
}
impl<T: IrcStream> Connection<T> {
/// Creates a new connection from any arbitrary IrcStream.
#[experimental]
pub fn new(stream: T) -> Connection<T> {
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<String> {
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<T, SslError> into an IoResult<T>.
#[cfg(feature = "ssl")]
fn ssl_to_io<T>(res: Result<T, SslError>) -> IoResult<T> {
@ -118,36 +182,6 @@ impl Writer for NetStream {
}
}
impl<T: IrcStream> Connection<T> {
/// Creates a new connection from any arbitrary IrcStream.
#[experimental]
pub fn new(stream: T) -> Connection<T> {
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<String> {
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<T: IrcWriter, U: IrcReader> {
@ -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");
}
}

View file

@ -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<String>,
/// 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();

View file

@ -5,6 +5,7 @@
#![unstable]
#![feature(if_let, slicing_syntax)]
extern crate encoding;
extern crate serialize;
#[cfg(feature = "ssl")] extern crate openssl;

View file

@ -63,7 +63,8 @@ impl IrcServer<BufferedStream<NetStream>> {
}
/// 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<IrcServer<BufferedStream<NetStream>>> {
@ -83,7 +84,7 @@ impl<'a, T> Server<'a, T> for IrcServer<T> 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<Message> for ServerIterator<'a, T> where T: IrcStream {
fn next(&mut self) -> Option<Message> {
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(),
}