itsi-server 0.1.1 → 0.2.2

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.
Files changed (457) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +4487 -0
  3. data/Cargo.toml +7 -0
  4. data/README.md +6 -0
  5. data/Rakefile +7 -4
  6. data/exe/itsi +152 -46
  7. data/ext/itsi_acme/Cargo.toml +86 -0
  8. data/ext/itsi_acme/examples/high_level.rs +63 -0
  9. data/ext/itsi_acme/examples/high_level_warp.rs +52 -0
  10. data/ext/itsi_acme/examples/low_level.rs +87 -0
  11. data/ext/itsi_acme/examples/low_level_axum.rs +66 -0
  12. data/ext/itsi_acme/src/acceptor.rs +81 -0
  13. data/ext/itsi_acme/src/acme.rs +354 -0
  14. data/ext/itsi_acme/src/axum.rs +86 -0
  15. data/ext/itsi_acme/src/cache.rs +39 -0
  16. data/ext/itsi_acme/src/caches/boxed.rs +80 -0
  17. data/ext/itsi_acme/src/caches/composite.rs +69 -0
  18. data/ext/itsi_acme/src/caches/dir.rs +106 -0
  19. data/ext/itsi_acme/src/caches/mod.rs +11 -0
  20. data/ext/itsi_acme/src/caches/no.rs +78 -0
  21. data/ext/itsi_acme/src/caches/test.rs +136 -0
  22. data/ext/itsi_acme/src/config.rs +172 -0
  23. data/ext/itsi_acme/src/https_helper.rs +69 -0
  24. data/ext/itsi_acme/src/incoming.rs +142 -0
  25. data/ext/itsi_acme/src/jose.rs +161 -0
  26. data/ext/itsi_acme/src/lib.rs +142 -0
  27. data/ext/itsi_acme/src/resolver.rs +59 -0
  28. data/ext/itsi_acme/src/state.rs +424 -0
  29. data/ext/itsi_error/Cargo.toml +3 -0
  30. data/ext/itsi_error/src/lib.rs +98 -24
  31. data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
  32. data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
  33. data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
  34. data/ext/itsi_error/target/debug/build/rb-sys-49f554618693db24/out/bindings-0.9.110-mri-arm64-darwin23-3.4.2.rs +8865 -0
  35. data/ext/itsi_error/target/debug/incremental/itsi_error-1mmt5sux7jb0i/s-h510z7m8v9-0bxu7yd.lock +0 -0
  36. data/ext/itsi_error/target/debug/incremental/itsi_error-2vn3jey74oiw0/s-h5113n0e7e-1v5qzs6.lock +0 -0
  37. data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510ykifhe-0tbnep2.lock +0 -0
  38. data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510yyocpj-0tz7ug7.lock +0 -0
  39. data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510z0xc8g-14ol18k.lock +0 -0
  40. data/ext/itsi_error/target/debug/incremental/itsi_error-3g5qf4y7d54uj/s-h5113n0e7d-1trk8on.lock +0 -0
  41. data/ext/itsi_error/target/debug/incremental/itsi_error-3lpfftm45d3e2/s-h510z7m8r3-1pxp20o.lock +0 -0
  42. data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510ykifek-1uxasnk.lock +0 -0
  43. data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510yyocki-11u37qm.lock +0 -0
  44. data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510z0xc93-0pmy0zm.lock +0 -0
  45. data/ext/itsi_instrument_entry/Cargo.toml +15 -0
  46. data/ext/itsi_instrument_entry/src/lib.rs +31 -0
  47. data/ext/itsi_rb_helpers/Cargo.toml +3 -0
  48. data/ext/itsi_rb_helpers/src/heap_value.rs +139 -0
  49. data/ext/itsi_rb_helpers/src/lib.rs +141 -10
  50. data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
  51. data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
  52. data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
  53. data/ext/itsi_rb_helpers/target/debug/build/rb-sys-eb9ed4ff3a60f995/out/bindings-0.9.110-mri-arm64-darwin23-3.4.2.rs +8865 -0
  54. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-040pxg6yhb3g3/s-h5113n7a1b-03bwlt4.lock +0 -0
  55. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h51113xnh3-1eik1ip.lock +0 -0
  56. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h5111704jj-0g4rj8x.lock +0 -0
  57. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-1q2d3drtxrzs5/s-h5113n79yl-0bxcqc5.lock +0 -0
  58. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h51113xoox-10de2hp.lock +0 -0
  59. data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h5111704w7-0vdq7gq.lock +0 -0
  60. data/ext/itsi_scheduler/Cargo.toml +24 -0
  61. data/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +56 -0
  62. data/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +44 -0
  63. data/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +44 -0
  64. data/ext/itsi_scheduler/src/itsi_scheduler.rs +314 -0
  65. data/ext/itsi_scheduler/src/lib.rs +39 -0
  66. data/ext/itsi_server/Cargo.lock +2956 -0
  67. data/ext/itsi_server/Cargo.toml +75 -14
  68. data/ext/itsi_server/extconf.rb +1 -1
  69. data/ext/itsi_server/src/default_responses/html/401.html +68 -0
  70. data/ext/itsi_server/src/default_responses/html/403.html +68 -0
  71. data/ext/itsi_server/src/default_responses/html/404.html +68 -0
  72. data/ext/itsi_server/src/default_responses/html/413.html +71 -0
  73. data/ext/itsi_server/src/default_responses/html/429.html +68 -0
  74. data/ext/itsi_server/src/default_responses/html/500.html +71 -0
  75. data/ext/itsi_server/src/default_responses/html/502.html +71 -0
  76. data/ext/itsi_server/src/default_responses/html/503.html +68 -0
  77. data/ext/itsi_server/src/default_responses/html/504.html +69 -0
  78. data/ext/itsi_server/src/default_responses/html/index.html +238 -0
  79. data/ext/itsi_server/src/default_responses/json/401.json +6 -0
  80. data/ext/itsi_server/src/default_responses/json/403.json +6 -0
  81. data/ext/itsi_server/src/default_responses/json/404.json +6 -0
  82. data/ext/itsi_server/src/default_responses/json/413.json +6 -0
  83. data/ext/itsi_server/src/default_responses/json/429.json +6 -0
  84. data/ext/itsi_server/src/default_responses/json/500.json +6 -0
  85. data/ext/itsi_server/src/default_responses/json/502.json +6 -0
  86. data/ext/itsi_server/src/default_responses/json/503.json +6 -0
  87. data/ext/itsi_server/src/default_responses/json/504.json +6 -0
  88. data/ext/itsi_server/src/default_responses/mod.rs +11 -0
  89. data/ext/itsi_server/src/env.rs +43 -0
  90. data/ext/itsi_server/src/lib.rs +133 -40
  91. data/ext/itsi_server/src/prelude.rs +2 -0
  92. data/ext/itsi_server/src/ruby_types/itsi_body_proxy/big_bytes.rs +109 -0
  93. data/ext/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +143 -0
  94. data/ext/itsi_server/src/ruby_types/itsi_grpc_call.rs +344 -0
  95. data/ext/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs +264 -0
  96. data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +362 -0
  97. data/ext/itsi_server/src/ruby_types/itsi_http_response.rs +391 -0
  98. data/ext/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +233 -0
  99. data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +565 -0
  100. data/ext/itsi_server/src/ruby_types/itsi_server.rs +86 -0
  101. data/ext/itsi_server/src/ruby_types/mod.rs +48 -0
  102. data/ext/itsi_server/src/server/binds/bind.rs +204 -0
  103. data/ext/itsi_server/src/server/binds/bind_protocol.rs +37 -0
  104. data/ext/itsi_server/src/server/binds/listener.rs +444 -0
  105. data/ext/itsi_server/src/server/binds/mod.rs +4 -0
  106. data/ext/itsi_server/src/server/binds/tls/locked_dir_cache.rs +132 -0
  107. data/ext/itsi_server/src/server/binds/tls.rs +278 -0
  108. data/ext/itsi_server/src/server/byte_frame.rs +32 -0
  109. data/ext/itsi_server/src/server/http_message_types.rs +97 -0
  110. data/ext/itsi_server/src/server/io_stream.rs +105 -0
  111. data/ext/itsi_server/src/server/lifecycle_event.rs +12 -0
  112. data/ext/itsi_server/src/server/middleware_stack/middleware.rs +170 -0
  113. data/ext/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +63 -0
  114. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +94 -0
  115. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +94 -0
  116. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +343 -0
  117. data/ext/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +151 -0
  118. data/ext/itsi_server/src/server/middleware_stack/middlewares/compression.rs +316 -0
  119. data/ext/itsi_server/src/server/middleware_stack/middlewares/cors.rs +301 -0
  120. data/ext/itsi_server/src/server/middleware_stack/middlewares/csp.rs +193 -0
  121. data/ext/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +64 -0
  122. data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +192 -0
  123. data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +171 -0
  124. data/ext/itsi_server/src/server/middleware_stack/middlewares/etag.rs +198 -0
  125. data/ext/itsi_server/src/server/middleware_stack/middlewares/header_interpretation.rs +82 -0
  126. data/ext/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +209 -0
  127. data/ext/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +82 -0
  128. data/ext/itsi_server/src/server/middleware_stack/middlewares/max_body.rs +47 -0
  129. data/ext/itsi_server/src/server/middleware_stack/middlewares/mod.rs +116 -0
  130. data/ext/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +411 -0
  131. data/ext/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +142 -0
  132. data/ext/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +55 -0
  133. data/ext/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +54 -0
  134. data/ext/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +51 -0
  135. data/ext/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +126 -0
  136. data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +187 -0
  137. data/ext/itsi_server/src/server/middleware_stack/middlewares/static_response.rs +55 -0
  138. data/ext/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +173 -0
  139. data/ext/itsi_server/src/server/middleware_stack/middlewares/token_source.rs +31 -0
  140. data/ext/itsi_server/src/server/middleware_stack/mod.rs +381 -0
  141. data/ext/itsi_server/src/server/mod.rs +13 -5
  142. data/ext/itsi_server/src/server/process_worker.rs +247 -0
  143. data/ext/itsi_server/src/server/redirect_type.rs +26 -0
  144. data/ext/itsi_server/src/server/request_job.rs +11 -0
  145. data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +354 -0
  146. data/ext/itsi_server/src/server/serve_strategy/mod.rs +30 -0
  147. data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +481 -0
  148. data/ext/itsi_server/src/server/signal.rs +77 -0
  149. data/ext/itsi_server/src/server/size_limited_incoming.rs +107 -0
  150. data/ext/itsi_server/src/server/thread_worker.rs +479 -0
  151. data/ext/itsi_server/src/services/cache_store.rs +74 -0
  152. data/ext/itsi_server/src/services/itsi_http_service.rs +257 -0
  153. data/ext/itsi_server/src/services/mime_types.rs +1416 -0
  154. data/ext/itsi_server/src/services/mod.rs +6 -0
  155. data/ext/itsi_server/src/services/password_hasher.rs +83 -0
  156. data/ext/itsi_server/src/services/rate_limiter.rs +580 -0
  157. data/ext/itsi_server/src/services/static_file_server.rs +1340 -0
  158. data/ext/itsi_tracing/Cargo.toml +5 -0
  159. data/ext/itsi_tracing/src/lib.rs +366 -7
  160. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0994n8rpvvt9m/s-h510hfz1f6-1kbycmq.lock +0 -0
  161. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0bob7bf4yq34i/s-h5113125h5-0lh4rag.lock +0 -0
  162. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2fcodulrxbbxo/s-h510h2infk-0hp5kjw.lock +0 -0
  163. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2iak63r1woi1l/s-h510h2in4q-0kxfzw1.lock +0 -0
  164. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2kk4qj9gn5dg2/s-h5113124kv-0enwon2.lock +0 -0
  165. data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2mwo0yas7dtw4/s-h510hfz1ha-1udgpei.lock +0 -0
  166. data/lib/itsi/http_request/response_status_shortcodes.rb +76 -0
  167. data/lib/itsi/http_request.rb +218 -0
  168. data/lib/itsi/http_response.rb +42 -0
  169. data/lib/itsi/passfile.rb +108 -0
  170. data/lib/itsi/server/config/config_helpers.rb +105 -0
  171. data/lib/itsi/server/config/dsl.rb +211 -0
  172. data/lib/itsi/server/config/known_paths/KitchensinkDirectories.txt +2346 -0
  173. data/lib/itsi/server/config/known_paths/Randomfiles.txt +24 -0
  174. data/lib/itsi/server/config/known_paths/UnixDotfiles.txt +52 -0
  175. data/lib/itsi/server/config/known_paths/backdoors/ASP_CommonBackdoors.txt +29 -0
  176. data/lib/itsi/server/config/known_paths/backdoors/bot_control_panels.txt +1668 -0
  177. data/lib/itsi/server/config/known_paths/backdoors/shells.txt +1167 -0
  178. data/lib/itsi/server/config/known_paths/cgi/CGI_HTTP_POST.txt +7 -0
  179. data/lib/itsi/server/config/known_paths/cgi/CGI_HTTP_POST_Windows.txt +6 -0
  180. data/lib/itsi/server/config/known_paths/cgi/CGI_Microsoft.txt +79 -0
  181. data/lib/itsi/server/config/known_paths/cgi/CGI_XPlatform.txt +3948 -0
  182. data/lib/itsi/server/config/known_paths/cms/README.md +5 -0
  183. data/lib/itsi/server/config/known_paths/cms/drupal_plugins.txt +6320 -0
  184. data/lib/itsi/server/config/known_paths/cms/drupal_themes.txt +828 -0
  185. data/lib/itsi/server/config/known_paths/cms/joomla_plugins.txt +224 -0
  186. data/lib/itsi/server/config/known_paths/cms/joomla_themes.txt +30 -0
  187. data/lib/itsi/server/config/known_paths/cms/php-nuke.txt +2142 -0
  188. data/lib/itsi/server/config/known_paths/cms/wordpress.txt +1566 -0
  189. data/lib/itsi/server/config/known_paths/cms/wp_common_theme_files.txt +46 -0
  190. data/lib/itsi/server/config/known_paths/cms/wp_plugins.txt +13366 -0
  191. data/lib/itsi/server/config/known_paths/cms/wp_plugins_full.txt +68662 -0
  192. data/lib/itsi/server/config/known_paths/cms/wp_plugins_top225.txt +225 -0
  193. data/lib/itsi/server/config/known_paths/cms/wp_themes.readme +12 -0
  194. data/lib/itsi/server/config/known_paths/cms/wp_themes.txt +7336 -0
  195. data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/3CharExtBrute.txt +17576 -0
  196. data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/CommonWebExtensions.txt +80 -0
  197. data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/Extensions.Backup.txt +14 -0
  198. data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/Extensions.Common.txt +865 -0
  199. data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/Extensions.Compressed.txt +186 -0
  200. data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/Extensions.Mostcommon.txt +30 -0
  201. data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/Extensions.Skipfish.txt +93 -0
  202. data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/WordlistSkipfish.txt +1918 -0
  203. data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/copy_of.txt +8 -0
  204. data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-large-directories-lowercase.txt +56180 -0
  205. data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-large-directories.txt +62290 -0
  206. data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-large-extensions-lowercase.txt +2367 -0
  207. data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-large-extensions.txt +2450 -0
  208. data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-large-files-lowercase.txt +35323 -0
  209. data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-large-files.txt +37037 -0
  210. data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-large-words-lowercase.txt +107982 -0
  211. data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-large-words.txt +119600 -0
  212. data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-medium-directories-lowercase.txt +26593 -0
  213. data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-medium-directories.txt +30009 -0
  214. data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-medium-extensions-lowercase.txt +1233 -0
  215. data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-medium-extensions.txt +1289 -0
  216. data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-medium-files-lowercase.txt +16243 -0
  217. data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-medium-files.txt +17128 -0
  218. data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-medium-words-lowercase.txt +56293 -0
  219. data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-medium-words.txt +63087 -0
  220. data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-small-directories-lowercase.txt +17776 -0
  221. data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-small-directories.txt +20122 -0
  222. data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-small-extensions-lowercase.txt +914 -0
  223. data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-small-extensions.txt +963 -0
  224. data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-small-files-lowercase.txt +10848 -0
  225. data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-small-files.txt +11424 -0
  226. data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-small-words-lowercase.txt +38267 -0
  227. data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/raft-small-words.txt +43003 -0
  228. data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/spanish.txt +445 -0
  229. data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/test_demo.txt +36 -0
  230. data/lib/itsi/server/config/known_paths/filename-dirname-bruteforce/upload_variants.txt +44 -0
  231. data/lib/itsi/server/config/known_paths/login-file-locations/Logins.txt +71 -0
  232. data/lib/itsi/server/config/known_paths/login-file-locations/cfm.txt +294 -0
  233. data/lib/itsi/server/config/known_paths/login-file-locations/html.txt +295 -0
  234. data/lib/itsi/server/config/known_paths/login-file-locations/jsp.txt +294 -0
  235. data/lib/itsi/server/config/known_paths/login-file-locations/php.txt +294 -0
  236. data/lib/itsi/server/config/known_paths/login-file-locations/windows-asp.txt +294 -0
  237. data/lib/itsi/server/config/known_paths/login-file-locations/windows-aspx.txt +294 -0
  238. data/lib/itsi/server/config/known_paths/password-file-locations/Passwords.txt +47 -0
  239. data/lib/itsi/server/config/known_paths/php/PHP.txt +30 -0
  240. data/lib/itsi/server/config/known_paths/php/PHP_CommonBackdoors.txt +5 -0
  241. data/lib/itsi/server/config/known_paths/proxy-conf.txt +31 -0
  242. data/lib/itsi/server/config/known_paths/tftp.txt +79 -0
  243. data/lib/itsi/server/config/known_paths/webservers-appservers/ADFS.txt +86 -0
  244. data/lib/itsi/server/config/known_paths/webservers-appservers/AdobeXML.txt +16 -0
  245. data/lib/itsi/server/config/known_paths/webservers-appservers/Apache.txt +101 -0
  246. data/lib/itsi/server/config/known_paths/webservers-appservers/ApacheTomcat.txt +47 -0
  247. data/lib/itsi/server/config/known_paths/webservers-appservers/Apache_Axis.txt +16 -0
  248. data/lib/itsi/server/config/known_paths/webservers-appservers/ColdFusion.txt +111 -0
  249. data/lib/itsi/server/config/known_paths/webservers-appservers/FatwireCMS.txt +390 -0
  250. data/lib/itsi/server/config/known_paths/webservers-appservers/Frontpage.txt +38 -0
  251. data/lib/itsi/server/config/known_paths/webservers-appservers/HP_System_Mgmt_Homepage.txt +239 -0
  252. data/lib/itsi/server/config/known_paths/webservers-appservers/HTTP_POST_Microsoft.txt +2 -0
  253. data/lib/itsi/server/config/known_paths/webservers-appservers/Hyperion.txt +578 -0
  254. data/lib/itsi/server/config/known_paths/webservers-appservers/IIS.txt +187 -0
  255. data/lib/itsi/server/config/known_paths/webservers-appservers/JBoss.txt +5 -0
  256. data/lib/itsi/server/config/known_paths/webservers-appservers/JRun.txt +13 -0
  257. data/lib/itsi/server/config/known_paths/webservers-appservers/JavaServlets_Common.txt +3 -0
  258. data/lib/itsi/server/config/known_paths/webservers-appservers/Joomla_exploitable.txt +1937 -0
  259. data/lib/itsi/server/config/known_paths/webservers-appservers/LotusNotes.txt +206 -0
  260. data/lib/itsi/server/config/known_paths/webservers-appservers/Netware.txt +18 -0
  261. data/lib/itsi/server/config/known_paths/webservers-appservers/Oracle9i.txt +60 -0
  262. data/lib/itsi/server/config/known_paths/webservers-appservers/OracleAppServer.txt +192 -0
  263. data/lib/itsi/server/config/known_paths/webservers-appservers/README.md +6 -0
  264. data/lib/itsi/server/config/known_paths/webservers-appservers/Ruby_Rails.txt +121 -0
  265. data/lib/itsi/server/config/known_paths/webservers-appservers/SAP.txt +463 -0
  266. data/lib/itsi/server/config/known_paths/webservers-appservers/Sharepoint.txt +1707 -0
  267. data/lib/itsi/server/config/known_paths/webservers-appservers/SiteMinder.txt +19 -0
  268. data/lib/itsi/server/config/known_paths/webservers-appservers/SunAppServerGlassfish.txt +51 -0
  269. data/lib/itsi/server/config/known_paths/webservers-appservers/SuniPlanet.txt +35 -0
  270. data/lib/itsi/server/config/known_paths/webservers-appservers/Vignette.txt +73 -0
  271. data/lib/itsi/server/config/known_paths/webservers-appservers/Weblogic.txt +160 -0
  272. data/lib/itsi/server/config/known_paths/webservers-appservers/Websphere.txt +366 -0
  273. data/lib/itsi/server/config/known_paths/wellknown-rfc5785.txt +30 -0
  274. data/lib/itsi/server/config/known_paths.rb +20 -0
  275. data/lib/itsi/server/config/middleware/_index.md +56 -0
  276. data/lib/itsi/server/config/middleware/allow_list.md +46 -0
  277. data/lib/itsi/server/config/middleware/allow_list.rb +42 -0
  278. data/lib/itsi/server/config/middleware/auth_api_key.md +90 -0
  279. data/lib/itsi/server/config/middleware/auth_api_key.rb +51 -0
  280. data/lib/itsi/server/config/middleware/auth_basic.md +45 -0
  281. data/lib/itsi/server/config/middleware/auth_basic.rb +44 -0
  282. data/lib/itsi/server/config/middleware/auth_jwt.md +82 -0
  283. data/lib/itsi/server/config/middleware/auth_jwt.rb +38 -0
  284. data/lib/itsi/server/config/middleware/cache_control.md +78 -0
  285. data/lib/itsi/server/config/middleware/cache_control.rb +45 -0
  286. data/lib/itsi/server/config/middleware/cidr_to_regex.rb +50 -0
  287. data/lib/itsi/server/config/middleware/compression.md +50 -0
  288. data/lib/itsi/server/config/middleware/compression.rb +37 -0
  289. data/lib/itsi/server/config/middleware/cors.md +93 -0
  290. data/lib/itsi/server/config/middleware/cors.rb +32 -0
  291. data/lib/itsi/server/config/middleware/csp.md +37 -0
  292. data/lib/itsi/server/config/middleware/csp.rb +44 -0
  293. data/lib/itsi/server/config/middleware/deny_list.md +45 -0
  294. data/lib/itsi/server/config/middleware/deny_list.rb +42 -0
  295. data/lib/itsi/server/config/middleware/endpoint/_index.md +159 -0
  296. data/lib/itsi/server/config/middleware/endpoint/controller.md +186 -0
  297. data/lib/itsi/server/config/middleware/endpoint/controller.rb +33 -0
  298. data/lib/itsi/server/config/middleware/endpoint/delete.md +12 -0
  299. data/lib/itsi/server/config/middleware/endpoint/delete.rb +42 -0
  300. data/lib/itsi/server/config/middleware/endpoint/endpoint.rb +99 -0
  301. data/lib/itsi/server/config/middleware/endpoint/get.md +12 -0
  302. data/lib/itsi/server/config/middleware/endpoint/get.rb +42 -0
  303. data/lib/itsi/server/config/middleware/endpoint/http_request.md +44 -0
  304. data/lib/itsi/server/config/middleware/endpoint/http_response.md +39 -0
  305. data/lib/itsi/server/config/middleware/endpoint/patch.md +12 -0
  306. data/lib/itsi/server/config/middleware/endpoint/patch.rb +42 -0
  307. data/lib/itsi/server/config/middleware/endpoint/post.md +12 -0
  308. data/lib/itsi/server/config/middleware/endpoint/post.rb +42 -0
  309. data/lib/itsi/server/config/middleware/endpoint/put.md +12 -0
  310. data/lib/itsi/server/config/middleware/endpoint/put.rb +42 -0
  311. data/lib/itsi/server/config/middleware/endpoint/schemas.md +122 -0
  312. data/lib/itsi/server/config/middleware/error_response.md +61 -0
  313. data/lib/itsi/server/config/middleware/error_response.rb +36 -0
  314. data/lib/itsi/server/config/middleware/etag.md +59 -0
  315. data/lib/itsi/server/config/middleware/etag.rb +27 -0
  316. data/lib/itsi/server/config/middleware/grpc.md +172 -0
  317. data/lib/itsi/server/config/middleware/grpc.rb +54 -0
  318. data/lib/itsi/server/config/middleware/intrusion_protection.md +124 -0
  319. data/lib/itsi/server/config/middleware/intrusion_protection.rb +61 -0
  320. data/lib/itsi/server/config/middleware/location.md +107 -0
  321. data/lib/itsi/server/config/middleware/location.rb +99 -0
  322. data/lib/itsi/server/config/middleware/log_requests.md +65 -0
  323. data/lib/itsi/server/config/middleware/log_requests.rb +31 -0
  324. data/lib/itsi/server/config/middleware/max_body.md +18 -0
  325. data/lib/itsi/server/config/middleware/max_body.rb +21 -0
  326. data/lib/itsi/server/config/middleware/proxy.md +62 -0
  327. data/lib/itsi/server/config/middleware/proxy.rb +41 -0
  328. data/lib/itsi/server/config/middleware/rackup_file.md +54 -0
  329. data/lib/itsi/server/config/middleware/rackup_file.rb +44 -0
  330. data/lib/itsi/server/config/middleware/rate_limit.md +126 -0
  331. data/lib/itsi/server/config/middleware/rate_limit.rb +34 -0
  332. data/lib/itsi/server/config/middleware/rate_limit_store.rb +25 -0
  333. data/lib/itsi/server/config/middleware/redirect.md +55 -0
  334. data/lib/itsi/server/config/middleware/redirect.rb +25 -0
  335. data/lib/itsi/server/config/middleware/request_headers.md +34 -0
  336. data/lib/itsi/server/config/middleware/request_headers.rb +24 -0
  337. data/lib/itsi/server/config/middleware/response_headers.md +33 -0
  338. data/lib/itsi/server/config/middleware/response_headers.rb +25 -0
  339. data/lib/itsi/server/config/middleware/run.md +60 -0
  340. data/lib/itsi/server/config/middleware/run.rb +43 -0
  341. data/lib/itsi/server/config/middleware/static_assets.md +73 -0
  342. data/lib/itsi/server/config/middleware/static_assets.rb +87 -0
  343. data/lib/itsi/server/config/middleware/static_response.md +44 -0
  344. data/lib/itsi/server/config/middleware/static_response.rb +29 -0
  345. data/lib/itsi/server/config/middleware/string_rewrite.md +67 -0
  346. data/lib/itsi/server/config/middleware/token_source.rb +32 -0
  347. data/lib/itsi/server/config/middleware.rb +13 -0
  348. data/lib/itsi/server/config/option.rb +14 -0
  349. data/lib/itsi/server/config/options/_index.md +37 -0
  350. data/lib/itsi/server/config/options/auto_reload_config.md +13 -0
  351. data/lib/itsi/server/config/options/auto_reload_config.rb +41 -0
  352. data/lib/itsi/server/config/options/bind.md +71 -0
  353. data/lib/itsi/server/config/options/bind.rb +26 -0
  354. data/lib/itsi/server/config/options/certificates.md +65 -0
  355. data/lib/itsi/server/config/options/daemonize.md +14 -0
  356. data/lib/itsi/server/config/options/daemonize.rb +19 -0
  357. data/lib/itsi/server/config/options/fiber_scheduler.md +34 -0
  358. data/lib/itsi/server/config/options/fiber_scheduler.rb +21 -0
  359. data/lib/itsi/server/config/options/header_read_timeout.md +17 -0
  360. data/lib/itsi/server/config/options/header_read_timeout.rb +19 -0
  361. data/lib/itsi/server/config/options/hooks/_index.md +11 -0
  362. data/lib/itsi/server/config/options/hooks/after_fork.md +13 -0
  363. data/lib/itsi/server/config/options/hooks/after_fork.rb +28 -0
  364. data/lib/itsi/server/config/options/hooks/after_memory_limit_reached.md +14 -0
  365. data/lib/itsi/server/config/options/hooks/after_memory_limit_reached.rb +28 -0
  366. data/lib/itsi/server/config/options/hooks/after_start.md +12 -0
  367. data/lib/itsi/server/config/options/hooks/after_start.rb +28 -0
  368. data/lib/itsi/server/config/options/hooks/before_fork.md +13 -0
  369. data/lib/itsi/server/config/options/hooks/before_fork.rb +28 -0
  370. data/lib/itsi/server/config/options/hooks/before_restart.md +12 -0
  371. data/lib/itsi/server/config/options/hooks/before_restart.rb +28 -0
  372. data/lib/itsi/server/config/options/hooks/before_shutdown.md +12 -0
  373. data/lib/itsi/server/config/options/hooks/before_shutdown.rb +28 -0
  374. data/lib/itsi/server/config/options/include.md +20 -0
  375. data/lib/itsi/server/config/options/include.rb +36 -0
  376. data/lib/itsi/server/config/options/listen_backlog.md +11 -0
  377. data/lib/itsi/server/config/options/listen_backlog.rb +19 -0
  378. data/lib/itsi/server/config/options/log_format.md +18 -0
  379. data/lib/itsi/server/config/options/log_format.rb +19 -0
  380. data/lib/itsi/server/config/options/log_level.md +34 -0
  381. data/lib/itsi/server/config/options/log_level.rb +20 -0
  382. data/lib/itsi/server/config/options/log_target.md +38 -0
  383. data/lib/itsi/server/config/options/log_target.rb +19 -0
  384. data/lib/itsi/server/config/options/log_target_filters.md +17 -0
  385. data/lib/itsi/server/config/options/log_target_filters.rb +19 -0
  386. data/lib/itsi/server/config/options/multithreaded_reactor.md +27 -0
  387. data/lib/itsi/server/config/options/multithreaded_reactor.rb +24 -0
  388. data/lib/itsi/server/config/options/nodelay.md +16 -0
  389. data/lib/itsi/server/config/options/nodelay.rb +19 -0
  390. data/lib/itsi/server/config/options/oob_gc_responses_threshold.md +19 -0
  391. data/lib/itsi/server/config/options/oob_gc_responses_threshold.rb +18 -0
  392. data/lib/itsi/server/config/options/pin_worker_cores.md +17 -0
  393. data/lib/itsi/server/config/options/pin_worker_cores.rb +19 -0
  394. data/lib/itsi/server/config/options/preload.md +21 -0
  395. data/lib/itsi/server/config/options/preload.rb +18 -0
  396. data/lib/itsi/server/config/options/recv_buffer_size.md +15 -0
  397. data/lib/itsi/server/config/options/recv_buffer_size.rb +19 -0
  398. data/lib/itsi/server/config/options/redirect_http_to_https.md +21 -0
  399. data/lib/itsi/server/config/options/redirect_http_to_https.rb +30 -0
  400. data/lib/itsi/server/config/options/request_timeout.md +23 -0
  401. data/lib/itsi/server/config/options/request_timeout.rb +19 -0
  402. data/lib/itsi/server/config/options/reuse_address.md +16 -0
  403. data/lib/itsi/server/config/options/reuse_address.rb +19 -0
  404. data/lib/itsi/server/config/options/reuse_port.md +16 -0
  405. data/lib/itsi/server/config/options/reuse_port.rb +19 -0
  406. data/lib/itsi/server/config/options/scheduler_threads.md +34 -0
  407. data/lib/itsi/server/config/options/scheduler_threads.rb +17 -0
  408. data/lib/itsi/server/config/options/shutdown_timeout.md +17 -0
  409. data/lib/itsi/server/config/options/shutdown_timeout.rb +19 -0
  410. data/lib/itsi/server/config/options/stream_body.md +32 -0
  411. data/lib/itsi/server/config/options/stream_body.rb +18 -0
  412. data/lib/itsi/server/config/options/threads.md +44 -0
  413. data/lib/itsi/server/config/options/threads.rb +17 -0
  414. data/lib/itsi/server/config/options/watch.md +16 -0
  415. data/lib/itsi/server/config/options/watch.rb +28 -0
  416. data/lib/itsi/server/config/options/worker_memory_limit.md +22 -0
  417. data/lib/itsi/server/config/options/worker_memory_limit.rb +18 -0
  418. data/lib/itsi/server/config/options/workers.md +42 -0
  419. data/lib/itsi/server/config/options/workers.rb +17 -0
  420. data/lib/itsi/server/config/typed_struct.rb +242 -0
  421. data/lib/itsi/server/config.rb +289 -0
  422. data/lib/itsi/server/default_app/default_app.rb +34 -0
  423. data/lib/itsi/server/default_app/index.html +115 -0
  424. data/lib/itsi/server/default_config/Itsi.rb +107 -0
  425. data/lib/itsi/server/grpc/grpc_call.rb +246 -0
  426. data/lib/itsi/server/grpc/grpc_interface.rb +107 -0
  427. data/lib/itsi/server/grpc/reflection/v1/reflection_pb.rb +26 -0
  428. data/lib/itsi/server/grpc/reflection/v1/reflection_services_pb.rb +122 -0
  429. data/lib/itsi/server/rack/handler/itsi.rb +27 -0
  430. data/lib/itsi/server/rack_interface.rb +94 -0
  431. data/lib/itsi/server/route_tester.rb +157 -0
  432. data/lib/itsi/server/scheduler_interface.rb +21 -0
  433. data/lib/itsi/server/scheduler_mode.rb +10 -0
  434. data/lib/itsi/server/signal_trap.rb +33 -0
  435. data/lib/itsi/server/typed_handlers/param_parser.rb +196 -0
  436. data/lib/itsi/server/typed_handlers/source_parser.rb +56 -0
  437. data/lib/itsi/server/typed_handlers.rb +25 -0
  438. data/lib/itsi/server/version.rb +1 -1
  439. data/lib/itsi/server.rb +265 -9
  440. data/lib/itsi/standard_headers.rb +86 -0
  441. data/lib/ruby_lsp/itsi/addon.rb +129 -0
  442. data/lib/shell_completions/completions.rb +26 -0
  443. metadata +454 -28
  444. data/CHANGELOG.md +0 -5
  445. data/CODE_OF_CONDUCT.md +0 -132
  446. data/LICENSE.txt +0 -21
  447. data/ext/itsi_server/src/request/itsi_request.rs +0 -143
  448. data/ext/itsi_server/src/request/mod.rs +0 -1
  449. data/ext/itsi_server/src/server/bind.rs +0 -138
  450. data/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +0 -32
  451. data/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +0 -52
  452. data/ext/itsi_server/src/server/itsi_server.rs +0 -182
  453. data/ext/itsi_server/src/server/listener.rs +0 -218
  454. data/ext/itsi_server/src/server/tls.rs +0 -138
  455. data/ext/itsi_server/src/server/transfer_protocol.rs +0 -23
  456. data/ext/itsi_server/src/stream_writer/mod.rs +0 -21
  457. data/lib/itsi/request.rb +0 -39
@@ -0,0 +1,6 @@
1
+ pub mod cache_store;
2
+ pub mod itsi_http_service;
3
+ pub mod mime_types;
4
+ pub mod password_hasher;
5
+ pub mod rate_limiter;
6
+ pub mod static_file_server;
@@ -0,0 +1,83 @@
1
+ use argon2::{
2
+ password_hash::{rand_core::OsRng, PasswordHasher, PasswordVerifier, SaltString},
3
+ Argon2, PasswordHash,
4
+ };
5
+
6
+ use itsi_error::ItsiError;
7
+ use magnus::{error::Result, Value};
8
+ use serde::Deserialize;
9
+ use serde_magnus::deserialize;
10
+ use sha_crypt::{
11
+ sha256_check, sha256_simple, sha512_check, sha512_simple, Sha256Params, Sha512Params,
12
+ };
13
+
14
+ #[derive(Debug, Deserialize)]
15
+ pub enum HashAlgorithm {
16
+ #[serde(rename(deserialize = "bcrypt"))]
17
+ Bcrypt,
18
+ #[serde(rename(deserialize = "sha256"))]
19
+ Sha256Crypt,
20
+ #[serde(rename(deserialize = "sha512"))]
21
+ Sha512Crypt,
22
+ #[serde(rename(deserialize = "argon2"))]
23
+ Argon2,
24
+ #[serde(rename(deserialize = "none"))]
25
+ None,
26
+ }
27
+
28
+ pub fn create_password_hash(password: String, algo: Value) -> Result<String> {
29
+ let hash_algorithm: HashAlgorithm = deserialize(algo)?;
30
+ match hash_algorithm {
31
+ HashAlgorithm::Bcrypt => {
32
+ // Use the bcrypt crate for password hashing.
33
+ bcrypt::hash(&password, bcrypt::DEFAULT_COST)
34
+ .map_err(ItsiError::new)
35
+ .map(Ok)?
36
+ }
37
+ HashAlgorithm::Sha256Crypt => {
38
+ let params = Sha256Params::new(1000).unwrap();
39
+ let hash = sha256_simple(&password, &params)
40
+ .map_err(|_| ItsiError::new("SHA256 hashing failed"))?;
41
+ Ok(hash)
42
+ }
43
+ HashAlgorithm::Sha512Crypt => {
44
+ let params = Sha512Params::new(1000).unwrap();
45
+ let hash = sha512_simple(&password, &params)
46
+ .map_err(|_| ItsiError::new("SHA512 hashing failed"))?;
47
+ Ok(hash)
48
+ }
49
+ HashAlgorithm::Argon2 => {
50
+ let salt = SaltString::generate(&mut OsRng);
51
+ let argon2 = Argon2::default();
52
+ let password_hash = argon2
53
+ .hash_password(password.as_bytes(), &salt)
54
+ .map_err(|_| ItsiError::new("Argon2 hashing failed"))?
55
+ .to_string();
56
+ Ok(password_hash)
57
+ }
58
+ HashAlgorithm::None => Ok(format!("$none${}", password)),
59
+ }
60
+ }
61
+
62
+ pub fn verify_password_hash(password: &str, hash: &str) -> Result<bool> {
63
+ if hash.starts_with("$2a$") || hash.starts_with("$2b$") || hash.starts_with("$2y$") {
64
+ Ok(bcrypt::verify(password, hash).map_err(ItsiError::new)?)
65
+ } else if hash.starts_with("$5$") {
66
+ Ok(sha256_check(password, hash).is_ok())
67
+ } else if hash.starts_with("$6$") {
68
+ Ok(sha512_check(password, hash).is_ok())
69
+ } else if hash.starts_with("$argon2") {
70
+ let parsed_hash =
71
+ PasswordHash::new(hash).map_err(|_| ItsiError::new("Argon2 hash parsing failed"))?;
72
+ Ok(Argon2::default()
73
+ .verify_password(password.as_bytes(), &parsed_hash)
74
+ .is_ok())
75
+ } else if hash
76
+ .strip_prefix("$none$")
77
+ .is_some_and(|stripped| stripped == password)
78
+ {
79
+ Ok(true)
80
+ } else {
81
+ Err(ItsiError::new("Unsupported hash algorithm").into())
82
+ }
83
+ }
@@ -0,0 +1,580 @@
1
+ use async_trait::async_trait;
2
+ use rand::Rng;
3
+ use redis::aio::ConnectionManager;
4
+ use redis::{Client, RedisError, Script};
5
+ use serde::Deserialize;
6
+ use std::any::Any;
7
+ use std::collections::{HashMap, HashSet};
8
+ use std::result::Result;
9
+ use std::sync::{Arc, LazyLock, Mutex};
10
+ use std::time::{Duration, Instant};
11
+ use tokio::sync::{Mutex as AsyncMutex, RwLock};
12
+ use tokio::time::timeout;
13
+ use tracing::warn;
14
+ use url::Url;
15
+
16
+ #[derive(Debug)]
17
+ pub enum RateLimitError {
18
+ RedisError(RedisError),
19
+ RateLimitExceeded { limit: u64, count: u64, ttl: u64 },
20
+ LockError,
21
+ ConnectionTimeout,
22
+ }
23
+
24
+ impl From<RedisError> for RateLimitError {
25
+ fn from(err: RedisError) -> Self {
26
+ RateLimitError::RedisError(err)
27
+ }
28
+ }
29
+
30
+ impl std::fmt::Display for RateLimitError {
31
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32
+ match self {
33
+ RateLimitError::RedisError(e) => write!(f, "Redis error: {}", e),
34
+ RateLimitError::RateLimitExceeded { limit, count, ttl } => {
35
+ write!(f, "Rate limit exceeded: {}/{} (ttl: {})", count, limit, ttl)
36
+ }
37
+ RateLimitError::LockError => write!(f, "Failed to acquire lock"),
38
+ RateLimitError::ConnectionTimeout => write!(f, "Connection timeout"),
39
+ }
40
+ }
41
+ }
42
+
43
+ /// A RateLimiter trait for limiting HTTP requests
44
+ #[async_trait]
45
+ pub trait RateLimiter: Send + Sync + std::fmt::Debug {
46
+ /// Increments the counter associated with `key` and sets its expiration.
47
+ /// Returns the new counter value.
48
+ ///
49
+ /// If the operation fails, returns Ok(0) to fail open.
50
+ async fn increment(&self, key: &str, timeout: Duration) -> Result<(u64, u64), RateLimitError>;
51
+
52
+ /// Checks if the rate limit is exceeded for the given key.
53
+ /// Returns Ok(current_count) if not exceeded, or Err(RateLimitExceeded) if exceeded.
54
+ ///
55
+ /// If there's an error (like connectivity issues), this will always return Ok
56
+ /// to allow the request through (fail open).
57
+ async fn check_limit(
58
+ &self,
59
+ key: &str,
60
+ limit: u64,
61
+ timeout: Duration,
62
+ ) -> Result<(u64, u64), RateLimitError>;
63
+
64
+ /// Returns self as Any for downcasting
65
+ fn as_any(&self) -> &dyn Any;
66
+ }
67
+
68
+ /// A Redis-backed rate limiter using an async connection manager.
69
+ /// This uses a TLS-enabled connection when the URL is prefixed with "rediss://".
70
+ #[derive(Clone)]
71
+ pub struct RedisRateLimiter {
72
+ connection: Arc<ConnectionManager>,
73
+ increment_script: Script,
74
+ }
75
+
76
+ impl std::fmt::Debug for RedisRateLimiter {
77
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78
+ f.debug_struct("RedisRateLimiter").finish()
79
+ }
80
+ }
81
+
82
+ impl RedisRateLimiter {
83
+ /// Constructs a new RedisRateLimiter with a timeout.
84
+ ///
85
+ /// Use a connection URL like:
86
+ /// - Standard: "redis://host:port/db"
87
+ /// - With auth: "redis://:password@host:port/db"
88
+ /// - With TLS: "rediss://host:port/db"
89
+ /// - With TLS and auth: "rediss://:password@host:port/db"
90
+ pub async fn new(connection_url: &str) -> Result<Self, RateLimitError> {
91
+ // Set a reasonable timeout for connection attempts (5 seconds)
92
+ const CONNECTION_TIMEOUT: Duration = Duration::from_secs(5);
93
+
94
+ // Parse URL to extract auth information if provided
95
+ let url_result = Url::parse(connection_url);
96
+ if let Err(e) = url_result {
97
+ tracing::error!("Invalid Redis URL format: {}", e);
98
+ return Err(RateLimitError::RedisError(RedisError::from((
99
+ redis::ErrorKind::InvalidClientConfig,
100
+ "Invalid Redis URL format",
101
+ ))));
102
+ }
103
+ let client = Client::open(connection_url).map_err(RateLimitError::RedisError)?;
104
+ let connection_manager_result =
105
+ timeout(CONNECTION_TIMEOUT, ConnectionManager::new(client)).await;
106
+ let connection_manager = match connection_manager_result {
107
+ Ok(result) => result.map_err(RateLimitError::RedisError)?,
108
+ Err(_) => return Err(RateLimitError::ConnectionTimeout),
109
+ };
110
+
111
+ // Create the Lua script once when initializing the rate limiter
112
+ let increment_script = Script::new(
113
+ r#"
114
+ -- Increment the counter
115
+ local current = redis.call('INCR', KEYS[1])
116
+
117
+ -- Fetch existing TTL
118
+ local ttl = redis.call('TTL', KEYS[1])
119
+ if ttl < 0 then
120
+ -- If no TTL is set, apply the window interval
121
+ local window = tonumber(ARGV[1])
122
+ redis.call('EXPIRE', KEYS[1], window)
123
+ ttl = window
124
+ end
125
+
126
+ -- Return both the current count and remaining TTL
127
+ return { current, ttl }
128
+ "#,
129
+ );
130
+
131
+ Ok(Self {
132
+ connection: Arc::new(connection_manager),
133
+ increment_script,
134
+ })
135
+ }
136
+
137
+ /// Bans an IP address for the specified duration
138
+ pub async fn ban_ip(
139
+ &self,
140
+ ip: &str,
141
+ reason: &str,
142
+ duration: Duration,
143
+ ) -> Result<(), RateLimitError> {
144
+ let ban_key = format!("ban:ip:{}", ip);
145
+ let timeout_secs = duration.as_secs();
146
+ let mut connection = (*self.connection).clone();
147
+
148
+ // Set the ban with the reason as the value
149
+ let _: Result<(), RedisError> = redis::cmd("SET")
150
+ .arg(&ban_key)
151
+ .arg(reason)
152
+ .arg("EX")
153
+ .arg(timeout_secs)
154
+ .query_async(&mut connection)
155
+ .await
156
+ .inspect_err(|e| warn!("Exception banning IP {:?}", e));
157
+
158
+ Ok(())
159
+ }
160
+
161
+ /// Checks if an IP address is banned
162
+ pub async fn is_banned(&self, ip: &str) -> Result<Option<String>, RateLimitError> {
163
+ let ban_key = format!("ban:ip:{}", ip);
164
+ let mut connection = (*self.connection).clone();
165
+
166
+ // Get the ban reason if it exists
167
+ let result: Option<String> = redis::cmd("GET")
168
+ .arg(&ban_key)
169
+ .query_async(&mut connection)
170
+ .await
171
+ .map_err(RateLimitError::RedisError)?;
172
+
173
+ Ok(result)
174
+ }
175
+ }
176
+
177
+ #[async_trait]
178
+ impl RateLimiter for RedisRateLimiter {
179
+ async fn increment(&self, key: &str, timeout: Duration) -> Result<(u64, u64), RateLimitError> {
180
+ let timeout_secs = timeout.as_secs();
181
+ let mut connection = (*self.connection).clone();
182
+
183
+ // Use the pre-compiled script (atomic approach)
184
+ match self
185
+ .increment_script
186
+ .key(key)
187
+ .arg(timeout_secs)
188
+ .invoke_async(&mut connection)
189
+ .await
190
+ {
191
+ Ok((count, ttl)) => Ok((count, ttl)),
192
+ Err(err) => {
193
+ // Log the error but return 0 to fail open
194
+ tracing::warn!("Redis rate limit error: {}", err);
195
+ Ok((0, timeout_secs))
196
+ }
197
+ }
198
+ }
199
+
200
+ async fn check_limit(
201
+ &self,
202
+ key: &str,
203
+ limit: u64,
204
+ timeout: Duration,
205
+ ) -> Result<(u64, u64), RateLimitError> {
206
+ match self.increment(key, timeout).await {
207
+ Ok((count, ttl)) if count <= limit => Ok((count, ttl)),
208
+ Ok((count, ttl)) if count > limit => {
209
+ Err(RateLimitError::RateLimitExceeded { limit, count, ttl })
210
+ }
211
+ // For any error or other case, fail open
212
+ _ => Ok((0, timeout.as_secs())),
213
+ }
214
+ }
215
+
216
+ fn as_any(&self) -> &dyn Any {
217
+ self
218
+ }
219
+ }
220
+
221
+ /// An entry in the in-memory rate limiter
222
+ #[derive(Debug)]
223
+ struct RateLimitEntry {
224
+ count: u64,
225
+ expires_at: Instant,
226
+ }
227
+
228
+ /// An in-memory implementation of the RateLimiter trait
229
+ #[derive(Debug)]
230
+ pub struct InMemoryRateLimiter {
231
+ entries: RwLock<HashMap<String, RateLimitEntry>>,
232
+ }
233
+
234
+ impl InMemoryRateLimiter {
235
+ /// Creates a new in-memory rate limiter
236
+ pub fn new() -> Self {
237
+ Self {
238
+ entries: RwLock::new(HashMap::new()),
239
+ }
240
+ }
241
+
242
+ /// Cleans up expired entries
243
+ async fn cleanup(&self) {
244
+ // Try to get the write lock, but fail open if we can't
245
+ if let Ok(mut entries) = self.entries.try_write() {
246
+ let now = Instant::now();
247
+ entries.retain(|_, entry| entry.expires_at > now);
248
+ }
249
+ }
250
+
251
+ /// Bans an IP address for the specified duration
252
+ pub async fn ban_ip(
253
+ &self,
254
+ ip: &str,
255
+ _: &str,
256
+ duration: Duration,
257
+ ) -> Result<(), RateLimitError> {
258
+ let now = Instant::now();
259
+ let ban_key = format!("ban:ip:{}", ip);
260
+
261
+ let mut entries = self.entries.try_write().map_err(|e| {
262
+ tracing::error!("Failed to acquire write lock: {}", e);
263
+ RateLimitError::LockError
264
+ })?;
265
+
266
+ entries.insert(
267
+ ban_key,
268
+ RateLimitEntry {
269
+ count: 1, // Use count=1 to indicate banned
270
+ expires_at: now + duration,
271
+ },
272
+ );
273
+
274
+ Ok(())
275
+ }
276
+
277
+ /// Checks if an IP address is banned
278
+ pub async fn is_banned(&self, ip: &str) -> Result<Option<String>, RateLimitError> {
279
+ let now = Instant::now();
280
+ let ban_key = format!("ban:ip:{}", ip);
281
+
282
+ let entries = self.entries.try_read().map_err(|e| {
283
+ tracing::error!("Failed to acquire read lock: {}", e);
284
+ RateLimitError::LockError
285
+ })?;
286
+
287
+ if let Some(entry) = entries.get(&ban_key) {
288
+ if entry.expires_at > now {
289
+ // IP is banned, return a generic reason since we don't store reasons
290
+ return Ok(Some("IP address banned".to_string()));
291
+ }
292
+ }
293
+
294
+ Ok(None)
295
+ }
296
+ }
297
+
298
+ impl Default for InMemoryRateLimiter {
299
+ fn default() -> Self {
300
+ Self::new()
301
+ }
302
+ }
303
+
304
+ #[async_trait]
305
+ impl RateLimiter for InMemoryRateLimiter {
306
+ async fn increment(&self, key: &str, timeout: Duration) -> Result<(u64, u64), RateLimitError> {
307
+ if rand::rng().random_bool(0.01) {
308
+ self.cleanup().await;
309
+ }
310
+
311
+ let now = Instant::now();
312
+
313
+ let mut entries = self.entries.write().await;
314
+
315
+ let entry = entries
316
+ .entry(key.to_string())
317
+ .or_insert_with(|| RateLimitEntry {
318
+ count: 0,
319
+ expires_at: now + timeout,
320
+ });
321
+
322
+ if entry.expires_at < now {
323
+ entry.expires_at = now + timeout;
324
+ entry.count = 1;
325
+ } else {
326
+ entry.count += 1;
327
+ }
328
+
329
+ let ttl = if entry.expires_at > now {
330
+ entry.expires_at.duration_since(now).as_secs()
331
+ } else {
332
+ 0
333
+ };
334
+
335
+ Ok((entry.count, ttl))
336
+ }
337
+
338
+ async fn check_limit(
339
+ &self,
340
+ key: &str,
341
+ limit: u64,
342
+ timeout: Duration,
343
+ ) -> Result<(u64, u64), RateLimitError> {
344
+ match self.increment(key, timeout).await {
345
+ Ok((count, ttl)) if count <= limit => Ok((count, ttl)),
346
+ Ok((count, ttl)) if count > limit => {
347
+ Err(RateLimitError::RateLimitExceeded { limit, count, ttl })
348
+ }
349
+ // For any error or other case, fail open
350
+ _ => Ok((0, timeout.as_secs())),
351
+ }
352
+ }
353
+
354
+ fn as_any(&self) -> &dyn Any {
355
+ self
356
+ }
357
+ }
358
+
359
+ /// Enum to represent different types of rate limiters that can ban IPs
360
+ #[derive(Debug, Clone)]
361
+ pub enum BanManager {
362
+ Redis(Arc<RedisRateLimiter>),
363
+ InMemory(Arc<InMemoryRateLimiter>),
364
+ }
365
+
366
+ impl BanManager {
367
+ /// Bans an IP address for the specified duration
368
+ pub async fn ban_ip(
369
+ &self,
370
+ ip: &str,
371
+ reason: &str,
372
+ duration: Duration,
373
+ ) -> Result<(), RateLimitError> {
374
+ match self {
375
+ BanManager::Redis(limiter) => limiter.ban_ip(ip, reason, duration).await,
376
+ BanManager::InMemory(limiter) => limiter.ban_ip(ip, reason, duration).await,
377
+ }
378
+ }
379
+
380
+ /// Checks if an IP address is banned
381
+ pub async fn is_banned(&self, ip: &str) -> Result<Option<String>, RateLimitError> {
382
+ match self {
383
+ BanManager::Redis(limiter) => limiter.is_banned(ip).await,
384
+ BanManager::InMemory(limiter) => limiter.is_banned(ip).await,
385
+ }
386
+ }
387
+ }
388
+
389
+ /// Utility function to create a rate limit key for a specific minute
390
+ pub fn create_rate_limit_key(api_key: &str, resource: &str) -> String {
391
+ // Get the current minute number (0-59)
392
+ let now = std::time::SystemTime::now()
393
+ .duration_since(std::time::UNIX_EPOCH)
394
+ .unwrap_or_default();
395
+
396
+ let minutes = now.as_secs() / 60 % 60;
397
+ format!("ratelimit:{}:{}:{}", api_key, resource, minutes)
398
+ }
399
+
400
+ /// Utility function to create a ban key for an IP address
401
+ pub fn create_ban_key(ip: &str) -> String {
402
+ format!("ban:ip:{}", ip)
403
+ }
404
+
405
+ // Global map of URL to mutex to ensure only one connection attempt per URL at a time
406
+ static CONNECTION_LOCKS: LazyLock<Mutex<HashMap<String, Arc<AsyncMutex<()>>>>> =
407
+ LazyLock::new(|| Mutex::new(HashMap::new()));
408
+
409
+ /// A global store for rate limiters, indexed by connection URL
410
+ pub struct RateLimiterStore {
411
+ redis_limiters: Mutex<HashMap<String, Arc<RedisRateLimiter>>>,
412
+ memory_limiter: Arc<InMemoryRateLimiter>,
413
+ // Track known bad Redis URLs to avoid repeated connection attempts
414
+ failed_urls: Mutex<HashSet<String>>,
415
+ }
416
+
417
+ impl RateLimiterStore {
418
+ /// Create a new store with a single in-memory rate limiter
419
+ fn new() -> Self {
420
+ Self {
421
+ redis_limiters: Mutex::new(HashMap::new()),
422
+ memory_limiter: Arc::new(InMemoryRateLimiter::new()),
423
+ failed_urls: Mutex::new(HashSet::new()),
424
+ }
425
+ }
426
+
427
+ /// Get an in-memory rate limiter
428
+ pub fn get_memory_limiter(&self) -> Arc<InMemoryRateLimiter> {
429
+ self.memory_limiter.clone()
430
+ }
431
+
432
+ /// Get a Redis rate limiter for the given connection URL, creating one if it doesn't exist
433
+ pub async fn get_redis_limiter(
434
+ &self,
435
+ connection_url: &str,
436
+ ) -> Result<Arc<RedisRateLimiter>, RateLimitError> {
437
+ // First check if this URL is known to fail
438
+ {
439
+ let failed_urls = self.failed_urls.lock().unwrap_or_else(|e| e.into_inner());
440
+ if failed_urls.contains(connection_url) {
441
+ return Err(RateLimitError::ConnectionTimeout);
442
+ }
443
+ }
444
+
445
+ // Then check if we already have a limiter for this URL
446
+ {
447
+ let limiters = self
448
+ .redis_limiters
449
+ .lock()
450
+ .unwrap_or_else(|e| e.into_inner());
451
+ if let Some(limiter) = limiters.get(connection_url) {
452
+ return Ok(limiter.clone());
453
+ }
454
+ }
455
+
456
+ // Get a dedicated mutex for this URL or create a new one if it doesn't exist
457
+ let url_mutex = {
458
+ let mut locks = CONNECTION_LOCKS.lock().unwrap_or_else(|e| e.into_inner());
459
+
460
+ // Get or create the mutex for this URL
461
+ locks
462
+ .entry(connection_url.to_string())
463
+ .or_insert_with(|| Arc::new(AsyncMutex::new(())))
464
+ .clone()
465
+ };
466
+
467
+ // Acquire the mutex with a timeout to avoid deadlocks
468
+ let lock_result = timeout(Duration::from_secs(5), url_mutex.lock()).await;
469
+ let _guard = match lock_result {
470
+ Ok(guard) => guard,
471
+ Err(_) => {
472
+ tracing::warn!("Timed out waiting for lock on URL: {}", connection_url);
473
+ return Err(RateLimitError::LockError);
474
+ }
475
+ };
476
+
477
+ // Check again if another thread created the limiter while we were waiting
478
+ {
479
+ let limiters = self
480
+ .redis_limiters
481
+ .lock()
482
+ .unwrap_or_else(|e| e.into_inner());
483
+ if let Some(limiter) = limiters.get(connection_url) {
484
+ return Ok(limiter.clone());
485
+ }
486
+ }
487
+
488
+ // Create a new limiter
489
+ tracing::info!("Initializing Redis rate limiter for {}", connection_url);
490
+ match RedisRateLimiter::new(connection_url).await {
491
+ Ok(limiter) => {
492
+ let limiter = Arc::new(limiter);
493
+
494
+ // Store it for future use
495
+ let mut limiters = self
496
+ .redis_limiters
497
+ .lock()
498
+ .unwrap_or_else(|e| e.into_inner());
499
+ limiters.insert(connection_url.to_string(), limiter.clone());
500
+
501
+ Ok(limiter)
502
+ }
503
+ Err(e) => {
504
+ tracing::error!("Failed to initialize Redis rate limiter: {}", e);
505
+ // Cache the failure
506
+ let mut failed_urls = self.failed_urls.lock().unwrap_or_else(|e| e.into_inner());
507
+ failed_urls.insert(connection_url.to_string());
508
+ Err(e)
509
+ }
510
+ }
511
+ }
512
+
513
+ /// Get a BanManager for the given RateLimiterConfig
514
+ pub async fn get_ban_manager(
515
+ &self,
516
+ config: &RateLimiterConfig,
517
+ ) -> Result<BanManager, RateLimitError> {
518
+ match config {
519
+ RateLimiterConfig::Memory => {
520
+ tracing::debug!("Using in-memory ban manager");
521
+ Ok(BanManager::InMemory(self.get_memory_limiter()))
522
+ }
523
+ RateLimiterConfig::Redis { connection_url } => {
524
+ match self.get_redis_limiter(connection_url).await {
525
+ Ok(limiter) => Ok(BanManager::Redis(limiter)),
526
+ Err(_) => Ok(BanManager::InMemory(self.get_memory_limiter())),
527
+ }
528
+ }
529
+ }
530
+ }
531
+ }
532
+
533
+ /// Global store of rate limiters
534
+ pub static RATE_LIMITER_STORE: LazyLock<RateLimiterStore> = LazyLock::new(RateLimiterStore::new);
535
+
536
+ /// Convenience function to get an in-memory rate limiter
537
+ pub fn get_memory_rate_limiter() -> Arc<impl RateLimiter> {
538
+ RATE_LIMITER_STORE.get_memory_limiter()
539
+ }
540
+
541
+ /// Convenience function to get a Redis rate limiter by connection URL
542
+ pub async fn get_redis_rate_limiter(
543
+ connection_url: &str,
544
+ ) -> Result<Arc<impl RateLimiter>, RateLimitError> {
545
+ RATE_LIMITER_STORE.get_redis_limiter(connection_url).await
546
+ }
547
+
548
+ /// Get a rate limiter based on configuration
549
+ pub async fn get_rate_limiter(
550
+ config: &RateLimiterConfig,
551
+ ) -> Result<Arc<dyn RateLimiter>, RateLimitError> {
552
+ match config {
553
+ RateLimiterConfig::Memory => Ok(get_memory_rate_limiter() as Arc<dyn RateLimiter>),
554
+ RateLimiterConfig::Redis { connection_url } => {
555
+ match get_redis_rate_limiter(connection_url).await {
556
+ Ok(limiter) => Ok(limiter as Arc<dyn RateLimiter>),
557
+ Err(_) => Ok(get_memory_rate_limiter() as Arc<dyn RateLimiter>),
558
+ }
559
+ }
560
+ }
561
+ }
562
+
563
+ /// Get a ban manager based on configuration
564
+ pub async fn get_ban_manager(config: &RateLimiterConfig) -> Result<BanManager, RateLimitError> {
565
+ RATE_LIMITER_STORE.get_ban_manager(config).await
566
+ }
567
+
568
+ /// Configuration for rate limiters
569
+ #[derive(Debug, Clone, Deserialize)]
570
+ pub enum RateLimiterConfig {
571
+ /// Use an in-memory rate limiter
572
+ #[serde(rename(deserialize = "in_memory"))]
573
+ Memory,
574
+ /// Use a Redis-backed rate limiter
575
+ #[serde(rename(deserialize = "redis"))]
576
+ Redis {
577
+ /// Connection URL, including database number if needed (e.g., "redis://localhost:6379/0")
578
+ connection_url: String,
579
+ },
580
+ }