1use crate::network::http::client::{
2 check_response_redirection, extract_response_headers, handle_response_status, headers_to_nu,
3 http_client, http_parse_redirect_mode, http_parse_url, request_add_authorization_header,
4 request_add_custom_headers, request_set_timeout, send_request_no_body,
5};
6use nu_engine::command_prelude::*;
7use nu_protocol::Signals;
8
9use super::client::RedirectMode;
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 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 "insecure",
54 "allow insecure server connections when using SSL",
55 Some('k'),
56 )
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
71 fn description(&self) -> &str {
72 "Get the headers from a URL."
73 }
74
75 fn extra_description(&self) -> &str {
76 "Performs HTTP HEAD operation."
77 }
78
79 fn search_terms(&self) -> Vec<&str> {
80 vec!["network", "request", "curl", "wget", "headers", "header"]
81 }
82
83 fn run(
84 &self,
85 engine_state: &EngineState,
86 stack: &mut Stack,
87 call: &Call,
88 input: PipelineData,
89 ) -> Result<PipelineData, ShellError> {
90 run_head(engine_state, stack, call, input)
91 }
92
93 fn examples(&self) -> Vec<Example<'_>> {
94 vec![
95 Example {
96 description: "Get headers from example.com",
97 example: "http head https://www.example.com",
98 result: None,
99 },
100 Example {
101 description: "Get headers from example.com, with username and password",
102 example: "http head --user myuser --password mypass https://www.example.com",
103 result: None,
104 },
105 Example {
106 description: "Get headers from example.com, with custom header using a record",
107 example: "http head --headers {my-header-key: my-header-value} https://www.example.com",
108 result: None,
109 },
110 Example {
111 description: "Get headers from example.com, with custom header using a list",
112 example: "http head --headers [my-header-key-A my-header-value-A my-header-key-B my-header-value-B] https://www.example.com",
113 result: None,
114 },
115 ]
116 }
117}
118
119struct Arguments {
120 url: Value,
121 headers: Option<Value>,
122 insecure: bool,
123 user: Option<String>,
124 password: Option<String>,
125 timeout: Option<Value>,
126 redirect: Option<Spanned<String>>,
127}
128
129fn run_head(
130 engine_state: &EngineState,
131 stack: &mut Stack,
132 call: &Call,
133 _input: PipelineData,
134) -> Result<PipelineData, ShellError> {
135 let args = Arguments {
136 url: call.req(engine_state, stack, 0)?,
137 headers: call.get_flag(engine_state, stack, "headers")?,
138 insecure: call.has_flag(engine_state, stack, "insecure")?,
139 user: call.get_flag(engine_state, stack, "user")?,
140 password: call.get_flag(engine_state, stack, "password")?,
141 timeout: call.get_flag(engine_state, stack, "max-time")?,
142 redirect: call.get_flag(engine_state, stack, "redirect-mode")?,
143 };
144
145 helper(engine_state, stack, call, args, engine_state.signals())
146}
147
148fn helper(
150 engine_state: &EngineState,
151 stack: &mut Stack,
152 call: &Call,
153 args: Arguments,
154 signals: &Signals,
155) -> Result<PipelineData, ShellError> {
156 let span = args.url.span();
157 let (requested_url, _) = http_parse_url(call, span, args.url)?;
158 let redirect_mode = http_parse_redirect_mode(args.redirect)?;
159
160 let client = http_client(args.insecure, redirect_mode, engine_state, stack)?;
161 let mut request = client.head(&requested_url);
162
163 request = request_set_timeout(args.timeout, request)?;
164 request = request_add_authorization_header(args.user, args.password, request);
165 request = request_add_custom_headers(args.headers, request)?;
166
167 let (response, _request_headers) = send_request_no_body(request, call.head, signals);
168 let response = response?;
169 check_response_redirection(redirect_mode, span, &response)?;
170 handle_response_status(&response, redirect_mode, &requested_url, span, false)?;
171 headers_to_nu(&extract_response_headers(&response), span)
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177
178 #[test]
179 fn test_examples() {
180 use crate::test_examples;
181
182 test_examples(HttpHead {})
183 }
184}