* feat: add support for ignoring HTTPS certificate errors * fix: update warning message for already running daemon to include ignore HTTPS errors option * docs: add documentation for --ignore-https-errors option in README and SKILL.md * feat: initialize ignore_https_errors flag in command context * fix: change launch_cmd to mutable for cdp value handling
286 lines
8.7 KiB
Rust
286 lines
8.7 KiB
Rust
use std::env;
|
|
|
|
pub struct Flags {
|
|
pub json: bool,
|
|
pub full: bool,
|
|
pub headed: bool,
|
|
pub debug: bool,
|
|
pub session: String,
|
|
pub headers: Option<String>,
|
|
pub executable_path: Option<String>,
|
|
pub cdp: Option<String>,
|
|
pub extensions: Vec<String>,
|
|
pub profile: Option<String>,
|
|
pub proxy: Option<String>,
|
|
pub proxy_bypass: Option<String>,
|
|
pub args: Option<String>,
|
|
pub user_agent: Option<String>,
|
|
pub provider: Option<String>,
|
|
pub ignore_https_errors: bool,
|
|
}
|
|
|
|
pub fn parse_flags(args: &[String]) -> Flags {
|
|
let extensions_env = env::var("AGENT_BROWSER_EXTENSIONS")
|
|
.ok()
|
|
.map(|s| s.split(',').map(|p| p.trim().to_string()).filter(|p| !p.is_empty()).collect::<Vec<_>>())
|
|
.unwrap_or_default();
|
|
|
|
let mut flags = Flags {
|
|
json: false,
|
|
full: false,
|
|
headed: false,
|
|
debug: false,
|
|
session: env::var("AGENT_BROWSER_SESSION").unwrap_or_else(|_| "default".to_string()),
|
|
headers: None,
|
|
executable_path: env::var("AGENT_BROWSER_EXECUTABLE_PATH").ok(),
|
|
cdp: None,
|
|
extensions: extensions_env,
|
|
profile: env::var("AGENT_BROWSER_PROFILE").ok(),
|
|
proxy: env::var("AGENT_BROWSER_PROXY").ok(),
|
|
proxy_bypass: env::var("AGENT_BROWSER_PROXY_BYPASS").ok(),
|
|
args: env::var("AGENT_BROWSER_ARGS").ok(),
|
|
user_agent: env::var("AGENT_BROWSER_USER_AGENT").ok(),
|
|
provider: env::var("AGENT_BROWSER_PROVIDER").ok(),
|
|
ignore_https_errors: false,
|
|
};
|
|
|
|
let mut i = 0;
|
|
while i < args.len() {
|
|
match args[i].as_str() {
|
|
"--json" => flags.json = true,
|
|
"--full" | "-f" => flags.full = true,
|
|
"--headed" => flags.headed = true,
|
|
"--debug" => flags.debug = true,
|
|
"--session" => {
|
|
if let Some(s) = args.get(i + 1) {
|
|
flags.session = s.clone();
|
|
i += 1;
|
|
}
|
|
}
|
|
"--headers" => {
|
|
if let Some(h) = args.get(i + 1) {
|
|
flags.headers = Some(h.clone());
|
|
i += 1;
|
|
}
|
|
}
|
|
"--executable-path" => {
|
|
if let Some(s) = args.get(i + 1) {
|
|
flags.executable_path = Some(s.clone());
|
|
i += 1;
|
|
}
|
|
},
|
|
"--extension" => {
|
|
if let Some(s) = args.get(i + 1) {
|
|
flags.extensions.push(s.clone());
|
|
i += 1;
|
|
}
|
|
},
|
|
"--cdp" => {
|
|
if let Some(s) = args.get(i + 1) {
|
|
flags.cdp = Some(s.clone());
|
|
i += 1;
|
|
}
|
|
}
|
|
"--profile" => {
|
|
if let Some(s) = args.get(i + 1) {
|
|
flags.profile = Some(s.clone());
|
|
i += 1;
|
|
}
|
|
}
|
|
"--proxy" => {
|
|
if let Some(p) = args.get(i + 1) {
|
|
flags.proxy = Some(p.clone());
|
|
i += 1;
|
|
}
|
|
}
|
|
"--proxy-bypass" => {
|
|
if let Some(s) = args.get(i + 1) {
|
|
flags.proxy_bypass = Some(s.clone());
|
|
i += 1;
|
|
}
|
|
}
|
|
"--args" => {
|
|
if let Some(s) = args.get(i + 1) {
|
|
flags.args = Some(s.clone());
|
|
i += 1;
|
|
}
|
|
}
|
|
"--user-agent" => {
|
|
if let Some(s) = args.get(i + 1) {
|
|
flags.user_agent = Some(s.clone());
|
|
i += 1;
|
|
}
|
|
}
|
|
"-p" | "--provider" => {
|
|
if let Some(p) = args.get(i + 1) {
|
|
flags.provider = Some(p.clone());
|
|
i += 1;
|
|
}
|
|
}
|
|
"--ignore-https-errors" => flags.ignore_https_errors = true,
|
|
_ => {}
|
|
}
|
|
i += 1;
|
|
}
|
|
flags
|
|
}
|
|
|
|
pub fn clean_args(args: &[String]) -> Vec<String> {
|
|
let mut result = Vec::new();
|
|
let mut skip_next = false;
|
|
|
|
// Global flags that should be stripped from command args
|
|
const GLOBAL_FLAGS: &[&str] = &["--json", "--full", "--headed", "--debug", "--ignore-https-errors"];
|
|
// Global flags that take a value (need to skip the next arg too)
|
|
const GLOBAL_FLAGS_WITH_VALUE: &[&str] = &[
|
|
"--session",
|
|
"--headers",
|
|
"--executable-path",
|
|
"--cdp",
|
|
"--extension",
|
|
"--profile",
|
|
"--proxy",
|
|
"--proxy-bypass",
|
|
"--args",
|
|
"--user-agent",
|
|
"-p",
|
|
"--provider",
|
|
];
|
|
|
|
for arg in args.iter() {
|
|
if skip_next {
|
|
skip_next = false;
|
|
continue;
|
|
}
|
|
if GLOBAL_FLAGS_WITH_VALUE.contains(&arg.as_str()) {
|
|
skip_next = true;
|
|
continue;
|
|
}
|
|
// Only strip known global flags, not command-specific flags
|
|
if GLOBAL_FLAGS.contains(&arg.as_str()) || arg == "-f" {
|
|
continue;
|
|
}
|
|
result.push(arg.clone());
|
|
}
|
|
result
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
fn args(s: &str) -> Vec<String> {
|
|
s.split_whitespace().map(String::from).collect()
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_headers_flag() {
|
|
let flags = parse_flags(&args(r#"open example.com --headers {"Auth":"token"}"#));
|
|
assert_eq!(flags.headers, Some(r#"{"Auth":"token"}"#.to_string()));
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_headers_flag_with_spaces() {
|
|
// Headers JSON is passed as a single quoted argument in shell
|
|
let input: Vec<String> = vec![
|
|
"open".to_string(),
|
|
"example.com".to_string(),
|
|
"--headers".to_string(),
|
|
r#"{"Authorization": "Bearer token"}"#.to_string(),
|
|
];
|
|
let flags = parse_flags(&input);
|
|
assert_eq!(
|
|
flags.headers,
|
|
Some(r#"{"Authorization": "Bearer token"}"#.to_string())
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_no_headers_flag() {
|
|
let flags = parse_flags(&args("open example.com"));
|
|
assert!(flags.headers.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_clean_args_removes_headers() {
|
|
let input: Vec<String> = vec![
|
|
"open".to_string(),
|
|
"example.com".to_string(),
|
|
"--headers".to_string(),
|
|
r#"{"Auth":"token"}"#.to_string(),
|
|
];
|
|
let clean = clean_args(&input);
|
|
assert_eq!(clean, vec!["open", "example.com"]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_clean_args_removes_headers_at_start() {
|
|
let input: Vec<String> = vec![
|
|
"--headers".to_string(),
|
|
r#"{"Auth":"token"}"#.to_string(),
|
|
"open".to_string(),
|
|
"example.com".to_string(),
|
|
];
|
|
let clean = clean_args(&input);
|
|
assert_eq!(clean, vec!["open", "example.com"]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_headers_with_other_flags() {
|
|
let input: Vec<String> = vec![
|
|
"open".to_string(),
|
|
"example.com".to_string(),
|
|
"--headers".to_string(),
|
|
r#"{"Auth":"token"}"#.to_string(),
|
|
"--json".to_string(),
|
|
"--headed".to_string(),
|
|
];
|
|
let flags = parse_flags(&input);
|
|
assert_eq!(flags.headers, Some(r#"{"Auth":"token"}"#.to_string()));
|
|
assert!(flags.json);
|
|
assert!(flags.headed);
|
|
|
|
let clean = clean_args(&input);
|
|
assert_eq!(clean, vec!["open", "example.com"]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_executable_path_flag() {
|
|
let flags = parse_flags(&args(
|
|
"--executable-path /path/to/chromium open example.com",
|
|
));
|
|
assert_eq!(flags.executable_path, Some("/path/to/chromium".to_string()));
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_executable_path_flag_no_value() {
|
|
let flags = parse_flags(&args("--executable-path"));
|
|
assert_eq!(flags.executable_path, None);
|
|
}
|
|
|
|
#[test]
|
|
fn test_clean_args_removes_executable_path() {
|
|
let cleaned = clean_args(&args(
|
|
"--executable-path /path/to/chromium open example.com",
|
|
));
|
|
assert_eq!(cleaned, vec!["open", "example.com"]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_clean_args_removes_executable_path_with_other_flags() {
|
|
let cleaned = clean_args(&args(
|
|
"--json --executable-path /path/to/chromium --headed open example.com",
|
|
));
|
|
assert_eq!(cleaned, vec!["open", "example.com"]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_flags_with_session_and_executable_path() {
|
|
let flags = parse_flags(&args(
|
|
"--session test --executable-path /custom/chrome open example.com",
|
|
));
|
|
assert_eq!(flags.session, "test");
|
|
assert_eq!(flags.executable_path, Some("/custom/chrome".to_string()));
|
|
}
|
|
}
|