repo_description: An open-source remote desktop application designed for self-hosting, as an alternative to TeamViewer.
repo_topics: android anydesk dart flatpak flutter flutter-apps ios linux macos p2p rdp remote-control remote-desktop rust rust-lang teamviewer vnc wayland windows
code: use hbb_common::{
      async_recursion::async_recursion,
      config::{Config, Socks5Server},
      log::{self, info},
      proxy::{Proxy, ProxyScheme},
      tls::{
          get_cached_tls_accept_invalid_cert, get_cached_tls_type, is_plain, upsert_tls_cache,
          TlsType,
      },
  };
  use reqwest::{blocking::Client as SyncClient, Client as AsyncClient};
  
  macro_rules! configure_http_client {
      ($builder:expr, $tls_type:expr, $danger_accept_invalid_cert:expr, $Client: ty) => {{
          // https://github.com/rustdesk/rustdesk/issues/11569
          // https://docs.rs/reqwest/latest/reqwest/struct.ClientBuilder.html#method.no_proxy
          let mut builder = $builder.no_proxy();
  
          match $tls_type {
              TlsType::Plain => {}
              TlsType::NativeTls => {
                  builder = builder.use_native_tls();
                  if $danger_accept_invalid_cert {
                      builder = builder.danger_accept_invalid_certs(true);
                  }
              }
              TlsType::Rustls => {
                  #[cfg(any(target_os = "android", target_os = "ios"))]
                  match hbb_common::verifier::client_config($danger_accept_invalid_cert) {
                      Ok(client_config) => {
                          builder = builder.use_preconfigured_tls(client_config);
                      }
                      Err(e) => {
                          hbb_common::log::error!("Failed to get client config: {}", e);
                      }
                  }
                  #[cfg(not(any(target_os = "android", target_os = "ios")))]
                  {
                      builder = builder.use_rustls_tls();
                      if $danger_accept_invalid_cert {
                          builder = builder.danger_accept_invalid_certs(true);
                      }
                  }
              }
          }
  
          let client = if let Some(conf) = Config::get_socks() {
              let proxy_result = Proxy::from_conf(&conf, None);
  
              match proxy_result {
                  Ok(proxy) => {
                      let proxy_setup = match &proxy.intercept {
                          ProxyScheme::Http { host, .. } => {
                              reqwest::Proxy::all(format!("http://{}", host))
                          }
                          ProxyScheme::Https { host, .. } => {
                              reqwest::Proxy::all(format!("https://{}", host))
                          }
                          ProxyScheme::Socks5 { addr, .. } => {
                              reqwest::Proxy::all(&format!("socks5://{}", addr))
                          }
                      };
  
                      match proxy_setup {
                          Ok(mut p) => {
                              if let Some(auth) = proxy.intercept.maybe_auth() {
                                  if !auth.username().is_empty() && !auth.password().is_empty() {
                                      p = p.basic_auth(auth.username(), auth.password());
                                  }
                              }
                              builder = builder.proxy(p);
                              builder.build().unwrap_or_else(|e| {
                                  info!("Failed to create a proxied client: {}", e);
                                  <$Client>::new()
                              })
                          }
                          Err(e) => {
                              info!("Failed to set up proxy: {}", e);
                              <$Client>::new()
                          }
                      }
                  }
                  Err(e) => {
                      info!("Failed to configure proxy: {}", e);
                      <$Client>::new()
                  }
              }
          } else {
              builder.build().unwrap_or_else(|e| {
                  info!("Failed to create a client: {}", e);
                  <$Client>::new()
              })
          };
  
          client
      }};
  }
  
  pub fn create_http_client(tls_type: TlsType, danger_accept_invalid_cert: bool) -> SyncClient {
      let builder = SyncClient::builder();
      configure_http_client!(builder, tls_type, danger_accept_invalid_cert, SyncClient)
  }
  
  pub fn create_http_client_async(
      tls_type: TlsType,
      danger_accept_invalid_cert: bool,
  ) -> AsyncClient {
      let builder = AsyncClient::builder();
      configure_http_client!(builder, tls_type, danger_accept_invalid_cert, AsyncClient)
  }
  
  pub fn get_url_for_tls<'a>(url: &'a str, proxy_conf: &'a Option<Socks5Server>) -> &'a str {
      if is_plain(url) {
          if let Some(conf) = proxy_conf {
              if conf.proxy.starts_with("https://") {
                  return &conf.proxy;
              }
          }
      }
      url
  }
  
  pub fn create_http_client_with_url(url: &str) -> SyncClient {
      let proxy_conf = Config::get_socks();
      let tls_url = get_url_for_tls(url, &proxy_conf);
      let tls_type = get_cached_tls_type(tls_url);
      let is_tls_type_cached = tls_type.is_some();
      let tls_type = tls_type.unwrap_or(TlsType::Rustls);
      let tls_danger_accept_invalid_cert = get_cached_tls_accept_invalid_cert(tls_url);
      create_http_client_with_url_(
          url,
          tls_url,
          tls_type,
          is_tls_type_cached,
          tls_danger_accept_invalid_cert,
          tls_danger_accept_invalid_cert,
      )
  }
  
  fn create_http_client_with_url_(
      url: &str,
      tls_url: &str,
      tls_type: TlsType,
      is_tls_type_cached: bool,
      danger_accept_invalid_cert: Option<bool>,
      original_danger_accept_invalid_cert: Option<bool>,
  ) -> SyncClient {
      let mut client = create_http_client(tls_type, danger_accept_invalid_cert.unwrap_or(false));
      if is_tls_type_cached && original_danger_accept_invalid_cert.is_some() {
          return client;
      }
      if let Err(e) = client.head(url).send() {
          if e.is_request() {
              match (tls_type, is_tls_type_cached, danger_accept_invalid_cert) {
                  (TlsType::Rustls, _, None) => {
                      log::warn!(
                          "Failed to connect to server {} with rustls-tls: {:?}, trying accept invalid cert",
                          tls_url,
                          e
                      );
                      client = create_http_client_with_url_(
                          url,
                          tls_url,
                          tls_type,
                          is_tls_type_cached,
                          Some(true),
                          original_danger_accept_invalid_cert,
                      );
                  }
                  (TlsType::Rustls, false, Some(_)) => {
                      log::warn!(
                          "Failed to connect to server {} with rustls-tls: {:?}, trying native-tls",
                          tls_url,
                          e
                      );
                      client = create_http_client_with_url_(
                          url,
                          tls_url,
                          TlsType::NativeTls,
                          is_tls_type_cached,
                          original_danger_accept_invalid_cert,
                          original_danger_accept_invalid_cert,
                      );
                  }
                  (TlsType::NativeTls, _, None) => {
                      log::warn!(
                          "Failed to connect to server {} with native-tls: {:?}, trying accept invalid cert",
                          tls_url,
                          e
                      );
                      client = create_http_client_with_url_(
                          url,
                          tls_url,
                          tls_type,
                          is_tls_type_cached,
                          Some(true),
                          original_danger_accept_invalid_cert,
                      );
                  }
                  _ => {
                      log::error!(
                          "Failed to connect to server {} with {:?}, err: {:?}.",
                          tls_url,
                          tls_type,
                          e
                      );
                  }
              }
          } else {
              log::warn!(
                  "Failed to connect to server {} with {:?}, err: {}.",
                  tls_url,
                  tls_type,
                  e
              );
          }
      } else {
          log::info!(
              "Successfully connected to server {} with {:?}",
              tls_url,
              tls_type
          );
          upsert_tls_cache(
              tls_url,
              tls_type,
              danger_accept_invalid_cert.unwrap_or(false),
          );
      }
      client
  }
  
  pub async fn create_http_client_async_with_url(url: &str) -> AsyncClient {
      let proxy_conf = Config::get_socks();
      let tls_url = get_url_for_tls(url, &proxy_conf);
      let tls_type = get_cached_tls_type(tls_url);
      let is_tls_type_cached = tls_type.is_some();
      let tls_type = tls_type.unwrap_or(TlsType::Rustls);
      let danger_accept_invalid_cert = get_cached_tls_accept_invalid_cert(tls_url);
      create_http_client_async_with_url_(
          url,
          tls_url,
          tls_type,
          is_tls_type_cached,
          danger_accept_invalid_cert,
          danger_accept_invalid_cert,
      )
      .await
  }
  
  #[async_recursion]
  async fn create_http_client_async_with_url_(
      url: &str,
      tls_url: &str,
      tls_type: TlsType,
      is_tls_type_cached: bool,
      danger_accept_invalid_cert: Option<bool>,
      original_danger_accept_invalid_cert: Option<bool>,
  ) -> AsyncClient {
      let mut client =
          create_http_client_async(tls_type, danger_accept_invalid_cert.unwrap_or(false));
      if is_tls_type_cached && original_danger_accept_invalid_cert.is_some() {
          return client;
      }
      if let Err(e) = client.head(url).send().await {
          match (tls_type, is_tls_type_cached, danger_accept_invalid_cert) {
              (TlsType::Rustls, _, None) => {
                  log::warn!(
                      "Failed to connect to server {} with rustls-tls: {:?}, trying accept invalid cert",
                      tls_url,
                      e
                  );
                  client = create_http_client_async_with_url_(
                      url,
                      tls_url,
                      tls_type,
                      is_tls_type_cached,
                      Some(true),
                      original_danger_accept_invalid_cert,
                  )
                  .await;
              }
              (TlsType::Rustls, false, Some(_)) => {
                  log::warn!(
                      "Failed to connect to server {} with rustls-tls: {:?}, trying native-tls",
                      tls_url,
                      e
                  );
                  client = create_http_client_async_with_url_(
                      url,
                      tls_url,
                      TlsType::NativeTls,
                      is_tls_type_cached,
                      original_danger_accept_invalid_cert,
                      original_danger_accept_invalid_cert,
                  )
                  .await;
              }
              (TlsType::NativeTls, _, None) => {
                  log::warn!(
                      "Failed to connect to server {} with native-tls: {:?}, trying accept invalid cert",
                      tls_url,
                      e
                  );
                  client = create_http_client_async_with_url_(
                      url,
                      tls_url,
                      tls_type,
                      is_tls_type_cached,
                      Some(true),
                      original_danger_accept_invalid_cert,
                  )
                  .await;
              }
              _ => {
                  log::error!(
                      "Failed to connect to server {} with {:?}, err: {:?}.",
                      tls_url,
                      tls_type,
                      e
                  );
              }
          }
      } else {
          log::info!(
              "Successfully connected to server {} with {:?}",
              tls_url,
              tls_type
          );
          upsert_tls_cache(
              tls_url,
              tls_type,
              danger_accept_invalid_cert.unwrap_or(false),
          );
      }
      client
  }
  