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