Initial release

This commit is contained in:
Ryze 2023-08-28 16:06:57 +03:00
parent 0c1570b50b
commit abb558b106
Signed by: ryze
GPG key ID: 9B296C5CEAEAAAC1
16 changed files with 1090 additions and 0 deletions

2
.cargo/config.toml Normal file
View file

@ -0,0 +1,2 @@
[build]
target = "x86_64-pc-windows-gnu"

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

466
Cargo.lock generated Normal file
View file

@ -0,0 +1,466 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "base64"
version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
[[package]]
name = "bumpalo"
version = "3.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1"
[[package]]
name = "cc"
version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01"
dependencies = [
"libc",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "crc32fast"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [
"cfg-if",
]
[[package]]
name = "dechrome"
version = "0.9.0"
dependencies = [
"is_elevated",
"ureq",
"winreg",
]
[[package]]
name = "flate2"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "form_urlencoded"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
dependencies = [
"percent-encoding",
]
[[package]]
name = "idna"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
dependencies = [
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "is_elevated"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5299060ff5db63e788015dcb9525ad9b84f4fd9717ed2cbdeba5018cbf42f9b5"
dependencies = [
"winapi",
]
[[package]]
name = "js-sys"
version = "0.3.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
[[package]]
name = "log"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
[[package]]
name = "miniz_oxide"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
dependencies = [
"adler",
]
[[package]]
name = "once_cell"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "percent-encoding"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
[[package]]
name = "proc-macro2"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965"
dependencies = [
"proc-macro2",
]
[[package]]
name = "ring"
version = "0.16.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
dependencies = [
"cc",
"libc",
"once_cell",
"spin",
"untrusted",
"web-sys",
"winapi",
]
[[package]]
name = "rustls"
version = "0.21.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d1feddffcfcc0b33f5c6ce9a29e341e4cd59c3f78e7ee45f4a40c038b1d6cbb"
dependencies = [
"log",
"ring",
"rustls-webpki 0.101.3",
"sct",
]
[[package]]
name = "rustls-webpki"
version = "0.100.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "rustls-webpki"
version = "0.101.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "261e9e0888cba427c3316e6322805653c9425240b6fd96cee7cb671ab70ab8d0"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "sct"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "spin"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "syn"
version = "2.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tinyvec"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "unicode-bidi"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
[[package]]
name = "unicode-ident"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
[[package]]
name = "unicode-normalization"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
dependencies = [
"tinyvec",
]
[[package]]
name = "untrusted"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "ureq"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b11c96ac7ee530603dcdf68ed1557050f374ce55a5a07193ebf8cbc9f8927e9"
dependencies = [
"base64",
"flate2",
"log",
"once_cell",
"rustls",
"rustls-webpki 0.100.1",
"url",
"webpki-roots",
]
[[package]]
name = "url"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
[[package]]
name = "web-sys"
version = "0.3.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "webpki-roots"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b03058f88386e5ff5310d9111d53f48b17d732b401aeb83a8d5190f2ac459338"
dependencies = [
"rustls-webpki 0.100.1",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.48.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
[[package]]
name = "windows_i686_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
[[package]]
name = "windows_i686_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
[[package]]
name = "winreg"
version = "0.50.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
dependencies = [
"cfg-if",
"windows-sys",
]

16
Cargo.toml Normal file
View file

@ -0,0 +1,16 @@
[package]
name = "dechrome"
version = "0.9.0"
edition = "2021"
[target.'cfg(windows)'.dependencies]
winreg = "~0.50.0"
is_elevated = "~0.1.2"
ureq = { version = "~2.7.1", features = ["tls"] }
[profile.release-full]
inherits = "release"
strip = "symbols"
lto = "fat"
codegen-units = 1
panic = "abort"

50
README.md Normal file
View file

@ -0,0 +1,50 @@
# Dechrome
Dechrome is a tool written in Rust for batch removing Chromium-based browsers and installing Firefox as a replacement.
> [!WARNING]
> The script is experimental and wasn't thoroughly tested on all Windows systems.
> [!NOTE]
> Executing as administrator is preferred in order to remove system-wide installations. Make sure to terminate msedge.exe processes through task manager beforing launching, as the script might fail to delete certain files locked by Edge
# Reasoning
Chromium-based browsers hold around 74.05% market share across all devices, 78.58% on desktop, 90.45%* on Windows, according to [GlobalStats statcounter](https://gs.statcounter.com/browser-market-share/desktop/worldwide/#monthly-202307-202307-bar) as of July 2023.
> *Calculated by excluding Safari market share from browser market share on desktop, may be inaccurate
This causes multiple issues:
1. Increased attack surface
2. Illusion of choice
3. Single entity control
### Increased attack surface
Having a single product spread across millions of machines imposes a severe risk in case of discovered vulnerability as the area of attack could be worldwide and the patches could take a long time to propagate, giving opportunity for attackers to take advantage of the situation.
### Illusion of choice
Users are given illusion of choice, no matter what they pick they are likely to end up using Google's product, either [Blink](https://www.chromium.org/blink) or Chromium, since most browsers are based on them and are not advertised as such.
### Single entity control
While Chromium project is open-source, ultimately Google is in full control of the changes being made to it. Giving away control of the web client to Google, entity controlling most of the web space, gives it the ability to shift and control market to it's will, both client-side and server-side. This provokes monopoly and hurts competition in the long run. See recent [Web Environment Integrity proposal](https://github.com/RupertBenWiser/Web-Environment-Integrity/blob/main/explainer.md).
# Implemented uninstallers
- Google Chrome
- Google Chrome Canary
- Microsoft Edge
- Brave
- Vivaldi
- Opera
- Opera GX
- Yandex Browser
# Contributing
All issues and pull requests are welcome! Feel free to open an issue if you've got an idea or a problem. You can open a pull request if you are able to implement it yourself.
---
<p align="center">
<sub><strong>
Made with ponies and love!
<br/>
GNU GPL © Ryze 2023
</strong></sub>
</p>

30
src/error.rs Normal file
View file

@ -0,0 +1,30 @@
use std::io;
use ureq;
// TODO: Well...
pub type DechromeResult<T> = Result<T, DechromeError>;
#[derive(Debug)]
pub enum DechromeError {
FileNotFound,
ExecutablePathNotFound,
MismatchedQuotes,
ContentLengthError,
IOError(io::Error),
NetworkError(Box<ureq::Error>),
}
impl From<io::Error> for DechromeError {
fn from(err: io::Error) -> Self {
Self::IOError(err)
}
}
impl From<ureq::Error> for DechromeError {
fn from(err: ureq::Error) -> Self {
match err {
ureq::Error::Status(code, _) if code == 404 => Self::FileNotFound,
_ => Self::NetworkError(Box::new(err)),
}
}
}

3
src/install.rs Normal file
View file

@ -0,0 +1,3 @@
pub mod firefox;
pub use firefox::Firefox;

41
src/install/firefox.rs Normal file
View file

@ -0,0 +1,41 @@
use std::{path::Path, process::Command};
use crate::{PrepareInstaller, Installer, SystemInfo, DechromeError};
use crate::utils;
const WIN_ARCH: &str = if cfg!(target_arch = "x86_64") { "win64" } else { "win32" };
pub struct PreparedFirefox;
impl Installer for PreparedFirefox {
fn install(self, info: &SystemInfo, installer_path: &Path) -> Result<(), DechromeError> {
let mut command = Command::new(installer_path);
command.arg("/S");
if !info.is_elevated {
command.arg("/InstallDirectoryPath".to_owned() + info.local_appdata.to_str().unwrap());
}
command.spawn()?.wait()?;
Ok(())
}
}
pub struct Firefox;
impl PrepareInstaller<PreparedFirefox> for Firefox {
fn prepare(info: &SystemInfo, installer_path: &Path) -> Result<PreparedFirefox, DechromeError> {
let urls = &info.preferred_languages.iter()
.map(|lang| format!("https://download.mozilla.org/?product=firefox-latest-ssl&os={WIN_ARCH}&lang={lang}"));
let res = utils::try_fetch_multiple(urls.clone(), installer_path);
match res {
Err(DechromeError::FileNotFound) => {
utils::fetch_file(&format!("https://download.mozilla.org/?product=firefox-latest-ssl&os={WIN_ARCH}&lang=en-US"), installer_path)?;
Ok(PreparedFirefox)
},
Ok(_) => Ok(PreparedFirefox),
Err(v) => Err(v)
}
}
}

9
src/lib.rs Normal file
View file

@ -0,0 +1,9 @@
pub mod error;
pub mod models;
pub mod utils;
pub mod install;
pub mod uninstall;
pub use error::*;
pub use models::*;

45
src/main.rs Normal file
View file

@ -0,0 +1,45 @@
#![windows_subsystem = "windows"]
use dechrome::{install, uninstall};
use dechrome::{SystemInfo, PrepareInstaller, Installer, Uninstaller};
fn main() {
let info = SystemInfo::build().unwrap();
let mut firefox_path = info.temp.clone();
firefox_path.push("firefox_installer.exe");
println!("Starting...");
match install::Firefox::prepare(&info, &firefox_path) {
Ok(firefox) => {
println!("Installer downloaded. Removing browsers");
remove_browsers(&info);
println!("Installing Firefox");
let res = firefox.install(&info, &firefox_path);
println!("Result: {res:?}");
},
Err(e) => eprintln!("Couldn't download Firefox: {e:?}")
}
}
fn remove_browsers(info: &SystemInfo) {
println!("Chrome based:");
for browser in uninstall::get_chrome_based(info) {
let res = browser.uninstall(info);
println!("Result: {res:?}");
}
println!("Opera based:");
for opera in uninstall::get_opera_based(info) {
let res = opera.uninstall(info);
println!("Result: {res:?}");
}
if let Some(edge) = uninstall::get_edge_based(info) {
let res = edge.uninstall(info);
println!("Edge: ");
println!("Result: {res:?}");
}
}

146
src/models.rs Normal file
View file

@ -0,0 +1,146 @@
use std::env;
use std::io;
use std::path::Path;
use std::path::PathBuf;
use std::process::Child;
use std::process::Command;
use winreg::enums::HKEY_CURRENT_USER;
use winreg::enums::HKEY_LOCAL_MACHINE;
use winreg::RegKey;
use is_elevated::is_elevated;
use crate::utils;
use crate::DechromeError;
use crate::DechromeResult;
const USER_PROFILE_REG_PATH: &str = r"Control Panel\International\User Profile";
pub trait PrepareInstaller<T: Installer> {
fn prepare(info: &SystemInfo, installer_path: &Path) -> DechromeResult<T>;
}
pub trait Installer {
fn install(self, info: &SystemInfo, installer_path: &Path) -> DechromeResult<()>;
}
pub trait Uninstaller {
fn uninstall(self, info: &SystemInfo) -> DechromeResult<()>;
}
#[derive(Debug, Hash, PartialEq, Eq)]
pub struct UninstallInfo {
pub display_version: String,
pub invoke_command: String,
pub invoke_args: Vec<String>,
}
impl UninstallInfo {
pub fn new(display_version: String, invoke_command: String, invoke_args: Vec<String>) -> Self {
Self {
display_version,
invoke_command,
invoke_args,
}
}
pub fn add_arg(&mut self, arg: &str) {
self.invoke_args.push(arg.to_owned())
}
pub fn invoke(self) -> io::Result<Child> {
Command::new(self.invoke_command)
.args(self.invoke_args)
.spawn()
}
}
impl TryFrom<RegKey> for UninstallInfo {
type Error = DechromeError;
fn try_from(regkey: RegKey) -> Result<Self, Self::Error> {
let display_version = regkey.get_value("DisplayVersion")?;
let uninstall_string: String = regkey.get_value("UninstallString")?;
let (invoke_command, invoke_args) = utils::parse_shell(&uninstall_string)?;
Ok(Self {
display_version,
invoke_command,
invoke_args,
})
}
}
#[derive(Debug)]
pub struct SystemInfo {
pub is_elevated: bool,
pub temp: PathBuf,
pub appdata: PathBuf,
pub local_appdata: PathBuf,
pub all_users_data: PathBuf,
pub public: PathBuf,
pub program_files_x86: PathBuf,
pub preferred_languages: Box<[String]>,
pub uninstall_regkeys: Box<[RegKey]>,
}
impl SystemInfo {
pub fn build() -> io::Result<Self> {
let is_elevated = is_elevated();
let temp = env::temp_dir();
let appdata = get_env_path("APPDATA");
let local_appdata = get_env_path("LOCALAPPDATA");
let program_files_x86 = get_env_path("PROGRAMFILES(X86)");
let all_users_data = get_env_path("ALLUSERSPROFILE");
let public = get_env_path("PUBLIC");
let preferred_languages = get_preferred_languages()?;
let uninstall_regkeys = get_uninstall_regkeys();
Ok(Self {
is_elevated,
temp,
appdata,
local_appdata,
all_users_data,
public,
program_files_x86,
preferred_languages,
uninstall_regkeys,
})
}
}
fn get_preferred_languages() -> io::Result<Box<[String]>> {
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
let desktop_key = hkcu.open_subkey(USER_PROFILE_REG_PATH)?;
let langs: Vec<String> = desktop_key.get_value("Languages")?;
Ok(langs.into())
}
fn get_env_path(var: &str) -> PathBuf {
env::var(var)
.expect("Variable doesn't exist")
.into()
}
fn get_uninstall_regkeys() -> Box<[RegKey]> {
let base_keys = [
RegKey::predef(HKEY_CURRENT_USER),
RegKey::predef(HKEY_LOCAL_MACHINE),
];
let bit_prefixes = [r"", r"WOW6432Node"];
let combinations = base_keys
.iter()
.flat_map(|base_key| bit_prefixes.iter().map(move |prefix| (base_key, prefix)));
let regkeys = combinations.filter_map(|(base_key, prefix)| {
let path = format!(r"SOFTWARE\{prefix}\Microsoft\Windows\CurrentVersion\Uninstall");
base_key.open_subkey(path).ok()
});
regkeys.collect()
}

27
src/uninstall.rs Normal file
View file

@ -0,0 +1,27 @@
pub mod chrome_based;
pub mod opera_based;
pub mod edge_based;
pub use chrome_based::get_chrome_based;
pub use opera_based::get_opera_based;
pub use edge_based::get_edge_based;
use crate::{DechromeResult, SystemInfo, UninstallInfo, Uninstaller};
#[derive(Debug)]
pub struct SimpleUninstaller {
uninstall_info: UninstallInfo,
}
impl SimpleUninstaller {
pub fn new(uninstall_info: UninstallInfo) -> Self {
Self { uninstall_info }
}
}
impl Uninstaller for SimpleUninstaller {
fn uninstall(self, _info: &SystemInfo) -> DechromeResult<()> {
self.uninstall_info.invoke()?;
Ok(())
}
}

View file

@ -0,0 +1,25 @@
use crate::uninstall::SimpleUninstaller;
use crate::utils;
use crate::SystemInfo;
const BROWSERS: [&str; 5] = [
"Google Chrome",
"Google Chrome SxS",
"BraveSoftware Brave-Browser",
"Vivaldi",
"YandexBrowser",
];
// Get uninstallers based on Chrome installer, which have an option --force-uninstall for silent execution
pub fn get_chrome_based(info: &SystemInfo) -> Vec<SimpleUninstaller> {
let uninstall_info = BROWSERS
.iter()
.flat_map(|browser| utils::find_browser_uninstall_info(info, browser));
let uninstallers = uninstall_info.map(|mut info| {
info.add_arg("--force-uninstall");
SimpleUninstaller::new(info)
});
uninstallers.collect()
}

View file

@ -0,0 +1,78 @@
use winreg::{RegKey, enums::HKEY_LOCAL_MACHINE};
use std::fs;
use crate::{Uninstaller, DechromeResult, SystemInfo};
const EDGE_PATHS: [&str; 3] = [
"Microsoft/Edge",
"Microsoft/EdgeCore",
"Microsoft/EdgeUpdate",
];
const EDGE_REGKEYS: [&str; 3] = [
r"SOFTWARE\Microsoft\Active Setup\Installed Components\{9459C573-B17A-45AE-9F64-1857B5D58CEE}",
r"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft Edge",
r"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Microsoft Edge Update"
];
const EDGE_UPDATE_REGKEY: &str = r"SOFTWARE\Microsoft\EdgeUpdate";
pub struct EdgeUninstaller;
#[allow(unused_must_use)] // Supress warnings of for io::Result
impl EdgeUninstaller {
fn remove_files(info: &SystemInfo) {
let program_files_x86 = &info.program_files_x86;
for path in EDGE_PATHS {
let mut full_path = program_files_x86.clone();
full_path.push(path);
fs::remove_dir_all(&full_path);
}
let mut link_path = info.public.clone();
link_path.push("Desktop/Microsoft Edge.lnk");
fs::remove_file(link_path);
let mut link_path = info.all_users_data.clone();
link_path.push("Microsoft/Windows/Start Menu/Programs/Microsoft Edge.lnk");
fs::remove_file(link_path);
let mut link_path = info.appdata.clone();
link_path.push("Microsoft/Internet Explorer/Quick Launch/User Pinned/TaskBar/Microsoft Edge.lnk");
fs::remove_file(link_path);
}
fn remove_regkeys() {
let hlkm = RegKey::predef(HKEY_LOCAL_MACHINE);
for path in EDGE_REGKEYS {
hlkm.delete_subkey_all(path);
}
}
fn disable_update() -> DechromeResult<()> {
let hlkm = RegKey::predef(HKEY_LOCAL_MACHINE);
let (edge_update_key, _) = hlkm.create_subkey(EDGE_UPDATE_REGKEY)?;
edge_update_key.set_value("DoNotUpdateToEdgeWithChromium", &1u32)?; // DWORD is u32
Ok(())
}
}
impl Uninstaller for EdgeUninstaller {
fn uninstall(self, info: &SystemInfo) -> DechromeResult<()> {
Self::remove_files(info);
Self::remove_regkeys();
Self::disable_update()
}
}
pub fn get_edge_based(info: &SystemInfo) -> Option<EdgeUninstaller> {
if info.is_elevated {
Some(EdgeUninstaller {})
} else {
None
}
}

View file

@ -0,0 +1,48 @@
use std::collections::HashSet;
use crate::utils;
use crate::{DechromeResult, SystemInfo, UninstallInfo, Uninstaller};
const BROWSERS: [&str; 2] = ["Opera", "Opera GX"];
#[derive(Hash, PartialEq, Eq)]
pub struct OperaUninstaller {
invoke_info: UninstallInfo,
}
impl OperaUninstaller {
pub fn new(invoke_info: UninstallInfo) -> Self {
Self { invoke_info }
}
}
impl Uninstaller for OperaUninstaller {
fn uninstall(self, _: &SystemInfo) -> DechromeResult<()> {
let mut uninstaller = self.invoke_info.invoke()?;
uninstaller.wait()?; // Wait for the installer to close, can't allow multiple of them to run
Ok(())
}
}
pub fn get_opera_based(info: &SystemInfo) -> HashSet<OperaUninstaller> {
let uninstall_info = BROWSERS
.iter()
.flat_map(|browser| utils::find_browser_uninstall_info_starting_with(info, browser));
let uninstallers = uninstall_info.map(|mut info| {
let version = &info.display_version;
// It's capitalised in the registry, but the actual filename isn't
// Blame Opera
let uninstaller_dir = info.invoke_command.trim_end_matches("Launcher.exe");
let new_command = format!("{uninstaller_dir}/{version}/installer.exe");
info.invoke_command = new_command;
info.add_arg("--runimmediately"); // Unattended execution
OperaUninstaller::new(info)
});
uninstallers.collect()
}

103
src/utils.rs Normal file
View file

@ -0,0 +1,103 @@
use std::{fs::File, io::Write, path::Path};
use ureq;
use crate::{DechromeError, DechromeResult, SystemInfo, UninstallInfo};
pub fn fetch_file(url: &str, path: &Path) -> DechromeResult<()> {
if path.exists() {
return Ok(());
}
let response = ureq::get(url).call()?;
let length = response
.header("Content-Length")
.ok_or(DechromeError::ContentLengthError)?;
let length = length
.parse()
.map_err(|_| DechromeError::ContentLengthError)?;
let mut buffer = Vec::with_capacity(length);
response.into_reader().read_to_end(&mut buffer)?;
let mut file = File::create(path)?;
file.write_all(&buffer)?;
Ok(())
}
pub fn try_fetch_multiple<I: IntoIterator<Item = String>>(
urls: I,
path: &Path,
) -> DechromeResult<()> {
for url in urls {
let res = fetch_file(&url, path);
if let Err(DechromeError::FileNotFound) = res {
continue;
}
if res.is_ok() {
return Ok(());
}
res?;
}
Err(DechromeError::FileNotFound)
}
pub fn parse_shell(input: &str) -> DechromeResult<(String, Vec<String>)> {
let mut in_quotes = false;
let input = input.trim();
let input_split = input.split(|char| match char {
'"' => {
in_quotes = !in_quotes;
false
}
' ' => !in_quotes,
_ => false,
});
let mut parsed = input_split.map(|split| split.replace('"', ""));
let command = parsed.next().ok_or(DechromeError::ExecutablePathNotFound)?;
let args = parsed.collect();
if in_quotes {
// Has to run after .collect because that's when everything runs
return Err(DechromeError::MismatchedQuotes);
}
Ok((command, args))
}
pub fn find_browser_uninstall_info<'a>(
info: &'a SystemInfo,
browser: &'a str,
) -> impl Iterator<Item = UninstallInfo> + 'a {
let regkeys = info
.uninstall_regkeys
.iter()
.filter_map(move |regkey| regkey.open_subkey(browser).ok());
regkeys.filter_map(|browser_key| {
UninstallInfo::try_from(browser_key).ok()
})
}
pub fn find_browser_uninstall_info_starting_with<'a>(
info: &'a SystemInfo,
starts_with: &'a str,
) -> impl Iterator<Item = UninstallInfo> + 'a {
let regkeys = info.uninstall_regkeys.iter().flat_map(move |regkey| {
regkey
.enum_keys()
.filter_map(|res| res.ok()) // Filter out successful ones
.filter(move |regkey| regkey.starts_with(starts_with)) // Get only matched
.filter_map(|regname| regkey.open_subkey(regname).ok()) // Try to open them
});
regkeys.filter_map(|browser_key| {
UninstallInfo::try_from(browser_key).ok()
})
}