1#![allow(
2 clippy::cast_possible_truncation,
3 clippy::cast_possible_wrap,
4 clippy::cast_sign_loss,
5 reason = "M175: SOCKS4/5/HTTP CONNECT proxy — wire-format byte/u16 fields fixed by spec"
6)]
7
8use std::io;
15use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
16
17use tokio::io::{AsyncReadExt, AsyncWriteExt};
18use tokio::net::{TcpStream, UdpSocket};
19
20pub use irontide_core::{ProxyConfig, ProxyType};
23
24pub(crate) async fn connect_through_proxy(
31 proxy: &ProxyConfig,
32 target: SocketAddr,
33) -> io::Result<TcpStream> {
34 let proxy_addr = format!("{}:{}", proxy.hostname, proxy.port);
35 let mut stream = TcpStream::connect(&proxy_addr).await?;
36
37 match proxy.proxy_type {
38 ProxyType::Socks4 => socks4_connect(&mut stream, target).await?,
39 ProxyType::Socks5 => socks5_connect(&mut stream, target, None).await?,
40 ProxyType::Socks5Password => {
41 let auth = match (&proxy.username, &proxy.password) {
42 (Some(u), Some(p)) => Some((u.as_str(), p.as_str())),
43 _ => None,
44 };
45 socks5_connect(&mut stream, target, auth).await?;
46 }
47 ProxyType::Http => http_connect(&mut stream, target, None).await?,
48 ProxyType::HttpPassword => {
49 let auth = match (&proxy.username, &proxy.password) {
50 (Some(u), Some(p)) => Some((u.as_str(), p.as_str())),
51 _ => None,
52 };
53 http_connect(&mut stream, target, auth).await?;
54 }
55 ProxyType::None => {
56 return Err(io::Error::new(
57 io::ErrorKind::InvalidInput,
58 "no proxy configured",
59 ));
60 }
61 }
62
63 Ok(stream)
64}
65
66async fn socks4_connect(stream: &mut TcpStream, target: SocketAddr) -> io::Result<()> {
73 let ip = match target.ip() {
74 IpAddr::V4(v4) => v4,
75 IpAddr::V6(_) => {
76 return Err(io::Error::new(
77 io::ErrorKind::InvalidInput,
78 "SOCKS4 does not support IPv6",
79 ));
80 }
81 };
82
83 let mut req = Vec::with_capacity(9);
85 req.push(4); req.push(1); req.extend_from_slice(&target.port().to_be_bytes());
88 req.extend_from_slice(&ip.octets());
89 req.push(0); stream.write_all(&req).await?;
91
92 let mut resp = [0u8; 8];
94 stream.read_exact(&mut resp).await?;
95
96 if resp[1] != 90 {
97 return Err(io::Error::new(
98 io::ErrorKind::ConnectionRefused,
99 format!("SOCKS4 request rejected (CD={})", resp[1]),
100 ));
101 }
102
103 Ok(())
104}
105
106async fn socks5_connect(
110 stream: &mut TcpStream,
111 target: SocketAddr,
112 auth: Option<(&str, &str)>,
113) -> io::Result<()> {
114 socks5_negotiate_method(stream, auth.is_some()).await?;
116
117 if let Some((user, pass)) = auth {
119 socks5_auth(stream, user, pass).await?;
120 }
121
122 socks5_send_connect(stream, target).await
124}
125
126async fn socks5_negotiate_method(stream: &mut TcpStream, want_auth: bool) -> io::Result<()> {
128 let methods: &[u8] = if want_auth {
129 &[5, 2, 0, 2] } else {
131 &[5, 1, 0] };
133 stream.write_all(methods).await?;
134
135 let mut resp = [0u8; 2];
136 stream.read_exact(&mut resp).await?;
137
138 if resp[0] != 5 {
139 return Err(io::Error::new(
140 io::ErrorKind::InvalidData,
141 format!("SOCKS5: unexpected version {}", resp[0]),
142 ));
143 }
144
145 match resp[1] {
146 0 => Ok(()), 2 if want_auth => Ok(()), 0xFF => Err(io::Error::new(
149 io::ErrorKind::PermissionDenied,
150 "SOCKS5: no acceptable methods",
151 )),
152 m => Err(io::Error::new(
153 io::ErrorKind::InvalidData,
154 format!("SOCKS5: unexpected method {m}"),
155 )),
156 }
157}
158
159async fn socks5_auth(stream: &mut TcpStream, user: &str, pass: &str) -> io::Result<()> {
164 if user.len() > 255 || pass.len() > 255 {
165 return Err(io::Error::new(
166 io::ErrorKind::InvalidInput,
167 "SOCKS5: username or password too long (max 255 bytes)",
168 ));
169 }
170
171 let mut req = Vec::with_capacity(3 + user.len() + pass.len());
172 req.push(1); req.push(user.len() as u8);
174 req.extend_from_slice(user.as_bytes());
175 req.push(pass.len() as u8);
176 req.extend_from_slice(pass.as_bytes());
177 stream.write_all(&req).await?;
178
179 let mut resp = [0u8; 2];
180 stream.read_exact(&mut resp).await?;
181
182 if resp[1] != 0 {
183 return Err(io::Error::new(
184 io::ErrorKind::PermissionDenied,
185 "SOCKS5: authentication failed",
186 ));
187 }
188
189 Ok(())
190}
191
192async fn socks5_send_connect(stream: &mut TcpStream, target: SocketAddr) -> io::Result<()> {
197 let mut req = Vec::with_capacity(22);
198 req.push(5); req.push(1); req.push(0); encode_socks5_addr(&mut req, target);
202 stream.write_all(&req).await?;
203
204 read_socks5_response(stream).await
205}
206
207fn encode_socks5_addr(buf: &mut Vec<u8>, addr: SocketAddr) {
209 match addr {
210 SocketAddr::V4(v4) => {
211 buf.push(1); buf.extend_from_slice(&v4.ip().octets());
213 buf.extend_from_slice(&v4.port().to_be_bytes());
214 }
215 SocketAddr::V6(v6) => {
216 buf.push(4); buf.extend_from_slice(&v6.ip().octets());
218 buf.extend_from_slice(&v6.port().to_be_bytes());
219 }
220 }
221}
222
223async fn read_socks5_response(stream: &mut TcpStream) -> io::Result<()> {
225 let mut header = [0u8; 4];
226 stream.read_exact(&mut header).await?;
227
228 if header[0] != 5 {
229 return Err(io::Error::new(
230 io::ErrorKind::InvalidData,
231 format!("SOCKS5: unexpected version {}", header[0]),
232 ));
233 }
234
235 if header[1] != 0 {
236 let msg = socks5_error_message(header[1]);
237 return Err(io::Error::new(io::ErrorKind::ConnectionRefused, msg));
238 }
239
240 match header[3] {
242 1 => {
243 let mut skip = [0u8; 6];
245 stream.read_exact(&mut skip).await?;
246 }
247 4 => {
248 let mut skip = [0u8; 18];
250 stream.read_exact(&mut skip).await?;
251 }
252 3 => {
253 let mut len = [0u8; 1];
255 stream.read_exact(&mut len).await?;
256 let mut skip = vec![0u8; len[0] as usize + 2];
257 stream.read_exact(&mut skip).await?;
258 }
259 atyp => {
260 return Err(io::Error::new(
261 io::ErrorKind::InvalidData,
262 format!("SOCKS5: unknown ATYP {atyp}"),
263 ));
264 }
265 }
266
267 Ok(())
268}
269
270async fn read_socks5_response_addr(stream: &mut TcpStream) -> io::Result<SocketAddr> {
272 let mut header = [0u8; 4];
273 stream.read_exact(&mut header).await?;
274
275 if header[0] != 5 {
276 return Err(io::Error::new(
277 io::ErrorKind::InvalidData,
278 format!("SOCKS5: unexpected version {}", header[0]),
279 ));
280 }
281
282 if header[1] != 0 {
283 let msg = socks5_error_message(header[1]);
284 return Err(io::Error::new(io::ErrorKind::ConnectionRefused, msg));
285 }
286
287 match header[3] {
288 1 => {
289 let mut buf = [0u8; 6];
290 stream.read_exact(&mut buf).await?;
291 let ip = Ipv4Addr::new(buf[0], buf[1], buf[2], buf[3]);
292 let port = u16::from_be_bytes([buf[4], buf[5]]);
293 Ok(SocketAddr::V4(SocketAddrV4::new(ip, port)))
294 }
295 4 => {
296 let mut buf = [0u8; 18];
297 stream.read_exact(&mut buf).await?;
298 let ip = Ipv6Addr::from(<[u8; 16]>::try_from(&buf[..16]).unwrap());
299 let port = u16::from_be_bytes([buf[16], buf[17]]);
300 Ok(SocketAddr::V6(SocketAddrV6::new(ip, port, 0, 0)))
301 }
302 atyp => Err(io::Error::new(
303 io::ErrorKind::InvalidData,
304 format!("SOCKS5: unsupported BND.ATYP {atyp} in UDP ASSOCIATE"),
305 )),
306 }
307}
308
309fn socks5_error_message(code: u8) -> String {
310 match code {
311 1 => "SOCKS5: general failure".into(),
312 2 => "SOCKS5: connection not allowed by ruleset".into(),
313 3 => "SOCKS5: network unreachable".into(),
314 4 => "SOCKS5: host unreachable".into(),
315 5 => "SOCKS5: connection refused".into(),
316 6 => "SOCKS5: TTL expired".into(),
317 7 => "SOCKS5: command not supported".into(),
318 8 => "SOCKS5: address type not supported".into(),
319 _ => format!("SOCKS5: unknown error ({code})"),
320 }
321}
322
323async fn http_connect(
327 stream: &mut TcpStream,
328 target: SocketAddr,
329 auth: Option<(&str, &str)>,
330) -> io::Result<()> {
331 let host_port = format!("{}:{}", target.ip(), target.port());
332
333 let mut request = format!("CONNECT {host_port} HTTP/1.1\r\nHost: {host_port}\r\n");
334
335 if let Some((user, pass)) = auth {
336 use std::fmt::Write;
337 let credentials = format!("{user}:{pass}");
338 let encoded = base64_encode(credentials.as_bytes());
339 let _ = write!(request, "Proxy-Authorization: Basic {encoded}\r\n");
340 }
341
342 request.push_str("\r\n");
343 stream.write_all(request.as_bytes()).await?;
344
345 let mut response_buf = Vec::with_capacity(256);
347 loop {
348 let mut byte = [0u8; 1];
349 stream.read_exact(&mut byte).await?;
350 response_buf.push(byte[0]);
351
352 if response_buf.len() >= 4 {
353 let len = response_buf.len();
354 if response_buf[len - 4..] == [b'\r', b'\n', b'\r', b'\n'] {
355 break;
356 }
357 }
358
359 if response_buf.len() > 8192 {
360 return Err(io::Error::new(
361 io::ErrorKind::InvalidData,
362 "HTTP CONNECT: response too large",
363 ));
364 }
365 }
366
367 let response_str = String::from_utf8_lossy(&response_buf);
368 let status_line = response_str.lines().next().unwrap_or("");
369
370 let parts: Vec<&str> = status_line.splitn(3, ' ').collect();
372 if parts.len() < 2 {
373 return Err(io::Error::new(
374 io::ErrorKind::InvalidData,
375 format!("HTTP CONNECT: malformed response: {status_line}"),
376 ));
377 }
378
379 let status_code: u16 = parts[1].parse().map_err(|_| {
380 io::Error::new(
381 io::ErrorKind::InvalidData,
382 format!("HTTP CONNECT: invalid status code: {}", parts[1]),
383 )
384 })?;
385
386 if status_code != 200 {
387 return Err(io::Error::new(
388 io::ErrorKind::ConnectionRefused,
389 format!("HTTP CONNECT: proxy returned status {status_code}"),
390 ));
391 }
392
393 Ok(())
394}
395
396fn base64_encode(data: &[u8]) -> String {
398 const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
399 let mut result = String::with_capacity(data.len().div_ceil(3) * 4);
400
401 for chunk in data.chunks(3) {
402 let b0 = u32::from(chunk[0]);
403 let b1 = if chunk.len() > 1 {
404 u32::from(chunk[1])
405 } else {
406 0
407 };
408 let b2 = if chunk.len() > 2 {
409 u32::from(chunk[2])
410 } else {
411 0
412 };
413 let triple = (b0 << 16) | (b1 << 8) | b2;
414
415 result.push(CHARS[((triple >> 18) & 0x3F) as usize] as char);
416 result.push(CHARS[((triple >> 12) & 0x3F) as usize] as char);
417
418 if chunk.len() > 1 {
419 result.push(CHARS[((triple >> 6) & 0x3F) as usize] as char);
420 } else {
421 result.push('=');
422 }
423
424 if chunk.len() > 2 {
425 result.push(CHARS[(triple & 0x3F) as usize] as char);
426 } else {
427 result.push('=');
428 }
429 }
430
431 result
432}
433
434pub(crate) async fn socks5_udp_associate(
442 proxy: &ProxyConfig,
443 local_addr: SocketAddr,
444) -> io::Result<(SocketAddr, TcpStream)> {
445 let proxy_addr = format!("{}:{}", proxy.hostname, proxy.port);
446 let mut stream = TcpStream::connect(&proxy_addr).await?;
447
448 let want_auth = proxy.proxy_type == ProxyType::Socks5Password;
449 socks5_negotiate_method(&mut stream, want_auth).await?;
450
451 if want_auth {
452 let user = proxy.username.as_deref().unwrap_or("");
453 let pass = proxy.password.as_deref().unwrap_or("");
454 socks5_auth(&mut stream, user, pass).await?;
455 }
456
457 let mut req = Vec::with_capacity(22);
459 req.push(5); req.push(3); req.push(0); if proxy.socks5_udp_send_local_ep {
464 encode_socks5_addr(&mut req, local_addr);
465 } else {
466 req.push(1); req.extend_from_slice(&[0, 0, 0, 0]); req.extend_from_slice(&[0, 0]); }
471
472 stream.write_all(&req).await?;
473
474 let relay_addr = read_socks5_response_addr(&mut stream).await?;
475
476 Ok((relay_addr, stream))
477}
478
479pub(crate) fn encode_socks5_udp_header(target: SocketAddr) -> Vec<u8> {
485 let mut hdr = Vec::with_capacity(10);
486 hdr.extend_from_slice(&[0, 0]); hdr.push(0); encode_socks5_addr(&mut hdr, target);
489 hdr
490}
491
492pub(crate) fn decode_socks5_udp_header(buf: &[u8]) -> io::Result<(SocketAddr, usize)> {
494 if buf.len() < 7 {
495 return Err(io::Error::new(
496 io::ErrorKind::InvalidData,
497 "SOCKS5 UDP header too short",
498 ));
499 }
500
501 let atyp = buf[3];
503
504 match atyp {
505 1 => {
506 if buf.len() < 10 {
508 return Err(io::Error::new(
509 io::ErrorKind::InvalidData,
510 "SOCKS5 UDP: IPv4 header truncated",
511 ));
512 }
513 let ip = Ipv4Addr::new(buf[4], buf[5], buf[6], buf[7]);
514 let port = u16::from_be_bytes([buf[8], buf[9]]);
515 Ok((SocketAddr::V4(SocketAddrV4::new(ip, port)), 10))
516 }
517 4 => {
518 if buf.len() < 22 {
520 return Err(io::Error::new(
521 io::ErrorKind::InvalidData,
522 "SOCKS5 UDP: IPv6 header truncated",
523 ));
524 }
525 let ip = Ipv6Addr::from(<[u8; 16]>::try_from(&buf[4..20]).unwrap());
526 let port = u16::from_be_bytes([buf[20], buf[21]]);
527 Ok((SocketAddr::V6(SocketAddrV6::new(ip, port, 0, 0)), 22))
528 }
529 _ => Err(io::Error::new(
530 io::ErrorKind::InvalidData,
531 format!("SOCKS5 UDP: unsupported ATYP {atyp}"),
532 )),
533 }
534}
535
536pub(crate) struct ProxiedUdpSocket {
543 socket: UdpSocket,
544 relay_addr: SocketAddr,
545 _control: TcpStream,
546}
547
548impl ProxiedUdpSocket {
549 pub fn new(socket: UdpSocket, relay_addr: SocketAddr, control: TcpStream) -> Self {
554 Self {
555 socket,
556 relay_addr,
557 _control: control,
558 }
559 }
560
561 pub async fn send_to(&self, data: &[u8], target: SocketAddr) -> io::Result<usize> {
563 let header = encode_socks5_udp_header(target);
564 let mut packet = Vec::with_capacity(header.len() + data.len());
565 packet.extend_from_slice(&header);
566 packet.extend_from_slice(data);
567 self.socket.send_to(&packet, self.relay_addr).await
568 }
569
570 pub async fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> {
575 let mut raw = vec![0u8; buf.len() + 22]; let (n, _relay) = self.socket.recv_from(&mut raw).await?;
577
578 let (source, offset) = decode_socks5_udp_header(&raw[..n])?;
579 let data_len = n - offset;
580
581 if data_len > buf.len() {
582 return Err(io::Error::new(
583 io::ErrorKind::InvalidData,
584 "SOCKS5 UDP: data exceeds buffer",
585 ));
586 }
587
588 buf[..data_len].copy_from_slice(&raw[offset..n]);
589 Ok((data_len, source))
590 }
591}
592
593#[cfg(test)]
596mod tests {
597 use super::*;
598
599 #[test]
602 fn socks5_encode_method_negotiation() {
603 let no_auth = [5u8, 1, 0];
605 assert_eq!(no_auth[0], 5); assert_eq!(no_auth[1], 1); assert_eq!(no_auth[2], 0); let with_auth = [5u8, 2, 0, 2];
611 assert_eq!(with_auth[0], 5);
612 assert_eq!(with_auth[1], 2);
613 assert_eq!(with_auth[2], 0); assert_eq!(with_auth[3], 2); }
616
617 #[test]
618 fn socks5_encode_connect_request_ipv4() {
619 let target: SocketAddr = "192.168.1.1:8080".parse().unwrap();
620 let mut buf = Vec::new();
621 buf.push(5); buf.push(1); buf.push(0); encode_socks5_addr(&mut buf, target);
625
626 assert_eq!(buf[0], 5); assert_eq!(buf[1], 1); assert_eq!(buf[2], 0); assert_eq!(buf[3], 1); assert_eq!(&buf[4..8], &[192, 168, 1, 1]);
631 assert_eq!(&buf[8..10], &8080u16.to_be_bytes());
632 }
633
634 #[test]
635 fn socks5_encode_connect_request_ipv6() {
636 let target: SocketAddr = "[::1]:9999".parse().unwrap();
637 let mut buf = Vec::new();
638 buf.push(5);
639 buf.push(1);
640 buf.push(0);
641 encode_socks5_addr(&mut buf, target);
642
643 assert_eq!(buf[3], 4); assert_eq!(buf.len(), 3 + 1 + 16 + 2); assert_eq!(&buf[20..22], &9999u16.to_be_bytes());
647 }
648
649 #[test]
650 fn socks5_error_messages() {
651 assert!(socks5_error_message(1).contains("general failure"));
652 assert!(socks5_error_message(5).contains("connection refused"));
653 assert!(socks5_error_message(99).contains("unknown"));
654 }
655
656 #[test]
659 fn socks5_auth_encode() {
660 let user = "alice";
662 let pass = "secret";
663
664 let mut req = Vec::new();
665 req.push(1); req.push(user.len() as u8);
667 req.extend_from_slice(user.as_bytes());
668 req.push(pass.len() as u8);
669 req.extend_from_slice(pass.as_bytes());
670
671 assert_eq!(req[0], 1); assert_eq!(req[1], 5); assert_eq!(&req[2..7], b"alice");
674 assert_eq!(req[7], 6); assert_eq!(&req[8..14], b"secret");
676 }
677
678 #[test]
681 fn socks4_encode_connect_request() {
682 let target: SocketAddr = "1.2.3.4:80".parse().unwrap();
683 let mut req = Vec::new();
684 req.push(4); req.push(1); req.extend_from_slice(&target.port().to_be_bytes());
687 req.extend_from_slice(&[1, 2, 3, 4]);
688 req.push(0); assert_eq!(req[0], 4);
691 assert_eq!(req[1], 1);
692 assert_eq!(&req[2..4], &80u16.to_be_bytes());
693 assert_eq!(&req[4..8], &[1, 2, 3, 4]);
694 assert_eq!(req[8], 0);
695 }
696
697 #[test]
698 fn socks4_response_granted() {
699 let resp = [0u8, 90, 0, 0, 0, 0, 0, 0];
700 assert_eq!(resp[1], 90); }
702
703 #[test]
704 fn socks4_response_rejected() {
705 let resp = [0u8, 91, 0, 0, 0, 0, 0, 0];
706 assert_ne!(resp[1], 90); }
708
709 #[test]
712 fn http_connect_request_no_auth() {
713 let target: SocketAddr = "93.184.216.34:443".parse().unwrap();
714 let host_port = format!("{}:{}", target.ip(), target.port());
715 let request = format!("CONNECT {host_port} HTTP/1.1\r\nHost: {host_port}\r\n\r\n");
716
717 assert!(request.starts_with("CONNECT 93.184.216.34:443 HTTP/1.1\r\n"));
718 assert!(request.contains("Host: 93.184.216.34:443\r\n"));
719 assert!(request.ends_with("\r\n\r\n"));
720 }
721
722 #[test]
723 fn http_connect_request_with_auth() {
724 let encoded = base64_encode(b"user:pass");
725 assert_eq!(encoded, "dXNlcjpwYXNz");
726
727 let header = format!("Proxy-Authorization: Basic {encoded}\r\n");
728 assert!(header.contains("dXNlcjpwYXNz"));
729 }
730
731 #[test]
732 fn http_connect_parse_200_response() {
733 let response = b"HTTP/1.1 200 Connection Established\r\n\r\n";
734 let response_str = String::from_utf8_lossy(response);
735 let status_line = response_str.lines().next().unwrap();
736 let parts: Vec<&str> = status_line.splitn(3, ' ').collect();
737 let status_code: u16 = parts[1].parse().unwrap();
738 assert_eq!(status_code, 200);
739 }
740
741 #[test]
742 fn http_connect_parse_407_response() {
743 let response = b"HTTP/1.1 407 Proxy Authentication Required\r\n\r\n";
744 let response_str = String::from_utf8_lossy(response);
745 let status_line = response_str.lines().next().unwrap();
746 let parts: Vec<&str> = status_line.splitn(3, ' ').collect();
747 let status_code: u16 = parts[1].parse().unwrap();
748 assert_eq!(status_code, 407);
749 }
750
751 #[test]
754 fn socks5_udp_header_ipv4_roundtrip() {
755 let target: SocketAddr = "10.0.0.1:6881".parse().unwrap();
756 let header = encode_socks5_udp_header(target);
757
758 assert_eq!(header[0], 0); assert_eq!(header[1], 0); assert_eq!(header[2], 0); assert_eq!(header[3], 1); assert_eq!(&header[4..8], &[10, 0, 0, 1]);
763 assert_eq!(&header[8..10], &6881u16.to_be_bytes());
764
765 let (decoded_addr, offset) = decode_socks5_udp_header(&header).unwrap();
766 assert_eq!(decoded_addr, target);
767 assert_eq!(offset, 10);
768 }
769
770 #[test]
771 fn socks5_udp_header_ipv6_roundtrip() {
772 let target: SocketAddr = "[2001:db8::1]:51413".parse().unwrap();
773 let header = encode_socks5_udp_header(target);
774
775 assert_eq!(header[3], 4); assert_eq!(header.len(), 22); let (decoded_addr, offset) = decode_socks5_udp_header(&header).unwrap();
779 assert_eq!(decoded_addr, target);
780 assert_eq!(offset, 22);
781 }
782
783 #[test]
784 fn socks5_udp_header_too_short() {
785 let short = [0u8; 5];
786 assert!(decode_socks5_udp_header(&short).is_err());
787 }
788
789 #[test]
792 fn proxy_config_to_url_none() {
793 let cfg = ProxyConfig::default();
794 assert_eq!(cfg.to_url(), "");
795 }
796
797 #[test]
798 fn proxy_config_to_url_socks5() {
799 let cfg = ProxyConfig {
800 proxy_type: ProxyType::Socks5,
801 hostname: "proxy.example.com".into(),
802 port: 1080,
803 ..Default::default()
804 };
805 assert_eq!(cfg.to_url(), "socks5://proxy.example.com:1080");
806 }
807
808 #[test]
809 fn proxy_config_to_url_socks5_password() {
810 let cfg = ProxyConfig {
811 proxy_type: ProxyType::Socks5Password,
812 hostname: "proxy.example.com".into(),
813 port: 1080,
814 username: Some("user".into()),
815 password: Some("pass".into()),
816 ..Default::default()
817 };
818 assert_eq!(cfg.to_url(), "socks5://user:pass@proxy.example.com:1080");
819 }
820
821 #[test]
822 fn proxy_config_to_url_http() {
823 let cfg = ProxyConfig {
824 proxy_type: ProxyType::Http,
825 hostname: "httpproxy.local".into(),
826 port: 8080,
827 ..Default::default()
828 };
829 assert_eq!(cfg.to_url(), "http://httpproxy.local:8080");
830 }
831
832 #[test]
833 fn proxy_config_to_url_http_password() {
834 let cfg = ProxyConfig {
835 proxy_type: ProxyType::HttpPassword,
836 hostname: "httpproxy.local".into(),
837 port: 3128,
838 username: Some("admin".into()),
839 password: Some("secret".into()),
840 ..Default::default()
841 };
842 assert_eq!(cfg.to_url(), "http://admin:secret@httpproxy.local:3128");
843 }
844
845 #[test]
846 fn proxy_config_to_url_socks4() {
847 let cfg = ProxyConfig {
848 proxy_type: ProxyType::Socks4,
849 hostname: "s4proxy".into(),
850 port: 1080,
851 ..Default::default()
852 };
853 assert_eq!(cfg.to_url(), "socks4://s4proxy:1080");
854 }
855
856 #[test]
857 fn base64_encode_basic() {
858 assert_eq!(base64_encode(b""), "");
859 assert_eq!(base64_encode(b"f"), "Zg==");
860 assert_eq!(base64_encode(b"fo"), "Zm8=");
861 assert_eq!(base64_encode(b"foo"), "Zm9v");
862 assert_eq!(base64_encode(b"foob"), "Zm9vYg==");
863 assert_eq!(base64_encode(b"fooba"), "Zm9vYmE=");
864 assert_eq!(base64_encode(b"foobar"), "Zm9vYmFy");
865 }
866
867 #[test]
868 fn proxy_config_default_flags() {
869 let cfg = ProxyConfig::default();
870 assert_eq!(cfg.proxy_type, ProxyType::None);
871 assert!(cfg.proxy_peer_connections);
872 assert!(cfg.proxy_tracker_connections);
873 assert!(cfg.proxy_hostnames);
874 assert!(!cfg.socks5_udp_send_local_ep);
875 }
876}