brizental pushed to branch tor-browser-153.2.0esr-16.0-1 at The Tor Project / Applications / Tor Browser Commits: 6752dfdd by Elena at 2026-09-09T22:34:24+02:00 fixup! TB 44806: Implement the tor integration in Rust. TB 44930: Implement the commands on the Rust control port Created TorController - - - - - 14c31b41 by Elena at 2026-09-09T22:34:24+02:00 fixup! TB 44806: Implement the tor integration in Rust. TB 44930: Implement the commands on the Rust control port Implemented the authenticate method. - - - - - 304b6706 by Elena at 2026-09-09T22:34:24+02:00 fixup! TB 44806: Implement the tor integration in Rust. TB 44930: Implement the commands on the Rust control port Implemented a few simple commands. - - - - - 23 changed files: - Cargo.lock - toolkit/components/tor-integration/tor_provider/Cargo.toml - toolkit/components/tor-integration/tor_provider/src/ctor/control_port/control_port.rs - toolkit/components/tor-integration/tor_provider/src/ctor/control_port/mod.rs - + toolkit/components/tor-integration/tor_provider/src/ctor/controller/commands/authenticate.rs - + toolkit/components/tor-integration/tor_provider/src/ctor/controller/commands/command.rs - + toolkit/components/tor-integration/tor_provider/src/ctor/controller/commands/mod.rs - + toolkit/components/tor-integration/tor_provider/src/ctor/controller/commands/reset_conf.rs - + toolkit/components/tor-integration/tor_provider/src/ctor/controller/commands/save_conf.rs - + toolkit/components/tor-integration/tor_provider/src/ctor/controller/commands/set_events.rs - + toolkit/components/tor-integration/tor_provider/src/ctor/controller/commands/signal.rs - + toolkit/components/tor-integration/tor_provider/src/ctor/controller/commands/take_ownership.rs - + toolkit/components/tor-integration/tor_provider/src/ctor/controller/controller.rs - + toolkit/components/tor-integration/tor_provider/src/ctor/controller/error.rs - toolkit/components/tor-integration/tor_provider/src/ctor/controller/mod.rs - + toolkit/components/tor-integration/tor_provider/src/ctor/controller/parsers/ack.rs - toolkit/components/tor-integration/tor_provider/src/ctor/controller/escape.rs → toolkit/components/tor-integration/tor_provider/src/ctor/controller/parsers/escape.rs - + toolkit/components/tor-integration/tor_provider/src/ctor/controller/parsers/mod.rs - + toolkit/components/tor-integration/tor_provider/src/ctor/controller/parsers/tests.rs - toolkit/components/tor-integration/tor_provider/src/ctor/controller/unescape.rs → toolkit/components/tor-integration/tor_provider/src/ctor/controller/parsers/unescape.rs - toolkit/components/tor-integration/tor_provider/src/ctor/mod.rs - toolkit/components/tor-integration/tor_provider/src/ctor/reply_parser/line.rs - toolkit/components/tor-integration/tor_service/src/control_port.rs Changes: ===================================== Cargo.lock ===================================== @@ -8122,6 +8122,7 @@ name = "tor_provider" version = "0.1.0" dependencies = [ "bytes", + "hex", "log", "memchr", "thiserror 2.0.12", ===================================== toolkit/components/tor-integration/tor_provider/Cargo.toml ===================================== @@ -6,6 +6,7 @@ edition = "2021" [dependencies] bytes = "1.4.0" +hex = "0.4.3" log = "0.4" memchr = "2.7.4" thiserror = "2" ===================================== toolkit/components/tor-integration/tor_provider/src/ctor/control_port/control_port.rs ===================================== @@ -15,8 +15,20 @@ use super::{ error::ControlPortError, message_pump::{MessagePump, ReadAction}, }; + use crate::ctor::reply_parser::{Reply, ReplyDispatcher, ReplyError}; +pub trait ControlPortInterface { + fn send_command( + &self, + command: Bytes, + handler: Box<dyn FnOnce(Result<Reply, ControlPortError>)>, + ); + fn set_async_handler(&self, cb: Option<Box<dyn Fn(Reply)>>); + fn close(&self) -> Result<(), ControlSocketError>; + fn set_close_handler(&self, cb: Box<dyn FnOnce()>); +} + /// The lower-level part of the control port implementation. /// It contains the logic for actually sending the command, and it hides its /// reference-counted nature from actual consumers. @@ -172,6 +184,9 @@ impl Drop for ControlPortInner { } } +// Wrap the inner type to make it clearer that its ownership is not supposed to +// be shared even though it uses an Rc, but the use of the Rc is due to the +// callback structure. pub struct ControlPort(Rc<ControlPortInner>); impl ControlPort { @@ -179,11 +194,11 @@ impl ControlPort { pub fn new(socket: Box<dyn ControlSocket>) -> Result<Self, ControlSocketError> { Ok(Self(ControlPortInner::new(Rc::from(socket))?)) } +} - // TODO: Keep only the methods speicifc to commands and remove this one - // (tor-browser#44930). +impl ControlPortInterface for ControlPort { #[inline] - pub fn send_command( + fn send_command( &self, command: Bytes, handler: Box<dyn FnOnce(Result<Reply, ControlPortError>)>, @@ -192,17 +207,17 @@ impl ControlPort { } #[inline] - pub fn set_async_handler(&self, cb: Option<Box<dyn Fn(Reply)>>) { + fn set_async_handler(&self, cb: Option<Box<dyn Fn(Reply)>>) { self.0.set_async_handler(cb); } #[inline] - pub fn close(&self) -> Result<(), ControlSocketError> { + fn close(&self) -> Result<(), ControlSocketError> { self.0.close() } #[inline] - pub fn set_close_handler(&self, cb: Box<dyn FnOnce()>) { + fn set_close_handler(&self, cb: Box<dyn FnOnce()>) { self.0.set_close_handler(cb); } } ===================================== toolkit/components/tor-integration/tor_provider/src/ctor/control_port/mod.rs ===================================== @@ -9,6 +9,6 @@ mod control_socket; mod error; mod message_pump; -pub use control_port::ControlPort; +pub use control_port::*; pub use control_socket::*; pub use error::ControlPortError; ===================================== toolkit/components/tor-integration/tor_provider/src/ctor/controller/commands/authenticate.rs ===================================== @@ -0,0 +1,23 @@ +// Licensed under the Apache License, Version 2.0, +// <http://apache.org/licenses/LICENSE-2.0> or the MIT license +// <http://opensource.org/licenses/MIT>, at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +use super::Command; +use crate::ctor::controller::{parsers::parse_ack, ControllerError}; + +pub fn authenticate(password: &[u8]) -> Result<Command<u16>, ControllerError> { + const COMMAND: &str = "AUTHENTICATE"; + let mut command = String::new(); + command.reserve(COMMAND.len() + 1 + password.len() * 2 + 2); + command.push_str(COMMAND); + if !password.is_empty() { + command.push(' '); + command.push_str(hex::encode(password).as_str()); + } + command.push_str("\r\n"); + Ok(Command { + command, + handler: Box::new(parse_ack), + }) +} ===================================== toolkit/components/tor-integration/tor_provider/src/ctor/controller/commands/command.rs ===================================== @@ -0,0 +1,11 @@ +// Licensed under the Apache License, Version 2.0, +// <http://apache.org/licenses/LICENSE-2.0> or the MIT license +// <http://opensource.org/licenses/MIT>, at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +use crate::ctor::{controller::ControllerError, reply_parser::Reply}; + +pub struct Command<T> { + pub command: String, + pub handler: Box<dyn Fn(Reply) -> Result<T, ControllerError>>, +} ===================================== toolkit/components/tor-integration/tor_provider/src/ctor/controller/commands/mod.rs ===================================== @@ -0,0 +1,20 @@ +// Licensed under the Apache License, Version 2.0, +// <http://apache.org/licenses/LICENSE-2.0> or the MIT license +// <http://opensource.org/licenses/MIT>, at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +mod authenticate; +mod command; +mod reset_conf; +mod save_conf; +mod set_events; +mod signal; +mod take_ownership; + +pub use authenticate::authenticate; +pub use command::Command; +pub use reset_conf::reset_owning_controller_process; +pub use save_conf::save_conf; +pub use set_events::set_events; +pub use signal::signal_newnym; +pub use take_ownership::take_ownership; ===================================== toolkit/components/tor-integration/tor_provider/src/ctor/controller/commands/reset_conf.rs ===================================== @@ -0,0 +1,14 @@ +// Licensed under the Apache License, Version 2.0, +// <http://apache.org/licenses/LICENSE-2.0> or the MIT license +// <http://opensource.org/licenses/MIT>, at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +use super::Command; +use crate::ctor::controller::{parsers::parse_ack, ControllerError}; + +pub fn reset_owning_controller_process() -> Result<Command<u16>, ControllerError> { + Ok(Command { + command: String::from("RESETCONF __OwningControllerProcess\r\n"), + handler: Box::new(parse_ack), + }) +} ===================================== toolkit/components/tor-integration/tor_provider/src/ctor/controller/commands/save_conf.rs ===================================== @@ -0,0 +1,14 @@ +// Licensed under the Apache License, Version 2.0, +// <http://apache.org/licenses/LICENSE-2.0> or the MIT license +// <http://opensource.org/licenses/MIT>, at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +use super::Command; +use crate::ctor::controller::{parsers::parse_ack, ControllerError}; + +pub fn save_conf() -> Result<Command<u16>, ControllerError> { + Ok(Command { + command: String::from("SAVECONF\r\n"), + handler: Box::new(parse_ack), + }) +} ===================================== toolkit/components/tor-integration/tor_provider/src/ctor/controller/commands/set_events.rs ===================================== @@ -0,0 +1,20 @@ +// Licensed under the Apache License, Version 2.0, +// <http://apache.org/licenses/LICENSE-2.0> or the MIT license +// <http://opensource.org/licenses/MIT>, at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +use super::Command; +use crate::ctor::controller::{parsers::parse_ack, ControllerError}; + +pub fn set_events(events: &[&str]) -> Result<Command<u16>, ControllerError> { + let mut command = String::from("SETEVENTS"); + for e in events { + command.push(' '); + command.push_str(e); + } + command.push_str("\r\n"); + Ok(Command { + command, + handler: Box::new(parse_ack), + }) +} ===================================== toolkit/components/tor-integration/tor_provider/src/ctor/controller/commands/signal.rs ===================================== @@ -0,0 +1,14 @@ +// Licensed under the Apache License, Version 2.0, +// <http://apache.org/licenses/LICENSE-2.0> or the MIT license +// <http://opensource.org/licenses/MIT>, at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +use super::Command; +use crate::ctor::controller::{parsers::parse_ack, ControllerError}; + +pub fn signal_newnym() -> Result<Command<u16>, ControllerError> { + Ok(Command { + command: String::from("SIGNAL NEWNYM\r\n"), + handler: Box::new(parse_ack), + }) +} ===================================== toolkit/components/tor-integration/tor_provider/src/ctor/controller/commands/take_ownership.rs ===================================== @@ -0,0 +1,14 @@ +// Licensed under the Apache License, Version 2.0, +// <http://apache.org/licenses/LICENSE-2.0> or the MIT license +// <http://opensource.org/licenses/MIT>, at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +use super::Command; +use crate::ctor::controller::{parsers::parse_ack, ControllerError}; + +pub fn take_ownership() -> Result<Command<u16>, ControllerError> { + Ok(Command { + command: String::from("TAKEOWNERSHIP\r\n"), + handler: Box::new(parse_ack), + }) +} ===================================== toolkit/components/tor-integration/tor_provider/src/ctor/controller/controller.rs ===================================== @@ -0,0 +1,143 @@ +// Licensed under the Apache License, Version 2.0, +// <http://apache.org/licenses/LICENSE-2.0> or the MIT license +// <http://opensource.org/licenses/MIT>, at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +use bytes::Bytes; + +use super::{ + commands::{self, Command}, + error::ControllerError, +}; +use crate::ctor::{ + control_port::{ControlPortInterface, ControlSocketError}, + reply_parser::Reply, +}; + +/// A controller for a tor daemon. +/// +/// TorController wraps an implementation of a ControlPortInterface (the lower-level transport that +/// exposes a command-based interface) and exposes a set of async, callback-based methods. +/// Every method correspond to a control port command. +/// +/// Callers should authenticate before issuing any other command, even when authentication is +/// disabled on the server side, and close the connection when done with it. +/// The tor daemon can be configured so that this controller is its owner, so that when it closes +/// the connection, the remote daemon shuts down. +pub struct TorController<CP: ControlPortInterface>(CP); + +impl<CP: ControlPortInterface> TorController<CP> { + #[inline] + pub fn new(control_port: CP) -> Self { + Self(control_port) + } + + #[inline] + pub fn close(&self) -> Result<(), ControllerError> { + match self.0.close() { + Ok(()) => Ok(()), + Err(ControlSocketError::ConnectionClosed) => Ok(()), + Err(ControlSocketError::ImplementationError(rv)) => { + Err(ControllerError::ConnectionError(rv)) + } + } + } + + fn send_command<T>( + &self, + command: Result<Command<T>, ControllerError>, + handler: Box<dyn FnOnce(Result<T, ControllerError>)>, + ) where + T: 'static, + { + let Command { + command, + handler: command_handler, + } = match command { + Ok(c) => c, + Err(e) => { + handler(Err(e)); + return; + } + }; + self.0.send_command( + command.into(), + Box::new(move |r| match r { + Ok(reply) => handler(command_handler(reply)), + Err(e) => handler(Err(e.into())), + }), + ); + } + + // Setup + + /// Authenticate to the tor daemon. + /// Notice that a failure in the authentication makes the connection close. + pub fn authenticate( + &self, + password: &[u8], + handler: Box<dyn FnOnce(Result<u16, ControllerError>)>, + ) { + self.send_command(commands::authenticate(password), handler); + } + + pub fn take_ownership(&self, handler: Box<dyn FnOnce(Result<u16, ControllerError>)>) { + self.send_command(commands::take_ownership(), handler); + } + + pub fn reset_owning_controller_process( + &self, + handler: Box<dyn FnOnce(Result<u16, ControllerError>)>, + ) { + self.send_command(commands::reset_owning_controller_process(), handler); + } + + pub fn set_events( + &self, + events: &[&str], + handler: Box<dyn FnOnce(Result<u16, ControllerError>)>, + ) { + self.send_command(commands::set_events(events), handler); + } + + // Connection management + + pub fn save_conf(&self, handler: Box<dyn FnOnce(Result<u16, ControllerError>)>) { + self.send_command(commands::save_conf(), handler); + } + + // Circuit display + + // Onion authentication + + // Miscellaneous + + pub fn signal_newnym(&self, handler: Box<dyn FnOnce(Result<u16, ControllerError>)>) { + self.send_command(commands::signal_newnym(), handler); + } +} + +// TODO: Remove once we merge all parts of tor-browser#44930. +impl<CP: ControlPortInterface> TorController<CP> { + #[inline] + pub fn send_raw_command( + &self, + command: Bytes, + handler: Box<dyn FnOnce(Result<Reply, ControllerError>)>, + ) { + self.0.send_command( + command, + Box::new(|res| handler(res.map_err(|e| ControllerError::from(e)))), + ); + } + + #[inline] + pub fn set_async_handler(&self, cb: Option<Box<dyn Fn(Reply)>>) { + self.0.set_async_handler(cb); + } + + #[inline] + pub fn set_close_handler(&self, cb: Box<dyn FnOnce()>) { + self.0.set_close_handler(cb); + } +} ===================================== toolkit/components/tor-integration/tor_provider/src/ctor/controller/error.rs ===================================== @@ -0,0 +1,57 @@ +// Licensed under the Apache License, Version 2.0, +// <http://apache.org/licenses/LICENSE-2.0> or the MIT license +// <http://opensource.org/licenses/MIT>, at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +use thiserror::Error; + +use crate::ctor::{ + control_port::ControlPortError, + reply_parser::{Reply, ReplyError}, +}; + +#[derive(Error, Debug, Clone, PartialEq, Eq)] +pub enum ControllerError { + #[error("connection error: {0:#x}")] + ConnectionError(u32), + #[error("protocol violation: {0}")] + ProtocolError(#[from] ReplyError), + #[error("unsuccessful command ({code}): {message}")] + TorError { + code: u16, + // Notice: tor does not dive any guarantee about charsets. + // However, we expect errors to be ASCII strings, and they are used only + // for logs, they will never be directly user-facing. + message: String, + }, + #[error("the reply contains lines with mixed codes")] + MixedCodes, + #[error("the reply does not match the expected format: {0}")] + WrongFormat(String), + #[error("the requested key {0} was not found")] + KeyNotFound(String), + #[error("malformed reply: {0}")] + MalformedReply(String), +} + +impl ControllerError { + pub(super) fn from_reply(reply: &Reply) -> Option<Self> { + if reply.end_line().is_error() { + Some(Self::TorError { + code: reply.end_line().code, + message: String::from_utf8_lossy(&reply.end_line().line).into_owned(), + }) + } else { + None + } + } +} + +impl From<ControlPortError> for ControllerError { + fn from(value: ControlPortError) -> Self { + match value { + ControlPortError::ConnectionError(rv) => ControllerError::ConnectionError(rv), + ControlPortError::ProtocolError(err) => ControllerError::ProtocolError(err), + } + } +} ===================================== toolkit/components/tor-integration/tor_provider/src/ctor/controller/mod.rs ===================================== @@ -3,47 +3,10 @@ // <http://opensource.org/licenses/MIT>, at your option. This file may not be // copied, modified, or distributed except according to those terms. -mod escape; -mod unescape; +mod commands; +mod controller; +mod error; +mod parsers; -use escape::*; -use unescape::*; - -#[cfg(test)] -mod tests { - use super::*; - - fn round_trip(buf: &[u8]) { - let mut escaped = String::new(); - tor_escape_into(buf, &mut escaped); - assert_eq!(&*tor_unescape(escaped.as_bytes()).unwrap(), buf); - } - - #[test] - fn round_trip_simple() { - round_trip(b"test"); - } - - #[test] - fn round_trip_empty() { - round_trip(b""); - } - - #[test] - fn round_trip_special_chars() { - round_trip(b"'\"\\\r\n\t"); - } - - #[test] - fn round_trip_non_utf8() { - round_trip(b"\xF5\xFF\xFE"); - } - - #[test] - fn round_trip_all_bytes() { - // Every possible byte value, escaped then unescaped, - // must come back unchanged. - let all_bytes: Vec<u8> = (0..=255).collect(); - round_trip(&all_bytes); - } -} +pub use controller::*; +pub use error::*; ===================================== toolkit/components/tor-integration/tor_provider/src/ctor/controller/parsers/ack.rs ===================================== @@ -0,0 +1,99 @@ +// Licensed under the Apache License, Version 2.0, +// <http://apache.org/licenses/LICENSE-2.0> or the MIT license +// <http://opensource.org/licenses/MIT>, at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +use crate::ctor::{reply_parser::Reply, ControllerError}; + +/// Check whether a reply contains a successful reply code and return it. +/// To be used with commands we only want to check whether they were successful. +/// It can be used also for notifications (and it will return their code), but +/// it does not make much sense to do it. +pub fn parse_ack(reply: Reply) -> Result<u16, ControllerError> { + // Ignore any detail line on purpose to allow compatbility with future + // protocol/command versions. + match ControllerError::from_reply(&reply) { + Some(e) => Err(e), + None => Ok(reply.end_line().code), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ctor::reply_parser::make_reply; + + #[test] + fn successful() { + assert_eq!(parse_ack(make_reply(b"250 OK\r\n")).unwrap(), 250); + assert_eq!( + parse_ack(make_reply(b"251 Also successful\r\n")).unwrap(), + 251, + ); + assert_eq!( + parse_ack(make_reply(b"399 Yet another one\r\n")).unwrap(), + 399, + ); + assert_eq!(parse_ack(make_reply(b"650 Notification\r\n")).unwrap(), 650); + } + + #[test] + fn error_code() { + assert_eq!( + parse_ack(make_reply(b"400 An error\r\n")).unwrap_err(), + ControllerError::TorError { + code: 400, + message: String::from("An error"), + }, + ); + assert_eq!( + parse_ack(make_reply(b"599 Last error\r\n")).unwrap_err(), + ControllerError::TorError { + code: 599, + message: String::from("Last error"), + }, + ); + } + + #[test] + fn invalid_unicode() { + assert_eq!(parse_ack(make_reply(b"250 \xFD\r\n")).unwrap(), 250); + assert_eq!( + parse_ack(make_reply(b"252-Line\r\n252 \xFD\r\n")).unwrap(), + 252 + ); + assert_eq!( + parse_ack(make_reply(b"500 Invalid \xFD codepoint\r\n")).unwrap_err(), + ControllerError::TorError { + code: 500, + message: String::from("Invalid \u{FFFD} codepoint"), + }, + ); + } + + #[test] + fn details_ignored() { + assert_eq!( + parse_ack(make_reply(b"250-Details\r\n250 OK\r\n")).unwrap(), + 250 + ); + assert_eq!( + parse_ack(make_reply( + b"255-Changing status code\r\n260 Does not impact\r\n" + )) + .unwrap(), + 260 + ); + assert_eq!( + parse_ack(make_reply(b"550-Details with error code\r\n250 OK\r\n")).unwrap(), + 250 + ); + assert_eq!( + parse_ack(make_reply(b"250-Succesful details\r\n450 Error status\r\n")).unwrap_err(), + ControllerError::TorError { + code: 450, + message: String::from("Error status"), + }, + ); + } +} ===================================== toolkit/components/tor-integration/tor_provider/src/ctor/controller/escape.rs → toolkit/components/tor-integration/tor_provider/src/ctor/controller/parsers/escape.rs ===================================== ===================================== toolkit/components/tor-integration/tor_provider/src/ctor/controller/parsers/mod.rs ===================================== @@ -0,0 +1,15 @@ +// Licensed under the Apache License, Version 2.0, +// <http://apache.org/licenses/LICENSE-2.0> or the MIT license +// <http://opensource.org/licenses/MIT>, at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +mod ack; +mod escape; +mod unescape; + +#[cfg(test)] +mod tests; + +pub use ack::parse_ack; +pub use escape::tor_escape_into; +pub use unescape::tor_unescape; ===================================== toolkit/components/tor-integration/tor_provider/src/ctor/controller/parsers/tests.rs ===================================== @@ -0,0 +1,40 @@ +// Licensed under the Apache License, Version 2.0, +// <http://apache.org/licenses/LICENSE-2.0> or the MIT license +// <http://opensource.org/licenses/MIT>, at your option. This file may not be +// copied, modified, or distributed except according to those terms. + +use super::{tor_escape_into, tor_unescape}; + +fn round_trip(buf: &[u8]) { + let mut escaped = String::new(); + tor_escape_into(buf, &mut escaped); + assert_eq!(&*tor_unescape(escaped.as_bytes()).unwrap(), buf); +} + +#[test] +fn round_trip_simple() { + round_trip(b"test"); +} + +#[test] +fn round_trip_empty() { + round_trip(b""); +} + +#[test] +fn round_trip_special_chars() { + round_trip(b"'\"\\\r\n\t"); +} + +#[test] +fn round_trip_non_utf8() { + round_trip(b"\xF5\xFF\xFE"); +} + +#[test] +fn round_trip_all_bytes() { + // Every possible byte value, escaped then unescaped, + // must come back unchanged. + let all_bytes: Vec<u8> = (0..=255).collect(); + round_trip(&all_bytes); +} ===================================== toolkit/components/tor-integration/tor_provider/src/ctor/controller/unescape.rs → toolkit/components/tor-integration/tor_provider/src/ctor/controller/parsers/unescape.rs ===================================== ===================================== toolkit/components/tor-integration/tor_provider/src/ctor/mod.rs ===================================== @@ -7,5 +7,6 @@ mod control_port; mod controller; mod reply_parser; -pub use control_port::{ControlPort, ControlPortError, ControlSocket, ControlSocketError}; +pub use control_port::{ControlPort, ControlSocket, ControlSocketError}; +pub use controller::*; pub use reply_parser::{ReplyDispatcher, ReplyError}; ===================================== toolkit/components/tor-integration/tor_provider/src/ctor/reply_parser/line.rs ===================================== @@ -9,6 +9,7 @@ use std::{ ops::Deref, }; +pub const ERROR_START: u16 = 400; pub const ASYNC_START: u16 = 600; /// An enum to represent MidReplyLine and DataReplyLine form the control port @@ -110,6 +111,11 @@ pub struct EndReplyLine { } impl EndReplyLine { + /// Tells whether the line is an error. + pub fn is_error(&self) -> bool { + self.code >= ERROR_START && self.code < ASYNC_START + } + /// Tells whether the line is part of an async reply. pub fn is_async(&self) -> bool { self.code >= ASYNC_START @@ -146,29 +152,106 @@ mod tests { } #[test] - fn is_async() { + fn check_type() { { let r = EndReplyLine { code: 250, line: Bytes::from_static(b"OK"), }; + assert!(!r.is_error()); + assert!(!r.is_async()); + } + { + let r = EndReplyLine { + code: 399, + line: Bytes::from_static(b"Success"), + }; + assert!(!r.is_error()); assert!(!r.is_async()); } + { + let r = EndReplyLine { + code: 400, + line: Bytes::from_static(b"Temporary error"), + }; + assert!(r.is_error()); + assert!(!r.is_async()); + } + { + let r = EndReplyLine { + code: 500, + line: Bytes::from_static(b"Permanent error"), + }; + assert!(r.is_error()); + assert!(!r.is_async()); + } + { + let r = EndReplyLine { + code: 599, + line: Bytes::from_static(b"Yet another error"), + }; + assert!(r.is_error()); + assert!(!r.is_async()); + } + { + let r = EndReplyLine { + code: 600, + line: Bytes::from_static(b"Notification"), + }; + assert!(!r.is_error()); + assert!(r.is_async()); + } { let r = EndReplyLine { code: 650, line: Bytes::from_static(b"CIRC BUILT"), }; + assert!(!r.is_error()); assert!(r.is_async()); } { let r = DetailReplyLine::MidReplyLine { code: 250, - line: Bytes::from_static(b"key=value"), + line: Bytes::from_static(b"OK"), + }; + assert!(!r.is_async()); + } + { + let r = DetailReplyLine::MidReplyLine { + code: 399, + line: Bytes::from_static(b"Success"), + }; + assert!(!r.is_async()); + } + { + let r = DetailReplyLine::MidReplyLine { + code: 400, + line: Bytes::from_static(b"Temporary error"), + }; + assert!(!r.is_async()); + } + { + let r = DetailReplyLine::MidReplyLine { + code: 500, + line: Bytes::from_static(b"Permanent error"), }; assert!(!r.is_async()); } + { + let r = DetailReplyLine::MidReplyLine { + code: 599, + line: Bytes::from_static(b"Yet another error"), + }; + assert!(!r.is_async()); + } + { + let r = DetailReplyLine::MidReplyLine { + code: 600, + line: Bytes::from_static(b"Notification"), + }; + assert!(r.is_async()); + } { let r = DetailReplyLine::MidReplyLine { code: 650, ===================================== toolkit/components/tor-integration/tor_service/src/control_port.rs ===================================== @@ -3,9 +3,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use nserror::nsresult; -use nserror::{NS_ERROR_NOT_CONNECTED, NS_OK}; +use nserror::{NS_ERROR_NOT_CONNECTED, NS_ERROR_UNEXPECTED, NS_OK}; use nsstring::{nsACString, nsCString}; -use tor_provider::ctor::{ControlPort, ControlSocketError}; +use tor_provider::ctor::{ControlPort, ControlSocketError, ControllerError, TorController}; use xpcom::interfaces::{nsIFile, torITorControlPortReceiver, torITorMessageHandler}; use xpcom::RefPtr; @@ -17,7 +17,7 @@ use super::control_socket::ControlSocketXpcom; #[xpcom(implement(torITorControlPort), atomic)] pub struct ControlPortXpcom { - control_port: ControlPort, + control_port: TorController<ControlPort>, } impl ControlPortXpcom { @@ -30,7 +30,7 @@ impl ControlPortXpcom { } fn new(socket: Box<ControlSocketXpcom>) -> Result<RefPtr<Self>, nsresult> { - let control_port = ControlPort::new(socket).map_err(Self::map_err)?; + let control_port = TorController::new(ControlPort::new(socket).map_err(Self::map_err)?); Ok(Self::allocate(InitControlPortXpcom { control_port })) } @@ -78,7 +78,7 @@ impl ControlPortXpcom { command.extend_from_slice(b"\r\n"); } let handler = RefPtr::new(handler); - self.control_port.send_command( + self.control_port.send_raw_command( command.into(), Box::new(move |reply| { let mut buf = Vec::new(); @@ -110,7 +110,10 @@ impl ControlPortXpcom { xpcom_method!(close => Close()); pub fn close(&self) -> Result<(), nsresult> { - self.control_port.close().map_err(Self::map_err) + self.control_port.close().map_err(|e| match e { + ControllerError::ConnectionError(rv) => nsresult(rv), + _ => NS_ERROR_UNEXPECTED, + }) } fn map_err(e: ControlSocketError) -> nsresult { View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/cba90d6... -- View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/cba90d6... You're receiving this email because of your account on gitlab.torproject.org. Manage all notifications: https://gitlab.torproject.org/-/profile/notifications | Help: https://gitlab.torproject.org/help
participants (1)
-
brizental (@brizental)