ronin-web-server 0.1.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|