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,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