1use crate::network::http::client::{
2 RequestFlags, check_response_redirection, http_client, http_parse_redirect_mode,
3 http_parse_url, request_add_authorization_header, request_add_custom_headers,
4 request_handle_response, request_set_timeout, send_request,
5};
6use nu_engine::command_prelude::*;
7
8use super::client::HttpBody;
9
10#[derive(Clone)]
11pub struct HttpGet;
12
13impl Command for HttpGet {
14 fn name(&self) -> &str {
15 "http get"
16 }
17
18 fn signature(&self) -> Signature {
19 Signature::build("http get")
20 .input_output_types(vec![(Type::Nothing, Type::Any)])
21 .allow_variants_without_examples(true)
22 .required(
23 "URL",
24 SyntaxShape::String,
25 "The URL to fetch the contents from.",
26 )
27 .named(
28 "user",
29 SyntaxShape::Any,
30 "the username when authenticating",
31 Some('u'),
32 )
33 .named(
34 "password",
35 SyntaxShape::Any,
36 "the password when authenticating",
37 Some('p'),
38 )
39 .named(
40 "max-time",
41 SyntaxShape::Duration,
42 "max duration before timeout occurs",
43 Some('m'),
44 )
45 .named(
46 "headers",
47 SyntaxShape::Any,
48 "custom headers you want to add ",
49 Some('H'),
50 )
51 .switch(
52 "raw",
53 "fetch contents as text rather than a table",
54 Some('r'),
55 )
56 .switch(
57 "insecure",
58 "allow insecure server connections when using SSL",
59 Some('k'),
60 )
61 .switch(
62 "full",
63 "returns the full response instead of only the body",
64 Some('f'),
65 )
66 .switch(
67 "allow-errors",
68 "do not fail if the server returns an error code",
69 Some('e'),
70 )
71 .named(
72 "redirect-mode",
73 SyntaxShape::String,
74 "What to do when encountering redirects. Default: 'follow'. Valid options: 'follow' ('f'), 'manual' ('m'), 'error' ('e').",
75 Some('R')
76 )
77 .filter()
78 .category(Category::Network)
79 }
80
81 fn description(&self) -> &str {
82 "Fetch the contents from a URL."
83 }
84
85 fn extra_description(&self) -> &str {
86 "Performs HTTP GET operation."
87 }
88
89 fn search_terms(&self) -> Vec<&str> {
90 vec![
91 "network", "fetch", "pull", "request", "download", "curl", "wget",
92 ]
93 }
94
95 fn run(
96 &self,
97 engine_state: &EngineState,
98 stack: &mut Stack,
99 call: &Call,
100 input: PipelineData,
101 ) -> Result<PipelineData, ShellError> {
102 run_get(engine_state, stack, call, input)
103 }
104
105 fn examples(&self) -> Vec<Example> {
106 vec![
107 Example {
108 description: "Get content from example.com",
109 example: "http get https://www.example.com",
110 result: None,
111 },
112 Example {
113 description: "Get content from example.com, with username and password",
114 example: "http get --user myuser --password mypass https://www.example.com",
115 result: None,
116 },
117 Example {
118 description: "Get content from example.com, with custom header using a record",
119 example: "http get --headers {my-header-key: my-header-value} https://www.example.com",
120 result: None,
121 },
122 Example {
123 description: "Get content from example.com, with custom headers using a list",
124 example: "http get --headers [my-header-key-A my-header-value-A my-header-key-B my-header-value-B] https://www.example.com",
125 result: None,
126 },
127 ]
128 }
129}
130
131struct Arguments {
132 url: Value,
133 headers: Option<Value>,
134 raw: bool,
135 insecure: bool,
136 user: Option<String>,
137 password: Option<String>,
138 timeout: Option<Value>,
139 full: bool,
140 allow_errors: bool,
141 redirect: Option<Spanned<String>>,
142}
143
144fn run_get(
145 engine_state: &EngineState,
146 stack: &mut Stack,
147 call: &Call,
148 _input: PipelineData,
149) -> Result<PipelineData, ShellError> {
150 let args = Arguments {
151 url: call.req(engine_state, stack, 0)?,
152 headers: call.get_flag(engine_state, stack, "headers")?,
153 raw: call.has_flag(engine_state, stack, "raw")?,
154 insecure: call.has_flag(engine_state, stack, "insecure")?,
155 user: call.get_flag(engine_state, stack, "user")?,
156 password: call.get_flag(engine_state, stack, "password")?,
157 timeout: call.get_flag(engine_state, stack, "max-time")?,
158 full: call.has_flag(engine_state, stack, "full")?,
159 allow_errors: call.has_flag(engine_state, stack, "allow-errors")?,
160 redirect: call.get_flag(engine_state, stack, "redirect-mode")?,
161 };
162 helper(engine_state, stack, call, args)
163}
164
165fn helper(
168 engine_state: &EngineState,
169 stack: &mut Stack,
170 call: &Call,
171 args: Arguments,
172) -> Result<PipelineData, ShellError> {
173 let span = args.url.span();
174 let (requested_url, _) = http_parse_url(call, span, args.url)?;
175 let redirect_mode = http_parse_redirect_mode(args.redirect)?;
176
177 let client = http_client(args.insecure, redirect_mode, engine_state, stack)?;
178 let mut request = client.get(&requested_url);
179
180 request = request_set_timeout(args.timeout, request)?;
181 request = request_add_authorization_header(args.user, args.password, request);
182 request = request_add_custom_headers(args.headers, request)?;
183
184 let response = send_request(
185 engine_state,
186 request.clone(),
187 HttpBody::None,
188 None,
189 call.head,
190 engine_state.signals(),
191 );
192
193 let request_flags = RequestFlags {
194 raw: args.raw,
195 full: args.full,
196 allow_errors: args.allow_errors,
197 };
198
199 check_response_redirection(redirect_mode, span, &response)?;
200 request_handle_response(
201 engine_state,
202 stack,
203 span,
204 &requested_url,
205 request_flags,
206 response,
207 request,
208 )
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214
215 #[test]
216 fn test_examples() {
217 use crate::test_examples;
218
219 test_examples(HttpGet {})
220 }
221}