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_parse_redirect_mode, http_parse_url,
5 request_add_authorization_header, request_add_custom_headers, request_set_timeout,
6 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 .param(
60 Flag::new("redirect-mode")
61 .short('R')
62 .arg(SyntaxShape::String)
63 .desc(
64 "What to do when encountering redirects. Default: 'follow'. Valid \
65 options: 'follow' ('f'), 'manual' ('m'), 'error' ('e').",
66 )
67 .completion(Completion::new_list(RedirectMode::MODES)),
68 )
69 .filter()
70 .category(Category::Network);
71
72 add_unix_socket_flag(sig)
73 }
74
75 fn description(&self) -> &str {
76 "Get the headers from a URL."
77 }
78
79 fn extra_description(&self) -> &str {
80 "Performs HTTP HEAD operation."
81 }
82
83 fn search_terms(&self) -> Vec<&str> {
84 vec!["network", "request", "curl", "wget", "headers", "header"]
85 }
86
87 fn run(
88 &self,
89 engine_state: &EngineState,
90 stack: &mut Stack,
91 call: &Call,
92 input: PipelineData,
93 ) -> Result<PipelineData, ShellError> {
94 run_head(engine_state, stack, call, input)
95 }
96
97 fn examples(&self) -> Vec<Example<'_>> {
98 vec![
99 Example {
100 description: "Get headers from example.com",
101 example: "http head https://www.example.com",
102 result: None,
103 },
104 Example {
105 description: "Get headers from example.com, with username and password",
106 example: "http head --user myuser --password mypass https://www.example.com",
107 result: None,
108 },
109 Example {
110 description: "Get headers from example.com, with custom header using a record",
111 example: "http head --headers {my-header-key: my-header-value} https://www.example.com",
112 result: None,
113 },
114 Example {
115 description: "Get headers from example.com, with custom header using a list",
116 example: "http head --headers [my-header-key-A my-header-value-A my-header-key-B my-header-value-B] https://www.example.com",
117 result: None,
118 },
119 ]
120 }
121}
122
123struct Arguments {
124 url: Value,
125 headers: Option<Value>,
126 insecure: bool,
127 user: Option<String>,
128 password: Option<String>,
129 timeout: Option<Value>,
130 redirect: Option<Spanned<String>>,
131 unix_socket: Option<Spanned<String>>,
132}
133
134fn run_head(
135 engine_state: &EngineState,
136 stack: &mut Stack,
137 call: &Call,
138 _input: PipelineData,
139) -> Result<PipelineData, ShellError> {
140 let args = Arguments {
141 url: call.req(engine_state, stack, 0)?,
142 headers: call.get_flag(engine_state, stack, "headers")?,
143 insecure: call.has_flag(engine_state, stack, "insecure")?,
144 user: call.get_flag(engine_state, stack, "user")?,
145 password: call.get_flag(engine_state, stack, "password")?,
146 timeout: call.get_flag(engine_state, stack, "max-time")?,
147 redirect: call.get_flag(engine_state, stack, "redirect-mode")?,
148 unix_socket: call.get_flag(engine_state, stack, "unix-socket")?,
149 };
150
151 helper(engine_state, stack, call, args, engine_state.signals())
152}
153
154fn helper(
156 engine_state: &EngineState,
157 stack: &mut Stack,
158 call: &Call,
159 args: Arguments,
160 signals: &Signals,
161) -> Result<PipelineData, ShellError> {
162 let span = args.url.span();
163 let Spanned {
164 item: (requested_url, _),
165 span: request_span,
166 } = http_parse_url(call, span, args.url)?;
167 let redirect_mode = http_parse_redirect_mode(args.redirect)?;
168
169 let cwd = engine_state.cwd(None)?;
170 let unix_socket_path = expand_unix_socket_path(args.unix_socket, &cwd);
171
172 let client = http_client(
173 args.insecure,
174 redirect_mode,
175 unix_socket_path,
176 engine_state,
177 stack,
178 )?;
179 let mut request = client.head(&requested_url);
180
181 request = request_set_timeout(args.timeout, request)?;
182 request = request_add_authorization_header(args.user, args.password, request);
183 request = request_add_custom_headers(args.headers, request)?;
184
185 let (response, _request_headers) =
186 send_request_no_body(request, request_span, call.head, signals);
187 let response = response?;
188 check_response_redirection(redirect_mode, span, &response)?;
189 handle_response_status(&response, redirect_mode, &requested_url, span, false)?;
190 headers_to_nu(&extract_response_headers(&response), span)
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 #[test]
198 fn test_examples() {
199 use crate::test_examples;
200
201 test_examples(HttpHead {})
202 }
203}