-
Notifications
You must be signed in to change notification settings - Fork 45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Escape URLs on Windows #11
Conversation
@wariuni thanks for the contribution. Can you elaborate if |
On Windows 7 at least PowerShell v2.0 seems to be pre-installed, which contains https://blogs.technet.microsoft.com/josebda/2009/11/25/download-for-powershell-v2-for-windows-7-no-need-its-already-there/ But on second thought, there is no specail reason to replace
https://github.com/python/cpython/blob/8c281ed403fd915284d5bba2405d7c47f8195066/Lib/webbrowser.py#L581
//! ```cargo
//! [package]
//! name = "startfile"
//! edition = "2018"
//!
//! [target.'cfg(windows)'.dependencies]
//! env_logger = "0.6.0"
//! log = "0.4.6"
//! percent-encoding = "1.0.1" # part of `url` crate
//! structopt = "0.2.14"
//! strum = "0.12.0"
//! strum_macros = "0.12.0"
//! widestring = "0.4.0"
//! winapi = { version = "0.3.6", features = ["combaseapi", "objbase", "shellapi"] }
//! ```
#![allow(unreachable_code)]
#[cfg(not(windows))]
compile_error!("");
use log::info;
use structopt::StructOpt;
use strum_macros::EnumString;
use widestring::U16CString;
use std::borrow::Cow;
use std::process::{Command, Stdio};
use std::{io, ptr};
#[derive(StructOpt)]
struct Opt {
file: String,
#[structopt(short = "m", long = "method", default_value = "winapi")]
method: Method,
}
#[derive(EnumString)]
#[strum(serialize_all = "snake_case")]
enum Method {
Winapi,
Cmd,
Powershell,
}
fn main() -> io::Result<()> {
env_logger::init();
let Opt { file, method } = Opt::from_args();
match method {
Method::Winapi => startfile_with_winapi(&file),
Method::Cmd => startfile_with_cmd(&encode_if_url(&file)),
Method::Powershell => startfile_with_powershell(&encode_if_url(&file)),
}
}
fn startfile_with_winapi(file: &str) -> io::Result<()> {
// https://docs.microsoft.com/en-us/windows/desktop/api/shellapi/nf-shellapi-shellexecutew
use winapi::um::combaseapi::CoInitializeEx;
use winapi::um::objbase::{COINIT_APARTMENTTHREADED, COINIT_DISABLE_OLE1DDE};
use winapi::um::shellapi::ShellExecuteW;
use winapi::um::winuser::SW_SHOW;
static OPEN: &[u16] = &[0x6f, 0x70, 0x65, 0x6e, 0x00];
let file =
U16CString::from_str(file).map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
let code = unsafe {
let _ = CoInitializeEx(
ptr::null_mut(),
COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE,
);
ShellExecuteW(
ptr::null_mut(),
OPEN.as_ptr(),
file.as_ptr(),
ptr::null(),
ptr::null(),
SW_SHOW,
) as usize
};
if code > 32 {
Ok(())
} else {
Err(unimplemented!("code: {}", code))
}
}
fn startfile_with_cmd(file: &str) -> io::Result<()> {
let cmd = file.chars().fold("start ".to_owned(), |mut cmd, c| {
cmd.push('^');
cmd.push(c);
cmd
});
info!("cmd = {:?}", cmd);
let status = Command::new("cmd")
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.arg("/C")
.arg(cmd)
.status()?;
if !status.success() {
return Err(unimplemented!("status: {}", status));
}
Ok(())
}
fn startfile_with_powershell(file: &str) -> io::Result<()> {
let (mut cmd, mut in_quotes) = ("Start-Process -FilePath ".to_owned(), false);
file.chars().for_each(|c| match c {
'"' if in_quotes => {
cmd += "\"`\"";
in_quotes = false;
}
'"' => cmd += "`\"",
c if in_quotes => cmd.push(c),
c => {
cmd.push('"');
cmd.push(c);
in_quotes = true;
}
});
if in_quotes {
cmd.push('"');
}
info!("cmd = {:?}", cmd);
let status = Command::new("powershell")
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.arg("-Command")
.arg(cmd)
.status()?;
if !status.success() {
return Err(unimplemented!("status: {}", status));
}
Ok(())
}
fn encode_if_url(file: &str) -> Cow<str> {
use percent_encoding::{utf8_percent_encode, SIMPLE_ENCODE_SET};
if file.contains("://") {
Cow::from(utf8_percent_encode(file, SIMPLE_ENCODE_SET).to_string())
} else {
Cow::from(file)
}
}
#[cfg(test)]
mod tests {
static FILE: &str =
"http://example.com/articles/non-ascii-text?q1=0&q2=0";
#[test]
fn test_startfile_with_winapi() {
super::startfile_with_winapi(FILE).unwrap();
}
#[test]
fn test_startfile_with_cmd() {
super::startfile_with_cmd(FILE).unwrap();
}
#[test]
fn test_startfile_with_poewrshell() {
super::startfile_with_powershell(FILE).unwrap();
}
} |
9294ddc
to
d2ff2af
Compare
My only recommendation would be to make the |
d2ff2af
to
81cb934
Compare
Done. |
Published 0.4.0 after fixing windows build |
In the current implementation on Windows, this crate cannot open a URL which has special characters for
cmd.exe
. (e.g.http://example.com?q1=0&q2=0
)This PR fixes the problem by escaping URLs.