Skip to main content

nu_command/network/http/
options.rs

1use crate::network::http::client::{
2    RedirectMode, RequestFlags, RequestMetadata, add_unix_socket_flag, expand_unix_socket_path,
3    http_client, http_client_pool, http_parse_url, request_add_authorization_header,
4    request_add_custom_headers, request_handle_response, 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        let sig = 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            .switch("pool", "Using a global pool as a client.", None)
60            .filter()
61            .category(Category::Network);
62
63        add_unix_socket_flag(sig)
64    }
65
66    fn description(&self) -> &str {
67        "Requests permitted communication options for a given URL."
68    }
69
70    fn extra_description(&self) -> &str {
71        "Performs an HTTP OPTIONS request. Most commonly used for making CORS preflight requests."
72    }
73
74    fn search_terms(&self) -> Vec<&str> {
75        vec!["network", "fetch", "pull", "request", "curl", "wget"]
76    }
77
78    fn run(
79        &self,
80        engine_state: &EngineState,
81        stack: &mut Stack,
82        call: &Call,
83        input: PipelineData,
84    ) -> Result<PipelineData, ShellError> {
85        run_get(engine_state, stack, call, input)
86    }
87
88    fn examples(&self) -> Vec<Example<'_>> {
89        vec![
90            Example {
91                description: "Get options from example.com.",
92                example: "http options https://www.example.com",
93                result: None,
94            },
95            Example {
96                description: "Get options from example.com, with username and password.",
97                example: "http options --user myuser --password mypass https://www.example.com",
98                result: None,
99            },
100            Example {
101                description: "Get options from example.com, with custom header using a record.",
102                example: "http options --headers {my-header-key: my-header-value} https://www.example.com",
103                result: None,
104            },
105            Example {
106                description: "Get options from example.com, with custom headers using a list.",
107                example: "http options --headers [my-header-key-A my-header-value-A my-header-key-B my-header-value-B] https://www.example.com",
108                result: None,
109            },
110            Example {
111                description: "Simulate a browser cross-origin preflight request from www.example.com to media.example.com.",
112                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]",
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    allow_errors: bool,
127    unix_socket: Option<Spanned<String>>,
128    pool: bool,
129}
130
131fn run_get(
132    engine_state: &EngineState,
133    stack: &mut Stack,
134    call: &Call,
135    _input: PipelineData,
136) -> Result<PipelineData, ShellError> {
137    let args = Arguments {
138        url: call.req(engine_state, stack, 0)?,
139        headers: call.get_flag(engine_state, stack, "headers")?,
140        insecure: call.has_flag(engine_state, stack, "insecure")?,
141        user: call.get_flag(engine_state, stack, "user")?,
142        password: call.get_flag(engine_state, stack, "password")?,
143        timeout: call.get_flag(engine_state, stack, "max-time")?,
144        allow_errors: call.has_flag(engine_state, stack, "allow-errors")?,
145        unix_socket: call.get_flag(engine_state, stack, "unix-socket")?,
146        pool: call.has_flag(engine_state, stack, "pool")?,
147    };
148    helper(engine_state, stack, call, args)
149}
150
151// Helper function that actually goes to retrieve the resource from the url given
152// The Option<String> return a possible file extension which can be used in AutoConvert commands
153fn helper(
154    engine_state: &EngineState,
155    stack: &mut Stack,
156    call: &Call,
157    args: Arguments,
158) -> Result<PipelineData, ShellError> {
159    let span = args.url.span();
160    let Spanned {
161        item: (requested_url, _),
162        span: request_span,
163    } = http_parse_url(call, span, args.url)?;
164    let redirect_mode = RedirectMode::Follow;
165
166    let cwd = engine_state.cwd(None)?;
167    let unix_socket_path = expand_unix_socket_path(args.unix_socket, &cwd);
168
169    let mut request = if args.pool {
170        http_client_pool(engine_state, stack)?.options(&requested_url)
171    } else {
172        let client = http_client(
173            args.insecure,
174            redirect_mode,
175            unix_socket_path,
176            engine_state,
177            stack,
178        )?;
179        client.options(&requested_url)
180    };
181    request = request_set_timeout(args.timeout, request)?;
182    request = request_add_authorization_header(args.user, args.password, request);
183    request = request_add_custom_headers(args.headers, request)?;
184
185    let (response, request_headers) =
186        send_request_no_body(request, request_span, call.head, engine_state.signals());
187
188    let response = response?;
189
190    // http options' response always showed in header, so we set full to true.
191    // And `raw` is useless too because options method doesn't return body, here we set to true
192    // too.
193    let request_flags = RequestFlags {
194        raw: true,
195        full: true,
196        allow_errors: args.allow_errors,
197    };
198
199    request_handle_response(
200        engine_state,
201        stack,
202        RequestMetadata {
203            requested_url: &requested_url,
204            span,
205            headers: request_headers,
206            redirect_mode,
207            flags: request_flags,
208        },
209        response,
210    )
211}
212
213#[cfg(test)]
214mod tests {
215    use super::*;
216
217    #[test]
218    fn test_examples() {
219        use crate::test_examples;
220
221        test_examples(HttpOptions {})
222    }
223}