1use std::env;
2use crate::entry_point::Config;
3
4#[cfg(test)]
5mod tests;
6
7pub fn override_environment_variables_from_command_line_args() {
8 println!("\n Start of Reading Command Line Arguments Section");
9
10 let args = env::args().collect();
11 let params = CommandLineArgument::get_command_line_arg_list();
12 CommandLineArgument::_parse(args, params);
13
14 println!(" End of Reading Command Line Arguments\n");
15}
16
17pub fn informational_flag_output(args: &[String]) -> Option<String> {
23 let flags = args.iter().skip(1);
24 for arg in flags {
25 match arg.as_str() {
26 "--version" | "-V" => {
27 return Some(format!("rws {}\n", env!("CARGO_PKG_VERSION")));
28 }
29 "--help" => return Some(help_text()),
30 _ => {}
31 }
32 }
33 None
34}
35
36fn help_text() -> String {
37 let mut text = format!(
38 "rws {} — HTTP/1.1, HTTP/2 and HTTP/3 web server and reverse proxy\n\n\
39 Usage: rws [OPTIONS]\n\n\
40 Serves the current directory on http://0.0.0.0:7878 by default. Drop an\n\
41 rws.config.toml with [[route]]/[[upstream]] sections in the working\n\
42 directory to run as a config-driven reverse proxy instead.\n\n\
43 Options (each also settable via the environment variable shown):\n",
44 env!("CARGO_PKG_VERSION")
45 );
46 for argument in CommandLineArgument::get_command_line_arg_list() {
47 let flag = if argument.short_form.is_empty() {
48 format!(" --{}=<value>", argument.long_form)
49 } else {
50 format!("-{}, --{}=<value>", argument.short_form, argument.long_form)
51 };
52 let hint = argument._hint.unwrap_or_default();
53 text.push_str(&format!(" {:<44} {}\n {:<44} [env: {}]\n", flag, hint, "", argument.environment_variable));
54 }
55 text.push_str(" --check[=<file>] Validate rws.config.toml (or <file>) and exit:\n");
56 text.push_str(" 0 if it has no errors, 1 otherwise\n");
57 text.push_str(" trust [--print] | untrust Put rws's local CA (tls = \"internal\") in the\n");
58 text.push_str(" system trust store, or take it out\n");
59 text.push_str(" ctl <command> Drive a running server's admin API\n");
60 text.push_str(" (rws ctl help; also installed as rwsctl)\n");
61 text.push_str(" -V, --version Print version and exit\n");
62 text.push_str(" --help Print this help and exit\n");
63 text.push_str(" --windows-service=<dir> (Windows) Run as a service serving <dir>\n\n");
64 text.push_str("Docs: https://rws8.tech\n");
65 text
66}
67
68pub struct CommandLineArgument {
69 pub short_form: String,
70 pub long_form: String,
71 pub environment_variable: String,
72 pub _hint: Option<String>,
73}
74
75impl CommandLineArgument {
76 pub fn get_command_line_arg_list() -> Vec<CommandLineArgument> {
77 let mut argument_list : Vec<CommandLineArgument> = vec![];
78
79 let argument = CommandLineArgument {
80 short_form: "p".to_string(),
81 long_form: "port".to_string(),
82 environment_variable: Config::RWS_CONFIG_PORT.to_string(),
83 _hint: Some("Port".to_string())
84 };
85 argument_list.push(argument);
86
87 let argument = CommandLineArgument {
88 short_form: "i".to_string(),
89 long_form: "ip".to_string(),
90 environment_variable: Config::RWS_CONFIG_IP.to_string(),
91 _hint: Some("IP or domain".to_string())
92 };
93 argument_list.push(argument);
94
95 let argument = CommandLineArgument {
96 short_form: "t".to_string(),
97 long_form: "thread-count".to_string(),
98 environment_variable: Config::RWS_CONFIG_THREAD_COUNT.to_string(),
99 _hint: Some("Number of threads".to_string())
100 };
101 argument_list.push(argument);
102
103
104 let argument = CommandLineArgument {
105 short_form: "a".to_string(),
106 long_form: "cors-allow-all".to_string(),
107 environment_variable: Config::RWS_CONFIG_CORS_ALLOW_ALL.to_string(),
108 _hint: Some("If set to true, will allow all CORS requests, other CORS properties will be ignored".to_string())
109 };
110 argument_list.push(argument);
111
112 let argument = CommandLineArgument {
113 short_form: "o".to_string(),
114 long_form: "cors-allow-origins".to_string(),
115 environment_variable: Config::RWS_CONFIG_CORS_ALLOW_ORIGINS.to_string(),
116 _hint: Some("Comma separated list of allowed origins, example: https://foo.example,https://bar.example".to_string())
117 };
118 argument_list.push(argument);
119
120 let argument = CommandLineArgument {
121 short_form: "m".to_string(),
122 long_form: "cors-allow-methods".to_string(),
123 environment_variable: Config::RWS_CONFIG_CORS_ALLOW_METHODS.to_string(),
124 _hint: Some("Comma separated list of allowed methods, example: POST,PUT".to_string())
125 };
126 argument_list.push(argument);
127
128 let argument = CommandLineArgument {
129 short_form: "h".to_string(),
130 long_form: "cors-allow-headers".to_string(),
131 environment_variable: Config::RWS_CONFIG_CORS_ALLOW_HEADERS.to_string(),
132 _hint: Some("Comma separated list of allowed request headers, in lowercase, example: content-type,x-custom-header".to_string())
133 };
134 argument_list.push(argument);
135
136 let argument = CommandLineArgument {
137 short_form: "c".to_string(),
138 long_form: "cors-allow-credentials".to_string(),
139 environment_variable: Config::RWS_CONFIG_CORS_ALLOW_CREDENTIALS.to_string(),
140 _hint: Some("If set to true, will allow to transmit credentials via CORS requests".to_string())
141 };
142 argument_list.push(argument);
143
144 let argument = CommandLineArgument {
145 short_form: "e".to_string(),
146 long_form: "cors-expose-headers".to_string(),
147 environment_variable: Config::RWS_CONFIG_CORS_EXPOSE_HEADERS.to_string(),
148 _hint: Some("Comma separated list of allowed response headers, in lowercase, example: content-type,x-custom-header".to_string())
149 };
150 argument_list.push(argument);
151
152 let argument = CommandLineArgument {
153 short_form: "g".to_string(),
154 long_form: "cors-max-age".to_string(),
155 environment_variable: Config::RWS_CONFIG_CORS_MAX_AGE.to_string(),
156 _hint: Some("How long results of preflight requests can be cached (in seconds)".to_string())
157 };
158 argument_list.push(argument);
159
160 let argument = CommandLineArgument {
161 short_form: "r".to_string(),
162 long_form: "request-allocation-size-in-bytes".to_string(),
163 environment_variable: Config::RWS_CONFIG_REQUEST_ALLOCATION_SIZE_IN_BYTES.to_string(),
164 _hint: Some("In bytes, how much memory to allocate for each request".to_string())
165 };
166 argument_list.push(argument);
167
168 let argument = CommandLineArgument {
169 short_form: "s".to_string(),
170 long_form: "tls-cert-file".to_string(),
171 environment_variable: Config::RWS_CONFIG_TLS_CERT_FILE.to_string(),
172 _hint: Some("Path to TLS certificate PEM file (enables HTTPS and HTTP/2)".to_string())
173 };
174 argument_list.push(argument);
175
176 let argument = CommandLineArgument {
177 short_form: "k".to_string(),
178 long_form: "tls-key-file".to_string(),
179 environment_variable: Config::RWS_CONFIG_TLS_KEY_FILE.to_string(),
180 _hint: Some("Path to TLS private key PEM file (enables HTTPS and HTTP/2)".to_string())
181 };
182 argument_list.push(argument);
183
184 let long_only = [
189 ("http-redirect-port", Config::RWS_CONFIG_HTTP_REDIRECT_PORT,
190 "Plain-HTTP port that 301-redirects to HTTPS (and answers ACME HTTP-01 challenges), e.g. 80"),
191 ("acme-domains", Config::RWS_CONFIG_ACME_DOMAINS,
192 "Comma separated domains to get a Let's Encrypt certificate for; enables automatic HTTPS"),
193 ("acme-email", Config::RWS_CONFIG_ACME_EMAIL,
194 "Contact email for the ACME account"),
195 ("acme-staging", Config::RWS_CONFIG_ACME_STAGING,
196 "If set to true, uses the Let's Encrypt staging environment (untrusted test certificates)"),
197 ("acme-directory", Config::RWS_CONFIG_ACME_DIRECTORY,
198 "ACME directory URL of a CA other than Let's Encrypt"),
199 ("acme-ca-file", Config::RWS_CONFIG_ACME_CA_FILE,
200 "PEM file of extra root CAs to trust for the ACME directory (private ACME CAs)"),
201 ("acme-challenge-port", Config::RWS_CONFIG_ACME_CHALLENGE_PORT,
202 "Port the CA connects to for HTTP-01 challenges (default 80)"),
203 ("acme-cert-path", Config::RWS_CONFIG_ACME_CERT_PATH,
204 "Where to store the issued certificate (default: tls-cert-file, else .rws/acme/cert.pem)"),
205 ("acme-key-path", Config::RWS_CONFIG_ACME_KEY_PATH,
206 "Where to store the certificate's private key (default: tls-key-file, else .rws/acme/key.pem)"),
207 ("acme-profile", Config::RWS_CONFIG_ACME_PROFILE,
208 "Certificate profile to order, as the CA offers them (e.g. classic, tlsserver, shortlived)"),
209 ("acme-challenge", Config::RWS_CONFIG_ACME_CHALLENGE,
210 "How to prove control of the domains: http-01 (default), tls-alpn-01, dns-01 (wildcards) or dns-persist-01"),
211 ("acme-dns-provider", Config::RWS_CONFIG_ACME_DNS_PROVIDER,
212 "DNS-01: rfc2136 (DNS UPDATE), cloudflare, route53 or exec (a program)"),
213 ("acme-dns-server", Config::RWS_CONFIG_ACME_DNS_SERVER,
214 "DNS-01 rfc2136: the primary DNS server, host[:port]"),
215 ("acme-dns-zone", Config::RWS_CONFIG_ACME_DNS_ZONE,
216 "DNS-01: the zone of the records (found by SOA lookup when not set)"),
217 ("acme-dns-tsig-key", Config::RWS_CONFIG_ACME_DNS_TSIG_KEY,
218 "DNS-01 rfc2136: TSIG key name (the secret goes in RWS_CONFIG_ACME_DNS_TSIG_SECRET)"),
219 ("acme-dns-tsig-algorithm", Config::RWS_CONFIG_ACME_DNS_TSIG_ALGORITHM,
220 "DNS-01 rfc2136: hmac-sha256 (default) or hmac-sha512"),
221 ("acme-dns-exec", Config::RWS_CONFIG_ACME_DNS_EXEC,
222 "DNS-01 exec: program run as <program> present|cleanup <fqdn> <value>"),
223 ("acme-dns-propagation-secs", Config::RWS_CONFIG_ACME_DNS_PROPAGATION_SECS,
224 "DNS-01: seconds to wait for the record to be visible (default 120)"),
225 ("acme-dns-api-url", Config::RWS_CONFIG_ACME_DNS_API_URL,
226 "DNS-01 cloudflare/route53: API base URL instead of the provider's (the token goes in RWS_CONFIG_ACME_DNS_API_TOKEN)"),
227 ("acme-dns-zone-id", Config::RWS_CONFIG_ACME_DNS_ZONE_ID,
228 "DNS-01 cloudflare/route53: the zone's id, instead of looking it up"),
229 ("acme-redis", Config::RWS_CONFIG_ACME_REDIS,
230 "Redis host:port shared by replicas: one orders each certificate, all install it"),
231 ("acme-redis-prefix", Config::RWS_CONFIG_ACME_REDIS_PREFIX,
232 "Key prefix in that Redis (default rws:acme)"),
233 ("acme-on-demand", Config::RWS_CONFIG_ACME_ON_DEMAND,
234 "true: order a certificate at the first handshake for a name the ask endpoint allows"),
235 ("acme-on-demand-ask", Config::RWS_CONFIG_ACME_ON_DEMAND_ASK,
236 "On-demand TLS: URL asked ?domain=<name> before each order; 2xx allows it"),
237 ];
238 for (long_form, environment_variable, hint) in long_only {
239 argument_list.push(CommandLineArgument {
240 short_form: "".to_string(),
241 long_form: long_form.to_string(),
242 environment_variable: environment_variable.to_string(),
243 _hint: Some(hint.to_string()),
244 });
245 }
246
247 let argument = CommandLineArgument {
248 short_form: "".to_string(),
249 long_form: "tls".to_string(),
250 environment_variable: "RWS_CONFIG_TLS".to_string(),
251 _hint: Some("internal: HTTPS from rws's own CA (local development; see rws trust)".to_string())
252 };
253 argument_list.push(argument);
254
255 argument_list
256 }
257
258 pub fn _parse(args: Vec<String>, argument_list : Vec<CommandLineArgument>) -> Vec<CommandLineArgument> {
259 for unparsed_argument in args.iter() {
260
261 let boxed_split = unparsed_argument.split_once('=');
262 if boxed_split.is_some() {
263
264 let (parameter, value) = boxed_split.unwrap();
265 println!("\n {}={}", parameter, crate::entry_point::shown(parameter, value));
266 let boxed_predefined_argument =
267 argument_list
268 .iter()
269 .find(
270 |predefined_argument| {
271 let short_form = predefined_argument.short_form.to_string();
272 let long_form = predefined_argument.long_form.to_string();
273 let _param_short_form = ["-", &short_form].join("");
274 let _param_long_form = ["--", &long_form].join("");
275
276 (!short_form.is_empty() && parameter.eq(_param_short_form.as_str())) ||
277 parameter.eq(_param_long_form.as_str())
278 });
279 if boxed_predefined_argument.is_some() {
280 let predefined_argument = boxed_predefined_argument.unwrap();
281 CommandLineArgument::set_environment_variable(predefined_argument, value.to_string());
282 }
283
284 }
285 }
286 argument_list
287 }
288
289 pub fn set_environment_variable(argument: &CommandLineArgument, value: String) {
290 if value.contains('\0') {
291 eprintln!(" Warning: value for --{} contains a NUL byte — ignored", argument.long_form);
293 return;
294 }
295 env::set_var(&argument.environment_variable, &value);
296 crate::server_config::invalidate();
297 println!(" Set env variable '{}' to value '{}'", argument.environment_variable, crate::entry_point::shown(&argument.environment_variable, &value));
298 }
299}