ronin-web-server 0.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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