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
144fn 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 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}