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