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,261 @@
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 'rack/file'
22
+ require 'rack/directory'
23
+
24
+ module Ronin
25
+ module Web
26
+ module Server
27
+ #
28
+ # Adds additional routing class methods to {Base}.
29
+ #
30
+ # @api semipublic
31
+ #
32
+ module Routing
33
+ #
34
+ # Adds {ClassMethods} to the class.
35
+ #
36
+ # @param [Class] base
37
+ # The application base class that is including {Routing}.
38
+ #
39
+ # @api private
40
+ #
41
+ def self.included(base)
42
+ base.extend ClassMethods
43
+ end
44
+
45
+ #
46
+ # Class methods to be added to the application base class.
47
+ #
48
+ module ClassMethods
49
+ #
50
+ # Route any type of request for a given URL pattern.
51
+ #
52
+ # @param [String] path
53
+ # The URL pattern to handle requests for.
54
+ #
55
+ # @param [Hash{Symbol => Object}] conditions
56
+ # Additional routing conditions.
57
+ #
58
+ # @yield []
59
+ # The block that will handle the request.
60
+ #
61
+ # @example
62
+ # any '/submit' do
63
+ # puts request.inspect
64
+ # end
65
+ #
66
+ # @api public
67
+ #
68
+ def any(path,conditions={},&block)
69
+ get(path,conditions,&block)
70
+ post(path,conditions,&block)
71
+ put(path,conditions,&block)
72
+ patch(path,conditions,&block)
73
+ delete(path,conditions,&block)
74
+ options(path,conditions,&block)
75
+ end
76
+
77
+ #
78
+ # Sets the default route.
79
+ #
80
+ # @yield []
81
+ # The block that will handle all other requests.
82
+ #
83
+ # @example
84
+ # default do
85
+ # status 200
86
+ # content_type :html
87
+ #
88
+ # %{
89
+ # <html>
90
+ # <body>
91
+ # <center><h1>YOU LOSE THE GAME</h1></center>
92
+ # </body>
93
+ # </html>
94
+ # }
95
+ # end
96
+ #
97
+ # @api public
98
+ #
99
+ def default(&block)
100
+ not_found(&block)
101
+ return self
102
+ end
103
+
104
+ #
105
+ # Enables Basic-Auth authentication for the entire app.
106
+ #
107
+ # @param [String] auth_user
108
+ # The desired username.
109
+ #
110
+ # @param [String] auth_password
111
+ # The desired password
112
+ #
113
+ # @param [String] realm
114
+ # The "realm" message to display in the Basic-Auth dialog.
115
+ #
116
+ # @example
117
+ # basic_auth 'admin', 's3cr3t'
118
+ #
119
+ # @api public
120
+ #
121
+ def basic_auth(auth_user,auth_password, realm: 'Restricted')
122
+ use Rack::Auth::Basic, realm do |user,password|
123
+ user == auth_user && passwrd == auth_password
124
+ end
125
+ end
126
+
127
+ #
128
+ # Sets up a 302 Redirect at the given path.
129
+ #
130
+ # @param [String] path
131
+ # The path the web server will respond to.
132
+ #
133
+ # @param [String] url
134
+ # The URL to redirect to.
135
+ #
136
+ # @example
137
+ # redirect '/path', 'https://example.com/'
138
+ #
139
+ # @api public
140
+ #
141
+ def redirect(path,url)
142
+ get(path) { redirect(url) }
143
+ end
144
+
145
+ #
146
+ # Hosts the contents of a file.
147
+ #
148
+ # @param [String, Regexp] remote_path
149
+ # The path the web server will host the file at.
150
+ #
151
+ # @param [String] local_path
152
+ # The path to the local file.
153
+ #
154
+ # @param [Hash{Symbol => Object}] conditions
155
+ # Additional routing conditions.
156
+ #
157
+ # @example
158
+ # file '/robots.txt', '/path/to/my_robots.txt'
159
+ #
160
+ # @api public
161
+ #
162
+ def file(remote_path,local_path,conditions={})
163
+ get(remote_path,conditions) { send_file(local_path) }
164
+ end
165
+
166
+ #
167
+ # Hosts the contents of the directory.
168
+ #
169
+ # @param [String] remote_path
170
+ # The path the web server will host the directory at.
171
+ #
172
+ # @param [String] local_path
173
+ # The path to the local directory.
174
+ #
175
+ # @param [Hash{Symbol => Object}] conditions
176
+ # Additional routing conditions.
177
+ #
178
+ # @example
179
+ # directory '/download/', '/tmp/files/'
180
+ #
181
+ # @api public
182
+ #
183
+ def directory(remote_path,local_path,conditions={})
184
+ dir = Rack::File.new(local_path)
185
+
186
+ get("#{remote_path}/*",conditions) do |sub_path|
187
+ response = dir.call(env.merge('PATH_INFO' => "/#{sub_path}"))
188
+
189
+ if response[0] == 200 then response
190
+ else pass
191
+ end
192
+ end
193
+ end
194
+
195
+ #
196
+ # Hosts the static contents within a given directory.
197
+ #
198
+ # @param [String] path
199
+ # The path to a directory to serve static content from.
200
+ #
201
+ # @param [Hash{Symbol => Object}] conditions
202
+ # Additional routing conditions.
203
+ #
204
+ # @example
205
+ # public_dir 'path/to/another/public'
206
+ #
207
+ # @api public
208
+ #
209
+ def public_dir(path,conditions={})
210
+ directory('',path,conditions)
211
+ end
212
+
213
+ #
214
+ # Routes all requests for a given virtual host to another
215
+ # Rack application.
216
+ #
217
+ # @param [Regexp, String] host
218
+ # The host name to match against.
219
+ #
220
+ # @param [#call] app
221
+ # The Rack application to route the requests to.
222
+ #
223
+ # @param [Hash] conditions
224
+ # Additional routing conditions.
225
+ #
226
+ # @api public
227
+ #
228
+ def vhost(host,app,conditions={})
229
+ any('*',conditions.merge(host: host)) do
230
+ app.call(env)
231
+ end
232
+ end
233
+
234
+ #
235
+ # Routes all requests within a given directory into another
236
+ # Rack application.
237
+ #
238
+ # @param [String] dir
239
+ # The directory that requests for will be routed from.
240
+ #
241
+ # @param [#call] app
242
+ # The Rack application to route requests to.
243
+ #
244
+ # @param [Hash{Symbol => Object}] conditions
245
+ # Additional routing conditions.
246
+ #
247
+ # @example
248
+ # mount '/subapp/', SubApp
249
+ #
250
+ # @api public
251
+ #
252
+ def mount(dir,app,conditions={})
253
+ any("#{dir}/?*",conditions) do |sub_path|
254
+ app.call(env.merge('PATH_INFO' => "/#{sub_path}"))
255
+ end
256
+ end
257
+ end
258
+ end
259
+ end
260
+ end
261
+ end
@@ -0,0 +1,28 @@
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
+ module Ronin
22
+ module Web
23
+ module Server
24
+ # ronin-web-server version
25
+ VERSION = '0.1.0.beta1'
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,62 @@
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/base'
22
+ require 'ronin/web/server/app'
23
+
24
+ module Ronin
25
+ module Web
26
+ #
27
+ # Returns the Ronin Web Server.
28
+ #
29
+ # @param [Hash] options
30
+ # Additional options.
31
+ #
32
+ # @yield [server]
33
+ # If a block is given, it will be passed the current web server.
34
+ #
35
+ # @yieldparam [Server::App]
36
+ # The current web server class.
37
+ #
38
+ # @return [Server::App]
39
+ # The current web server class.
40
+ #
41
+ # @example
42
+ # Web.server do
43
+ # get '/hello' do
44
+ # 'world'
45
+ # end
46
+ # end
47
+ #
48
+ # @see Server::Base.run!
49
+ #
50
+ # @api public
51
+ #
52
+ def self.server(options={},&block)
53
+ unless @server
54
+ @server = Server::App
55
+ @server.run!(options.merge(background: true))
56
+ end
57
+
58
+ @server.class_eval(&block) if block
59
+ return @server
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,59 @@
1
+ require 'yaml'
2
+
3
+ Gem::Specification.new do |gem|
4
+ gemspec = YAML.load_file('gemspec.yml')
5
+
6
+ gem.name = gemspec.fetch('name')
7
+ gem.version = gemspec.fetch('version') do
8
+ lib_dir = File.join(File.dirname(__FILE__),'lib')
9
+ $LOAD_PATH << lib_dir unless $LOAD_PATH.include?(lib_dir)
10
+
11
+ require 'ronin/web/server/version'
12
+ Ronin::Web::Server::VERSION
13
+ end
14
+
15
+ gem.summary = gemspec['summary']
16
+ gem.description = gemspec['description']
17
+ gem.licenses = Array(gemspec['license'])
18
+ gem.authors = Array(gemspec['authors'])
19
+ gem.email = gemspec['email']
20
+ gem.homepage = gemspec['homepage']
21
+ gem.metadata = gemspec['metadata'] if gemspec['metadata']
22
+
23
+ glob = lambda { |patterns| gem.files & Dir[*patterns] }
24
+
25
+ gem.files = `git ls-files`.split($/)
26
+ gem.files = glob[gemspec['files']] if gemspec['files']
27
+ gem.files += Array(gemspec['generated_files'])
28
+
29
+ gem.executables = gemspec.fetch('executables') do
30
+ glob['bin/*'].map { |path| File.basename(path) }
31
+ end
32
+
33
+ gem.extensions = glob[gemspec['extensions'] || 'ext/**/extconf.rb']
34
+ gem.test_files = glob[gemspec['test_files'] || 'spec/{**/}*_spec.rb']
35
+ gem.extra_rdoc_files = glob[gemspec['extra_doc_files'] || '*.{txt,md}']
36
+
37
+ gem.require_paths = Array(gemspec.fetch('require_paths') {
38
+ %w[ext lib].select { |dir| File.directory?(dir) }
39
+ })
40
+
41
+ gem.requirements = gemspec['requirements']
42
+ gem.required_ruby_version = gemspec['required_ruby_version']
43
+ gem.required_rubygems_version = gemspec['required_rubygems_version']
44
+ gem.post_install_message = gemspec['post_install_message']
45
+
46
+ split = lambda { |string| string.split(/,\s*/) }
47
+
48
+ if gemspec['dependencies']
49
+ gemspec['dependencies'].each do |name,versions|
50
+ gem.add_dependency(name,split[versions])
51
+ end
52
+ end
53
+
54
+ if gemspec['development_dependencies']
55
+ gemspec['development_dependencies'].each do |name,versions|
56
+ gem.add_development_dependency(name,split[versions])
57
+ end
58
+ end
59
+ end
data/spec/base_spec.rb ADDED
@@ -0,0 +1,73 @@
1
+ require 'spec_helper'
2
+ require 'ronin/web/server/base'
3
+
4
+ require 'classes/test_app'
5
+ require 'helpers/rack_app'
6
+
7
+ describe Ronin::Web::Server::Base do
8
+ include Helpers::Web::RackApp
9
+
10
+ before(:all) do
11
+ self.app = TestApp
12
+ end
13
+
14
+ it "should still bind blocks to paths" do
15
+ get '/tests/get'
16
+
17
+ expect(last_response).to be_ok
18
+ expect(last_response.body).to eq('block tested')
19
+ end
20
+
21
+ it "should bind a block to a path for all request types" do
22
+ post '/tests/any'
23
+
24
+ expect(last_response).to be_ok
25
+ expect(last_response.body).to eq('any tested')
26
+ end
27
+
28
+ it "should have a default response" do
29
+ get '/totally/non/existant/path'
30
+
31
+ expect(last_response).not_to be_ok
32
+ expect(last_response.body).to be_empty
33
+ end
34
+
35
+ it "should allow for defining custom responses" do
36
+ TestApp.default do
37
+ halt 404, 'nothing to see here'
38
+ end
39
+
40
+ get '/whats/here'
41
+
42
+ expect(last_response).not_to be_ok
43
+ expect(last_response.body).to eq('nothing to see here')
44
+ end
45
+
46
+ it "should map paths to sub-apps" do
47
+ get '/tests/subapp/'
48
+
49
+ expect(last_response).to be_ok
50
+ expect(last_response.body).to eq('SubApp')
51
+ end
52
+
53
+ it "should not modify the path_info as it maps paths to sub-apps" do
54
+ get '/tests/subapp/hello'
55
+
56
+ expect(last_response).to be_ok
57
+ expect(last_response.body).to eq('SubApp greets you')
58
+ end
59
+
60
+ it "should host static content from public directories" do
61
+ get '/static1.txt'
62
+
63
+ expect(last_response).to be_ok
64
+ expect(last_response.body).to eq("Static file1.\n")
65
+ end
66
+
67
+ it "should host static content from multiple public directories" do
68
+ get '/static2.txt'
69
+
70
+ expect(last_response).to be_ok
71
+ expect(last_response.body).to eq("Static file2.\n")
72
+ end
73
+ end
@@ -0,0 +1 @@
1
+ Static file1.
@@ -0,0 +1 @@
1
+ Static file2.
@@ -0,0 +1,13 @@
1
+ require 'ronin/web/server/base'
2
+
3
+ class SubApp < Ronin::Web::Server::Base
4
+
5
+ get '/hello' do
6
+ 'SubApp greets you'
7
+ end
8
+
9
+ get '/' do
10
+ 'SubApp'
11
+ end
12
+
13
+ end
@@ -0,0 +1,20 @@
1
+ require 'ronin/web/server/base'
2
+
3
+ require 'classes/sub_app'
4
+
5
+ class TestApp < Ronin::Web::Server::Base
6
+
7
+ get '/tests/get' do
8
+ 'block tested'
9
+ end
10
+
11
+ any '/tests/any' do
12
+ 'any tested'
13
+ end
14
+
15
+ mount '/tests/subapp', SubApp
16
+
17
+ public_dir File.join(File.dirname(__FILE__),'public1')
18
+ public_dir File.join(File.dirname(__FILE__),'public2')
19
+
20
+ end
@@ -0,0 +1,24 @@
1
+ require 'rack/test'
2
+
3
+ module Helpers
4
+ module Web
5
+ module RackApp
6
+ include Rack::Test::Methods
7
+
8
+ attr_reader :app
9
+
10
+ def app=(server)
11
+ @app = server
12
+ @app.set :environment, :test
13
+ end
14
+
15
+ def get_host(path,host,params={},headers={})
16
+ get(path,params,headers.merge('HTTP_HOST' => host))
17
+ end
18
+
19
+ def post_host(path,host,params={},headers={})
20
+ post(path,params,headers.merge('HTTP_HOST' => host))
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+ require 'ronin/web/server/request'
3
+
4
+ describe Ronin::Web::Server::Request do
5
+ it "must provide the same methods as Sinatra::Request" do
6
+ expect(described_class).to be < Sinatra::Request
7
+ end
8
+
9
+ let(:env) { {} }
10
+
11
+ subject { described_class.new(env) }
12
+
13
+ describe "#ip_with_port" do
14
+ let(:ip) { '127.0.0.1' }
15
+
16
+ context "when REMOTE_PORT is set" do
17
+ let(:port) { 8000 }
18
+ let(:env) do
19
+ {'REMOTE_ADDR' => ip, 'REMOTE_PORT' => port}
20
+ end
21
+
22
+ it "must return REMOTE_ADDR:REMOTE_PORT" do
23
+ expect(subject.ip_with_port).to eq("#{ip}:#{port}")
24
+ end
25
+ end
26
+
27
+ context "when REMOTE_PORT is not set" do
28
+ let(:env) do
29
+ {'REMOTE_ADDR' => ip}
30
+ end
31
+
32
+ it "must return the REMOTE_ADDR" do
33
+ expect(subject.ip_with_port).to eq(ip)
34
+ end
35
+ end
36
+ end
37
+
38
+ describe "#headers" do
39
+ let(:header1) { 'header1 value' }
40
+ let(:header2) { 'header2 value' }
41
+ let(:env) do
42
+ {
43
+ 'HTTP_HEADER1' => header1,
44
+ 'FOO_BAR' => 'foo',
45
+ 'HTTP_HEADER2' => header2
46
+ }
47
+ end
48
+
49
+ it "must return a Hash of all HTTP_* headers" do
50
+ expect(subject.headers).to eq(
51
+ {
52
+ 'Header1' => header1,
53
+ 'Header2' => header2
54
+ }
55
+ )
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+ require 'ronin/web/server/response'
3
+
4
+ describe Ronin::Web::Server::Response do
5
+ it "must provide the same methods as Sinatra::Response" do
6
+ expect(described_class).to be < Sinatra::Response
7
+ end
8
+ end