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,200 @@
1
+ require 'spec_helper'
2
+ require 'ronin/web/server/reverse_proxy/request'
3
+
4
+ describe Ronin::Web::Server::ReverseProxy::Request do
5
+ it "must provide the same methods as Ronin::Web::Server::Request" do
6
+ expect(described_class).to be < Ronin::Web::Server::Request
7
+ end
8
+
9
+ let(:env) { {} }
10
+
11
+ subject { described_class.new(env) }
12
+
13
+ describe "#host=" do
14
+ let(:host) { 'example.com' }
15
+ let(:new_host) { 'evil.com' }
16
+ let(:env) do
17
+ {'HTTP_HOST' => host}
18
+ end
19
+
20
+ before { subject.host = new_host }
21
+
22
+ it "must set HTTP_HOST" do
23
+ expect(env['HTTP_HOST']).to eq(new_host)
24
+ end
25
+ end
26
+
27
+ describe "#port=" do
28
+ let(:port) { 80 }
29
+ let(:new_port) { 8080 }
30
+ let(:env) do
31
+ {'SERVER_PORT' => port}
32
+ end
33
+
34
+ before { subject.port = new_port }
35
+
36
+ it "must set SERVER_PORT" do
37
+ expect(env['SERVER_PORT']).to eq(new_port)
38
+ end
39
+ end
40
+
41
+ describe "#scheme=" do
42
+ let(:scheme) { 'https' }
43
+ let(:new_scheme) { 'http' }
44
+ let(:env) do
45
+ {'rack.url_scheme' => scheme}
46
+ end
47
+
48
+ before { subject.scheme = new_scheme }
49
+
50
+ it "must set rack.url_scheme" do
51
+ expect(env['rack.url_scheme']).to eq(new_scheme)
52
+ end
53
+ end
54
+
55
+ describe "#ssl=" do
56
+ context "when given true" do
57
+ before { subject.ssl = true }
58
+
59
+ it "must set #port to 443" do
60
+ expect(subject.port).to eq(443)
61
+ end
62
+
63
+ it "must set #scheme to 'https'" do
64
+ expect(subject.scheme).to eq('https')
65
+ end
66
+ end
67
+
68
+ context "when given false" do
69
+ before { subject.ssl = false }
70
+
71
+ it "must set #port to 80" do
72
+ expect(subject.port).to eq(80)
73
+ end
74
+
75
+ it "must set #scheme to 'http'" do
76
+ expect(subject.scheme).to eq('http')
77
+ end
78
+ end
79
+ end
80
+
81
+ describe "#request_method=" do
82
+ let(:request_method) { 'GET' }
83
+ let(:new_request_method) { 'POST' }
84
+ let(:env) do
85
+ {'REQUEST_METHOD' => request_method}
86
+ end
87
+
88
+ before { subject.request_method = new_request_method }
89
+
90
+ it "must set REQUEST_METHOD" do
91
+ expect(env['REQUEST_METHOD']).to eq(new_request_method)
92
+ end
93
+ end
94
+
95
+ describe "#query_string=" do
96
+ let(:query_string) { 'GET' }
97
+ let(:new_query_string) { 'POST' }
98
+ let(:env) do
99
+ {'QUERY_STRING' => query_string}
100
+ end
101
+
102
+ before { subject.query_string = new_query_string }
103
+
104
+ it "must set QUERY_STRING" do
105
+ expect(env['QUERY_STRING']).to eq(new_query_string)
106
+ end
107
+ end
108
+
109
+ describe "#xhr=" do
110
+ context "when given true" do
111
+ before { subject.xhr = true }
112
+
113
+ it "must set HTTP_X_REQUESTED_WITH to 'XMLHttpRequest'" do
114
+ expect(env['HTTP_X_REQUESTED_WITH']).to eq('XMLHttpRequest')
115
+ end
116
+ end
117
+
118
+ context "when given false" do
119
+ let(:env) do
120
+ {'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest'}
121
+ end
122
+
123
+ before { subject.xhr = false }
124
+
125
+ it "must delete the HTTP_X_REQUESTED_WITH header" do
126
+ expect(env['HTTP_X_REQUESTED_WITH']).to be(nil)
127
+ end
128
+ end
129
+ end
130
+
131
+ describe "#content_type=" do
132
+ let(:content_type) { 'text/html' }
133
+ let(:new_content_type) { 'text/xml' }
134
+ let(:env) do
135
+ {'CONTENT_TYPE' => content_type}
136
+ end
137
+
138
+ before { subject.content_type = new_content_type }
139
+
140
+ it "must set CONTENT_TYPE" do
141
+ expect(env['CONTENT_TYPE']).to eq(new_content_type)
142
+ end
143
+ end
144
+
145
+ describe "#accept_encoding=" do
146
+ let(:accept_encoding) { 'gzip' }
147
+ let(:new_accept_encoding) { '*' }
148
+ let(:env) do
149
+ {'HTTP_ACCEPT_ENCODING' => accept_encoding}
150
+ end
151
+
152
+ before { subject.accept_encoding = new_accept_encoding }
153
+
154
+ it "must set HTTP_ACCEPT_ENCODING" do
155
+ expect(env['HTTP_ACCEPT_ENCODING']).to eq(new_accept_encoding)
156
+ end
157
+ end
158
+
159
+ describe "#user_agent=" do
160
+ let(:user_agent) { 'FireFox' }
161
+ let(:new_user_agent) { 'Chrome' }
162
+ let(:env) do
163
+ {'HTTP_USER_AGENT' => user_agent}
164
+ end
165
+
166
+ before { subject.user_agent = new_user_agent }
167
+
168
+ it "must set HTTP_USER_AGENT" do
169
+ expect(env['HTTP_USER_AGENT']).to eq(new_user_agent)
170
+ end
171
+ end
172
+
173
+ describe "#referer=" do
174
+ let(:referer) { 'http://example.com/' }
175
+ let(:new_referer) { 'http://evil.com/' }
176
+ let(:env) do
177
+ {'HTTP_REFERER' => referer}
178
+ end
179
+
180
+ before { subject.referer = new_referer }
181
+
182
+ it "must set HTTP_REFERER" do
183
+ expect(env['HTTP_REFERER']).to eq(new_referer)
184
+ end
185
+ end
186
+
187
+ describe "#body=" do
188
+ let(:body) { '<html><body>test</body></html>' }
189
+ let(:new_body) { '<html><body>rewritten!</body></html>' }
190
+ let(:env) do
191
+ {'rack.input' => body}
192
+ end
193
+
194
+ before { subject.body = new_body }
195
+
196
+ it "must set rack.input" do
197
+ expect(env['rack.input']).to eq(new_body)
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+ require 'ronin/web/server/reverse_proxy/response'
3
+
4
+ describe Ronin::Web::Server::ReverseProxy::Response do
5
+ it "must provide the same methods as Ronin::Web::Server::Response" do
6
+ expect(described_class).to be < Ronin::Web::Server::Response
7
+ end
8
+ end
@@ -0,0 +1,223 @@
1
+ require 'spec_helper'
2
+ require 'ronin/web/server/reverse_proxy'
3
+
4
+ require 'webmock/rspec'
5
+
6
+ describe Ronin::Web::Server::ReverseProxy do
7
+ describe "#initialize" do
8
+ context "when given a block" do
9
+ it "must pass the newly created reverse proxy object" do
10
+ expect { |b|
11
+ described_class.new(&b)
12
+ }.to yield_with_args(described_class)
13
+ end
14
+ end
15
+ end
16
+
17
+ describe "#call" do
18
+ let(:request_method) { 'GET' }
19
+ let(:path) { '/' }
20
+ let(:port) { 80 }
21
+ let(:host) { 'example.com' }
22
+ let(:body) { '' }
23
+ let(:env) do
24
+ {
25
+ 'REQUEST_METHOD' => request_method,
26
+ 'REQUEST_PATH' => path,
27
+ 'PATH_INFO' => path,
28
+ 'HTTP_HOST' => host,
29
+ 'SERVER_PORT' => port,
30
+ 'rack.input' => StringIO.new(body)
31
+ }
32
+ end
33
+
34
+ let(:http_request_method) { request_method.downcase.to_sym }
35
+ let(:http_request_uri) do
36
+ URI::HTTP.build(host: host, port: port, path: path)
37
+ end
38
+
39
+ let(:http_response_status) { 200 }
40
+ let(:http_response_headers) do
41
+ {'X-Foo' => 'bar'}
42
+ end
43
+ let(:http_response_body) { nil }
44
+
45
+ before do
46
+ stub_request(:get,http_request_uri).to_return(
47
+ status: http_response_status,
48
+ headers: http_response_headers,
49
+ body: http_response_body
50
+ )
51
+ end
52
+
53
+ it "must return a #{described_class::Response} object" do
54
+ expect(subject.call(env)).to be_kind_of(described_class::Response)
55
+ end
56
+
57
+ it "must make an HTTP request for the requested Host header and path" do
58
+ subject.call(env)
59
+
60
+ expect(WebMock).to have_requested(http_request_method,http_request_uri)
61
+ end
62
+
63
+ it "must return all HTTP response headers in the respones object" do
64
+ response = subject.call(env)
65
+
66
+ expect(response.headers).to include(http_response_headers)
67
+ end
68
+
69
+ it "must default the response body to an empty String" do
70
+ response = subject.call(env)
71
+
72
+ expect(response.body).to eq([''])
73
+ end
74
+
75
+ context "when the #on_request callback is set" do
76
+ it "must pass the request object to the #on_request callback" do
77
+ yielded_request = nil
78
+
79
+ reverse_proxy = described_class.new do |proxy|
80
+ proxy.on_request do |request|
81
+ yielded_request = request
82
+ end
83
+ end
84
+
85
+ reverse_proxy.call(env)
86
+
87
+ expect(yielded_request).to be_kind_of(described_class::Request)
88
+ end
89
+ end
90
+
91
+ context "when the #on_response callback is set" do
92
+ it "must pass the response object to the #on_response callback" do
93
+ yielded_response = nil
94
+
95
+ reverse_proxy = described_class.new do |proxy|
96
+ proxy.on_response do |response|
97
+ yielded_response = response
98
+ end
99
+ end
100
+
101
+ reverse_proxy.call(env)
102
+
103
+ expect(yielded_response).to be_kind_of(described_class::Response)
104
+ end
105
+
106
+ context "when the #on_response callback accepts two arguments" do
107
+ it "must pass both the request and the response objects" do
108
+ yielded_request = nil
109
+ yielded_response = nil
110
+
111
+ reverse_proxy = described_class.new do |proxy|
112
+ proxy.on_response do |request,response|
113
+ yielded_request = request
114
+ yielded_response = response
115
+ end
116
+ end
117
+
118
+ reverse_proxy.call(env)
119
+
120
+ expect(yielded_request).to be_kind_of(described_class::Request)
121
+ expect(yielded_response).to be_kind_of(described_class::Response)
122
+ end
123
+ end
124
+ end
125
+ end
126
+
127
+ describe "#connection_for" do
128
+ let(:host) { 'example.com' }
129
+ let(:port) { 443 }
130
+ let(:ssl) { true }
131
+
132
+ context "when there is no connection for the host/port/ssl combination" do
133
+ it "must return a new Ronin::Support::Network::HTTP instance for the host/port/ssl combination" do
134
+ http = subject.connection_for(host,port,ssl: ssl)
135
+
136
+ expect(http).to be_kind_of(Ronin::Support::Network::HTTP)
137
+ expect(http.host).to eq(host)
138
+ expect(http.port).to eq(port)
139
+ expect(http.ssl?).to eq(ssl)
140
+ end
141
+ end
142
+
143
+ context "when there is an existing connection for the host/port/ssl combination" do
144
+ it "must return the existing Ronin::Support::Network::HTTP instance for the host/port/ssl combination" do
145
+ expect(subject.connection_for(host,port,ssl: ssl)).to be(
146
+ subject.connection_for(host,port,ssl: ssl)
147
+ )
148
+ end
149
+ end
150
+ end
151
+
152
+ describe "#reverse_proxy" do
153
+ let(:request_method) { 'GET' }
154
+ let(:path) { '/' }
155
+ let(:port) { 80 }
156
+ let(:host) { 'example.com' }
157
+ let(:body) { '' }
158
+ let(:env) do
159
+ {
160
+ 'REQUEST_METHOD' => request_method,
161
+ 'REQUEST_PATH' => path,
162
+ 'PATH_INFO' => path,
163
+ 'HTTP_HOST' => host,
164
+ 'SERVER_PORT' => port,
165
+ 'rack.input' => StringIO.new(body)
166
+ }
167
+ end
168
+ let(:request) { described_class::Request.new(env) }
169
+
170
+ let(:http_request_method) { request_method.downcase.to_sym }
171
+ let(:http_request_uri) do
172
+ URI::HTTP.build(host: host, port: port, path: path)
173
+ end
174
+
175
+ let(:http_response_status) { 200 }
176
+ let(:http_response_headers) do
177
+ {'X-Foo' => 'bar'}
178
+ end
179
+ let(:http_response_body) { nil }
180
+
181
+ before do
182
+ stub_request(:get,http_request_uri).to_return(
183
+ status: http_response_status,
184
+ headers: http_response_headers,
185
+ body: http_response_body
186
+ )
187
+ end
188
+
189
+ it "must return a #{described_class::Response} object" do
190
+ expect(subject.reverse_proxy(request)).to be_kind_of(described_class::Response)
191
+ end
192
+
193
+ it "must make an HTTP request for the requested Host header and path" do
194
+ subject.reverse_proxy(request)
195
+
196
+ expect(WebMock).to have_requested(http_request_method,http_request_uri)
197
+ end
198
+
199
+ it "must return all HTTP response headers in the respones object" do
200
+ response = subject.reverse_proxy(request)
201
+
202
+ expect(response.headers).to include(http_response_headers)
203
+ end
204
+
205
+ it "must default the response body to an empty String" do
206
+ response = subject.reverse_proxy(request)
207
+
208
+ expect(response.body).to eq([''])
209
+ end
210
+
211
+ context "when the response contains the 'Transfer-Encoding' header" do
212
+ let(:http_response_headers) do
213
+ {'Transfer-Encoding' => 'chunked'}
214
+ end
215
+
216
+ it "must omit the 'Transfer-Encoding' header from the response" do
217
+ response = subject.reverse_proxy(request)
218
+
219
+ expect(response.headers).to_not have_key('Transfer-Encoding')
220
+ end
221
+ end
222
+ end
223
+ end
@@ -0,0 +1,9 @@
1
+ require 'rspec'
2
+ require 'simplecov'
3
+ require 'ronin/web/server/version'
4
+
5
+ SimpleCov.start
6
+
7
+ RSpec.configure do |c|
8
+ c.include Ronin::Web::Server
9
+ end
metadata ADDED
@@ -0,0 +1,180 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ronin-web-server
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0.beta1
5
+ platform: ruby
6
+ authors:
7
+ - Postmodern
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-01-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: webrick
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rack
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rack-user_agent
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.5'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.5'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sinatra
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: ronin-support
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 1.0.0.beta1
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 1.0.0.beta1
83
+ - !ruby/object:Gem::Dependency
84
+ name: bundler
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '2.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.0'
97
+ description: |
98
+ ronin-web-server is a custom Ruby web server based on Sinatra tailored for
99
+ security research and development.
100
+ email: postmodern.mod3@gmail.com
101
+ executables: []
102
+ extensions: []
103
+ extra_rdoc_files:
104
+ - COPYING.txt
105
+ - ChangeLog.md
106
+ - README.md
107
+ files:
108
+ - ".document"
109
+ - ".github/workflows/ruby.yml"
110
+ - ".gitignore"
111
+ - ".rspec"
112
+ - ".rubocop.yml"
113
+ - ".ruby-version"
114
+ - ".yardopts"
115
+ - COPYING.txt
116
+ - ChangeLog.md
117
+ - Gemfile
118
+ - README.md
119
+ - Rakefile
120
+ - gemspec.yml
121
+ - lib/ronin/web/server.rb
122
+ - lib/ronin/web/server/app.rb
123
+ - lib/ronin/web/server/base.rb
124
+ - lib/ronin/web/server/conditions.rb
125
+ - lib/ronin/web/server/helpers.rb
126
+ - lib/ronin/web/server/request.rb
127
+ - lib/ronin/web/server/response.rb
128
+ - lib/ronin/web/server/reverse_proxy.rb
129
+ - lib/ronin/web/server/reverse_proxy/request.rb
130
+ - lib/ronin/web/server/reverse_proxy/response.rb
131
+ - lib/ronin/web/server/routing.rb
132
+ - lib/ronin/web/server/version.rb
133
+ - ronin-web-server.gemspec
134
+ - spec/base_spec.rb
135
+ - spec/classes/public1/static1.txt
136
+ - spec/classes/public2/static2.txt
137
+ - spec/classes/sub_app.rb
138
+ - spec/classes/test_app.rb
139
+ - spec/helpers/rack_app.rb
140
+ - spec/request_spec.rb
141
+ - spec/response_spec.rb
142
+ - spec/reverse_proxy/request_spec.rb
143
+ - spec/reverse_proxy/response_spec.rb
144
+ - spec/reverse_proxy_spec.rb
145
+ - spec/spec_helper.rb
146
+ homepage: https://ronin-rb.dev/
147
+ licenses:
148
+ - LGPL-3.0
149
+ metadata:
150
+ documentation_uri: https://rubydoc.info/gems/ronin-web-server
151
+ source_code_uri: https://github.com/postmodern/ronin-web-server
152
+ bug_tracker_uri: https://github.com/postmodern/ronin-web-server/issues
153
+ changelog_uri: https://github.com/postmodern/ronin-web-server/blob/master/ChangeLog.md
154
+ rubygems_mfa_required: 'true'
155
+ post_install_message:
156
+ rdoc_options: []
157
+ require_paths:
158
+ - lib
159
+ required_ruby_version: !ruby/object:Gem::Requirement
160
+ requirements:
161
+ - - ">="
162
+ - !ruby/object:Gem::Version
163
+ version: 3.0.0
164
+ required_rubygems_version: !ruby/object:Gem::Requirement
165
+ requirements:
166
+ - - ">="
167
+ - !ruby/object:Gem::Version
168
+ version: '0'
169
+ requirements: []
170
+ rubygems_version: 3.3.26
171
+ signing_key:
172
+ specification_version: 4
173
+ summary: A custom Ruby web server based on Sinatra.
174
+ test_files:
175
+ - spec/base_spec.rb
176
+ - spec/request_spec.rb
177
+ - spec/response_spec.rb
178
+ - spec/reverse_proxy/request_spec.rb
179
+ - spec/reverse_proxy/response_spec.rb
180
+ - spec/reverse_proxy_spec.rb