Parse a `curl` command string into a [`ParsedCurl`]. Supports the most common flags: `-X/--request`, `-H/--header`, `-d/--data/--data-raw/--data-binary/--data-urlencode`, `-F/--form`, `-u/--user`, `-A/--user-agent`, `-e/--referer`, `-b/--cookie`, `--url`, `--location`, `--get`, `-G`, `--compressed`, `--insecure`, `-k`.
(input: &str)
| 24 | /// `-X/--request`, `-H/--header`, `-d/--data/--data-raw/--data-binary/--data-urlencode`, |
| 25 | /// `-F/--form`, `-u/--user`, `-A/--user-agent`, `-e/--referer`, `-b/--cookie`, |
| 26 | /// `--url`, `--location`, `--get`, `-G`, `--compressed`, `--insecure`, `-k`. |
| 27 | pub fn parse_curl(input: &str) -> Result<ParsedCurl, String> { |
| 28 | let tokens = tokenize(input)?; |
| 29 | if tokens.is_empty() { |
| 30 | return Err("Empty curl command".into()); |
| 31 | } |
| 32 | |
| 33 | let mut iter = tokens.into_iter().peekable(); |
| 34 | |
| 35 | // Skip leading "curl" |
| 36 | let first = iter.next().ok_or("Missing curl")?; |
| 37 | if !first.eq_ignore_ascii_case("curl") { |
| 38 | return Err("Command does not start with curl".into()); |
| 39 | } |
| 40 | |
| 41 | let mut url: Option<String> = None; |
| 42 | let mut method: Option<HttpMethod> = None; |
| 43 | let mut headers: Vec<Header> = Vec::new(); |
| 44 | let mut data_parts: Vec<String> = Vec::new(); |
| 45 | // (key, value, is_file): is_file marks a `-F key=@path` upload field. |
| 46 | let mut form_parts: Vec<(String, String, bool)> = Vec::new(); |
| 47 | let mut basic_auth: Option<String> = None; |
| 48 | let mut force_get = false; |
| 49 | |
| 50 | while let Some(arg) = iter.next() { |
| 51 | match arg.as_str() { |
| 52 | "-X" | "--request" => { |
| 53 | let v = iter.next().ok_or("Missing value for -X")?; |
| 54 | method = Some(parse_method(&v)); |
| 55 | } |
| 56 | "-H" | "--header" => { |
| 57 | let v = iter.next().ok_or("Missing value for -H")?; |
| 58 | if let Some(h) = parse_header(&v) { |
| 59 | headers.push(h); |
| 60 | } |
| 61 | } |
| 62 | "-d" | "--data" | "--data-raw" | "--data-binary" | "--data-ascii" => { |
| 63 | let v = iter.next().ok_or("Missing value for -d")?; |
| 64 | data_parts.push(v); |
| 65 | } |
| 66 | "--data-urlencode" => { |
| 67 | let v = iter.next().ok_or("Missing value for --data-urlencode")?; |
| 68 | data_parts.push(v); |
| 69 | } |
| 70 | "-F" | "--form" => { |
| 71 | let v = iter.next().ok_or("Missing value for -F")?; |
| 72 | if let Some((k, val)) = split_once(&v, '=') { |
| 73 | if let Some(rest) = val.strip_prefix('@') { |
| 74 | // `-F key=@path[;type=...;filename=...]` — strip parameters, |
| 75 | // keep just the path. |
| 76 | let path = rest.split(';').next().unwrap_or(rest); |
| 77 | form_parts.push((k.to_string(), path.to_string(), true)); |
| 78 | } else { |
| 79 | form_parts.push((k.to_string(), val.to_string(), false)); |
| 80 | } |
| 81 | } |
| 82 | } |
| 83 | "--form-string" => { |