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