diff --git a/src/api/icons.rs b/src/api/icons.rs index da83d0c4..273e406c 100644 --- a/src/api/icons.rs +++ b/src/api/icons.rs @@ -532,7 +532,14 @@ async fn download_icon(domain: &str) -> Result<(Bytes, Option<&str>), Error> { use data_url::DataUrl; - for icon in icon_result.iconlist.iter().take(5) { + let mut iconlist: Vec = icon_result.iconlist.into_iter().take(5).collect(); + // add URL from Fallback Icon service + if !CONFIG.icon_service_fallback().is_empty() { + let fallback_url = CONFIG._icon_service_fallback_url().replace("{}", domain); + iconlist.push(Icon::new(0, fallback_url)); + } + + for icon in iconlist.iter() { if icon.href.starts_with("data:image") { let Ok(datauri) = DataUrl::process(&icon.href) else { continue; diff --git a/src/config.rs b/src/config.rs index 6ff09467..857204ab 100644 --- a/src/config.rs +++ b/src/config.rs @@ -686,6 +686,12 @@ make_config! { /// has been decided on, consider using permanent redirects for cacheability. The legacy codes /// are currently better supported by the Bitwarden clients. icon_redirect_code: u32, true, def, 302; + /// Icon service to use as URL fallback for internal Icon service. Works only when icon_service is set to "internal" + /// Same predefined/custom values as icon_service (except "internal"). Unlike Icon service, the Fallback Icon service + /// does not send any redirect to the client and instead use the internal Icon service to download the icon located at the external service + icon_service_fallback: String, false, def, String::new(); + /// _icon_service_fallback + _icon_service_fallback_url: String, false, generated, |c| generate_icon_service_url(&c.icon_service_fallback); /// Positive icon cache expiry |> Number of seconds to consider that an already cached icon is fresh. After this period, the icon will be refreshed icon_cache_ttl: u64, true, def, 2_592_000; /// Negative icon cache expiry |> Number of seconds before trying to download an icon that failed again. @@ -1188,6 +1194,27 @@ fn validate_config(cfg: &ConfigItems, on_update: bool) -> Result<(), Error> { } } + // Check if the Fallback Icon service is valid + let icon_service_fallback = cfg.icon_service_fallback.as_str(); + if !icon_service_fallback.is_empty() { + if icon_service != "internal" { + err!(format!("Fallback Icon service can only be used for \"internal\" Icon service, you are currently using \"{icon_service}\"")) + } + match icon_service_fallback { + "bitwarden" | "duckduckgo" | "google" => (), + _ => { + if !icon_service_fallback.starts_with("http") { + err!(format!("Fallback Icon service URL `{icon_service_fallback}` must start with \"http\"")) + } + match icon_service_fallback.matches("{}").count() { + 1 => (), // nominal + 0 => err!(format!("Fallback Icon service URL `{icon_service_fallback}` has no placeholder \"{{}}\"")), + _ => err!(format!("Fallback Icon service URL `{icon_service_fallback}` has more than one placeholder \"{{}}\"")), + } + } + } + } + // Check if the icon redirect code is valid match cfg.icon_redirect_code { 301 | 302 | 307 | 308 => (),