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