ronin-web-server 0.1.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.github/workflows/ruby.yml +41 -0
- data/.gitignore +13 -0
- data/.rspec +1 -0
- data/.rubocop.yml +154 -0
- data/.ruby-version +1 -0
- data/.yardopts +1 -0
- data/COPYING.txt +165 -0
- data/ChangeLog.md +38 -0
- data/Gemfile +35 -0
- data/README.md +177 -0
- data/Rakefile +34 -0
- data/gemspec.yml +31 -0
- data/lib/ronin/web/server/app.rb +33 -0
- data/lib/ronin/web/server/base.rb +214 -0
- data/lib/ronin/web/server/conditions.rb +443 -0
- data/lib/ronin/web/server/helpers.rb +67 -0
- data/lib/ronin/web/server/request.rb +78 -0
- data/lib/ronin/web/server/response.rb +36 -0
- data/lib/ronin/web/server/reverse_proxy/request.rb +230 -0
- data/lib/ronin/web/server/reverse_proxy/response.rb +35 -0
- data/lib/ronin/web/server/reverse_proxy.rb +265 -0
- data/lib/ronin/web/server/routing.rb +261 -0
- data/lib/ronin/web/server/version.rb +28 -0
- data/lib/ronin/web/server.rb +62 -0
- data/ronin-web-server.gemspec +59 -0
- data/spec/base_spec.rb +73 -0
- data/spec/classes/public1/static1.txt +1 -0
- data/spec/classes/public2/static2.txt +1 -0
- data/spec/classes/sub_app.rb +13 -0
- data/spec/classes/test_app.rb +20 -0
- data/spec/helpers/rack_app.rb +24 -0
- data/spec/request_spec.rb +58 -0
- data/spec/response_spec.rb +8 -0
- data/spec/reverse_proxy/request_spec.rb +200 -0
- data/spec/reverse_proxy/response_spec.rb +8 -0
- data/spec/reverse_proxy_spec.rb +223 -0
- data/spec/spec_helper.rb +9 -0
- metadata +180 -0
@@ -0,0 +1,230 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# ronin-web-server - A custom Ruby web server based on Sinatra.
|
4
|
+
#
|
5
|
+
# Copyright (c) 2006-2022 Hal Brodigan (postmodern.mod3 at gmail.com)
|
6
|
+
#
|
7
|
+
# ronin-web-server is free software: you can redistribute it and/or modify
|
8
|
+
# it under the terms of the GNU Lesser General Public License as published
|
9
|
+
# by the Free Software Foundation, either version 3 of the License, or
|
10
|
+
# (at your option) any later version.
|
11
|
+
#
|
12
|
+
# ronin-web-server is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# GNU Lesser General Public License for more details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU Lesser General Public License
|
18
|
+
# along with ronin-web-server. If not, see <https://www.gnu.org/licenses/>.
|
19
|
+
#
|
20
|
+
|
21
|
+
require 'ronin/web/server/request'
|
22
|
+
|
23
|
+
module Ronin
|
24
|
+
module Web
|
25
|
+
module Server
|
26
|
+
class ReverseProxy
|
27
|
+
#
|
28
|
+
# Class that represents a reverse proxied request.
|
29
|
+
#
|
30
|
+
class Request < Server::Request
|
31
|
+
|
32
|
+
#
|
33
|
+
# Changes the HTTP Host header of the request.
|
34
|
+
#
|
35
|
+
# @param [String] new_host
|
36
|
+
# The new value of the HTTP Host header.
|
37
|
+
#
|
38
|
+
# @return [String]
|
39
|
+
# The new HTTP Host header.
|
40
|
+
#
|
41
|
+
# @api public
|
42
|
+
#
|
43
|
+
def host=(new_host)
|
44
|
+
@env['HTTP_HOST'] = new_host
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# Changes the port the request is being sent to.
|
49
|
+
#
|
50
|
+
# @param [Integer] new_port
|
51
|
+
# The new port the request will be sent to.
|
52
|
+
#
|
53
|
+
# @return [Integer]
|
54
|
+
# The new port the request will be sent to.
|
55
|
+
#
|
56
|
+
# @api public
|
57
|
+
#
|
58
|
+
def port=(new_port)
|
59
|
+
@env['SERVER_PORT'] = new_port
|
60
|
+
end
|
61
|
+
|
62
|
+
#
|
63
|
+
# Changes the URI scheme of the request.
|
64
|
+
#
|
65
|
+
# @param [String] new_scheme
|
66
|
+
# The new URI scheme for the request.
|
67
|
+
#
|
68
|
+
# @return [String]
|
69
|
+
# The new URI scheme of the request.
|
70
|
+
#
|
71
|
+
# @api public
|
72
|
+
#
|
73
|
+
def scheme=(new_scheme)
|
74
|
+
@env['rack.url_scheme'] = new_scheme
|
75
|
+
end
|
76
|
+
|
77
|
+
#
|
78
|
+
# Toggles whether the request to be proxied over SSL.
|
79
|
+
#
|
80
|
+
# @param [Boolean] ssl
|
81
|
+
# Specifies whether to enable or disable SSL.
|
82
|
+
#
|
83
|
+
# @return [Boolean]
|
84
|
+
# Whether SSL is enabled or disabled.
|
85
|
+
#
|
86
|
+
# @api public
|
87
|
+
#
|
88
|
+
def ssl=(ssl)
|
89
|
+
if ssl
|
90
|
+
self.port = 443
|
91
|
+
self.scheme = 'https'
|
92
|
+
else
|
93
|
+
self.port = 80
|
94
|
+
self.scheme = 'http'
|
95
|
+
end
|
96
|
+
|
97
|
+
return ssl
|
98
|
+
end
|
99
|
+
|
100
|
+
#
|
101
|
+
# Changes the HTTP Request Method of the request.
|
102
|
+
#
|
103
|
+
# @param [String] new_method
|
104
|
+
# The new HTTP Request Method.
|
105
|
+
#
|
106
|
+
# @return [String]
|
107
|
+
# The new HTTP Request Method.
|
108
|
+
#
|
109
|
+
# @api public
|
110
|
+
#
|
111
|
+
def request_method=(new_method)
|
112
|
+
@env['REQUEST_METHOD'] = new_method
|
113
|
+
end
|
114
|
+
|
115
|
+
#
|
116
|
+
# Changes the HTTP Query String of the request.
|
117
|
+
#
|
118
|
+
# @param [String] new_query
|
119
|
+
# The new HTTP Query String for the request.
|
120
|
+
#
|
121
|
+
# @return [String]
|
122
|
+
# The new HTTP Query String of the request.
|
123
|
+
#
|
124
|
+
# @api public
|
125
|
+
#
|
126
|
+
def query_string=(new_query)
|
127
|
+
@env['QUERY_STRING'] = new_query
|
128
|
+
end
|
129
|
+
|
130
|
+
#
|
131
|
+
# Toggles whether the request is a XML HTTP Request.
|
132
|
+
#
|
133
|
+
# @param [Boolean] xhr
|
134
|
+
# Specifies whether the request is an XML HTTP Request.
|
135
|
+
#
|
136
|
+
# @return [Boolean]
|
137
|
+
# Specifies whether the request is an XML HTTP Request.
|
138
|
+
#
|
139
|
+
# @api public
|
140
|
+
#
|
141
|
+
def xhr=(xhr)
|
142
|
+
if xhr then @env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
|
143
|
+
else @env.delete('HTTP_X_REQUESTED_WITH')
|
144
|
+
end
|
145
|
+
|
146
|
+
return xhr
|
147
|
+
end
|
148
|
+
|
149
|
+
#
|
150
|
+
# Changes the HTTP Content-Type header of the request.
|
151
|
+
#
|
152
|
+
# @param [String] new_content_type
|
153
|
+
# The new HTTP Content-Type for the request.
|
154
|
+
#
|
155
|
+
# @return [String]
|
156
|
+
# The new HTTP Content-Type of the request.
|
157
|
+
#
|
158
|
+
# @api public
|
159
|
+
#
|
160
|
+
def content_type=(new_content_type)
|
161
|
+
@env['CONTENT_TYPE'] = new_content_type
|
162
|
+
end
|
163
|
+
|
164
|
+
#
|
165
|
+
# Changes the HTTP Accept-Encoding header of the request.
|
166
|
+
#
|
167
|
+
# @param [String] new_encoding
|
168
|
+
# The new HTTP Accept-Encoding for the request.
|
169
|
+
#
|
170
|
+
# @return [String]
|
171
|
+
# The new HTTP Accept-Encoding of the request.
|
172
|
+
#
|
173
|
+
# @api public
|
174
|
+
#
|
175
|
+
def accept_encoding=(new_encoding)
|
176
|
+
@env['HTTP_ACCEPT_ENCODING'] = new_encoding
|
177
|
+
end
|
178
|
+
|
179
|
+
#
|
180
|
+
# Sets the HTTP User-Agent header of the request.
|
181
|
+
#
|
182
|
+
# @param [String] new_user_agent
|
183
|
+
# The new User-Agent header to use.
|
184
|
+
#
|
185
|
+
# @return [String]
|
186
|
+
# The new User-Agent header.
|
187
|
+
#
|
188
|
+
# @api public
|
189
|
+
#
|
190
|
+
def user_agent=(new_user_agent)
|
191
|
+
@env['HTTP_USER_AGENT'] = new_user_agent
|
192
|
+
end
|
193
|
+
|
194
|
+
#
|
195
|
+
# Changes the HTTP `Referer` header of the request.
|
196
|
+
#
|
197
|
+
# @param [String] new_referer
|
198
|
+
# The new HTTP `Referer` header for the request.
|
199
|
+
#
|
200
|
+
# @return [String]
|
201
|
+
# The new HTTP `Referer` header of the request.
|
202
|
+
#
|
203
|
+
# @api public
|
204
|
+
#
|
205
|
+
def referer=(new_referer)
|
206
|
+
@env['HTTP_REFERER'] = new_referer
|
207
|
+
end
|
208
|
+
|
209
|
+
alias referrer= referer=
|
210
|
+
|
211
|
+
#
|
212
|
+
# Changes the body of the request.
|
213
|
+
#
|
214
|
+
# @param [String] new_body
|
215
|
+
# The new body for the request.
|
216
|
+
#
|
217
|
+
# @return [String]
|
218
|
+
# The new body of the request.
|
219
|
+
#
|
220
|
+
# @api public
|
221
|
+
#
|
222
|
+
def body=(new_body)
|
223
|
+
@env['rack.input'] = new_body
|
224
|
+
end
|
225
|
+
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# ronin-web-server - A custom Ruby web server based on Sinatra.
|
4
|
+
#
|
5
|
+
# Copyright (c) 2006-2022 Hal Brodigan (postmodern.mod3 at gmail.com)
|
6
|
+
#
|
7
|
+
# ronin-web-server is free software: you can redistribute it and/or modify
|
8
|
+
# it under the terms of the GNU Lesser General Public License as published
|
9
|
+
# by the Free Software Foundation, either version 3 of the License, or
|
10
|
+
# (at your option) any later version.
|
11
|
+
#
|
12
|
+
# ronin-web-server is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# GNU Lesser General Public License for more details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU Lesser General Public License
|
18
|
+
# along with ronin-web-server. If not, see <https://www.gnu.org/licenses/>.
|
19
|
+
#
|
20
|
+
|
21
|
+
require 'ronin/web/server/response'
|
22
|
+
|
23
|
+
module Ronin
|
24
|
+
module Web
|
25
|
+
module Server
|
26
|
+
class ReverseProxy
|
27
|
+
#
|
28
|
+
# Class that represents a reverse proxied response.
|
29
|
+
#
|
30
|
+
class Response < Server::Response
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,265 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# ronin-web-server - A custom Ruby web server based on Sinatra.
|
4
|
+
#
|
5
|
+
# Copyright (c) 2006-2022 Hal Brodigan (postmodern.mod3 at gmail.com)
|
6
|
+
#
|
7
|
+
# ronin-web-server is free software: you can redistribute it and/or modify
|
8
|
+
# it under the terms of the GNU Lesser General Public License as published
|
9
|
+
# by the Free Software Foundation, either version 3 of the License, or
|
10
|
+
# (at your option) any later version.
|
11
|
+
#
|
12
|
+
# ronin-web-server is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# GNU Lesser General Public License for more details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU Lesser General Public License
|
18
|
+
# along with ronin-web-server. If not, see <https://www.gnu.org/licenses/>.
|
19
|
+
#
|
20
|
+
|
21
|
+
require 'ronin/web/server/reverse_proxy/request'
|
22
|
+
require 'ronin/web/server/reverse_proxy/response'
|
23
|
+
require 'ronin/support/network/http'
|
24
|
+
|
25
|
+
require 'rack'
|
26
|
+
|
27
|
+
module Ronin
|
28
|
+
module Web
|
29
|
+
module Server
|
30
|
+
#
|
31
|
+
# Reverse proxies Rack requests to other HTTP web servers.
|
32
|
+
#
|
33
|
+
# ## Examples
|
34
|
+
#
|
35
|
+
# ### Standalone Server
|
36
|
+
#
|
37
|
+
# reverse_proxy = Ronin::Web::Server::ReverseProxy.new do |proxy|
|
38
|
+
# proxy.on_request do |request|
|
39
|
+
# # ...
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# proxy.on_response do |response|
|
43
|
+
# # ...
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
# reverse_proxy.run!(host: '0.0.0.0', port: 8080)
|
47
|
+
#
|
48
|
+
# ### App
|
49
|
+
#
|
50
|
+
# class App < Ronin::Web::Server::Base
|
51
|
+
#
|
52
|
+
# mount '/signin', Ronin::Web::Server::ReverseProxy.new
|
53
|
+
#
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
# @api public
|
57
|
+
#
|
58
|
+
class ReverseProxy
|
59
|
+
|
60
|
+
#
|
61
|
+
# Creates a new reverse proxy application.
|
62
|
+
#
|
63
|
+
# @yield [reverse_proxy]
|
64
|
+
# If a block is given, it will be passed the new proxy.
|
65
|
+
#
|
66
|
+
# @yieldparam [ReverseProxy] proxy
|
67
|
+
# The new proxy object.
|
68
|
+
#
|
69
|
+
def initialize
|
70
|
+
@connections = {}
|
71
|
+
|
72
|
+
yield self if block_given?
|
73
|
+
end
|
74
|
+
|
75
|
+
#
|
76
|
+
# Registers a callback to run on each request.
|
77
|
+
#
|
78
|
+
# @yield [request]
|
79
|
+
# The given block will be passed each received request before it has
|
80
|
+
# been reverse proxied.
|
81
|
+
#
|
82
|
+
def on_request(&block)
|
83
|
+
@on_request_callback = block
|
84
|
+
end
|
85
|
+
|
86
|
+
#
|
87
|
+
# Registers a callback to run on each response.
|
88
|
+
#
|
89
|
+
# @yield [response]
|
90
|
+
# The given block will be passed each response before it has been
|
91
|
+
# returned.
|
92
|
+
#
|
93
|
+
# @yield [request, response]
|
94
|
+
# If the block accepts two arguments then both the request and the
|
95
|
+
# response objects will be yielded.
|
96
|
+
#
|
97
|
+
# @yieldparam [ReverseProxy::Response] response
|
98
|
+
# A response object.
|
99
|
+
#
|
100
|
+
# @yieldparam [ReverseProxy::Request] request
|
101
|
+
# A request object.
|
102
|
+
#
|
103
|
+
def on_response(&block)
|
104
|
+
@on_response_callback = block
|
105
|
+
end
|
106
|
+
|
107
|
+
#
|
108
|
+
# Reverse proxies every request using the `Host` header.
|
109
|
+
#
|
110
|
+
# @param [Hash{String => Object}] env
|
111
|
+
# The rack request env Hash.
|
112
|
+
#
|
113
|
+
# @return [ReverseProxy::Response]
|
114
|
+
# The rack response.
|
115
|
+
#
|
116
|
+
def call(env)
|
117
|
+
request = Request.new(env)
|
118
|
+
@on_request_callback.call(request) if @on_request_callback
|
119
|
+
|
120
|
+
response = reverse_proxy(request)
|
121
|
+
|
122
|
+
if @on_response_callback
|
123
|
+
if @on_response_callback.arity == 1
|
124
|
+
@on_response_callback.call(response)
|
125
|
+
else
|
126
|
+
@on_response_callback.call(request,response)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
return response
|
131
|
+
end
|
132
|
+
|
133
|
+
#
|
134
|
+
# Creates a new connection or fetches an existing connection.
|
135
|
+
#
|
136
|
+
# @param [String] host
|
137
|
+
# The host to connect to.
|
138
|
+
#
|
139
|
+
# @param [Integer] port
|
140
|
+
# The port to connect to.
|
141
|
+
#
|
142
|
+
# @param [Boolean] ssl
|
143
|
+
# Indicates whether to use SSL.
|
144
|
+
#
|
145
|
+
# @return [Ronin::Support::Network::HTTP]
|
146
|
+
# The HTTP connection.
|
147
|
+
#
|
148
|
+
# @api private
|
149
|
+
#
|
150
|
+
def connection_for(host,port, ssl: nil)
|
151
|
+
key = [host,port,ssl]
|
152
|
+
|
153
|
+
@connections.fetch(key) do
|
154
|
+
@connections[key] = Support::Network::HTTP.new(host,port, ssl: ssl)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Blacklisted HTTP response Headers.
|
159
|
+
IGNORED_HEADERS = Set[
|
160
|
+
'Transfer-Encoding'
|
161
|
+
]
|
162
|
+
|
163
|
+
#
|
164
|
+
# Reverse proxies the given request.
|
165
|
+
#
|
166
|
+
# @param [ReverseProxy::Request] request
|
167
|
+
# The incoming request to reverse proxy.
|
168
|
+
#
|
169
|
+
# @return [ReverseProxy::Response]
|
170
|
+
# The reverse proxied response.
|
171
|
+
#
|
172
|
+
def reverse_proxy(request)
|
173
|
+
host = request.host
|
174
|
+
port = request.port
|
175
|
+
ssl = request.scheme == 'https'
|
176
|
+
method = request.request_method.downcase.to_sym
|
177
|
+
path = request.path
|
178
|
+
query = request.query_string
|
179
|
+
headers = request.headers
|
180
|
+
body = request.body.read
|
181
|
+
|
182
|
+
http = connection_for(host,port, ssl: ssl)
|
183
|
+
http_response = http.request(method,path, query: query,
|
184
|
+
headers: headers,
|
185
|
+
body: body)
|
186
|
+
response_headers = {}
|
187
|
+
|
188
|
+
http_response.each_capitalized do |name,value|
|
189
|
+
unless IGNORED_HEADERS.include?(name)
|
190
|
+
response_headers[name] = value
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
response_body = http_response.body || ''
|
195
|
+
response_status = http_response.code.to_i
|
196
|
+
|
197
|
+
return Response.new(response_body,response_status,response_headers)
|
198
|
+
end
|
199
|
+
|
200
|
+
#
|
201
|
+
# @group Standalone Server Methods
|
202
|
+
#
|
203
|
+
|
204
|
+
# Default host the Proxy will bind to
|
205
|
+
DEFAULT_HOST = '0.0.0.0'
|
206
|
+
|
207
|
+
# Default port the Proxy will listen on
|
208
|
+
DEFAULT_PORT = 8080
|
209
|
+
|
210
|
+
# Default server the Proxy will run on
|
211
|
+
DEFAULT_SERVER = 'webrick'
|
212
|
+
|
213
|
+
#
|
214
|
+
# Runs the reverse proxy as a standalone HTTP server.
|
215
|
+
#
|
216
|
+
# @param [String] host
|
217
|
+
# The host to bind to.
|
218
|
+
#
|
219
|
+
# @param [Integer] port
|
220
|
+
# The port to listen on.
|
221
|
+
#
|
222
|
+
# @param [String] server
|
223
|
+
# The Rack server to run the reverse proxy under.
|
224
|
+
#
|
225
|
+
# @param [Hash{Symbol => Object}] rack_options
|
226
|
+
# Additional options to pass to [Rack::Server.new](https://rubydoc.info/gems/rack/Rack/Server#initialize-instance_method).
|
227
|
+
#
|
228
|
+
def run!(host: DEFAULT_HOST, port: DEFAULT_PORT, server: DEFAULT_SERVER,
|
229
|
+
**rack_options)
|
230
|
+
server = Rack::Server.new(
|
231
|
+
app: self,
|
232
|
+
server: server,
|
233
|
+
Host: host,
|
234
|
+
Port: port,
|
235
|
+
**rack_options
|
236
|
+
)
|
237
|
+
|
238
|
+
server.start do |handler|
|
239
|
+
trap(:INT) { quit!(server,handler) }
|
240
|
+
trap(:TERM) { quit!(server,handler) }
|
241
|
+
end
|
242
|
+
|
243
|
+
return self
|
244
|
+
end
|
245
|
+
|
246
|
+
#
|
247
|
+
# Stops the reverse proxy server.
|
248
|
+
#
|
249
|
+
# @param [Rack::Server] server
|
250
|
+
# The Rack Handler server.
|
251
|
+
#
|
252
|
+
# @param [#stop!, #stop] handler
|
253
|
+
# The Rack Handler.
|
254
|
+
#
|
255
|
+
# @api private
|
256
|
+
#
|
257
|
+
def quit!(server,handler)
|
258
|
+
# Use thins' hard #stop! if available, otherwise just #stop
|
259
|
+
handler.respond_to?(:stop!) ? handler.stop! : handler.stop
|
260
|
+
end
|
261
|
+
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|