1use crate::network::http::client::{
2 RedirectMode, RequestFlags, RequestMetadata, add_unix_socket_flag, check_response_redirection,
3 discard_response_body, expand_unix_socket_path, extract_response_headers,
4 handle_response_status, headers_to_nu, http_client, http_client_pool, http_parse_url,
5 request_add_authorization_header, request_add_custom_headers, request_handle_response,
6 request_set_timeout, send_request_no_body,
7};
8use nu_engine::command_prelude::*;
9
10#[derive(Clone)]
11pub struct HttpOptions;
12
13impl Command for HttpOptions {
14 fn name(&self) -> &str {
15 "http options"
16 }
17
18 fn signature(&self) -> Signature {
19 let sig = Signature::build("http options")
20 .input_output_types(vec![(Type::Nothing, Type::Any)])
21 .allow_variants_without_examples(true)
22 .required(
23 "URL",
24 SyntaxShape::String,
25 "The URL to fetch the options from.",
26 )
27 .named(
28 "user",
29 SyntaxShape::Any,
30 "The username when authenticating.",
31 Some('u'),
32 )
33 .named(
34 "password",
35 SyntaxShape::Any,
36 "The password when authenticating.",
37 Some('p'),
38 )
39 .named(
40 "max-time",
41 SyntaxShape::Duration,
42 "Max duration before timeout occurs.",
43 Some('m'),
44 )
45 .named(
46 "headers",
47 SyntaxShape::Any,
48 "Custom headers you want to add.",
49 Some('H'),
50 )
51 .switch(
52 "full",
53 "Returns the full response instead of only the headers.",
54 Some('f'),
55 )
56 .switch(
57 "insecure",
58 "Allow insecure server connections when using SSL.",
59 Some('k'),
60 )
61 .switch(
62 "allow-errors",
63 "Do not fail if the server returns an error code.",
64 Some('e'),
65 )
66 .switch("pool", "Using a global pool as a client.", None)
67 .filter()
68 .category(Category::Network);
69
70 add_unix_socket_flag(sig)
71 }
72
73 fn description(&self) -> &str {
74 "Requests permitted communication options for a given URL."
75 }
76
77 fn extra_description(&self) -> &str {
78 "Performs an HTTP OPTIONS request. Most commonly used for making CORS preflight requests."
79 }
80
81 fn search_terms(&self) -> Vec<&str> {
82 vec!["network", "fetch", "pull", "request", "curl", "wget"]
83 }
84
85 fn run(
86 &self,
87 engine_state: &EngineState,
88 stack: &mut Stack,
89 call: &Call,
90 input: PipelineData,
91 ) -> Result<PipelineData, ShellError> {
92 run_get(engine_state, stack, call, input)
93 }
94
95 fn examples(&self) -> Vec<Example<'_>> {
96 vec![
97 Example {
98 description: "Get options from example.com.",
99 example: "http options https://www.example.com",
100 result: None,
101 },
102 Example {
103 description: "Get the full OPTIONS response record (status, headers, etc.).",
104 example: "http options --full https://www.example.com",
105 result: None,
106 },
107 Example {
108 description: "Get options from example.com, with username and password.",
109 example: "http options --user myuser --password mypass https://www.example.com",
110 result: None,
111 },
112 Example {
113 description: "Get options from example.com, with custom header using a record.",
114 example: "http options --headers {my-header-key: my-header-value} https://www.example.com",
115 result: None,
116 },
117 Example {
118 description: "Get options from example.com, with custom headers using a list.",
119 example: "http options --headers [my-header-key-A my-header-value-A my-header-key-B my-header-value-B] https://www.example.com",
120 result: None,
121 },
122 Example {
123 description: "Simulate a browser cross-origin preflight request from www.example.com to media.example.com.",
124 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]",
125 result: None,
126 },
127 ]
128 }
129}
130
131struct Arguments {
132 url: Value,
133 headers: Option<Value>,
134 full: bool,
135 insecure: bool,
136 user: Option<String>,
137 password: Option<String>,
138 timeout: Option<Value>,
139 allow_errors: bool,
140 unix_socket: Option<Spanned<String>>,
141 pool: bool,
142}
143
144fn run_get(
145 engine_state: &EngineState,
146 stack: &mut Stack,
147 call: &Call,
148 _input: PipelineData,
149) -> Result<PipelineData, ShellError> {
150 let args = Arguments {
151 url: call.req(engine_state, stack, 0)?,
152 headers: call.get_flag(engine_state, stack, "headers")?,
153 full: call.has_flag(engine_state, stack, "full")?,
154 insecure: call.has_flag(engine_state, stack, "insecure")?,
155 user: call.get_flag(engine_state, stack, "user")?,
156 password: call.get_flag(engine_state, stack, "password")?,
157 timeout: call.get_flag(engine_state, stack, "max-time")?,
158 allow_errors: call.has_flag(engine_state, stack, "allow-errors")?,
159 unix_socket: call.get_flag(engine_state, stack, "unix-socket")?,
160 pool: call.has_flag(engine_state, stack, "pool")?,
161 };
162 helper(engine_state, stack, call, args)
163}
164
165fn helper(
168 engine_state: &EngineState,
169 stack: &mut Stack,
170 call: &Call,
171 args: Arguments,
172) -> Result<PipelineData, ShellError> {
173 let span = args.url.span();
174 let Spanned {
175 item: (requested_url, _),
176 span: request_span,
177 } = http_parse_url(call, span, args.url)?;
178 let redirect_mode = RedirectMode::Follow;
179
180 let cwd = engine_state.cwd(None)?;
181 let unix_socket_path = expand_unix_socket_path(args.unix_socket, &cwd);
182
183 let mut request = if args.pool {
184 http_client_pool(engine_state, stack)?.options(&requested_url)
185 } else {
186 let client = http_client(
187 args.insecure,
188 redirect_mode,
189 unix_socket_path,
190 engine_state,
191 stack,
192 )?;
193 client.options(&requested_url)
194 };
195 request = request_set_timeout(args.timeout, request)?;
196 request = request_add_authorization_header(args.user, args.password, request);
197 request = request_add_custom_headers(args.headers, request)?;
198
199 let (response, request_headers) =
200 send_request_no_body(request, request_span, call.head, engine_state.signals());
201
202 let response = response?;
203 check_response_redirection(redirect_mode, span, &response)?;
204
205 if args.full {
206 let request_flags = RequestFlags {
208 raw: true,
209 full: true,
210 allow_errors: args.allow_errors,
211 };
212
213 return request_handle_response(
214 engine_state,
215 stack,
216 RequestMetadata {
217 requested_url: &requested_url,
218 span,
219 headers: request_headers,
220 redirect_mode,
221 flags: request_flags,
222 },
223 response,
224 );
225 }
226
227 let response_headers = extract_response_headers(&response);
228 handle_response_status(
229 &response,
230 redirect_mode,
231 &requested_url,
232 span,
233 args.allow_errors,
234 )?;
235 discard_response_body(response, span)?;
239 headers_to_nu(&response_headers, span)
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245
246 #[test]
247 fn test_examples() -> nu_test_support::Result {
248 nu_test_support::test().examples(HttpOptions)
249 }
250}