1#![cfg_attr(docsrs, feature(doc_cfg))]
95
96use snafu::{Backtrace, OptionExt, ResultExt, Snafu};
97use std::collections::HashMap;
98use std::fmt::{Debug, Write as FmtWrite};
99use std::io::{self, BufRead, BufReader, Write};
100use std::net::{Shutdown, TcpStream, ToSocketAddrs};
101use std::string::FromUtf8Error;
102use std::time::Duration;
103
104mod data;
105#[cfg_attr(docsrs, doc(cfg(feature = "managed")))]
106#[cfg(feature = "managed")]
107pub mod managed;
108pub mod raw;
109
110pub use data::*;
111use io::Read;
112use raw::*;
113use std::fmt;
114
115pub enum MessageTarget {
117 Client(ClientId),
119 Channel,
121 Server,
123}
124
125impl fmt::Display for MessageTarget {
126 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
127 match *self {
128 Self::Client(id) => write!(f, "targetmode=1 target={}", id),
129 Self::Channel => write!(f, "targetmode=2"),
130 Self::Server => write!(f, "targetmode=3"),
131 }
132 }
133}
134
135#[derive(Snafu, Debug)]
136pub enum Ts3Error {
137 #[snafu(display("Input was invalid UTF-8: {}", source))]
139 Utf8Error { source: FromUtf8Error },
140 #[snafu(display("IO Error: {}{}, kind: {:?}", context, source, source.kind()))]
142 Io {
143 context: &'static str,
147 source: io::Error,
148 },
149 #[snafu(display("IO Error: Connection closed"))]
151 ConnectionClosed { backtrace: Backtrace },
152 #[snafu(display("No valid socket address provided."))]
153 InvalidSocketAddress { backtrace: Backtrace },
154 #[snafu(display("Received invalid response, {}{:?}", context, data))]
156 InvalidResponse {
157 context: &'static str,
161 data: String,
162 },
163 #[snafu(display("Got invalid int response {}: {}", data, source))]
164 InvalidIntResponse {
165 data: String,
166 source: std::num::ParseIntError,
167 backtrace: Backtrace,
168 },
169 #[snafu(display("Server responded with error: {}", response))]
171 ServerError {
172 response: ErrorResponse,
173 backtrace: Backtrace,
174 },
175 #[snafu(display("Invalid response, DDOS limit reached: {:?}", response))]
179 ResponseLimit {
180 response: Vec<String>,
181 backtrace: Backtrace,
182 },
183 #[cfg(feature = "managed")]
185 #[snafu(display("Invalid name length: {} max: {}!", length, expected))]
186 InvalidNameLength { length: usize, expected: usize },
187 #[snafu(display("Expected entry for key {}, key not found!", key))]
189 NoEntryResponse {
190 key: &'static str,
191 backtrace: Backtrace,
192 },
193 #[snafu(display("Expected value for key {}, got none!", key))]
195 NoValueResponse {
196 key: &'static str,
197 backtrace: Backtrace,
198 },
199}
200
201impl Ts3Error {
202 pub fn is_error_response(&self) -> bool {
204 match self {
205 Ts3Error::ServerError { .. } => true,
206 _ => false,
207 }
208 }
209 pub fn error_response(&self) -> Option<&ErrorResponse> {
211 match self {
212 Ts3Error::ServerError { response, .. } => Some(response),
213 _ => None,
214 }
215 }
216}
217
218impl From<io::Error> for Ts3Error {
219 fn from(error: io::Error) -> Self {
220 Ts3Error::Io {
221 context: "",
222 source: error,
223 }
224 }
225}
226
227pub struct QueryClient {
229 rx: BufReader<TcpStream>,
230 tx: TcpStream,
231 limit_lines: usize,
232 limit_lines_bytes: u64,
233}
234
235pub const LIMIT_READ_LINES: usize = 100;
237pub const LIMIT_LINE_BYTES: u64 = 64_000;
239
240type Result<T> = ::std::result::Result<T, Ts3Error>;
241
242impl Drop for QueryClient {
243 fn drop(&mut self) {
244 #[allow(unused_variables)]
245 if let Err(e) = self.quit() {
246 #[cfg(feature = "debug_response")]
247 eprintln!("Can't quit on drop: {}", e);
248 }
249 let _ = self.tx.shutdown(Shutdown::Both);
250 }
251}
252
253impl QueryClient {
254 pub fn new<A: ToSocketAddrs>(addr: A) -> Result<Self> {
256 let (rx, tx) = Self::new_inner(addr, None, None)?;
257
258 Ok(Self {
259 rx,
260 tx,
261 limit_lines: LIMIT_READ_LINES,
262 limit_lines_bytes: LIMIT_LINE_BYTES,
263 })
264 }
265
266 pub fn with_timeout<A: ToSocketAddrs>(
270 addr: A,
271 t_connect: Option<Duration>,
272 timeout: Option<Duration>,
273 ) -> Result<Self> {
274 let (rx, tx) = Self::new_inner(addr, timeout, t_connect)?;
275
276 Ok(Self {
277 rx,
278 tx,
279 limit_lines: LIMIT_READ_LINES,
280 limit_lines_bytes: LIMIT_LINE_BYTES,
281 })
282 }
283
284 pub fn limit_lines(&mut self, limit: usize) {
286 self.limit_lines = limit;
287 }
288
289 pub fn limit_line_bytes(&mut self, limit: u64) {
292 self.limit_lines_bytes = limit;
293 }
294
295 pub fn rename<T: AsRef<str>>(&mut self, name: T) -> Result<()> {
297 writeln!(
298 &mut self.tx,
299 "clientupdate client_nickname={}",
300 escape_arg(name)
301 )?;
302 let _ = self.read_response()?;
303 Ok(())
304 }
305
306 pub fn rename_channel<T: AsRef<str>>(&mut self, channel: ChannelId, name: T) -> Result<()> {
308 writeln!(
309 &mut self.tx,
310 "channeledit cid={} channel_name={}",
311 channel,escape_arg(name)
312 )?;
313 let _ = self.read_response()?;
314 Ok(())
315 }
316
317 pub fn update_description<T: AsRef<str>>(
321 &mut self,
322 descr: T,
323 target: Option<ClientId>,
324 ) -> Result<()> {
325 if let Some(clid) = target {
326 writeln!(
327 &mut self.tx,
328 "clientedit clid={} CLIENT_DESCRIPTION={}",
329 clid,
330 escape_arg(descr)
331 )?;
332 } else {
333 writeln!(
334 &mut self.tx,
335 "clientupdate CLIENT_DESCRIPTION={}",
336 escape_arg(descr)
337 )?;
338 }
339 let _ = self.read_response()?;
340 Ok(())
341 }
342
343 pub fn poke_client<T: AsRef<str>>(&mut self, client: ClientId, msg: T) -> Result<()> {
347 writeln!(
348 &mut self.tx,
349 "clientpoke clid={} msg={}",
350 client,
351 msg.as_ref()
352 )?;
353 let _ = self.read_response()?;
354 Ok(())
355 }
356
357 pub fn send_message<T: AsRef<str>>(&mut self, target: MessageTarget, msg: T) -> Result<()> {
359 writeln!(
360 &mut self.tx,
361 "sendtextmessage {} msg={}",
362 target,
363 escape_arg(msg)
364 )?;
365 let _ = self.read_response()?;
366 Ok(())
367 }
368
369 fn quit(&mut self) -> Result<()> {
371 writeln!(&mut self.tx, "quit")?;
372 let _ = self.read_response()?;
373 Ok(())
374 }
375
376 fn new_inner<A: ToSocketAddrs>(
378 addr: A,
379 timeout: Option<Duration>,
380 conn_timeout: Option<Duration>,
381 ) -> Result<(BufReader<TcpStream>, TcpStream)> {
382 let addr = addr
383 .to_socket_addrs()
384 .context(Io {
385 context: "invalid socket address",
386 })?
387 .next()
388 .context(InvalidSocketAddress {})?;
389 let stream = if let Some(dur) = conn_timeout {
390 TcpStream::connect_timeout(&addr, dur).context(Io {
391 context: "while connecting: ",
392 })?
393 } else {
394 TcpStream::connect(addr).context(Io {
395 context: "while connecting: ",
396 })?
397 };
398
399 stream.set_write_timeout(timeout).context(Io {
400 context: "setting write timeout: ",
401 })?;
402 stream.set_read_timeout(timeout).context(Io {
403 context: "setting read timeout: ",
404 })?;
405
406 stream.set_nodelay(true).context(Io {
407 context: "setting nodelay: ",
408 })?;
409
410 let mut reader = BufReader::new(stream.try_clone().context(Io {
411 context: "splitting connection: ",
412 })?);
413
414 let mut buffer = Vec::new();
416 reader.read_until(b'\r', &mut buffer).context(Io {
417 context: "reading response: ",
418 })?;
419
420 buffer.clear();
421 if let Err(e) = reader.read_until(b'\r', &mut buffer) {
422 use std::io::ErrorKind::*;
423 match e.kind() {
424 TimedOut | WouldBlock => (), _ => return Err(e.into()),
426 }
427 }
428
429 Ok((reader, stream))
430 }
431
432 pub fn raw_command<T: AsRef<str>>(&mut self, command: T) -> Result<Vec<String>> {
436 writeln!(&mut self.tx, "{}", command.as_ref())?;
437 let v = self.read_response()?;
438 Ok(v)
439 }
440
441 pub fn whoami(&mut self, unescape: bool) -> Result<HashMap<String, Option<String>>> {
445 writeln!(&mut self.tx, "whoami")?;
446 let v = self.read_response()?;
447 Ok(parse_hashmap(v, unescape))
448 }
449
450 pub fn logout(&mut self) -> Result<()> {
452 writeln!(&mut self.tx, "logout")?;
453 let _ = self.read_response()?;
454 Ok(())
455 }
456
457 pub fn login<T: AsRef<str>, S: AsRef<str>>(&mut self, user: T, password: S) -> Result<()> {
461 writeln!(
462 &mut self.tx,
463 "login {} {}",
464 escape_arg(user),
465 escape_arg(password)
466 )?;
467
468 let _ = self.read_response()?;
469
470 Ok(())
471 }
472
473 pub fn select_server_by_port(&mut self, port: u16) -> Result<()> {
477 writeln!(&mut self.tx, "use port={}", port)?;
478
479 let _ = self.read_response()?;
480 Ok(())
481 }
482
483 pub fn move_client(
487 &mut self,
488 client: ClientId,
489 channel: ChannelId,
490 password: Option<&str>,
491 ) -> Result<()> {
492 let pw_arg = if let Some(pw) = password {
493 format!("cpw={}", raw::escape_arg(pw).as_str())
494 } else {
495 String::new()
496 };
497 writeln!(
498 &mut self.tx,
499 "clientmove clid={} cid={} {}",
500 client, channel, pw_arg
501 )?;
502 let _ = self.read_response()?;
503 Ok(())
504 }
505
506 pub fn kick_client(
510 &mut self,
511 client: ClientId,
512 server: bool,
513 message: Option<&str>,
514 ) -> Result<()> {
515 let msg_arg = if let Some(pw) = message {
516 format!("reasonmsg={}", raw::escape_arg(pw).as_str())
517 } else {
518 String::new()
519 };
520 let rid = if server { 5 } else { 4 };
521 writeln!(
522 &mut self.tx,
523 "clientkick clid={} reasonid={} {}",
524 client, rid, msg_arg
525 )?;
526 let _ = self.read_response()?;
527 Ok(())
528 }
529
530 pub fn create_dir<T: AsRef<str>>(&mut self, channel: ChannelId, path: T) -> Result<()> {
534 writeln!(
535 &mut self.tx,
536 "ftcreatedir cid={} cpw= dirname={}",
537 channel,
538 escape_arg(path)
539 )?;
540 let _ = self.read_response()?;
541 Ok(())
542 }
543
544 pub fn delete_file<T: AsRef<str>>(&mut self, channel: ChannelId, path: T) -> Result<()> {
550 writeln!(
551 &mut self.tx,
552 "ftdeletefile cid={} cpw= name={}",
553 channel,
554 escape_arg(path)
555 )?;
556 let _ = self.read_response()?;
557 Ok(())
558 }
559
560 pub fn ping(&mut self) -> Result<()> {
564 writeln!(&mut self.tx, "whoami")?;
565 let _ = self.read_response()?;
566 Ok(())
567 }
568
569 pub fn select_server_by_id(&mut self, sid: ServerId) -> Result<()> {
573 writeln!(&mut self.tx, "use sid={}", sid)?;
574
575 let _ = self.read_response()?;
576 Ok(())
577 }
578
579 pub fn server_group_del_clients(
582 &mut self,
583 group: ServerGroupID,
584 cldbid: &[usize],
585 ) -> Result<()> {
586 if cldbid.is_empty() {
587 return Ok(());
588 }
589 writeln!(
590 &mut self.tx,
591 "servergroupdelclient sgid={} {}",
592 group,
593 Self::format_cldbids(cldbid)
594 )?;
595 let _ = self.read_response()?;
596 Ok(())
597 }
598
599 pub fn server_group_add_clients(
602 &mut self,
603 group: ServerGroupID,
604 cldbid: &[usize],
605 ) -> Result<()> {
606 if cldbid.is_empty() {
607 return Ok(());
608 }
609 let v = Self::format_cldbids(cldbid);
610 writeln!(&mut self.tx, "servergroupaddclient sgid={} {}", group, v)?;
611 let _ = self.read_response()?;
612 Ok(())
613 }
614
615 fn format_cldbids(it: &[usize]) -> String {
617 let mut res = String::new();
620 let mut it = it.iter();
621 if let Some(n) = it.next() {
622 write!(res, "cldbid={}", n).unwrap();
623 }
624 for n in it {
625 write!(res, "|cldbid={}", n).unwrap();
626 }
627 res
628 }
629
630 fn read_response(&mut self) -> Result<Vec<String>> {
632 let mut result: Vec<String> = Vec::new();
633 let mut lr = (&mut self.rx).take(self.limit_lines_bytes);
634 for _ in 0..self.limit_lines {
635 let mut buffer = Vec::new();
636 if lr.read_until(b'\r', &mut buffer).context(Io {
638 context: "reading response: ",
639 })? == 0
640 {
641 return ConnectionClosed {}.fail();
642 }
643 if buffer.ends_with(&[b'\r']) {
645 buffer.pop();
646 if buffer.ends_with(&[b'\n']) {
647 buffer.pop();
648 }
649 } else if lr.limit() == 0 {
650 return ResponseLimit { response: result }.fail();
651 } else {
652 return InvalidResponse {
653 context: "expected \\r delimiter, got: ",
654 data: String::from_utf8_lossy(&buffer),
655 }
656 .fail();
657 }
658
659 if !buffer.is_empty() {
660 let line = String::from_utf8(buffer).context(Utf8Error)?;
661 #[cfg(feature = "debug_response")]
662 println!("Read: {:?}", &line);
663 if line.starts_with("error ") {
664 Self::check_ok(&line)?;
665 return Ok(result);
666 }
667 result.push(line);
668 }
669 lr.set_limit(LIMIT_LINE_BYTES);
670 }
671 ResponseLimit { response: result }.fail()
672 }
673
674 pub fn online_clients_full(&mut self) -> Result<Vec<OnlineClientFull>> {
678 writeln!(
679 &mut self.tx,
680 "clientlist -uid -away -voice -times -groups -info -country -ip -badges"
681 )?;
682 let res = self.read_response()?;
683
684 let clients = raw::parse_multi_hashmap(res, false)
685 .into_iter()
686 .map(|v| Ok(OnlineClientFull::from_raw(v)?))
687 .collect::<Result<_>>()?;
688
689 Ok(clients)
690 }
691
692 pub fn online_clients(&mut self) -> Result<Vec<OnlineClient>> {
696 writeln!(&mut self.tx, "clientlist")?;
697 let res = self.read_response()?;
698
699 let clients = raw::parse_multi_hashmap(res, false)
700 .into_iter()
701 .map(|v| Ok(OnlineClient::from_raw(v)?))
702 .collect::<Result<_>>()?;
703
704 Ok(clients)
705 }
706
707 pub fn channels(&mut self) -> Result<Vec<Channel>> {
711 writeln!(&mut self.tx, "channellist")?;
712 let res = self.read_response()?;
713
714 let channels = raw::parse_multi_hashmap(res, false)
715 .into_iter()
716 .map(|v| Ok(Channel::from_raw(v)?))
717 .collect::<Result<_>>()?;
718
719 Ok(channels)
720 }
721
722 pub fn channels_full(&mut self) -> Result<Vec<ChannelFull>> {
726 writeln!(
727 &mut self.tx,
728 "channellist -topic -flags -voice -limits -icon -secondsempty"
729 )?;
730 let res = self.read_response()?;
731
732 let channels = raw::parse_multi_hashmap(res, false)
733 .into_iter()
734 .map(|v| Ok(ChannelFull::from_raw(v)?))
735 .collect::<Result<_>>()?;
736
737 Ok(channels)
738 }
739
740 pub fn delete_channel(&mut self, id: ChannelId, force: bool) -> Result<()> {
744 writeln!(
745 &mut self.tx,
746 "channeldelete cid={} force={}",
747 id,
748 if force { 1 } else { 0 }
749 )?;
750 let _ = self.read_response()?;
751
752 Ok(())
753 }
754
755 pub fn create_channel(&mut self, channel: &ChannelEdit) -> Result<ChannelId> {
758 writeln!(&mut self.tx, "channelcreate{}", &channel.to_raw())?;
759 let res = self.read_response()?;
760
761 let mut response = raw::parse_hashmap(res, false);
762 let cid = int_val_parser(&mut response, "cid")?;
763
764 Ok(cid)
765 }
766
767 pub fn server_groups(&mut self) -> Result<Vec<ServerGroup>> {
771 writeln!(&mut self.tx, "servergrouplist")?;
772 let res = self.read_response()?;
773
774 let groups = raw::parse_multi_hashmap(res, false)
775 .into_iter()
776 .map(|v| Ok(ServerGroup::from_raw(v)?))
777 .collect::<Result<_>>()?;
778
779 Ok(groups)
780 }
781
782 pub fn servergroup_client_cldbids(&mut self, group: ServerGroupID) -> Result<Vec<usize>> {
786 writeln!(&mut self.tx, "servergroupclientlist sgid={}", group)?;
787
788 let resp = self.read_response()?;
789 if let Some(line) = resp.get(0) {
790 let data: Vec<usize> = line
791 .split('|')
792 .map(|e| {
793 if let Some(cldbid) = e.split('=').collect::<Vec<_>>().get(1) {
794 Ok(cldbid
795 .parse::<usize>()
796 .map_err(|_| Ts3Error::InvalidResponse {
797 context: "expected usize, got ",
798 data: line.to_string(),
799 })?)
800 } else {
801 Err(Ts3Error::InvalidResponse {
802 context: "expected data of cldbid=1, got ",
803 data: line.to_string(),
804 })
805 }
806 })
807 .collect::<Result<Vec<usize>>>()?;
808 Ok(data)
809 } else {
810 Ok(Vec::new())
811 }
812 }
813
814 fn check_ok(msg: &str) -> Result<()> {
816 let result: Vec<&str> = msg.split(' ').collect();
817 #[cfg(debug)]
818 {
819 assert_eq!(
821 "check_ok invoked on non-error line",
822 result.get(0),
823 Some(&"error")
824 );
825 }
826 if let (Some(id), Some(msg)) = (result.get(1), result.get(2)) {
827 let split_id: Vec<&str> = id.split('=').collect();
828 let split_msg: Vec<&str> = msg.split('=').collect();
829 if let (Some(id), Some(msg)) = (split_id.get(1), split_msg.get(1)) {
830 let id = id.parse::<usize>().map_err(|_| Ts3Error::InvalidResponse {
831 context: "expected usize, got ",
832 data: (*msg).to_string(), })?;
834 if id != 0 {
835 return ServerError {
836 response: ErrorResponse {
837 id,
838 msg: unescape_val(*msg),
839 },
840 }
841 .fail();
842 } else {
843 return Ok(());
844 }
845 }
846 }
847 Err(Ts3Error::InvalidResponse {
848 context: "expected id and msg, got ",
849 data: msg.to_string(),
850 })
851 }
852}
853
854#[cfg(test)]
855mod test {
856 use super::*;
857
858 #[test]
859 fn test_format_cldbids() {
860 let ids = vec![0, 1, 2, 3];
861 assert_eq!(
862 "cldbid=0|cldbid=1|cldbid=2|cldbid=3",
863 QueryClient::format_cldbids(&ids)
864 );
865 assert_eq!("", QueryClient::format_cldbids(&[]));
866 assert_eq!("cldbid=0", QueryClient::format_cldbids(&ids[0..1]));
867 }
868}