itsi-server 0.1.19 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Cargo.lock +950 -239
- data/README.md +2 -0
- data/exe/itsi +5 -5
- data/ext/itsi_acme/Cargo.toml +86 -0
- data/ext/itsi_acme/examples/high_level.rs +63 -0
- data/ext/itsi_acme/examples/high_level_warp.rs +52 -0
- data/ext/itsi_acme/examples/low_level.rs +87 -0
- data/ext/itsi_acme/examples/low_level_axum.rs +66 -0
- data/ext/itsi_acme/src/acceptor.rs +81 -0
- data/ext/itsi_acme/src/acme.rs +354 -0
- data/ext/itsi_acme/src/axum.rs +86 -0
- data/ext/itsi_acme/src/cache.rs +39 -0
- data/ext/itsi_acme/src/caches/boxed.rs +80 -0
- data/ext/itsi_acme/src/caches/composite.rs +69 -0
- data/ext/itsi_acme/src/caches/dir.rs +106 -0
- data/ext/itsi_acme/src/caches/mod.rs +11 -0
- data/ext/itsi_acme/src/caches/no.rs +78 -0
- data/ext/itsi_acme/src/caches/test.rs +136 -0
- data/ext/itsi_acme/src/config.rs +172 -0
- data/ext/itsi_acme/src/https_helper.rs +69 -0
- data/ext/itsi_acme/src/incoming.rs +142 -0
- data/ext/itsi_acme/src/jose.rs +161 -0
- data/ext/itsi_acme/src/lib.rs +142 -0
- data/ext/itsi_acme/src/resolver.rs +59 -0
- data/ext/itsi_acme/src/state.rs +424 -0
- data/ext/itsi_server/Cargo.toml +3 -3
- data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +2 -2
- data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +150 -19
- data/ext/itsi_server/src/ruby_types/itsi_server.rs +1 -0
- data/ext/itsi_server/src/server/binds/listener.rs +34 -29
- data/ext/itsi_server/src/server/binds/tls/locked_dir_cache.rs +2 -2
- data/ext/itsi_server/src/server/binds/tls.rs +1 -1
- data/ext/itsi_server/src/server/middleware_stack/middleware.rs +33 -28
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +56 -3
- data/ext/itsi_server/src/server/middleware_stack/middlewares/csp.rs +179 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/mod.rs +25 -2
- data/ext/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +3 -3
- data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +2 -1
- data/ext/itsi_server/src/server/middleware_stack/mod.rs +32 -34
- data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +10 -4
- data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +30 -7
- data/ext/itsi_server/src/server/thread_worker.rs +2 -2
- data/ext/itsi_server/src/services/static_file_server.rs +30 -28
- data/ext/itsi_tracing/src/lib.rs +39 -8
- data/lib/itsi/server/config/config_helpers.rb +93 -0
- data/lib/itsi/server/config/dsl.rb +81 -33
- data/lib/itsi/server/config/known_paths/KitchensinkDirectories.txt +2346 -0
- data/lib/itsi/server/config/known_paths/Randomfiles.txt +24 -0
- data/lib/itsi/server/config/known_paths/UnixDotfiles.txt +52 -0
- data/lib/itsi/server/config/known_paths/backdoors/ASP_CommonBackdoors.txt +29 -0
- data/lib/itsi/server/config/known_paths/backdoors/bot_control_panels.txt +1668 -0
- data/lib/itsi/server/config/known_paths/backdoors/shells.txt +1167 -0
- data/lib/itsi/server/config/known_paths/cgi/CGI_HTTP_POST.txt +7 -0
- data/lib/itsi/server/config/known_paths/cgi/CGI_HTTP_POST_Windows.txt +6 -0
- data/lib/itsi/server/config/known_paths/cgi/CGI_Microsoft.txt +79 -0
- data/lib/itsi/server/config/known_paths/cgi/CGI_XPlatform.txt +3948 -0
- data/lib/itsi/server/config/known_paths/cms/README.md +5 -0
- data/lib/itsi/server/config/known_paths/cms/drupal_plugins.txt +6320 -0
- data/lib/itsi/server/config/known_paths/cms/drupal_themes.txt +828 -0
- data/lib/itsi/server/config/known_paths/cms/joomla_plugins.txt +224 -0
- data/lib/itsi/server/config/known_paths/cms/joomla_themes.txt +30 -0
- data/lib/itsi/server/config/known_paths/cms/php-nuke.txt +2142 -0
- data/lib/itsi/server/config/known_paths/cms/wordpress.txt +1566 -0
- data/lib/itsi/server/config/known_paths/cms/wp_common_theme_files.txt +46 -0
- data/lib/itsi/server/config/known_paths/cms/wp_plugins.txt +13366 -0
- data/lib/itsi/server/config/known_paths/cms/wp_plugins_full.txt +68662 -0
- data/lib/itsi/server/config/known_paths/cms/wp_plugins_top225.txt +225 -0
- data/lib/itsi/server/config/known_paths/cms/wp_themes.readme +12 -0
- data/lib/itsi/server/config/known_paths/cms/wp_themes.txt +7336 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/3CharExtBrute.txt +17576 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/CommonWebExtensions.txt +80 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/Extensions.Backup.txt +14 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/Extensions.Common.txt +865 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/Extensions.Compressed.txt +186 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/Extensions.Mostcommon.txt +30 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/Extensions.Skipfish.txt +93 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/WordlistSkipfish.txt +1918 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/copy_of.txt +8 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-large-directories-lowercase.txt +56180 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-large-directories.txt +62290 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-large-extensions-lowercase.txt +2367 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-large-extensions.txt +2450 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-large-files-lowercase.txt +35323 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-large-files.txt +37037 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-large-words-lowercase.txt +107982 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-large-words.txt +119600 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-medium-directories-lowercase.txt +26593 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-medium-directories.txt +30009 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-medium-extensions-lowercase.txt +1233 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-medium-extensions.txt +1289 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-medium-files-lowercase.txt +16243 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-medium-files.txt +17128 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-medium-words-lowercase.txt +56293 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-medium-words.txt +63087 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-small-directories-lowercase.txt +17776 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-small-directories.txt +20122 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-small-extensions-lowercase.txt +914 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-small-extensions.txt +963 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-small-files-lowercase.txt +10848 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-small-files.txt +11424 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-small-words-lowercase.txt +38267 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-small-words.txt +43003 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/spanish.txt +445 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/test_demo.txt +36 -0
- data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/upload_variants.txt +44 -0
- data/lib/itsi/server/config/known_paths/login-file-locations/Logins.txt +71 -0
- data/lib/itsi/server/config/known_paths/login-file-locations/cfm.txt +294 -0
- data/lib/itsi/server/config/known_paths/login-file-locations/html.txt +295 -0
- data/lib/itsi/server/config/known_paths/login-file-locations/jsp.txt +294 -0
- data/lib/itsi/server/config/known_paths/login-file-locations/php.txt +294 -0
- data/lib/itsi/server/config/known_paths/login-file-locations/windows-asp.txt +294 -0
- data/lib/itsi/server/config/known_paths/login-file-locations/windows-aspx.txt +294 -0
- data/lib/itsi/server/config/known_paths/password-file-locations/Passwords.txt +47 -0
- data/lib/itsi/server/config/known_paths/php/PHP.txt +30 -0
- data/lib/itsi/server/config/known_paths/php/PHP_CommonBackdoors.txt +5 -0
- data/lib/itsi/server/config/known_paths/proxy-conf.txt +31 -0
- data/lib/itsi/server/config/known_paths/tftp.txt +79 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/ADFS.txt +86 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/AdobeXML.txt +16 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/Apache.txt +101 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/ApacheTomcat.txt +47 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/Apache_Axis.txt +16 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/ColdFusion.txt +111 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/FatwireCMS.txt +390 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/Frontpage.txt +38 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/HP_System_Mgmt_Homepage.txt +239 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/HTTP_POST_Microsoft.txt +2 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/Hyperion.txt +578 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/IIS.txt +187 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/JBoss.txt +5 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/JRun.txt +13 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/JavaServlets_Common.txt +3 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/Joomla_exploitable.txt +1937 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/LotusNotes.txt +206 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/Netware.txt +18 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/Oracle9i.txt +60 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/OracleAppServer.txt +192 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/README.md +6 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/Ruby_Rails.txt +121 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/SAP.txt +463 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/Sharepoint.txt +1707 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/SiteMinder.txt +19 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/SunAppServerGlassfish.txt +51 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/SuniPlanet.txt +35 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/Vignette.txt +73 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/Weblogic.txt +160 -0
- data/lib/itsi/server/config/known_paths/webservers-appservers/Websphere.txt +366 -0
- data/lib/itsi/server/config/known_paths/wellknown-rfc5785.txt +30 -0
- data/lib/itsi/server/config/known_paths.rb +17 -0
- data/lib/itsi/server/config/middleware/_index.md +54 -0
- data/lib/itsi/server/config/middleware/log_requests.md +63 -0
- data/lib/itsi/server/config/middleware/log_requests.rb +33 -0
- data/lib/itsi/server/config/middleware.rb +9 -0
- data/lib/itsi/server/config/option.rb +9 -0
- data/lib/itsi/server/config/options/_index.md +36 -0
- data/lib/itsi/server/config/options/fiber_scheduler.md +35 -0
- data/lib/itsi/server/config/options/fiber_scheduler.rb +18 -0
- data/lib/itsi/server/config/options/threads.md +39 -0
- data/lib/itsi/server/config/options/threads.rb +17 -0
- data/lib/itsi/server/config/options/workers.md +43 -0
- data/lib/itsi/server/config/options/workers.rb +17 -0
- data/lib/itsi/server/config/typed_struct.rb +203 -0
- data/lib/itsi/server/config.rb +124 -30
- data/lib/itsi/server/signal_trap.rb +5 -1
- data/lib/itsi/server/typed_handlers/source_parser.rb +1 -1
- data/lib/itsi/server/version.rb +1 -1
- data/lib/itsi/server.rb +27 -6
- data/lib/ruby_lsp/itsi/addon.rb +64 -48
- metadata +141 -5
- data/CHANGELOG.md +0 -10
- data/CODE_OF_CONDUCT.md +0 -139
- data/LICENSE.txt +0 -21
- data/_index.md +0 -6
@@ -90,6 +90,7 @@ impl SingleMode {
|
|
90
90
|
}
|
91
91
|
|
92
92
|
pub fn stop(&self) -> Result<()> {
|
93
|
+
SHUTDOWN_REQUESTED.store(true, std::sync::atomic::Ordering::SeqCst);
|
93
94
|
self.lifecycle_channel.send(LifecycleEvent::Shutdown).ok();
|
94
95
|
Ok(())
|
95
96
|
}
|
@@ -231,14 +232,17 @@ impl SingleMode {
|
|
231
232
|
);
|
232
233
|
|
233
234
|
let (shutdown_sender, _) = watch::channel(RunningPhase::Running);
|
234
|
-
let
|
235
|
+
let monitor_thread = self.clone().start_monitors(thread_workers.clone());
|
235
236
|
if SHUTDOWN_REQUESTED.load(Ordering::SeqCst) {
|
236
237
|
return Ok(());
|
237
238
|
}
|
238
|
-
runtime.block_on(
|
239
|
+
let result = runtime.block_on(
|
239
240
|
async {
|
240
241
|
let server_params = self.server_config.server_params.read().clone();
|
241
|
-
|
242
|
+
if let Err(err) = server_params.initialize_middleware().await {
|
243
|
+
error!("Failed to initialize middleware: {}", err);
|
244
|
+
return Err(ItsiError::new("Failed to initialize middleware"))
|
245
|
+
}
|
242
246
|
let tokio_listeners = server_params.listeners.lock()
|
243
247
|
.drain(..)
|
244
248
|
.map(|list| {
|
@@ -310,7 +314,19 @@ impl SingleMode {
|
|
310
314
|
drop(tokio_listeners);
|
311
315
|
|
312
316
|
Ok::<(), ItsiError>(())
|
313
|
-
})
|
317
|
+
});
|
318
|
+
|
319
|
+
debug!("Single mode runtime exited.");
|
320
|
+
|
321
|
+
if result.is_err() {
|
322
|
+
for _i in 0..thread_workers.len() {
|
323
|
+
job_sender.send_blocking(RequestJob::Shutdown).unwrap();
|
324
|
+
nonblocking_sender
|
325
|
+
.send_blocking(RequestJob::Shutdown)
|
326
|
+
.unwrap();
|
327
|
+
}
|
328
|
+
self.lifecycle_channel.send(LifecycleEvent::Shutdown).ok();
|
329
|
+
}
|
314
330
|
|
315
331
|
shutdown_sender.send(RunningPhase::Shutdown).ok();
|
316
332
|
let deadline = Instant::now()
|
@@ -318,12 +334,13 @@ impl SingleMode {
|
|
318
334
|
|
319
335
|
runtime.shutdown_timeout(Duration::from_millis(100));
|
320
336
|
|
337
|
+
debug!("Shutdown timeout finished.");
|
321
338
|
loop {
|
322
339
|
if thread_workers
|
323
340
|
.iter()
|
324
341
|
.all(|worker| call_with_gvl(move |_| !worker.poll_shutdown(deadline)))
|
325
342
|
{
|
326
|
-
funcall_no_ret(
|
343
|
+
funcall_no_ret(monitor_thread, "join", ()).ok();
|
327
344
|
break;
|
328
345
|
}
|
329
346
|
sleep(Duration::from_millis(50));
|
@@ -335,7 +352,7 @@ impl SingleMode {
|
|
335
352
|
self.run()?;
|
336
353
|
}
|
337
354
|
debug!("Runtime has shut down");
|
338
|
-
|
355
|
+
result
|
339
356
|
}
|
340
357
|
|
341
358
|
pub(crate) async fn serve_connection(
|
@@ -367,7 +384,7 @@ impl SingleMode {
|
|
367
384
|
let mut serve = Box::pin(
|
368
385
|
binding
|
369
386
|
.timer(TokioTimer::new())
|
370
|
-
.header_read_timeout(
|
387
|
+
.header_read_timeout(self.server_config.server_params.read().header_read_timeout)
|
371
388
|
.serve_connection_with_upgrades(io, service),
|
372
389
|
);
|
373
390
|
|
@@ -401,6 +418,9 @@ impl SingleMode {
|
|
401
418
|
/// Not that when running in single mode this will not unload
|
402
419
|
/// old code. If you need a clean restart, use the `restart` (SIGHUP) method instead
|
403
420
|
pub fn reload(&self) -> Result<()> {
|
421
|
+
if !self.server_config.check_config() {
|
422
|
+
return Ok(());
|
423
|
+
}
|
404
424
|
let should_reexec = self.server_config.clone().reload(false)?;
|
405
425
|
if should_reexec {
|
406
426
|
self.server_config.dup_fds()?;
|
@@ -414,6 +434,9 @@ impl SingleMode {
|
|
414
434
|
|
415
435
|
/// Restart the server while keeping connections open.
|
416
436
|
pub fn restart(&self) -> Result<()> {
|
437
|
+
if !self.server_config.check_config() {
|
438
|
+
return Ok(());
|
439
|
+
}
|
417
440
|
self.server_config.dup_fds()?;
|
418
441
|
self.server_config.reload_exec()?;
|
419
442
|
Ok(())
|
@@ -159,11 +159,10 @@ impl ThreadWorker {
|
|
159
159
|
pub fn poll_shutdown(&self, deadline: Instant) -> bool {
|
160
160
|
if let Some(thread) = self.thread.read().deref() {
|
161
161
|
if Instant::now() > deadline {
|
162
|
-
warn!("Worker shutdown timed out. Killing thread");
|
162
|
+
warn!("Worker shutdown timed out. Killing thread {:?}", thread);
|
163
163
|
self.terminated.store(true, Ordering::SeqCst);
|
164
164
|
kill_threads(vec![thread.as_value()]);
|
165
165
|
}
|
166
|
-
debug!("Checking thread status");
|
167
166
|
if thread.funcall::<_, _, bool>(*ID_ALIVE, ()).unwrap_or(false) {
|
168
167
|
return true;
|
169
168
|
}
|
@@ -184,6 +183,7 @@ impl ThreadWorker {
|
|
184
183
|
call_with_gvl(|_| {
|
185
184
|
*self.thread.write() = Some(
|
186
185
|
create_ruby_thread(move || {
|
186
|
+
debug!("Ruby thread worker started");
|
187
187
|
if let Some(scheduler_class) = scheduler_class {
|
188
188
|
if let Err(err) = self_ref.fiber_accept_loop(
|
189
189
|
params,
|
@@ -50,6 +50,7 @@ pub static ROOT_STATIC_FILE_SERVER: LazyLock<StaticFileServer> = LazyLock::new(|
|
|
50
50
|
serve_hidden_files: false,
|
51
51
|
allowed_extensions: vec!["html".to_string(), "css".to_string(), "js".to_string()],
|
52
52
|
})
|
53
|
+
.unwrap()
|
53
54
|
});
|
54
55
|
|
55
56
|
#[derive(Debug, Clone, Deserialize)]
|
@@ -209,14 +210,27 @@ struct ServeCacheArgs<'a>(
|
|
209
210
|
);
|
210
211
|
|
211
212
|
impl StaticFileServer {
|
212
|
-
pub fn new(config: StaticFileServerConfig) -> Self {
|
213
|
+
pub fn new(config: StaticFileServerConfig) -> Result<Self> {
|
213
214
|
let cache = Cache::builder().max_capacity(config.max_entries).build();
|
215
|
+
if !config.root_dir.exists() {
|
216
|
+
return Err(ItsiError::InternalError(format!(
|
217
|
+
"Root directory {} for static file server doesn't exist",
|
218
|
+
config.root_dir.display()
|
219
|
+
)));
|
220
|
+
}
|
221
|
+
|
222
|
+
if std::fs::read_dir(&config.root_dir).is_err() {
|
223
|
+
return Err(ItsiError::InternalError(format!(
|
224
|
+
"Root directory {} for static file server is not readable",
|
225
|
+
config.root_dir.display()
|
226
|
+
)));
|
227
|
+
}
|
214
228
|
|
215
|
-
StaticFileServer {
|
229
|
+
Ok(StaticFileServer {
|
216
230
|
config: Arc::new(config),
|
217
231
|
cache,
|
218
232
|
key_to_path: Arc::new(Mutex::new(HashMap::new())),
|
219
|
-
}
|
233
|
+
})
|
220
234
|
}
|
221
235
|
|
222
236
|
#[allow(clippy::too_many_arguments)]
|
@@ -483,14 +497,19 @@ impl StaticFileServer {
|
|
483
497
|
|
484
498
|
tokio::pin!(entries);
|
485
499
|
while let Some(entry) = entries.next_entry().await.unwrap_or(None) {
|
486
|
-
if entry
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
500
|
+
if let Ok(metadata) = entry.metadata().await {
|
501
|
+
if entry
|
502
|
+
.file_name()
|
503
|
+
.to_str()
|
504
|
+
.is_some_and(|name| name.eq_ignore_ascii_case("index.html"))
|
505
|
+
&& metadata.is_file()
|
506
|
+
{
|
507
|
+
index_file = Some(entry.path());
|
508
|
+
break;
|
509
|
+
}
|
510
|
+
} else {
|
511
|
+
error!("Failed to retrieve metadata for entry: {:?}", entry.path());
|
512
|
+
return Err(self.config.not_found_behavior.clone());
|
494
513
|
}
|
495
514
|
}
|
496
515
|
}
|
@@ -999,23 +1018,6 @@ impl std::fmt::Display for StaticFileServer {
|
|
999
1018
|
}
|
1000
1019
|
}
|
1001
1020
|
|
1002
|
-
impl Default for StaticFileServer {
|
1003
|
-
fn default() -> Self {
|
1004
|
-
let config = StaticFileServerConfig {
|
1005
|
-
root_dir: "public".into(),
|
1006
|
-
max_file_size: 10 * 1024 * 1024,
|
1007
|
-
max_entries: 100,
|
1008
|
-
recheck_interval: Duration::from_secs(60),
|
1009
|
-
try_html_extension: true,
|
1010
|
-
auto_index: true,
|
1011
|
-
not_found_behavior: NotFoundBehavior::Error(ErrorResponse::not_found()),
|
1012
|
-
serve_hidden_files: false,
|
1013
|
-
allowed_extensions: vec!["html".to_string(), "css".to_string(), "js".to_string()],
|
1014
|
-
};
|
1015
|
-
Self::new(config)
|
1016
|
-
}
|
1017
|
-
}
|
1018
|
-
|
1019
1021
|
async fn generate_directory_listing(
|
1020
1022
|
dir_path: &Path,
|
1021
1023
|
config: &StaticFileServerConfig,
|
data/ext/itsi_tracing/src/lib.rs
CHANGED
@@ -3,6 +3,7 @@ use std::{
|
|
3
3
|
env,
|
4
4
|
sync::{Mutex, OnceLock},
|
5
5
|
};
|
6
|
+
use tracing::Level;
|
6
7
|
pub use tracing::{debug, error, info, trace, warn};
|
7
8
|
use tracing_appender::rolling;
|
8
9
|
use tracing_subscriber::fmt::writer::BoxMakeWriter;
|
@@ -110,7 +111,7 @@ fn build_fmt_layer(
|
|
110
111
|
.compact()
|
111
112
|
.with_file(false)
|
112
113
|
.with_line_number(false)
|
113
|
-
.with_target(
|
114
|
+
.with_target(true)
|
114
115
|
.with_thread_ids(false)
|
115
116
|
.with_writer(BoxMakeWriter::new(std::io::stdout))
|
116
117
|
.with_ansi(config.use_ansi)
|
@@ -119,7 +120,7 @@ fn build_fmt_layer(
|
|
119
120
|
.compact()
|
120
121
|
.with_file(false)
|
121
122
|
.with_line_number(false)
|
122
|
-
.with_target(
|
123
|
+
.with_target(true)
|
123
124
|
.with_thread_ids(false)
|
124
125
|
.with_writer(BoxMakeWriter::new(std::io::stdout))
|
125
126
|
.with_ansi(config.use_ansi)
|
@@ -133,7 +134,7 @@ fn build_fmt_layer(
|
|
133
134
|
.compact()
|
134
135
|
.with_file(false)
|
135
136
|
.with_line_number(false)
|
136
|
-
.with_target(
|
137
|
+
.with_target(true)
|
137
138
|
.with_thread_ids(false)
|
138
139
|
.with_writer(BoxMakeWriter::new(move || {
|
139
140
|
rolling::daily(".", file_clone.clone())
|
@@ -146,7 +147,7 @@ fn build_fmt_layer(
|
|
146
147
|
.compact()
|
147
148
|
.with_file(false)
|
148
149
|
.with_line_number(false)
|
149
|
-
.with_target(
|
150
|
+
.with_target(true)
|
150
151
|
.with_thread_ids(false)
|
151
152
|
.with_writer(BoxMakeWriter::new(move || {
|
152
153
|
rolling::daily(".", file_clone.clone())
|
@@ -165,7 +166,7 @@ fn build_fmt_layer(
|
|
165
166
|
.compact()
|
166
167
|
.with_file(false)
|
167
168
|
.with_line_number(false)
|
168
|
-
.with_target(
|
169
|
+
.with_target(true)
|
169
170
|
.with_thread_ids(false)
|
170
171
|
.with_writer(BoxMakeWriter::new(std::io::stdout))
|
171
172
|
.with_ansi(config.use_ansi);
|
@@ -173,7 +174,7 @@ fn build_fmt_layer(
|
|
173
174
|
.compact()
|
174
175
|
.with_file(false)
|
175
176
|
.with_line_number(false)
|
176
|
-
.with_target(
|
177
|
+
.with_target(true)
|
177
178
|
.with_thread_ids(false)
|
178
179
|
.with_writer(BoxMakeWriter::new(move || {
|
179
180
|
rolling::daily(".", file_clone.clone())
|
@@ -186,7 +187,7 @@ fn build_fmt_layer(
|
|
186
187
|
.compact()
|
187
188
|
.with_file(false)
|
188
189
|
.with_line_number(false)
|
189
|
-
.with_target(
|
190
|
+
.with_target(true)
|
190
191
|
.with_thread_ids(false)
|
191
192
|
.with_writer(BoxMakeWriter::new(std::io::stdout))
|
192
193
|
.with_ansi(config.use_ansi)
|
@@ -195,7 +196,7 @@ fn build_fmt_layer(
|
|
195
196
|
.compact()
|
196
197
|
.with_file(false)
|
197
198
|
.with_line_number(false)
|
198
|
-
.with_target(
|
199
|
+
.with_target(true)
|
199
200
|
.with_thread_ids(false)
|
200
201
|
.with_writer(BoxMakeWriter::new(move || {
|
201
202
|
rolling::daily(".", file_clone.clone())
|
@@ -304,6 +305,36 @@ pub fn set_format(new_format: &str) {
|
|
304
305
|
eprintln!("Current configuration not initialized; call init() first.");
|
305
306
|
}
|
306
307
|
}
|
308
|
+
pub fn set_target_filters(targets: Vec<(&str, Level)>) {
|
309
|
+
if let Some(reload_handle_mutex) = RELOAD_HANDLE.get() {
|
310
|
+
if let Ok(handle_guard) = reload_handle_mutex.lock() {
|
311
|
+
if let Some(handle) = handle_guard.as_ref() {
|
312
|
+
let mut new_filter = EnvFilter::new("");
|
313
|
+
|
314
|
+
if let Some(config_mutex) = CURRENT_CONFIG.get() {
|
315
|
+
if let Ok(config) = config_mutex.lock() {
|
316
|
+
if let Ok(directive) = config.level.parse() {
|
317
|
+
new_filter = new_filter.add_directive(directive);
|
318
|
+
}
|
319
|
+
}
|
320
|
+
}
|
321
|
+
|
322
|
+
for (target, level) in targets {
|
323
|
+
let directive_str = format!("{}={}", target, level);
|
324
|
+
if let Ok(directive) = directive_str.parse() {
|
325
|
+
new_filter = new_filter.add_directive(directive);
|
326
|
+
}
|
327
|
+
}
|
328
|
+
|
329
|
+
if let Err(e) = handle.modify(|filter| *filter = new_filter) {
|
330
|
+
eprintln!("Failed to update filter with target directives: {}", e);
|
331
|
+
}
|
332
|
+
}
|
333
|
+
}
|
334
|
+
} else {
|
335
|
+
eprintln!("Reload handle for filter not initialized; call init() first.");
|
336
|
+
}
|
337
|
+
}
|
307
338
|
|
308
339
|
/// Run a function silently by temporarily setting a no-op subscriber.
|
309
340
|
pub fn run_silently<F, R>(f: F) -> R
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module Itsi
|
2
|
+
class Server
|
3
|
+
module Config
|
4
|
+
module ConfigHelpers
|
5
|
+
|
6
|
+
def self.load_and_register(klass)
|
7
|
+
config_type = klass.name.split("::").last.downcase
|
8
|
+
listing = [
|
9
|
+
Dir[File.expand_path(File.dirname(__FILE__) + "/#{config_type}/**.rb")],
|
10
|
+
Dir[File.expand_path(File.dirname(__FILE__) + "/#{config_type}s/**.rb")]
|
11
|
+
].flatten
|
12
|
+
|
13
|
+
listing.each do |file|
|
14
|
+
current = klass.subclasses
|
15
|
+
require file
|
16
|
+
following = klass.subclasses
|
17
|
+
new_class = (following - current).first
|
18
|
+
|
19
|
+
documentation_file = "#{file[/(.*)\.rb/,1]}.md"
|
20
|
+
if File.exist?(documentation_file) && new_class
|
21
|
+
new_class.documentation IO.read(documentation_file)
|
22
|
+
.gsub(/^---.*?\n.*?-+/m,'') # Strip frontmatter
|
23
|
+
.gsub(/^(```.*?)\{.*?\}.*$/, "\\1") # Strip filename from code blocks
|
24
|
+
.gsub(/^\{\{[^\}]+\}\}/, "") # Strip Hugo blocks
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def normalize_keys!(hash, expected=[])
|
30
|
+
hash.keys.each do |key|
|
31
|
+
value = hash.delete(key)
|
32
|
+
key = key.to_s.downcase.to_sym
|
33
|
+
hash[key] = value
|
34
|
+
raise "Unexpected key: #{key}" unless expected.include?(key)
|
35
|
+
expected -= [key]
|
36
|
+
end
|
37
|
+
raise "Missing required keys: #{expected.join(', ')}" unless expected.empty?
|
38
|
+
hash
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.included(cls)
|
42
|
+
def cls.inherited(base)
|
43
|
+
%i[detail documentation insert_text schema].each do |attr|
|
44
|
+
base.define_singleton_method(attr) do |value=nil|
|
45
|
+
@middleware_class_attrs ||= {}
|
46
|
+
if value
|
47
|
+
@middleware_class_attrs[attr] = value
|
48
|
+
else
|
49
|
+
@middleware_class_attrs[attr]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
base.define_method(attr) do |value=nil|
|
54
|
+
self.class.send(attr)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def base.schema(value=nil, &blk)
|
59
|
+
@middleware_class_attrs ||= {}
|
60
|
+
if blk
|
61
|
+
@middleware_class_attrs[:schema] = TypedStruct.new(&blk)
|
62
|
+
else
|
63
|
+
@middleware_class_attrs[:schema]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
load_and_register(cls)
|
69
|
+
|
70
|
+
config_type = cls.name.split("::").last.downcase
|
71
|
+
|
72
|
+
cls.define_singleton_method("#{config_type}_name") do |name=self.name|
|
73
|
+
@config_name ||= name.split("::").last.gsub(/([a-z])([A-Z])/, '\1_\2').downcase.to_sym
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def initialize(location, params)
|
78
|
+
@location = location
|
79
|
+
@params = case self.schema
|
80
|
+
when TypedStruct::Validation
|
81
|
+
self.schema.validate!(params)
|
82
|
+
else
|
83
|
+
self.schema.new(params).to_h
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def build!
|
88
|
+
@params
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -2,18 +2,25 @@ module Itsi
|
|
2
2
|
class Server
|
3
3
|
module Config
|
4
4
|
class DSL
|
5
|
-
|
5
|
+
require_relative "config_helpers"
|
6
|
+
require_relative "option"
|
7
|
+
require_relative "middleware"
|
8
|
+
|
9
|
+
attr_reader :parent, :children, :middleware, :controller_class, :routes, :http_methods, :protocols,
|
6
10
|
:hosts, :ports, :extensions, :content_types, :accepts, :options
|
7
11
|
|
8
12
|
def self.evaluate(config = Itsi::Server::Config.config_file_path, &blk)
|
9
|
-
new(routes: ["/"]) do
|
13
|
+
config = new(routes: ["/"]) do
|
10
14
|
if blk
|
11
15
|
instance_exec(&blk)
|
12
16
|
else
|
13
17
|
code = IO.read(config)
|
14
18
|
instance_eval(code, config.to_s, 1)
|
15
19
|
end
|
16
|
-
end
|
20
|
+
end
|
21
|
+
[config.options, config.errors]
|
22
|
+
rescue Exception => e
|
23
|
+
[{}, [[e, e.backtrace[0]]]]
|
17
24
|
end
|
18
25
|
|
19
26
|
def initialize(
|
@@ -36,7 +43,7 @@ module Itsi
|
|
36
43
|
|
37
44
|
@controller = controller
|
38
45
|
@routes = Array(routes).flatten
|
39
|
-
@
|
46
|
+
@http_methods = methods.map { |s| s.is_a?(Regexp) ? s : s.to_s }
|
40
47
|
@protocols = protocols.map { |s| s.is_a?(Regexp) ? s : s.to_s }
|
41
48
|
@hosts = hosts.map { |s| s.is_a?(Regexp) ? s : s.to_s }
|
42
49
|
@ports = ports.map { |s| s.is_a?(Regexp) ? s : s.to_s }
|
@@ -54,19 +61,30 @@ module Itsi
|
|
54
61
|
end
|
55
62
|
}
|
56
63
|
|
64
|
+
@errors = []
|
57
65
|
instance_exec(&block)
|
58
66
|
end
|
59
67
|
|
60
|
-
def
|
61
|
-
|
62
|
-
|
63
|
-
@options[:workers] = [workers.to_i, 1].max
|
68
|
+
def errors
|
69
|
+
@children.map(&:errors).flatten + @errors
|
64
70
|
end
|
65
71
|
|
66
|
-
|
67
|
-
|
72
|
+
Option.subclasses.each do |option|
|
73
|
+
option_name = option.option_name
|
74
|
+
define_method(option_name) do |*args, **kwargs, &blk|
|
75
|
+
@options[option_name] = option.new(self, *args, **kwargs, &blk).build!
|
76
|
+
rescue => e
|
77
|
+
@errors << [e, caller[1]]
|
78
|
+
end
|
79
|
+
end
|
68
80
|
|
69
|
-
|
81
|
+
Middleware.subclasses.each do |middleware|
|
82
|
+
middleware_name = middleware.middleware_name
|
83
|
+
define_method(middleware_name) do |*args, **kwargs, &blk|
|
84
|
+
@middleware[middleware_name] = middleware.new(@parent, *args, **kwargs, &blk).build!
|
85
|
+
rescue => e
|
86
|
+
@errors << [e, caller[1]]
|
87
|
+
end
|
70
88
|
end
|
71
89
|
|
72
90
|
def oob_gc_responses_threshold(threshold)
|
@@ -81,18 +99,24 @@ module Itsi
|
|
81
99
|
@options[:log_level] = level.to_s
|
82
100
|
end
|
83
101
|
|
84
|
-
def log_format(format)
|
85
|
-
raise "Log format must be set at the root" unless @parent.nil?
|
86
|
-
|
87
|
-
@options[:log_format] = format.to_s
|
88
|
-
end
|
89
|
-
|
90
102
|
def log_target(target)
|
91
103
|
raise "Log target must be set at the root" unless @parent.nil?
|
92
104
|
|
93
105
|
@options[:log_target] = target.to_s
|
94
106
|
end
|
95
107
|
|
108
|
+
def log_target_filters(target_filters)
|
109
|
+
raise "Log target filters must be set at the root" unless @parent.nil?
|
110
|
+
|
111
|
+
@options[:log_target_filters] = target_filters
|
112
|
+
end
|
113
|
+
|
114
|
+
def log_format(target)
|
115
|
+
raise "Log format must be set at the root" unless @parent.nil?
|
116
|
+
|
117
|
+
@options[:log_format] = target.to_s
|
118
|
+
end
|
119
|
+
|
96
120
|
def get(route, app_proc = nil, nonblocking: false, &blk)
|
97
121
|
endpoint(route, [:get], app_proc, nonblocking: nonblocking, &blk)
|
98
122
|
end
|
@@ -138,11 +162,10 @@ module Itsi
|
|
138
162
|
app_proc = Itsi::Server::TypedHandlers.handler_for(app_proc, params_schema)
|
139
163
|
end
|
140
164
|
|
141
|
-
|
142
|
-
if route || methods.any?
|
165
|
+
if route || http_methods.any?
|
143
166
|
# For endpoints, it's usually assumed trailing slash and non-trailing slash behaviour is the same
|
144
167
|
routes = route == "/" ? ["", "/"] : [route]
|
145
|
-
location(*routes, methods:
|
168
|
+
location(*routes, methods: http_methods) do
|
146
169
|
@middleware[:app] = { preloader: -> { app_proc }, nonblocking: nonblocking }
|
147
170
|
end
|
148
171
|
else
|
@@ -204,6 +227,31 @@ module Itsi
|
|
204
227
|
instance_eval(code, "#{path}.rb", 1)
|
205
228
|
end
|
206
229
|
|
230
|
+
def reuse_address(reuse_address)
|
231
|
+
raise "reuse_address must be set at the root" unless @parent.nil?
|
232
|
+
@options[:reuse_address] = reuse_address
|
233
|
+
end
|
234
|
+
|
235
|
+
def reuse_port(reuse_port)
|
236
|
+
raise "reuse_port must be set at the root" unless @parent.nil?
|
237
|
+
@options[:reuse_port] = reuse_port
|
238
|
+
end
|
239
|
+
|
240
|
+
def listen_backlog(listen_backlog)
|
241
|
+
raise "listen_backlog must be set at the root" unless @parent.nil?
|
242
|
+
@options[:listen_backlog] = listen_backlog
|
243
|
+
end
|
244
|
+
|
245
|
+
def nodelay(nodelay)
|
246
|
+
raise "nodelay must be set at the root" unless @parent.nil?
|
247
|
+
@options[:nodelay] = nodelay
|
248
|
+
end
|
249
|
+
|
250
|
+
def recv_buffer_size(recv_buffer_size)
|
251
|
+
raise "recv_buffer_size must be set at the root" unless @parent.nil?
|
252
|
+
@options[:recv_buffer_size] = recv_buffer_size
|
253
|
+
end
|
254
|
+
|
207
255
|
def bind(bind_str)
|
208
256
|
raise "Bind must be set at the root" unless @parent.nil?
|
209
257
|
|
@@ -296,6 +344,11 @@ module Itsi
|
|
296
344
|
@options[:shutdown_timeout] = shutdown_timeout.to_f
|
297
345
|
end
|
298
346
|
|
347
|
+
def header_read_timeout(header_read_timeout)
|
348
|
+
raise "Header read timeout must be set at the root" unless @parent.nil?
|
349
|
+
|
350
|
+
@options[:header_read_timeout] = header_read_timeout.to_f
|
351
|
+
end
|
299
352
|
|
300
353
|
def stream_body(stream_body)
|
301
354
|
raise "Stream body must be set at the root" unless @parent.nil?
|
@@ -309,7 +362,7 @@ module Itsi
|
|
309
362
|
@children << DSL.new(
|
310
363
|
self,
|
311
364
|
routes: routes,
|
312
|
-
methods: Array(methods) | self.
|
365
|
+
methods: Array(methods) | self.http_methods,
|
313
366
|
protocols: Array(protocols) | self.protocols,
|
314
367
|
hosts: Array(hosts) | self.hosts,
|
315
368
|
ports: Array(ports) | self.ports,
|
@@ -327,9 +380,6 @@ module Itsi
|
|
327
380
|
end
|
328
381
|
end
|
329
382
|
|
330
|
-
def log_requests(**args)
|
331
|
-
@middleware[:log_requests] = args
|
332
|
-
end
|
333
383
|
|
334
384
|
def allow_list(**args)
|
335
385
|
args[:allowed_patterns] = Array(args[:allowed_patterns]).map do |pattern|
|
@@ -358,7 +408,6 @@ module Itsi
|
|
358
408
|
end
|
359
409
|
|
360
410
|
def auth_basic(**args)
|
361
|
-
|
362
411
|
if File.exist?(".itsi-credentials") && !args[:credential_file]
|
363
412
|
args[:credential_file] = ".itsi-credentials"
|
364
413
|
end
|
@@ -432,8 +481,12 @@ module Itsi
|
|
432
481
|
@middleware[:etag] = args
|
433
482
|
end
|
434
483
|
|
484
|
+
def csp(**args)
|
485
|
+
@middleware[:csp] = args
|
486
|
+
end
|
487
|
+
|
435
488
|
def intrusion_protection(**args)
|
436
|
-
args[:banned_url_patterns] = Array(args[:banned_url_patterns]).map do |pattern|
|
489
|
+
args[:banned_url_patterns] = Array(args[:banned_url_patterns]).flatten.map do |pattern|
|
437
490
|
if pattern.is_a?(Regexp)
|
438
491
|
pattern.source
|
439
492
|
else
|
@@ -485,7 +538,7 @@ module Itsi
|
|
485
538
|
result << deep_stringify_keys(
|
486
539
|
{
|
487
540
|
route: Regexp.new("^#{route_options}/?$"),
|
488
|
-
methods: @
|
541
|
+
methods: @http_methods.any? ? @http_methods : nil,
|
489
542
|
protocols: @protocols.any? ? @protocols : nil,
|
490
543
|
hosts: @hosts.any? ? @hosts : nil,
|
491
544
|
ports: @ports.any? ? @ports : nil,
|
@@ -535,11 +588,6 @@ module Itsi
|
|
535
588
|
end
|
536
589
|
|
537
590
|
def effective_middleware
|
538
|
-
merged = merge_ancestor_middleware
|
539
|
-
merged.map { |k, v| { type: k.to_s, parameters: v } }
|
540
|
-
end
|
541
|
-
|
542
|
-
def merge_ancestor_middleware
|
543
591
|
chain = []
|
544
592
|
node = self
|
545
593
|
while node
|
@@ -558,7 +606,7 @@ module Itsi
|
|
558
606
|
merged[k] = v
|
559
607
|
end
|
560
608
|
end
|
561
|
-
merged
|
609
|
+
deep_stringify_keys(merged)
|
562
610
|
end
|
563
611
|
|
564
612
|
def deep_stringify_keys(obj)
|