1use crate::network::http::client::{
2 http_client, http_parse_url, request_add_authorization_header, request_add_custom_headers,
3 request_handle_response, request_set_timeout, send_request, RedirectMode, RequestFlags,
4};
5use nu_engine::command_prelude::*;
6
7use super::client::HttpBody;
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 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
64 fn description(&self) -> &str {
65 "Requests permitted communication options for a given URL."
66 }
67
68 fn extra_description(&self) -> &str {
69 "Performs an HTTP OPTIONS request. Most commonly used for making CORS preflight requests."
70 }
71
72 fn search_terms(&self) -> Vec<&str> {
73 vec!["network", "fetch", "pull", "request", "curl", "wget"]
74 }
75
76 fn run(
77 &self,
78 engine_state: &EngineState,
79 stack: &mut Stack,
80 call: &Call,
81 input: PipelineData,
82 ) -> Result<PipelineData, ShellError> {
83 run_get(engine_state, stack, call, input)
84 }
85
86 fn examples(&self) -> Vec<Example> {
87 vec![
88 Example {
89 description: "Get options from example.com",
90 example: "http options https://www.example.com",
91 result: None,
92 },
93 Example {
94 description: "Get options from example.com, with username and password",
95 example: "http options --user myuser --password mypass https://www.example.com",
96 result: None,
97 },
98 Example {
99 description: "Get options from example.com, with custom header",
100 example: "http options --headers [my-header-key my-header-value] https://www.example.com",
101 result: None,
102 },
103 Example {
104 description: "Get options from example.com, with custom headers",
105 example: "http options --headers [my-header-key-A my-header-value-A my-header-key-B my-header-value-B] https://www.example.com",
106 result: None,
107 },
108 Example {
109 description: "Simulate a browser cross-origin preflight request from www.example.com to media.example.com",
110 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]",
111 result: None,
112 },
113 ]
114 }
115}
116
117struct Arguments {
118 url: Value,
119 headers: Option<Value>,
120 insecure: bool,
121 user: Option<String>,
122 password: Option<String>,
123 timeout: Option<Value>,
124 allow_errors: bool,
125}
126
127fn run_get(
128 engine_state: &EngineState,
129 stack: &mut Stack,
130 call: &Call,
131 _input: PipelineData,
132) -> Result<PipelineData, ShellError> {
133 let args = Arguments {
134 url: call.req(engine_state, stack, 0)?,
135 headers: call.get_flag(engine_state, stack, "headers")?,
136 insecure: call.has_flag(engine_state, stack, "insecure")?,
137 user: call.get_flag(engine_state, stack, "user")?,
138 password: call.get_flag(engine_state, stack, "password")?,
139 timeout: call.get_flag(engine_state, stack, "max-time")?,
140 allow_errors: call.has_flag(engine_state, stack, "allow-errors")?,
141 };
142 helper(engine_state, stack, call, args)
143}
144
145fn helper(
148 engine_state: &EngineState,
149 stack: &mut Stack,
150 call: &Call,
151 args: Arguments,
152) -> Result<PipelineData, ShellError> {
153 let span = args.url.span();
154 let (requested_url, _) = http_parse_url(call, span, args.url)?;
155
156 let client = http_client(args.insecure, RedirectMode::Follow, engine_state, stack)?;
157 let mut request = client.request("OPTIONS", &requested_url);
158
159 request = request_set_timeout(args.timeout, request)?;
160 request = request_add_authorization_header(args.user, args.password, request);
161 request = request_add_custom_headers(args.headers, request)?;
162
163 let response = send_request(
164 engine_state,
165 request.clone(),
166 HttpBody::None,
167 None,
168 call.head,
169 engine_state.signals(),
170 );
171
172 let request_flags = RequestFlags {
176 raw: true,
177 full: true,
178 allow_errors: args.allow_errors,
179 };
180
181 request_handle_response(
182 engine_state,
183 stack,
184 span,
185 &requested_url,
186 request_flags,
187 response,
188 request,
189 )
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 #[test]
197 fn test_examples() {
198 use crate::test_examples;
199
200 test_examples(HttpOptions {})
201 }
202}