nu_command/network/http/
options.rs

1use crate::network::http::client::add_unix_socket_flag;
2use crate::network::http::client::{
3    RedirectMode, RequestFlags, RequestMetadata, expand_unix_socket_path, http_client,
4    http_parse_url, request_add_authorization_header, request_add_custom_headers,
5    request_handle_response, request_set_timeout, send_request_no_body,
6};
7use nu_engine::command_prelude::*;
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        let sig = 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        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}
129
130fn run_get(
131    engine_state: &EngineState,
132    stack: &mut Stack,
133    call: &Call,
134    _input: PipelineData,
135) -> Result<PipelineData, ShellError> {
136    let args = Arguments {
137        url: call.req(engine_state, stack, 0)?,
138        headers: call.get_flag(engine_state, stack, "headers")?,
139        insecure: call.has_flag(engine_state, stack, "insecure")?,
140        user: call.get_flag(engine_state, stack, "user")?,
141        password: call.get_flag(engine_state, stack, "password")?,
142        timeout: call.get_flag(engine_state, stack, "max-time")?,
143        allow_errors: call.has_flag(engine_state, stack, "allow-errors")?,
144        unix_socket: call.get_flag(engine_state, stack, "unix-socket")?,
145    };
146    helper(engine_state, stack, call, args)
147}
148
149// Helper function that actually goes to retrieve the resource from the url given
150// The Option<String> return a possible file extension which can be used in AutoConvert commands
151fn helper(
152    engine_state: &EngineState,
153    stack: &mut Stack,
154    call: &Call,
155    args: Arguments,
156) -> Result<PipelineData, ShellError> {
157    let span = args.url.span();
158    let Spanned {
159        item: (requested_url, _),
160        span: request_span,
161    } = http_parse_url(call, span, args.url)?;
162    let redirect_mode = RedirectMode::Follow;
163
164    let cwd = engine_state.cwd(None)?;
165    let unix_socket_path = expand_unix_socket_path(args.unix_socket, &cwd);
166
167    let client = http_client(
168        args.insecure,
169        redirect_mode,
170        unix_socket_path,
171        engine_state,
172        stack,
173    )?;
174    let mut request = client.options(&requested_url);
175
176    request = request_set_timeout(args.timeout, request)?;
177    request = request_add_authorization_header(args.user, args.password, request);
178    request = request_add_custom_headers(args.headers, request)?;
179
180    let (response, request_headers) =
181        send_request_no_body(request, request_span, call.head, engine_state.signals());
182
183    let response = response?;
184
185    // http options' response always showed in header, so we set full to true.
186    // And `raw` is useless too because options method doesn't return body, here we set to true
187    // too.
188    let request_flags = RequestFlags {
189        raw: true,
190        full: true,
191        allow_errors: args.allow_errors,
192    };
193
194    request_handle_response(
195        engine_state,
196        stack,
197        RequestMetadata {
198            requested_url: &requested_url,
199            span,
200            headers: request_headers,
201            redirect_mode,
202            flags: request_flags,
203        },
204        response,
205    )
206}
207
208#[cfg(test)]
209mod tests {
210    use super::*;
211
212    #[test]
213    fn test_examples() {
214        use crate::test_examples;
215
216        test_examples(HttpOptions {})
217    }
218}