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