itsi 0.1.14 → 0.1.19

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 (169) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +126 -272
  3. data/Cargo.toml +6 -0
  4. data/crates/itsi_error/Cargo.toml +1 -0
  5. data/crates/itsi_error/src/lib.rs +100 -10
  6. data/crates/itsi_scheduler/src/itsi_scheduler.rs +1 -1
  7. data/crates/itsi_server/Cargo.toml +12 -11
  8. data/crates/itsi_server/src/default_responses/html/401.html +68 -0
  9. data/crates/itsi_server/src/default_responses/html/403.html +68 -0
  10. data/crates/itsi_server/src/default_responses/html/404.html +68 -0
  11. data/crates/itsi_server/src/default_responses/html/413.html +71 -0
  12. data/crates/itsi_server/src/default_responses/html/429.html +68 -0
  13. data/crates/itsi_server/src/default_responses/html/500.html +71 -0
  14. data/crates/itsi_server/src/default_responses/html/502.html +71 -0
  15. data/crates/itsi_server/src/default_responses/html/503.html +68 -0
  16. data/crates/itsi_server/src/default_responses/html/504.html +69 -0
  17. data/crates/itsi_server/src/default_responses/html/index.html +238 -0
  18. data/crates/itsi_server/src/default_responses/json/401.json +6 -0
  19. data/crates/itsi_server/src/default_responses/json/403.json +6 -0
  20. data/crates/itsi_server/src/default_responses/json/404.json +6 -0
  21. data/crates/itsi_server/src/default_responses/json/413.json +6 -0
  22. data/crates/itsi_server/src/default_responses/json/429.json +6 -0
  23. data/crates/itsi_server/src/default_responses/json/500.json +6 -0
  24. data/crates/itsi_server/src/default_responses/json/502.json +6 -0
  25. data/crates/itsi_server/src/default_responses/json/503.json +6 -0
  26. data/crates/itsi_server/src/default_responses/json/504.json +6 -0
  27. data/crates/itsi_server/src/default_responses/mod.rs +11 -0
  28. data/crates/itsi_server/src/lib.rs +58 -26
  29. data/crates/itsi_server/src/prelude.rs +2 -0
  30. data/crates/itsi_server/src/ruby_types/README.md +21 -0
  31. data/crates/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +8 -6
  32. data/crates/itsi_server/src/ruby_types/itsi_grpc_call.rs +344 -0
  33. data/crates/itsi_server/src/ruby_types/{itsi_grpc_stream → itsi_grpc_response_stream}/mod.rs +121 -73
  34. data/crates/itsi_server/src/ruby_types/itsi_http_request.rs +103 -40
  35. data/crates/itsi_server/src/ruby_types/itsi_http_response.rs +8 -5
  36. data/crates/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +4 -4
  37. data/crates/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +37 -17
  38. data/crates/itsi_server/src/ruby_types/itsi_server.rs +4 -3
  39. data/crates/itsi_server/src/ruby_types/mod.rs +6 -13
  40. data/crates/itsi_server/src/server/{bind.rs → binds/bind.rs} +23 -4
  41. data/crates/itsi_server/src/server/{listener.rs → binds/listener.rs} +24 -10
  42. data/crates/itsi_server/src/server/binds/mod.rs +4 -0
  43. data/crates/itsi_server/src/server/{tls.rs → binds/tls.rs} +9 -4
  44. data/crates/itsi_server/src/server/http_message_types.rs +97 -0
  45. data/crates/itsi_server/src/server/io_stream.rs +2 -1
  46. data/crates/itsi_server/src/server/middleware_stack/middleware.rs +28 -16
  47. data/crates/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +17 -8
  48. data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +47 -18
  49. data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +13 -9
  50. data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +50 -29
  51. data/crates/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +5 -2
  52. data/crates/itsi_server/src/server/middleware_stack/middlewares/compression.rs +37 -48
  53. data/crates/itsi_server/src/server/middleware_stack/middlewares/cors.rs +25 -20
  54. data/crates/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +14 -7
  55. data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +190 -0
  56. data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +125 -95
  57. data/crates/itsi_server/src/server/middleware_stack/middlewares/etag.rs +9 -5
  58. data/crates/itsi_server/src/server/middleware_stack/middlewares/header_interpretation.rs +1 -4
  59. data/crates/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +25 -19
  60. data/crates/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +4 -4
  61. data/crates/itsi_server/src/server/middleware_stack/middlewares/max_body.rs +47 -0
  62. data/crates/itsi_server/src/server/middleware_stack/middlewares/mod.rs +9 -4
  63. data/crates/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +260 -62
  64. data/crates/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +29 -22
  65. data/crates/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +6 -6
  66. data/crates/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +6 -5
  67. data/crates/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +4 -2
  68. data/crates/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +51 -18
  69. data/crates/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +31 -13
  70. data/crates/itsi_server/src/server/middleware_stack/middlewares/static_response.rs +55 -0
  71. data/crates/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +13 -8
  72. data/crates/itsi_server/src/server/middleware_stack/mod.rs +101 -69
  73. data/crates/itsi_server/src/server/mod.rs +3 -9
  74. data/crates/itsi_server/src/server/process_worker.rs +21 -3
  75. data/crates/itsi_server/src/server/request_job.rs +2 -2
  76. data/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs +8 -3
  77. data/crates/itsi_server/src/server/serve_strategy/single_mode.rs +26 -26
  78. data/crates/itsi_server/src/server/signal.rs +24 -41
  79. data/crates/itsi_server/src/server/size_limited_incoming.rs +101 -0
  80. data/crates/itsi_server/src/server/thread_worker.rs +59 -28
  81. data/crates/itsi_server/src/services/itsi_http_service.rs +239 -0
  82. data/crates/itsi_server/src/services/mime_types.rs +1416 -0
  83. data/crates/itsi_server/src/services/mod.rs +6 -0
  84. data/crates/itsi_server/src/services/password_hasher.rs +83 -0
  85. data/crates/itsi_server/src/{server → services}/rate_limiter.rs +35 -31
  86. data/crates/itsi_server/src/{server → services}/static_file_server.rs +521 -181
  87. data/crates/itsi_tracing/src/lib.rs +145 -55
  88. data/{Itsi.rb → foo/Itsi.rb} +6 -9
  89. data/gems/scheduler/Cargo.lock +7 -0
  90. data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
  91. data/gems/scheduler/test/helpers/test_helper.rb +0 -1
  92. data/gems/scheduler/test/test_address_resolve.rb +0 -1
  93. data/gems/scheduler/test/test_network_io.rb +1 -1
  94. data/gems/scheduler/test/test_process_wait.rb +0 -1
  95. data/gems/server/Cargo.lock +126 -272
  96. data/gems/server/exe/itsi +65 -19
  97. data/gems/server/itsi-server.gemspec +4 -3
  98. data/gems/server/lib/itsi/http_request/response_status_shortcodes.rb +74 -0
  99. data/gems/server/lib/itsi/http_request.rb +117 -17
  100. data/gems/server/lib/itsi/http_response.rb +2 -0
  101. data/gems/server/lib/itsi/passfile.rb +109 -0
  102. data/gems/server/lib/itsi/server/config/dsl.rb +171 -99
  103. data/gems/server/lib/itsi/server/config.rb +58 -23
  104. data/gems/server/lib/itsi/server/default_app/default_app.rb +25 -29
  105. data/gems/server/lib/itsi/server/default_app/index.html +113 -89
  106. data/gems/server/lib/itsi/server/{Itsi.rb → default_config/Itsi-rackup.rb} +1 -1
  107. data/gems/server/lib/itsi/server/default_config/Itsi.rb +107 -0
  108. data/gems/server/lib/itsi/server/grpc/grpc_call.rb +246 -0
  109. data/gems/server/lib/itsi/server/grpc/grpc_interface.rb +100 -0
  110. data/gems/server/lib/itsi/server/grpc/reflection/v1/reflection_pb.rb +26 -0
  111. data/gems/server/lib/itsi/server/grpc/reflection/v1/reflection_services_pb.rb +122 -0
  112. data/gems/server/lib/itsi/server/route_tester.rb +107 -0
  113. data/gems/server/lib/itsi/server/typed_handlers/param_parser.rb +200 -0
  114. data/gems/server/lib/itsi/server/typed_handlers/source_parser.rb +55 -0
  115. data/gems/server/lib/itsi/server/typed_handlers.rb +17 -0
  116. data/gems/server/lib/itsi/server/version.rb +1 -1
  117. data/gems/server/lib/itsi/server.rb +82 -12
  118. data/gems/server/lib/ruby_lsp/itsi/addon.rb +111 -0
  119. data/gems/server/lib/shell_completions/completions.rb +26 -0
  120. data/gems/server/test/helpers/test_helper.rb +2 -1
  121. data/lib/itsi/version.rb +1 -1
  122. data/sandbox/README.md +5 -0
  123. data/sandbox/itsi_file/Gemfile +4 -2
  124. data/sandbox/itsi_file/Gemfile.lock +48 -6
  125. data/sandbox/itsi_file/Itsi.rb +327 -129
  126. data/sandbox/itsi_file/call.json +1 -0
  127. data/sandbox/itsi_file/echo_client/Gemfile +10 -0
  128. data/sandbox/itsi_file/echo_client/Gemfile.lock +27 -0
  129. data/sandbox/itsi_file/echo_client/README.md +95 -0
  130. data/sandbox/itsi_file/echo_client/echo_client.rb +164 -0
  131. data/sandbox/itsi_file/echo_client/gen_proto.sh +17 -0
  132. data/sandbox/itsi_file/echo_client/lib/echo_pb.rb +16 -0
  133. data/sandbox/itsi_file/echo_client/lib/echo_services_pb.rb +29 -0
  134. data/sandbox/itsi_file/echo_client/run_client.rb +64 -0
  135. data/sandbox/itsi_file/echo_client/test_compressions.sh +20 -0
  136. data/sandbox/itsi_file/echo_service_nonitsi/Gemfile +10 -0
  137. data/sandbox/itsi_file/echo_service_nonitsi/Gemfile.lock +79 -0
  138. data/sandbox/itsi_file/echo_service_nonitsi/echo.proto +26 -0
  139. data/sandbox/itsi_file/echo_service_nonitsi/echo_pb.rb +16 -0
  140. data/sandbox/itsi_file/echo_service_nonitsi/echo_services_pb.rb +29 -0
  141. data/sandbox/itsi_file/echo_service_nonitsi/server.rb +52 -0
  142. data/sandbox/itsi_sandbox_async/config.ru +0 -1
  143. data/sandbox/itsi_sandbox_rack/Gemfile.lock +2 -2
  144. data/sandbox/itsi_sandbox_rails/Gemfile +2 -2
  145. data/sandbox/itsi_sandbox_rails/Gemfile.lock +76 -2
  146. data/sandbox/itsi_sandbox_rails/app/controllers/home_controller.rb +15 -0
  147. data/sandbox/itsi_sandbox_rails/config/environments/development.rb +1 -0
  148. data/sandbox/itsi_sandbox_rails/config/environments/production.rb +1 -0
  149. data/sandbox/itsi_sandbox_rails/config/routes.rb +2 -0
  150. data/sandbox/itsi_sinatra/app.rb +0 -1
  151. data/sandbox/static_files/.env +1 -0
  152. data/sandbox/static_files/404.html +25 -0
  153. data/sandbox/static_files/_DSC0102.NEF.jpg +0 -0
  154. data/sandbox/static_files/about.html +68 -0
  155. data/sandbox/static_files/tiny.html +1 -0
  156. data/sandbox/static_files/writebook.zip +0 -0
  157. data/tasks.txt +28 -33
  158. metadata +87 -26
  159. data/crates/itsi_error/src/from.rs +0 -68
  160. data/crates/itsi_server/src/ruby_types/itsi_grpc_request.rs +0 -147
  161. data/crates/itsi_server/src/ruby_types/itsi_grpc_response.rs +0 -19
  162. data/crates/itsi_server/src/server/itsi_service.rs +0 -172
  163. data/crates/itsi_server/src/server/middleware_stack/middlewares/grpc_service.rs +0 -72
  164. data/crates/itsi_server/src/server/types.rs +0 -43
  165. data/gems/server/lib/itsi/server/grpc_interface.rb +0 -213
  166. data/sandbox/itsi_file/public/assets/index.html +0 -1
  167. /data/crates/itsi_server/src/server/{bind_protocol.rs → binds/bind_protocol.rs} +0 -0
  168. /data/crates/itsi_server/src/server/{tls → binds/tls}/locked_dir_cache.rs +0 -0
  169. /data/crates/itsi_server/src/{server → services}/cache_store.rs +0 -0
data/gems/server/exe/itsi CHANGED
@@ -4,28 +4,48 @@
4
4
  require "itsi/server"
5
5
  require "optparse"
6
6
 
7
- Itsi::Server::Config.save_argv!
7
+
8
+ COMMANDS = {
9
+ "init" => "Initialize a new Itsi.rb server configuration file",
10
+ "status" => "Show the status of the server",
11
+ "start" => "Start the Isti server",
12
+ "serve" => "Start the Isti server",
13
+ "stop" => "Stop the server",
14
+ "reload" => "Reload the server",
15
+ "restart" => "Restart the server",
16
+ "add_worker" => "Add a new worker to the server cluster",
17
+ "remove_worker" => "Remove a worker from the server cluster",
18
+ "routes" => "Print the routes of the server",
19
+ "passfile" => "Manage hashed users and passwords in a passfile (like .htpasswd). [add, remove, list]",
20
+ "test_route" => "Test which route a request will be routed to",
21
+ "static" => "Serve static assets in the given directory"
22
+ }
23
+
24
+ Itsi::Server::Config.prep_reexec!
8
25
 
9
26
  options = {}
10
27
 
11
- OptionParser.new do |opts|
12
- opts.banner = "Usage: itsi [options]"
28
+ parser = OptionParser.new do |opts|
29
+ opts.banner = "Usage: itsi [COMMAND] [options]"
13
30
 
14
31
  opts.on("-C", "--config CONFIG_FILE", String, "Itsi Configuration file to use (default: Itsi.rb)") do |config_file|
15
32
  options[:config_file] = config_file
16
33
  end
17
34
 
18
- opts.on("-w", "--workers WORKERS", Integer, "Number of workers (default: #{Etc.nprocessors})") do |w|
35
+ opts.on("-w", "--workers WORKERS", Integer, "Number of workers") do |w|
19
36
  options[:workers] = w
20
37
  end
21
38
 
39
+ opts.on("-d", "--daemonize", "Run the process as a daemon") do
40
+ Process.daemon(true)
41
+ end
42
+
22
43
  opts.on("-t", "--threads THREADS", Integer, "Number of threads (default: 1)") do |t|
23
44
  options[:threads] = t
24
45
  end
25
46
 
26
- opts.on("-mtr", "--multithreaded-reactor true/false", String,
27
- "Use a multithreaded reactor (default: true)") do |mtr|
28
- options[:multithreaded_reactor] = mtr.to_s == "true"
47
+ opts.on("--[no-]multithreaded-reactor", "Use a multithreaded reactor") do |mtr|
48
+ options[:multithreaded_reactor] = mtr
29
49
  end
30
50
 
31
51
  opts.on("-r", "--rackup_file FILE", String, "Rackup file to use (default: config.ru)") do |rf|
@@ -97,9 +117,6 @@ OptionParser.new do |opts|
97
117
  options[:shutdown_timeout] = shutdown_timeout
98
118
  end
99
119
 
100
- opts.on("--script_name SCRIPT_NAME", String, "Script name to inject into Rack ENV") do |script_name|
101
- options[:script_name] = script_name
102
- end
103
120
 
104
121
  opts.on("--stream-body", TrueClass, "Stream body frames (default: false for best compatibility)") do |stream_body|
105
122
  options[:stream_body] = stream_body
@@ -107,6 +124,10 @@ OptionParser.new do |opts|
107
124
 
108
125
  opts.on("-h", "--help", "Show this help message") do
109
126
  puts opts
127
+ puts "COMMAND: "
128
+ COMMANDS.each do |command, description|
129
+ puts " #{command} - #{description}"
130
+ end
110
131
  exit
111
132
  end
112
133
 
@@ -117,17 +138,42 @@ OptionParser.new do |opts|
117
138
  opts.on("--listeners LISTENERS", String, "Listeners for reexec") do |listeners|
118
139
  options[:listeners] = listeners
119
140
  end
120
- end.parse!
121
141
 
142
+ opts.on("--passfile PASSFILE", String, "Passfile") do |passfile|
143
+ options[:passfile] = passfile
144
+ end
145
+
146
+ opts.on("--algorithm ALGORITHM", String, "Algorithm for password hashing") do |algorithm|
147
+ options[:algorithm] = algorithm
148
+ end
149
+ end
150
+
151
+ if ENV['COMP_LINE'] || ARGV.include?('--completion')
152
+ puts COMMANDS.keys
153
+ exit
154
+ end
122
155
 
123
- case (command = ARGV.pop)
124
- when "init"
125
- Itsi::Server::Config.write_default
126
- exit(0)
127
- when "reload", "restart", "down", "up", "add_worker", "remove_worker", "status"
128
- Itsi::Server.send(command)
129
- when nil, "start", "serve"
156
+ parser.parse!
157
+
158
+ case (command = ARGV.shift)
159
+ when *COMMANDS.keys
160
+ required_arity = Itsi::Server.method(command).parameters&.select{|c| c.first == :req }&.length&.succ || 2
161
+ case required_arity
162
+ when 1 then Itsi::Server.send(command)
163
+ when 2 then Itsi::Server.send(command, options)
164
+ else
165
+ if ARGV.length != required_arity - 2
166
+ puts "Command #{command} requires #{required_arity - 2} subcommands. "
167
+ exit(0)
168
+ end
169
+ Itsi::Server.send(command, options, *ARGV)
170
+ end
171
+ when nil
130
172
  Itsi::Server.start(options)
131
173
  else
132
- puts "Invalid command #{command}"
174
+ puts "Invalid command #{command}.\n"
175
+ puts "COMMAND: "
176
+ COMMANDS.each do |command, description|
177
+ puts " #{command} - #{description}"
178
+ end
133
179
  end
@@ -27,7 +27,7 @@ Gem::Specification.new do |spec|
27
27
  (f == gemspec) ||
28
28
  f.start_with?(*%w[bin/ test/ spec/ features/ .git appveyor Gemfile])
29
29
  end
30
- end + Dir["../../crates/**/*.{toml,rs,lock}"].map do |ext_file|
30
+ end + Dir["../../crates/**/*.{toml,rs,lock,html,json}"].map do |ext_file|
31
31
  "ext/#{ext_file[%r{.*crates/(.*?)$}, 1]}"
32
32
  end.compact
33
33
 
@@ -36,10 +36,11 @@ Gem::Specification.new do |spec|
36
36
  spec.require_paths = ["lib"]
37
37
  spec.extensions = ["ext/itsi_server/extconf.rb"]
38
38
 
39
- # Uncomment to register a new dependency of your gem
40
- # spec.add_dependency "example-gem", "~> 1.0"
41
39
  spec.add_dependency "rack", ">= 1.6"
40
+ spec.add_dependency "json", '~> 2'
42
41
  spec.add_dependency "rb_sys", "~> 0.9.91"
42
+
43
+ spec.add_development_dependency "ruby-lsp"
43
44
  # For more information and examples about making a new gem, check out our
44
45
  # guide at: https://bundler.io/guides/creating_gem.html
45
46
  end
@@ -0,0 +1,74 @@
1
+ module Itsi
2
+ class HttpRequest
3
+ module ResponseStatusShortcodes
4
+
5
+ HTTP_STATUS_CODES = {
6
+ 100 => :continue,
7
+ 101 => :switching_protocols,
8
+ 102 => :processing,
9
+ 200 => :ok,
10
+ 201 => :created,
11
+ 202 => :accepted,
12
+ 203 => :non_authoritative_information,
13
+ 204 => :no_content,
14
+ 205 => :reset_content,
15
+ 206 => :partial_content,
16
+ 207 => :multi_status,
17
+ 208 => :already_reported,
18
+ 226 => :im_used,
19
+ 300 => :multiple_choices,
20
+ 301 => :moved_permanently,
21
+ 302 => :found,
22
+ 303 => :see_other,
23
+ 304 => :not_modified,
24
+ 305 => :use_proxy,
25
+ 307 => :temporary_redirect,
26
+ 308 => :permanent_redirect,
27
+ 400 => :bad_request,
28
+ 401 => :unauthorized,
29
+ 402 => :payment_required,
30
+ 403 => :forbidden,
31
+ 404 => :not_found,
32
+ 405 => :method_not_allowed,
33
+ 406 => :not_acceptable,
34
+ 407 => :proxy_authentication_required,
35
+ 408 => :request_timeout,
36
+ 409 => :conflict,
37
+ 410 => :gone,
38
+ 411 => :length_required,
39
+ 412 => :precondition_failed,
40
+ 413 => :payload_too_large,
41
+ 414 => :uri_too_long,
42
+ 415 => :unsupported_media_type,
43
+ 416 => :range_not_satisfiable,
44
+ 417 => :expectation_failed,
45
+ 418 => :im_a_teapot,
46
+ 421 => :misdirected_request,
47
+ 422 => :unprocessable_entity,
48
+ 423 => :locked,
49
+ 424 => :failed_dependency,
50
+ 425 => :too_early,
51
+ 426 => :upgrade_required,
52
+ 428 => :precondition_required,
53
+ 429 => :too_many_requests,
54
+ 431 => :request_header_fields_too_large,
55
+ 451 => :unavailable_for_legal_reasons,
56
+ 500 => :internal_server_error,
57
+ 501 => :not_implemented,
58
+ 502 => :bad_gateway,
59
+ 503 => :service_unavailable,
60
+ 504 => :gateway_timeout,
61
+ 505 => :http_version_not_supported,
62
+ 506 => :variant_also_negotiates,
63
+ 507 => :insufficient_storage,
64
+ 508 => :loop_detected,
65
+ 510 => :not_extended,
66
+ 511 => :network_authentication_required
67
+ }.freeze
68
+
69
+ HTTP_STATUS_CODES.each do |code, name|
70
+ define_method(name) {|*args, **kwargs| respond(*args, **kwargs, status: code) }
71
+ end
72
+ end
73
+ end
74
+ end
@@ -2,12 +2,16 @@
2
2
 
3
3
  require "stringio"
4
4
  require "socket"
5
+ require "uri"
6
+ require_relative 'http_request/response_status_shortcodes'
5
7
 
6
8
  module Itsi
7
9
  class HttpRequest
10
+ include Server::TypedHandlers::ParamParser
11
+ include ResponseStatusShortcodes
8
12
  attr_accessor :hijacked
9
13
 
10
- EMPTY_IO = StringIO.new("").freeze
14
+ EMPTY_IO = StringIO.new("")
11
15
  RACK_HEADER_MAP = StandardHeaders::ALL.map do |header|
12
16
  rack_form = if header == "content-type"
13
17
  "CONTENT_TYPE"
@@ -25,10 +29,11 @@ module Itsi
25
29
  path = self.path
26
30
  host = self.host
27
31
  version = self.version
32
+
28
33
  {
29
34
  "SERVER_SOFTWARE" => "Itsi",
30
35
  "SCRIPT_NAME" => script_name,
31
- "REQUEST_METHOD" => method,
36
+ "REQUEST_METHOD" => request_method,
32
37
  "PATH_INFO" => path,
33
38
  "REQUEST_PATH" => path,
34
39
  "QUERY_STRING" => query_string,
@@ -49,39 +54,134 @@ module Itsi
49
54
  "rack.run_once" => false,
50
55
  "rack.hijack?" => true,
51
56
  "rack.multipart.buffer_size" => 16_384,
52
- "rack.hijack" => build_hijack_proc
57
+ "rack.hijack" => method(:hijack)
53
58
  }.tap do |r|
54
59
  headers.each do |(k, v)|
55
- r[RACK_HEADER_MAP[k]] = v
60
+ r[case k
61
+ when "content-type" then "CONTENT_TYPE"
62
+ when "content-length" then "CONTENT_LENGTH"
63
+ when "accept" then "HTTP_ACCEPT"
64
+ when "accept-encoding" then "HTTP_ACCEPT_ENCODING"
65
+ when "accept-language" then "HTTP_ACCEPT_LANGUAGE"
66
+ when "user-agent" then "HTTP_USER_AGENT"
67
+ when "referer" then "HTTP_REFERER"
68
+ when "origin" then "HTTP_ORIGIN"
69
+ when "cookie" then "HTTP_COOKIE"
70
+ when "authorization" then "HTTP_AUTHORIZATION"
71
+ when "x-forwarded-for" then "HTTP_X_FORWARDED_FOR"
72
+ when "x-forwarded-proto" then "HTTP_X_FORWARDED_PROTO"
73
+ else RACK_HEADER_MAP[k]
74
+ end
75
+ ] = v
56
76
  end
57
77
  end
58
78
  end
59
79
 
60
- def respond(_body = nil, _status = 200, _header = nil, status: _status, headers: _header, body: _body,
61
- hijack: false, &blk)
80
+ def respond(
81
+ _body = nil, _status = 200, _headers = nil,
82
+ json: nil,
83
+ html: nil,
84
+ text: nil,
85
+ xml: nil,
86
+ hijack: false,
87
+ as: nil,
88
+ status: _status,
89
+ headers: _headers,
90
+ body: _body,
91
+ &blk
92
+ )
93
+
94
+ if json
95
+ validate!(json, as: as) if as
96
+ body = json.to_json
97
+ headers ||= {}
98
+ headers["Content-Type"] ||= "application/json"
99
+ elsif html
100
+ body = html
101
+ headers ||= {}
102
+ headers["Content-Type"] ||= "text/html"
103
+ elsif xml
104
+ body = xml
105
+ headers ||= {}
106
+ headers["Content-Type"] ||= "application/xml"
107
+ elsif text
108
+ body = text
109
+ headers ||= {}
110
+ headers["Content-Type"] ||= "text/plain"
111
+ end
112
+
62
113
  response.respond(status: status, headers: headers, body: body, hijack: hijack, &blk)
63
114
  end
64
115
 
65
- def build_hijack_proc
66
- lambda do
67
- self.hijacked = true
68
- UNIXSocket.pair.yield_self do |(server_sock, app_sock)|
69
- server_sock.autoclose = false
70
- response.hijack(server_sock.fileno)
71
- server_sock.sync = true
72
- app_sock.sync = true
73
- app_sock
74
- end
116
+ def hijack
117
+ self.hijacked = true
118
+ UNIXSocket.pair.yield_self do |(server_sock, app_sock)|
119
+ server_sock.autoclose = false
120
+ self.response.hijack(server_sock.fileno)
121
+ server_sock.sync = true
122
+ app_sock.sync = true
123
+ app_sock
75
124
  end
76
125
  end
77
126
 
78
127
  def build_input_io
79
128
  case body
80
- when nil then StringIO.new("")
129
+ when nil then EMPTY_IO
81
130
  when String then StringIO.new(body)
82
131
  when Array then File.open(body.first, "rb")
83
132
  else body
84
133
  end
85
134
  end
135
+
136
+ def validate!(params, as: nil)
137
+ as ? apply_schema!(params, as) : params
138
+ end
139
+
140
+ def params(schema=nil)
141
+ params = case
142
+ when url_encoded? then URI.decode_www_form(build_input_io.read).to_h
143
+ when json? then JSON.parse(build_input_io.read)
144
+ when multipart?
145
+ Rack::Multipart::Parser.parse(
146
+ build_input_io,
147
+ content_length,
148
+ content_type,
149
+ Rack::Multipart::Parser::TEMPFILE_FACTORY,
150
+ Rack::Multipart::Parser::BUFSIZE,
151
+ Rack::Utils.default_query_parser
152
+ ).params
153
+ else
154
+ {}
155
+ end
156
+
157
+ params.merge!(query_params).merge!(url_params)
158
+
159
+ yield schema ? apply_schema!(params, schema) : params
160
+
161
+ rescue StandardError => e
162
+ if response.json?
163
+ respond(json: {error: e.message}, status: 400)
164
+ else
165
+ respond(e.message, 400)
166
+ end
167
+ ensure
168
+ clean_temp_files(params)
169
+ end
170
+
171
+ def clean_temp_files(params)
172
+ case params
173
+ when Hash
174
+ if params.key?(:tempfile)
175
+ params[:tempfile].unlink
176
+ else
177
+ params.each_value { |v| clean_temp_files(v) }
178
+ end
179
+ when Array then params.each { |v| clean_temp_files(v) }
180
+ end
181
+ end
182
+
183
+ def query_params
184
+ URI.decode_www_form(query_string).to_h
185
+ end
86
186
  end
87
187
  end
@@ -10,6 +10,8 @@ module Itsi
10
10
  def respond _body=nil, _status=200, _header=nil, status: _status, headers: _header, body: _body, hijack: false, &blk
11
11
  self.status = status
12
12
 
13
+ body = body.to_s unless body.is_a?(String)
14
+
13
15
  if headers
14
16
  headers.each do |key, value|
15
17
  if value.is_a?(Array)
@@ -0,0 +1,109 @@
1
+ module Itsi
2
+ class Server
3
+
4
+ module Passfile
5
+ require 'io/console'
6
+
7
+ module_function
8
+
9
+ def load(filename)
10
+ if filename.nil? || filename.strip.empty?
11
+ puts "Error: a valid filename is required."
12
+ return nil
13
+ end
14
+
15
+ creds = {}
16
+ if File.exist?(filename)
17
+ File.foreach(filename) do |line|
18
+ line.chomp!
19
+ next if line.empty?
20
+
21
+ user, pass = line.split(':', 2)
22
+ creds[user] = pass
23
+ end
24
+ end
25
+ creds
26
+ end
27
+
28
+ def save(creds, filename)
29
+ File.open(filename, 'w', 0o600) do |f|
30
+ creds.each do |u, p|
31
+ f.puts "#{u}:#{p}"
32
+ end
33
+ end
34
+ end
35
+
36
+ def echo(filename, algorithm)
37
+ return unless (creds = load(filename))
38
+ print "Enter username: "
39
+ username = $stdin.gets.chomp
40
+
41
+ print "Enter password: "
42
+
43
+ password = $stdin.noecho(&:gets).chomp
44
+ puts
45
+
46
+ print "Confirm password: "
47
+ password_confirm = $stdin.noecho(&:gets).chomp
48
+ puts
49
+
50
+ if password != password_confirm
51
+ puts "Error: Passwords do not match!"
52
+ exit(1)
53
+ end
54
+
55
+ puts "#{username}:#{Itsi.create_password_hash(password, algorithm)}"
56
+ end
57
+
58
+ def add(filename, algorithm)
59
+ return unless (creds = load(filename))
60
+ print "Enter username: "
61
+ username = $stdin.gets.chomp
62
+
63
+ print "Enter password: "
64
+
65
+ password = $stdin.noecho(&:gets).chomp
66
+ puts
67
+
68
+ print "Confirm password: "
69
+ password_confirm = $stdin.noecho(&:gets).chomp
70
+ puts
71
+
72
+ if password != password_confirm
73
+ puts "Error: Passwords do not match!"
74
+ exit(1)
75
+ end
76
+
77
+ creds[username] = Itsi.create_password_hash(password, algorithm)
78
+
79
+ save(creds, filename)
80
+
81
+ puts "User '#{username}' added."
82
+ end
83
+
84
+ def remove(filename)
85
+ return unless (creds = load(filename))
86
+
87
+ print "Enter username to remove: "
88
+ username = $stdin.gets.chomp
89
+
90
+ if creds.key?(username)
91
+ creds.delete(username)
92
+ save(creds, filename)
93
+ puts "User '#{username}' removed."
94
+ else
95
+ puts "Warning: User '#{username}' not found."
96
+ end
97
+ end
98
+
99
+ def list(filename)
100
+ puts "Current credentials in '#{filename}':"
101
+ return unless (creds = load(filename))
102
+ creds.each do |u, p|
103
+ puts "#{u}:#{p}"
104
+ end
105
+ end
106
+
107
+ end
108
+ end
109
+ end