docker_pose/
http.rs

1use crate::Verbosity;
2use clap::crate_version;
3use colored::Colorize;
4use http::{Response, StatusCode};
5use std::fs::File;
6use std::path::Path;
7use std::time::Duration;
8use std::{io, process};
9use ureq::config::Config;
10use ureq::{Agent, Body};
11use url::Url;
12
13pub fn get_and_save(
14    url: &str,
15    script: &Option<(String, String)>,
16    output: &Option<String>,
17    timeout_connect_secs: u16,
18    max_time: u16,
19    headers: &[(String, String)],
20    verbosity: Verbosity,
21) {
22    let mut url = url.to_string();
23    let parsed_url = match Url::parse(&url) {
24        Ok(r) => r,
25        Err(e) => {
26            eprintln!("{}: invalid URL - {}", "ERROR".red(), e);
27            process::exit(3);
28        }
29    };
30    let path = if parsed_url.path() == "/" {
31        output.as_ref().unwrap_or_else(|| {
32            eprintln!(
33                "{}: URL without filename, you have to provide \
34                the filename where to store the file with the argument {}",
35                "ERROR".red(),
36                "-o, --output".yellow()
37            );
38            process::exit(4);
39        })
40    } else {
41        parsed_url.path()
42    };
43    if let Some(script) = script
44        && !url.contains(&script.0)
45    {
46        eprintln!(
47            "{}: the left part of the script '{}' is not part of the URL",
48            "ERROR".red(),
49            script.0.yellow()
50        );
51        process::exit(10);
52    }
53    let path = Path::new(path);
54    let config: Config = Agent::config_builder()
55        .timeout_connect(Option::from(Duration::from_secs(
56            timeout_connect_secs.into(),
57        )))
58        .timeout_global(Option::from(Duration::from_secs(max_time.into())))
59        .user_agent(format!("pose/{}", crate_version!()).as_str())
60        .http_status_as_error(false)
61        .build();
62    let mut result = _get_and_save(
63        &url,
64        output,
65        path,
66        config.clone(),
67        headers,
68        verbosity.clone(),
69    );
70    if !result && let Some(script) = script {
71        url = url.replace(&script.0, &script.1);
72        result = _get_and_save(&url, output, path, config, headers, verbosity.clone());
73    }
74    if !result {
75        eprintln!("{}: Download failed", "ERROR".red());
76        process::exit(1);
77    }
78}
79
80fn _get_and_save(
81    url: &str,
82    output: &Option<String>,
83    path: &Path,
84    config: Config,
85    headers: &[(String, String)],
86    verbosity: Verbosity,
87) -> bool {
88    if !matches!(verbosity, Verbosity::Quiet) {
89        eprint!("{}: Downloading {} ... ", "DEBUG".green(), url);
90    }
91    let agent: Agent = config.into();
92    let mut request = agent.get(url);
93    for header in headers {
94        request = request.header(&header.0, &header.1);
95    }
96    match request.call() {
97        Ok(mut resp) => {
98            if resp.status().is_success() {
99                if !matches!(verbosity, Verbosity::Quiet) {
100                    eprintln!("{}", "found".green());
101                }
102                save(resp, path, output, verbosity.clone());
103                true
104            } else if resp.status() == StatusCode::NOT_FOUND {
105                if !matches!(verbosity, Verbosity::Quiet) {
106                    eprintln!("{}", "not found".purple());
107                }
108                false
109            } else {
110                if !matches!(verbosity, Verbosity::Quiet) {
111                    eprintln!("{}", "failed".red())
112                }
113                eprintln!(
114                    "{}: {:?} {} {}",
115                    "ERROR".red(),
116                    resp.version(),
117                    resp.status().as_u16(),
118                    resp.status().canonical_reason().unwrap_or("")
119                );
120                let error_msg = resp.body_mut().read_to_string().unwrap_or_else(|e| {
121                    eprintln!("{}: reading download content - {}", "ERROR".red(), e);
122                    process::exit(5);
123                });
124                eprintln!("{}", error_msg);
125                process::exit(5);
126            }
127        }
128        Err(e) => {
129            if !matches!(verbosity, Verbosity::Quiet) {
130                eprintln!("{}", "failed".red())
131            }
132            eprintln!("{}: {}", "ERROR".red(), e);
133            process::exit(7);
134        }
135    }
136}
137
138fn save(resp: Response<Body>, path: &Path, output: &Option<String>, verbosity: Verbosity) {
139    let filename = if let Some(filename) = output {
140        if !matches!(verbosity, Verbosity::Quiet) {
141            eprint!(
142                "{}: Saving downloaded file as {} ... ",
143                "DEBUG".green(),
144                filename.yellow()
145            );
146        }
147        filename
148    } else {
149        path.file_name().unwrap().to_str().unwrap()
150    };
151    let mut content = resp.into_body().into_reader();
152    let mut file = File::create(filename).unwrap_or_else(|e| {
153        if !matches!(verbosity, Verbosity::Quiet) {
154            eprintln!("{}", "failed".red())
155        }
156        eprintln!(
157            "{}: creating file '{}' - {}",
158            "ERROR".red(),
159            filename.yellow(),
160            e
161        );
162        process::exit(5);
163    });
164    io::copy(&mut content, &mut file).unwrap_or_else(|e| {
165        if !matches!(verbosity, Verbosity::Quiet) {
166            eprintln!("{}", "failed".red());
167        }
168        eprintln!(
169            "{}: writing output to file '{}': {}",
170            "ERROR".red(),
171            filename.yellow(),
172            e
173        );
174        process::exit(6);
175    });
176    if !matches!(verbosity, Verbosity::Quiet) && output.is_some() {
177        eprintln!("{}", "done".green());
178    }
179}