nu_command/network/http/
options.rs

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