1use crate::network::http::client::{
2 RedirectMode, add_unix_socket_flag, check_response_redirection, expand_unix_socket_path,
3 extract_response_headers, handle_response_status, headers_to_nu, http_client, http_client_pool,
4 http_parse_redirect_mode, http_parse_url, request_add_authorization_header,
5 request_add_custom_headers, request_set_timeout, send_request_no_body,
6};
7use nu_engine::command_prelude::*;
8use nu_protocol::Signals;
9
10#[derive(Clone)]
11pub struct HttpHead;
12
13impl Command for HttpHead {
14 fn name(&self) -> &str {
15 "http head"
16 }
17
18 fn signature(&self) -> Signature {
19 let sig = Signature::build("http head")
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 "insecure",
53 "Allow insecure server connections when using SSL.",
54 Some('k'),
55 )
56 .switch("pool", "Using a global pool as a client.", None)
57 .param(
58 Flag::new("redirect-mode")
59 .short('R')
60 .arg(SyntaxShape::String)
61 .desc(
62 "What to do when encountering redirects. Default: 'follow'. Valid \
63 options: 'follow' ('f'), 'manual' ('m'), 'error' ('e').",
64 )
65 .completion(Completion::new_list(RedirectMode::MODES)),
66 )
67 .filter()
68 .category(Category::Network);
69
70 add_unix_socket_flag(sig)
71 }
72
73 fn description(&self) -> &str {
74 "Get the headers from a URL."
75 }
76
77 fn extra_description(&self) -> &str {
78 "Performs HTTP HEAD operation."
79 }
80
81 fn search_terms(&self) -> Vec<&str> {
82 vec!["network", "request", "curl", "wget", "headers", "header"]
83 }
84
85 fn run(
86 &self,
87 engine_state: &EngineState,
88 stack: &mut Stack,
89 call: &Call,
90 input: PipelineData,
91 ) -> Result<PipelineData, ShellError> {
92 run_head(engine_state, stack, call, input)
93 }
94
95 fn examples(&self) -> Vec<Example<'_>> {
96 vec![
97 Example {
98 description: "Get headers from example.com.",
99 example: "http head https://www.example.com",
100 result: None,
101 },
102 Example {
103 description: "Get headers from example.com, with username and password.",
104 example: "http head --user myuser --password mypass https://www.example.com",
105 result: None,
106 },
107 Example {
108 description: "Get headers from example.com, with custom header using a record.",
109 example: "http head --headers {my-header-key: my-header-value} https://www.example.com",
110 result: None,
111 },
112 Example {
113 description: "Get headers from example.com, with custom header using a list.",
114 example: "http head --headers [my-header-key-A my-header-value-A my-header-key-B my-header-value-B] https://www.example.com",
115 result: None,
116 },
117 ]
118 }
119}
120
121struct Arguments {
122 url: Value,
123 headers: Option<Value>,
124 insecure: bool,
125 user: Option<String>,
126 password: Option<String>,
127 timeout: Option<Value>,
128 redirect: Option<Spanned<String>>,
129 unix_socket: Option<Spanned<String>>,
130 pool: bool,
131}
132
133fn run_head(
134 engine_state: &EngineState,
135 stack: &mut Stack,
136 call: &Call,
137 _input: PipelineData,
138) -> Result<PipelineData, ShellError> {
139 let args = Arguments {
140 url: call.req(engine_state, stack, 0)?,
141 headers: call.get_flag(engine_state, stack, "headers")?,
142 insecure: call.has_flag(engine_state, stack, "insecure")?,
143 user: call.get_flag(engine_state, stack, "user")?,
144 password: call.get_flag(engine_state, stack, "password")?,
145 timeout: call.get_flag(engine_state, stack, "max-time")?,
146 redirect: call.get_flag(engine_state, stack, "redirect-mode")?,
147 unix_socket: call.get_flag(engine_state, stack, "unix-socket")?,
148 pool: call.has_flag(engine_state, stack, "pool")?,
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 mut request = if args.pool {
173 http_client_pool(engine_state, stack)?.head(&requested_url)
174 } else {
175 let client = http_client(
176 args.insecure,
177 redirect_mode,
178 unix_socket_path,
179 engine_state,
180 stack,
181 )?;
182 client.head(&requested_url)
183 };
184
185 request = request_set_timeout(args.timeout, request)?;
186 request = request_add_authorization_header(args.user, args.password, request);
187 request = request_add_custom_headers(args.headers, request)?;
188
189 let (response, _request_headers) =
190 send_request_no_body(request, request_span, call.head, signals);
191 let response = response?;
192 check_response_redirection(redirect_mode, span, &response)?;
193 handle_response_status(&response, redirect_mode, &requested_url, span, false)?;
194 headers_to_nu(&extract_response_headers(&response), span)
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200
201 #[test]
202 fn test_examples() {
203 use crate::test_examples;
204
205 test_examples(HttpHead {})
206 }
207}