tor_interface/
censorship_circumvention.rs1use std::net::SocketAddr;
3use std::path::PathBuf;
4use std::str::FromStr;
5use std::sync::OnceLock;
6
7use regex::Regex;
9
10#[derive(Clone, Debug)]
11pub struct PluggableTransportConfig {
13 transports: Vec<String>,
14 path_to_binary: PathBuf,
15 options: Vec<String>,
16}
17
18#[derive(thiserror::Error, Debug)]
19pub enum PluggableTransportConfigError {
21 #[error("pluggable transport name '{0}' is invalid")]
22 TransportNameInvalid(String),
24 #[error("unable to use '{0}' as pluggable transport binary path, {1}")]
25 BinaryPathInvalid(String, String),
27}
28
29static TRANSPORT_PATTERN: OnceLock<Regex> = OnceLock::new();
31fn init_transport_pattern() -> Regex {
32 Regex::new(r"(?m)^[a-zA-Z_][a-zA-Z0-9_]*$").unwrap()
33}
34
35impl PluggableTransportConfig {
37 pub fn new(
39 transports: Vec<String>,
40 path_to_binary: PathBuf,
41 ) -> Result<Self, PluggableTransportConfigError> {
42 let transport_pattern = TRANSPORT_PATTERN.get_or_init(init_transport_pattern);
43 for transport in &transports {
45 if !transport_pattern.is_match(&transport) {
46 return Err(PluggableTransportConfigError::TransportNameInvalid(
47 transport.clone(),
48 ));
49 }
50 }
51
52 if !path_to_binary.is_absolute() {
55 return Err(PluggableTransportConfigError::BinaryPathInvalid(
56 format!("{:?}", path_to_binary.display()),
57 "must be an absolute path".to_string(),
58 ));
59 }
60
61 Ok(Self {
62 transports,
63 path_to_binary,
64 options: Default::default(),
65 })
66 }
67
68 pub fn transports(&self) -> &Vec<String> {
70 &self.transports
71 }
72
73 pub fn path_to_binary(&self) -> &PathBuf {
75 &self.path_to_binary
76 }
77
78 pub fn options(&self) -> &Vec<String> {
80 &self.options
81 }
82
83 pub fn add_option(&mut self, arg: String) {
85 self.options.push(arg);
86 }
87}
88
89#[derive(Clone, Debug)]
91pub struct BridgeLine {
92 transport: String,
93 address: SocketAddr,
94 fingerprint: String,
95 keyvalues: Vec<(String, String)>,
96}
97
98#[derive(thiserror::Error, Debug)]
99pub enum BridgeLineError {
101 #[error("bridge line '{0}' missing transport")]
102 TransportMissing(String),
104
105 #[error("bridge line '{0}' missing address")]
106 AddressMissing(String),
108
109 #[error("bridge line '{0}' missing fingerprint")]
110 FingerprintMissing(String),
112
113 #[error("transport name '{0}' is invalid")]
114 TransportNameInvalid(String),
116
117 #[error("address '{0}' cannot be parsed as IP:PORT")]
118 AddressParseFailed(String),
120
121 #[error("key=value '{0}' is invalid")]
122 KeyValueInvalid(String),
124
125 #[error("bridge address port must not be 0")]
126 AddressPortInvalid,
128
129 #[error("fingerprint '{0}' is invalid")]
130 FingerprintInvalid(String),
132}
133
134impl BridgeLine {
137 pub fn new(
141 transport: String,
142 address: SocketAddr,
143 fingerprint: String,
144 keyvalues: Vec<(String, String)>,
145 ) -> Result<BridgeLine, BridgeLineError> {
146 let transport_pattern = TRANSPORT_PATTERN.get_or_init(init_transport_pattern);
147
148 if !transport_pattern.is_match(&transport) {
150 return Err(BridgeLineError::TransportNameInvalid(transport));
151 }
152
153 if address.port() == 0 {
155 return Err(BridgeLineError::AddressPortInvalid);
156 }
157
158 static BRIDGE_FINGERPRINT_PATTERN: OnceLock<Regex> = OnceLock::new();
159 let bridge_fingerprint_pattern = BRIDGE_FINGERPRINT_PATTERN
160 .get_or_init(|| Regex::new(r"(?m)^[0-9a-fA-F]{40}$").unwrap());
161
162 if !bridge_fingerprint_pattern.is_match(&fingerprint) {
164 return Err(BridgeLineError::FingerprintInvalid(fingerprint));
165 }
166
167 for (key, value) in &keyvalues {
169 if key.contains(' ') || key.contains('=') || key.len() == 0 {
170 return Err(BridgeLineError::KeyValueInvalid(format!("{key}={value}")));
171 }
172 }
173
174 Ok(Self {
175 transport,
176 address,
177 fingerprint,
178 keyvalues,
179 })
180 }
181
182 pub fn transport(&self) -> &String {
184 &self.transport
185 }
186
187 pub fn address(&self) -> &SocketAddr {
189 &self.address
190 }
191
192 pub fn fingerprint(&self) -> &String {
194 &self.fingerprint
195 }
196
197 pub fn keyvalues(&self) -> &Vec<(String, String)> {
199 &self.keyvalues
200 }
201
202 #[cfg(feature = "legacy-tor-provider")]
203 pub fn as_legacy_tor_setconf_value(&self) -> String {
205 let transport = &self.transport;
206 let address = self.address.to_string();
207 let fingerprint = self.fingerprint.to_string();
208 let keyvalues: Vec<String> = self
209 .keyvalues
210 .iter()
211 .map(|(key, value)| format!("{key}={value}"))
212 .collect();
213 let keyvalues = keyvalues.join(" ");
214
215 format!("{transport} {address} {fingerprint} {keyvalues}")
216 }
217}
218
219impl FromStr for BridgeLine {
220 type Err = BridgeLineError;
221 fn from_str(s: &str) -> Result<Self, Self::Err> {
222 let mut tokens = s.split(' ');
223 let transport = if let Some(transport) = tokens.next() {
225 transport
226 } else {
227 return Err(BridgeLineError::TransportMissing(s.to_string()));
228 };
229 let address = if let Some(address) = tokens.next() {
231 if let Ok(address) = SocketAddr::from_str(address) {
232 address
233 } else {
234 return Err(BridgeLineError::AddressParseFailed(address.to_string()));
235 }
236 } else {
237 return Err(BridgeLineError::AddressMissing(s.to_string()));
238 };
239 let fingerprint = if let Some(fingerprint) = tokens.next() {
241 fingerprint
242 } else {
243 return Err(BridgeLineError::FingerprintMissing(s.to_string()));
244 };
245
246 static BRIDGE_OPTION_PATTERN: OnceLock<Regex> = OnceLock::new();
248 let bridge_option_pattern = BRIDGE_OPTION_PATTERN
249 .get_or_init(|| Regex::new(r"(?m)^(?<key>[^=]+)=(?<value>.*)$").unwrap());
250
251 let mut keyvalues: Vec<(String, String)> = Default::default();
252 while let Some(keyvalue) = tokens.next() {
253 if let Some(caps) = bridge_option_pattern.captures(&keyvalue) {
254 let key = caps
255 .name("key")
256 .expect("missing key group")
257 .as_str()
258 .to_string();
259 let value = caps
260 .name("value")
261 .expect("missing value group")
262 .as_str()
263 .to_string();
264 keyvalues.push((key, value));
265 } else {
266 return Err(BridgeLineError::KeyValueInvalid(keyvalue.to_string()));
267 }
268 }
269
270 BridgeLine::new(
271 transport.to_string(),
272 address,
273 fingerprint.to_string(),
274 keyvalues,
275 )
276 }
277}