Skip to main content

nu_command/network/http/
head.rs

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
169// Helper function that actually goes to retrieve the resource from the url given
170fn 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            // HEAD responses are header-only, so body handling is effectively a no-op.
212            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}