nu_command/network/http/
options.rs

1use crate::network::http::client::{
2    RedirectMode, RequestFlags, RequestMetadata, http_client, http_parse_url,
3    request_add_authorization_header, request_add_custom_headers, request_handle_response,
4    request_set_timeout, send_request_no_body,
5};
6use nu_engine::command_prelude::*;
7
8#[derive(Clone)]
9pub struct HttpOptions;
10
11impl Command for HttpOptions {
12    fn name(&self) -> &str {
13        "http options"
14    }
15
16    fn signature(&self) -> Signature {
17        Signature::build("http options")
18            .input_output_types(vec![(Type::Nothing, Type::Any)])
19            .allow_variants_without_examples(true)
20            .required(
21                "URL",
22                SyntaxShape::String,
23                "The URL to fetch the options from.",
24            )
25            .named(
26                "user",
27                SyntaxShape::Any,
28                "the username when authenticating",
29                Some('u'),
30            )
31            .named(
32                "password",
33                SyntaxShape::Any,
34                "the password when authenticating",
35                Some('p'),
36            )
37            .named(
38                "max-time",
39                SyntaxShape::Duration,
40                "max duration before timeout occurs",
41                Some('m'),
42            )
43            .named(
44                "headers",
45                SyntaxShape::Any,
46                "custom headers you want to add ",
47                Some('H'),
48            )
49            .switch(
50                "insecure",
51                "allow insecure server connections when using SSL",
52                Some('k'),
53            )
54            .switch(
55                "allow-errors",
56                "do not fail if the server returns an error code",
57                Some('e'),
58            )
59            .filter()
60            .category(Category::Network)
61    }
62
63    fn description(&self) -> &str {
64        "Requests permitted communication options for a given URL."
65    }
66
67    fn extra_description(&self) -> &str {
68        "Performs an HTTP OPTIONS request. Most commonly used for making CORS preflight requests."
69    }
70
71    fn search_terms(&self) -> Vec<&str> {
72        vec!["network", "fetch", "pull", "request", "curl", "wget"]
73    }
74
75    fn run(
76        &self,
77        engine_state: &EngineState,
78        stack: &mut Stack,
79        call: &Call,
80        input: PipelineData,
81    ) -> Result<PipelineData, ShellError> {
82        run_get(engine_state, stack, call, input)
83    }
84
85    fn examples(&self) -> Vec<Example<'_>> {
86        vec![
87            Example {
88                description: "Get options from example.com",
89                example: "http options https://www.example.com",
90                result: None,
91            },
92            Example {
93                description: "Get options from example.com, with username and password",
94                example: "http options --user myuser --password mypass https://www.example.com",
95                result: None,
96            },
97            Example {
98                description: "Get options from example.com, with custom header using a record",
99                example: "http options --headers {my-header-key: my-header-value} https://www.example.com",
100                result: None,
101            },
102            Example {
103                description: "Get options from example.com, with custom headers using a list",
104                example: "http options --headers [my-header-key-A my-header-value-A my-header-key-B my-header-value-B] https://www.example.com",
105                result: None,
106            },
107            Example {
108                description: "Simulate a browser cross-origin preflight request from www.example.com to media.example.com",
109                example: "http options https://media.example.com/api/ --headers [Origin https://www.example.com Access-Control-Request-Headers \"Content-Type, X-Custom-Header\" Access-Control-Request-Method GET]",
110                result: None,
111            },
112        ]
113    }
114}
115
116struct Arguments {
117    url: Value,
118    headers: Option<Value>,
119    insecure: bool,
120    user: Option<String>,
121    password: Option<String>,
122    timeout: Option<Value>,
123    allow_errors: bool,
124}
125
126fn run_get(
127    engine_state: &EngineState,
128    stack: &mut Stack,
129    call: &Call,
130    _input: PipelineData,
131) -> Result<PipelineData, ShellError> {
132    let args = Arguments {
133        url: call.req(engine_state, stack, 0)?,
134        headers: call.get_flag(engine_state, stack, "headers")?,
135        insecure: call.has_flag(engine_state, stack, "insecure")?,
136        user: call.get_flag(engine_state, stack, "user")?,
137        password: call.get_flag(engine_state, stack, "password")?,
138        timeout: call.get_flag(engine_state, stack, "max-time")?,
139        allow_errors: call.has_flag(engine_state, stack, "allow-errors")?,
140    };
141    helper(engine_state, stack, call, args)
142}
143
144// Helper function that actually goes to retrieve the resource from the url given
145// The Option<String> return a possible file extension which can be used in AutoConvert commands
146fn helper(
147    engine_state: &EngineState,
148    stack: &mut Stack,
149    call: &Call,
150    args: Arguments,
151) -> Result<PipelineData, ShellError> {
152    let span = args.url.span();
153    let (requested_url, _) = http_parse_url(call, span, args.url)?;
154    let redirect_mode = RedirectMode::Follow;
155    let client = http_client(args.insecure, redirect_mode, engine_state, stack)?;
156    let mut request = client.options(&requested_url);
157
158    request = request_set_timeout(args.timeout, request)?;
159    request = request_add_authorization_header(args.user, args.password, request);
160    request = request_add_custom_headers(args.headers, request)?;
161
162    let (response, request_headers) =
163        send_request_no_body(request, call.head, engine_state.signals());
164
165    let response = response?;
166
167    // http options' response always showed in header, so we set full to true.
168    // And `raw` is useless too because options method doesn't return body, here we set to true
169    // too.
170    let request_flags = RequestFlags {
171        raw: true,
172        full: true,
173        allow_errors: args.allow_errors,
174    };
175
176    request_handle_response(
177        engine_state,
178        stack,
179        RequestMetadata {
180            requested_url: &requested_url,
181            span,
182            headers: request_headers,
183            redirect_mode,
184            flags: request_flags,
185        },
186        response,
187    )
188}
189
190#[cfg(test)]
191mod tests {
192    use super::*;
193
194    #[test]
195    fn test_examples() {
196        use crate::test_examples;
197
198        test_examples(HttpOptions {})
199    }
200}