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.
@@ -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