ctow/
lib.rs

1//!
2//! ctow
3//! Converts cURL command-line arguments to wget
4//!
5
6use std::error;
7use std::fmt;
8
9/// Define some constants for pretty printing using ANSI colour codes.
10pub const BOLD: &str = "\x1b[1m";
11pub const RED: &str = "\x1b[31;1m";
12pub const RESET: &str = "\x1b[0m";
13pub const GREY: &str = "\x1b[90;3m";
14
15/// Public error types
16#[derive(Debug, PartialEq)]
17pub enum Errors {
18    ArgConversion(String),
19    InvalidArgument(String),
20    UnrecognisedCommand(String),
21}
22
23impl fmt::Display for Errors {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        match &self {
26            Errors::ArgConversion(err) => {
27                write!(f, "Conversion: {}", err)
28            }
29            Errors::InvalidArgument(err) => {
30                write!(f, "Invalid Argument: {}", err)
31            }
32            Errors::UnrecognisedCommand(err) => {
33                write!(f, "Unrecognized command: {}", err)
34            }
35        }
36    }
37}
38
39impl error::Error for Errors {}
40
41/// converts a curl command (with or without starting with `curl`) to a wget command
42///
43/// ## Example:
44///
45/// ```
46/// use ctow::convert;
47///
48/// let input = "curl -H 'User-Agent: Mozilla...'"; 
49/// let wget = convert(&[input.to_string()]);
50/// ```
51pub fn convert(curl: &[String]) -> Result<String, Errors> {
52    let curl_args = curl.join(" "); // this makes the input all one long string
53    let mut args: Vec<String> = vec![];
54    let mut url: Vec<String> = vec!["<url>".to_string()];
55
56    for arg in curl_args.split(' ') {
57        if arg == "curl" {
58            continue; // discard a "curl" - bugfix needed, only remove curl at start of command.
59        } else if arg.starts_with("http") {
60            // if there is a " http", assume that it's the url (grabs the last one in the command)
61            url = vec![("'".to_owned() + arg + "'").to_string()];
62        } else if arg.starts_with('-') {
63            // if the arg starts with a dash, assume it's a new argument
64            args.append(&mut vec![arg.to_string()]);
65        } else {
66            // else, append the rest of the arg to the previous arg, this helps with arguments
67            // with spaces in them
68            let len = args.len();
69            if len > 0 {
70                args[len - 1] += " ";
71                args[len - 1] += arg;
72            } else {
73                return Err(Errors::InvalidArgument(arg.to_string()));
74            }
75        }
76    }
77
78    args.append(&mut url); // append the url last
79
80    // converts the arg from curl to wget
81    let mut wget_args: Vec<String> = Vec::with_capacity(args.len());
82    for (i, arg) in args.iter().enumerate() {
83        wget_args.insert(i, convert_arg(arg)?);
84    }
85
86    Ok("wget ".to_owned() + &wget_args.join(" "))
87}
88
89/// Converts a curl argument to a wget argument
90///
91/// ## Example
92///
93/// ```
94/// use ctow::convert_arg;
95///
96/// let curl_argument = "-H 'User-Agent: Mozilla...'";
97/// let wget_argument = convert_arg(curl_argument);
98/// ```
99pub fn convert_arg(arg: &str) -> Result<String, Errors> {
100    // if it's the url, don't touch it
101    if arg.starts_with("<url>") {
102        Ok(String::from("<url>"))
103    } else if arg.starts_with("'http") {
104        Ok(arg.to_owned())
105    } else {
106        // else, replace the curl with the wget
107        match arg.split(' ').collect::<Vec<&str>>()[0] {
108            "-b" => Ok(arg.replace("-b ", "--load-cookies=")),
109            "-c" => Ok(arg.replace("-c ", "--save-cookies=")),
110            "-d" => Ok(arg.replace("-d ", "--post-data=")),
111            "-e" => Ok(arg.replace("-e ", "--header=\"Referer: ") + "\""),
112            "-g" => Ok(arg.replace("-g", "--no-glob")),
113            "-k" => Ok(arg.replace("-k", "--no-check-certificate")),
114            "-m" => Ok(arg.replace("-m ", "--timeout=")),
115            "-o" => Ok(arg.replace("-o ", "--output-document=")),
116            "-r" => Ok(arg.replace("-r ", "--header=\"Range: bytes=") + "\""),
117            "-s" => Ok(arg.replace("-s", "--quiet")),
118            "-u" => Ok(arg.replace("-u ", "--user=")),
119            "-z" => Ok(arg.replace("-z ", "--header=\"If-Modified-Since: ") + "\""),
120
121            "-A" => Ok(arg.replace("-A ", "--header=\"User-Agent: ") + "\""),
122            "-C" => Ok(arg.replace("-C ", "--start-pos=")),
123            "-E" => Ok(arg.replace("-E ", "--certificate=")),
124            "-H" => Ok(arg.replace("-H ", "--header '").replace('\\', "\\\\") + "'"),
125            "-I" => Ok(arg.replace("-I", "--method=HEAD")),
126            "-T" => Ok(arg.replace("-T ", "--method=PUT --body-file=")),
127            "-X" => Ok(arg.replace("-X ", "--method=")),
128
129            "--compressed" => Ok(arg.replace("--compressed", "--compression=auto")),
130            "--connect-timeout" => Ok(arg.replace("--connect-timeout ", "--timeout=")),
131            "--retry" => Ok(arg.replace("--retry ", "--tries=")),
132            _ => Err(Errors::ArgConversion(format!(
133                "{RED}No valid substitution for argument: {arg}!{RESET}",
134            ))),
135        }
136    }
137}
138
139#[cfg(test)]
140mod test {
141    use super::*;
142
143    #[test]
144    fn test_convert() {
145        // tests the conversion of a whole command
146        let test_curl1 = "curl http://example.com".to_string();
147        assert_eq!(
148            convert(&[test_curl1]),
149            Ok("wget 'http://example.com'".into())
150        );
151    }
152
153    #[test]
154    fn test_convert_args() {
155        // in this current format, ctow does not support -<option><var> (e.g. -bLOAD-COOKIE) there
156        // has to be a space in between option and var
157        let test_args = vec![
158            //"-bLOAD-COOKIE",
159            "-b LOAD-COOKIE",
160            //"-cSAVE-COOKIE", 
161            "-c SAVE-COOKIE",
162            //"-dPOST-DATA",
163            "-d POST-DATA",
164            //"-eREFERER",
165            "-e REFERER",
166            "-g",
167            "-k",
168            //"-m999",
169            "-m 9999",
170            //"-oOUTPUT",
171            "-o OUTPUT",
172            //"-r1-2",
173            "-r 2-3",
174            "-s",
175            //"-uUSER",
176            "-u USER",
177            //"-zMODIFIED",
178            "-z MODIFIED",
179
180            //"-AAGENT",
181            "-A AGENT",
182            //"-C1",
183            "-C 2",
184            //"-ECERT",
185            "-E CERT",
186            "-H User-Agent: Example",
187            "-I",
188            //"-TBODY",
189            "-T BODY",
190            //"-XMETHOD",
191            "-X METHOD",
192
193            "--compressed",
194            "--connect-timeout 5",
195            "--retry 3",
196        ];
197        let result_args = vec![
198            //"--load-cookies=LOAD-COOKIE",
199            "--load-cookies=LOAD-COOKIE",
200            //"--save-cookies=SAVE-COOKIE",
201            "--save-cookies=SAVE-COOKIE",
202            //"--post-data=POST-DATA",
203            "--post-data=POST-DATA",
204            //"--header=\"Referer: REFERER\"",
205            "--header=\"Referer: REFERER\"",
206            "--no-glob",
207            "--no-check-certificate",
208            //"--timeout=999",
209            "--timeout=9999",
210            //"--output-document=OUTPUT",
211            "--output-document=OUTPUT",
212            //"--header=\"Range: bytes=1-2\"",
213            "--header=\"Range: bytes=2-3\"",
214            "--quiet",
215            //"--user=USER",
216            "--user=USER",
217            //"--header=\"If-Modified-Since: MODIFIED\"",
218            "--header=\"If-Modified-Since: MODIFIED\"",
219
220            //"--header=\"User-Agent: AGENT\"",
221            "--header=\"User-Agent: AGENT\"",
222            //"--start-pos=1",
223            "--start-pos=2",
224            //"--certificate=CERT",
225            "--certificate=CERT",
226            "--header 'User-Agent: Example'",
227            "--method=HEAD",
228            //"--method=PUT --body-file=BODY",
229            "--method=PUT --body-file=BODY",
230            "--method=METHOD",
231
232            "--compression=auto",
233            "--timeout=5",
234            "--tries=3",
235        ];
236
237        for (i, test_arg) in test_args.iter().enumerate() {
238            assert_eq!(convert_arg(test_arg), Ok(result_args[i].to_string()));
239        }
240    }
241
242    #[test]
243    fn test_convert_url() {
244        let test_str1 = "'http";
245
246        assert_eq!(convert_arg(test_str1), Ok("'http".into()));
247    }
248}