itsi-server 0.2.22-aarch64-linux

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