intranet-core 1.2.0 → 2.1.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: b297a91ba8ca9fc6a44723c4f9f738d879193828
4
- data.tar.gz: bc44f0b1858f045978da8d39824ad36a0a52c485
2
+ SHA256:
3
+ metadata.gz: e1d7d91fa0a962e4835681a46f50c7e49b5aaa77327ab112d2999aa90b26a312
4
+ data.tar.gz: 3c67a863896c42b5b83de33863e372678bae92ee831c05f91a6484343ecff88e
5
5
  SHA512:
6
- metadata.gz: a5094ed435333e80537e85603359276059b345554ae185cdb6c56e9d843ca385b31fc1e1e008e329114c3f5584e509d89087dab4e3e24423a2fbb35f0081457b
7
- data.tar.gz: e06fea82c25af97e56230a51df3f4b4f9447fd2218d9ca7b28b0663f83d43d3713cda474aab06c15c45a5860680d02edf5adf95742ee552331e19755e01a19ad
6
+ metadata.gz: 2df2d7de87df4c1a267c6fa39f3188ec8231b265c7206351bcde3a1203ad4c4c5d6b163a747888f1967e783c8fd1e1d0a1bbf5d081bd8d5f864eee83ae67ef03
7
+ data.tar.gz: 0ca99259824749ffe9f08c3c8c32a0faa7b7f6afaa063c5e45072fe883355ed684beb35af5ab878f94ff2a037ca391bb9a4a05c918ec3e31ea9e817b716531b7
@@ -22,6 +22,15 @@ module Intranet
22
22
  true
23
23
  end
24
24
 
25
+ # Specifies the absolute path to the resources directory for that module. This directory should
26
+ # contain three subdirectories: +haml/+, +locales/+ and +www/+.
27
+ # This method should be redefined to overwrite this default value with the actual resources
28
+ # directory path.
29
+ # @return [String] The absolute path to the resources directory for the module.
30
+ def resources_dir
31
+ File.join(__dir__, 'resources')
32
+ end
33
+
25
34
  # Destroys the responder instance.
26
35
  # This method gets called when server is shut down.
27
36
  def finalize
@@ -36,16 +45,20 @@ module Intranet
36
45
  [404, '', '']
37
46
  end
38
47
 
39
- # Provides the list of Cascade Style Sheets (CSS) dependencies for this module.
48
+ # Provides the list of Cascade Style Sheets (CSS) dependencies for this module, either using
49
+ # absolute or relative (from the module root) paths.
40
50
  # If redefined, this method should probably append dependencies rather than overwriting them.
41
- # @return [Array] The list of CSS dependencies, as URL path from server root
51
+ # @return [Array] The list of CSS dependencies, as absolute path or relative to the module root
52
+ # URL.
42
53
  def css_dependencies
43
54
  ['/design/style.css']
44
55
  end
45
56
 
46
- # Provides the list of Javascript files (JS) dependencies for this module.
57
+ # Provides the list of Javascript files (JS) dependencies for this module, either using
58
+ # absolute or relative (from the module root) paths.
47
59
  # If redefined, this method should probably append dependencies rather than overwriting them.
48
- # @return [Array] The list of JS dependencies, as URL path from server root
60
+ # @return [Array] The list of JS dependencies, as absolute path or relative to the module root
61
+ # URL.
49
62
  def js_dependencies
50
63
  ['/design/nav.js']
51
64
  end
@@ -40,22 +40,19 @@ module Intranet
40
40
  mount_default_servlets(www_dir)
41
41
  end
42
42
 
43
- # Registers a new module to the core. The server must not be running. If +path+ is empty, the
44
- # module will be used as Home module. Otherwise, the module will be accessible through the
45
- # given +path+ under the web server root.
43
+ # Registers a new module to the core. The server must not be running. The module will then be
44
+ # accessible through the given +path+ under the web server root.
46
45
  # @param responder [Intranet::AbstractResponder] The module responder instance.
47
46
  # @param path [Array] The path, relative to the web server root, representing the module root
48
- # directory. If empty, the responder will be registered as Home responder
49
- # (to serve /index.html in particular). Subdirectories are allowed using
50
- # an array element for each directory level. Each element must only contain
51
- # the following characters: +-_a-z0-9+.
47
+ # directory. The path cannot be empty and have more than 2 elements. Each
48
+ # element must only contain the following characters: +-_a-z0-9+.
52
49
  # @param resources_dir [String] The path to the directory that contains additional resources
53
50
  # required by the module. This directory should contain three
54
- # subdirectories: +haml/+, +locales/+ and +www/+.
55
- # @raise [ArgumentError] If one of the element of the +path+ contains invalid characters.
51
+ # subdirectories: +haml/+, +locales/+ and +www/+. The +www/+ is
52
+ # mounted under +<path>/design+ on the web server.
53
+ # @raise [ArgumentError] If the +path+ is not valid.
56
54
  # @raise [Errno::EALREADY] If the server is already running.
57
- def register_module(responder, path, resources_dir)
58
- raise ArgumentError unless path.all?(&:urlized?)
55
+ def register_module(responder, path, resources_dir = responder.resources_dir)
59
56
  raise Errno::EALREADY if @server.status != :Stop
60
57
 
61
58
  @builder.register(responder, path)
@@ -64,6 +61,13 @@ module Intranet
64
61
  module_add_haml_templates(resources_dir)
65
62
  end
66
63
 
64
+ # Defines the URL of the home page.
65
+ # @param url [String] The absolute URL of the home page.
66
+ # @raise [ArgumentError] If the URL is not an absolute path.
67
+ def home_url=(url)
68
+ @builder.home_url = url
69
+ end
70
+
67
71
  # Starts the web server.
68
72
  def start
69
73
  @logger.info('Intranet::Core: using locale \'' + I18n.default_locale.to_s + '\'')
@@ -76,6 +80,8 @@ module Intranet
76
80
  def stop
77
81
  @logger.info('Intranet::Runner: requesting system shutdown...')
78
82
  @server.shutdown
83
+ while @server.status != :Stop
84
+ end
79
85
  @builder.finalize
80
86
  @logger.close
81
87
  end
@@ -109,7 +115,7 @@ module Intranet
109
115
  end
110
116
 
111
117
  def module_add_servlet(path, resources_dir)
112
- @server.mount('/design/' + path.join('/'),
118
+ @server.mount('/' + path.join('/') + '/design',
113
119
  WEBrick::HTTPServlet::FileHandler,
114
120
  File.join(resources_dir, 'www'))
115
121
  end
@@ -20,12 +20,17 @@ module Intranet
20
20
  # @return [Hash]
21
21
  attr_reader :modules
22
22
 
23
+ # The Home page URL
24
+ # @return [String]
25
+ attr_reader :home_url
26
+
23
27
  # Initializes a new builder.
24
28
  # @param logger [Object] The logger.
25
29
  def initialize(logger)
26
30
  @logger = logger
27
31
  @responders = CoreExtensions::Tree.new
28
32
  @modules = { NAME => { version: VERSION, homepage: HOMEPAGE_URL } }
33
+ @home_url = '/index.html'
29
34
  end
30
35
 
31
36
  # Finalizes the builder. Each registered responder is called for +finalize+.
@@ -47,13 +52,13 @@ module Intranet
47
52
  # @param query [Hash] The content of the GET parameters of the URL.
48
53
  # @return [Array] The HTTP return code, the MIME type and the answer body.
49
54
  def do_get(path, query = {})
50
- responder, responder_path = get_responder(path)
51
- status, mime_type, body = responder.generate_page(responder_path, query)
55
+ responder, parsed_path, unparsed_path = get_responder(path)
56
+ status, mime_type, body = responder.generate_page(unparsed_path, query)
52
57
 
53
58
  # Generate header and footer when partial content is returned by the responder
54
59
  if status == 206 && mime_type == 'text/html'
55
60
  body = to_markup('skeleton', body: body, css: responder.css_dependencies,
56
- js: responder.js_dependencies)
61
+ js: responder.js_dependencies, current_path: parsed_path)
57
62
  status = 200
58
63
  end
59
64
  [status, mime_type, body]
@@ -65,35 +70,54 @@ module Intranet
65
70
  # one overrides the old one.
66
71
  # @param responder [Intranet::AbstractResponder] The responder instance of the module.
67
72
  # @param path [Array] The path, relative to the web server root, representing the module root
68
- # directory. If empty, the responder will be registered as Home responder
69
- # (to serve /index.html in particular). Subdirectories are allowed using
70
- # an array element for each directory level.
73
+ # directory.
71
74
  # @raise [ArgumentError] If the +responder+ is not a valid responder instance.
72
- # @raise [ArgumentError] If one of the element of the +path+ contains invalid characters.
75
+ # @raise [ArgumentError] If the +path+ is invalid.
73
76
  def register(responder, path = [])
74
77
  raise ArgumentError unless responder.class.superclass.to_s == 'Intranet::AbstractResponder'
75
- raise ArgumentError unless path.all?(&:urlized?)
78
+ raise ArgumentError unless valid_path?(path)
76
79
 
77
80
  insert_responder(responder, path)
78
81
  store_module_metadata(responder)
79
82
  end
80
83
 
84
+ # Defines the URL of the home page.
85
+ # @param url [String] The absolute URL of the home page.
86
+ # @raise [ArgumentError] If the URL is not an absolute path.
87
+ def home_url=(url)
88
+ raise ArgumentError unless url.start_with?('/')
89
+
90
+ @home_url = url
91
+ end
92
+
81
93
  private
82
94
 
83
95
  # Get the responder instance associated with the given path.
84
96
  # @param path [String] The absolute URL path.
85
- # @return [Array] The responder instance (possibly nil) and the remaining of the URL path that
86
- # has not been parsed.
97
+ # @return [Intranet::AbstractResponder, String, String] The responder instance (possibly nil)
98
+ # the parsed part of the path, and the
99
+ # unparsed part of the path.
87
100
  def get_responder(path)
101
+ parsed_path = []
88
102
  current_treenode = @responders
89
- relative_path = path[1..-1].split('/').delete_if do |part|
103
+ unparsed_path = path[1..-1].split('/').delete_if do |part|
90
104
  if current_treenode.child_exists?(part)
91
105
  current_treenode = current_treenode.child_node(part)
106
+ parsed_path << part
92
107
  true
93
108
  end
94
109
  end
95
110
 
96
- [current_treenode.value, '/' + relative_path.join('/')]
111
+ [current_treenode.value, '/' + parsed_path.join('/'), '/' + unparsed_path.join('/')]
112
+ end
113
+
114
+ # Check whether a path is valid.
115
+ # @param path [Array] The path to test.
116
+ # @return [Boolean] True if the path is valid, False otherwise.
117
+ def valid_path?(path)
118
+ (path.size == 1 || path.size == 2) && path.none?(&:empty?) && path.all?(&:urlized?)
119
+ rescue NoMethodError
120
+ false
97
121
  end
98
122
 
99
123
  # Inserts a responder instance in the appropriate Tree node according the given +path+.
@@ -24,12 +24,14 @@ module Intranet
24
24
  def do_GET(request, response) # rubocop:disable Naming/MethodName
25
25
  # See https://github.com/nahi/webrick/blob/master/lib/webrick/httprequest.rb
26
26
  # We could use request.request_uri (which is a URI object) but its path is not normalized
27
- # (it may contain '../' or event start with '..'). Hence we use request.past which has been
27
+ # (it may contain '../' or event start with '..'). Hence we use request.path which has been
28
28
  # normalized with HTTPUtils::normalize_path, and request.query for the URL parameters.
29
- path = request.path
30
- path += 'index.html' if path[-1] == '/' # path is normalized (ie. does not contain '../')
29
+ path = request.path.force_encoding('UTF-8')
30
+ path += 'index.html' if path.end_with?('/')
31
31
 
32
- status, content_type, body = @builder.do_get(path, request.query)
32
+ handle_redirections(request, path, response, '/index.html' => @builder.home_url)
33
+
34
+ status, content_type, body = @builder.do_get(path, encode_query(request.query))
33
35
 
34
36
  raise WEBrick::HTTPStatus[status] if WEBrick::HTTPStatus.error?(status)
35
37
 
@@ -37,6 +39,25 @@ module Intranet
37
39
  response.content_type = content_type
38
40
  response.body = body
39
41
  end
42
+
43
+ private
44
+
45
+ # Issues an HTTP redirection (307) in +response+ if the +path+ associated to the +request+
46
+ # has an entry if the redirection table +redirects+.
47
+ def handle_redirections(request, path, response, redirects)
48
+ target = redirects.fetch(path)
49
+ location = URI.join(request.request_uri, target).to_s
50
+ response.set_redirect(WEBrick::HTTPStatus[307], location) if path != target
51
+ rescue KeyError
52
+ # nothing to do
53
+ end
54
+
55
+ # Reencodes the +query+ in UTF-8 strings for compatibility.
56
+ def encode_query(query)
57
+ query.map do |k, v|
58
+ { k.dup.force_encoding('UTF-8') => v.force_encoding('UTF-8') }
59
+ end.reduce({}, :merge)
60
+ end
40
61
  end
41
62
  end
42
63
  end
@@ -6,7 +6,7 @@ module Intranet
6
6
  NAME = 'intranet-core'
7
7
 
8
8
  # The version of the gem, according to semantic versionning.
9
- VERSION = '1.2.0'
9
+ VERSION = '2.1.4'
10
10
 
11
11
  # The URL of the gem homepage.
12
12
  HOMEPAGE_URL = 'https://rubygems.org/gems/intranet-core'
@@ -5,9 +5,15 @@
5
5
  %meta{charset: 'utf-8'}
6
6
  %link{rel: 'icon', type: 'image/x-icon', href: '/design/favicon.ico'}
7
7
  - css.each do |url|
8
- %link{rel: 'stylesheet', type: 'text/css', href: url}
8
+ - if url.start_with?('/')
9
+ %link{rel: 'stylesheet', type: 'text/css', href: url}
10
+ - else
11
+ %link{rel: 'stylesheet', type: 'text/css', href: current_path + '/' + url}
9
12
  - js.each do |url|
10
- %script{src: url, defer: 'defer'}
13
+ - if url.start_with?('/')
14
+ %script{src: url, defer: 'defer'}
15
+ - else
16
+ %script{src: current_path + '/' + url, defer: 'defer'}
11
17
  %body
12
18
  %header
13
19
  %a{id: 'openmenu', onclick: 'openNavMenu();'}= '&#9776;'
@@ -10,7 +10,8 @@ function openNavMenu() {
10
10
  }
11
11
 
12
12
  function closeNavMenu() {
13
- document.querySelectorAll('header nav')[0].style.width = '0';
13
+ /* Remove value property set in openNavMenu() */
14
+ document.querySelectorAll('header nav')[0].style.width = '';
14
15
  }
15
16
 
16
17
  function openModal() {
@@ -45,7 +45,6 @@ header {
45
45
  top: 0px;
46
46
  width: 100%;
47
47
  margin: auto;
48
- height: 57px;
49
48
  background-color: #1e262b;
50
49
  color: white;
51
50
  z-index: 2;
@@ -82,18 +82,21 @@ RSpec.describe Intranet::Core do
82
82
  end
83
83
  end
84
84
  end
85
+ end
85
86
 
87
+ describe '#register_module' do
86
88
  context 'registering a module when the server is running' do
87
89
  it 'should fail' do
88
90
  begin
89
91
  @intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
92
+ r = Intranet::TestResponder.new('/index.html' => [200, 'text/html', ''])
90
93
  thread = Thread.new do
91
94
  @intranet.start
92
95
  end
93
96
  while @intranet.instance_variable_get(:@server).status != :Running
94
97
  end
95
98
 
96
- expect { @intranet.register_module(nil, [], '') }.to raise_error Errno::EALREADY
99
+ expect { @intranet.register_module(r, ['path'], '') }.to raise_error Errno::EALREADY
97
100
  ensure
98
101
  Thread.kill(thread)
99
102
  thread.join
@@ -104,119 +107,82 @@ RSpec.describe Intranet::Core do
104
107
  context 'registering a module with an invalid path' do
105
108
  it 'should fail' do
106
109
  @intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
107
- expect { @intranet.register_module(nil, ['Invalid'], '') }.to raise_error ArgumentError
110
+ r = Intranet::TestResponder.new('/index.html' => [200, 'text/html', ''])
111
+ expect { @intranet.register_module(r, [], '') }.to raise_error ArgumentError
112
+ expect { @intranet.register_module(r, %w[1 2 3], '') }.to raise_error ArgumentError
113
+ expect { @intranet.register_module(r, ['', 'valid'], '') }.to raise_error ArgumentError
114
+ expect { @intranet.register_module(r, ['Invalid'], '') }.to raise_error ArgumentError
115
+ expect { @intranet.register_module(r, 'fo', '') }.to raise_error ArgumentError
108
116
  end
109
117
  end
110
118
 
111
119
  context 'registering an invalid module' do
112
120
  it 'should fail' do
113
121
  @intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
114
- expect { @intranet.register_module(nil, [], '') }.to raise_error ArgumentError
122
+ expect { @intranet.register_module(nil, ['path'], '') }.to raise_error ArgumentError
115
123
  end
116
124
  end
117
125
 
118
- context 'when a valid module is registered with no path (home module)' do
119
- it 'should be used to serve all URI' do
120
- begin
121
- @intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
122
- responder = Intranet::TestResponder.new('/index.html' => [200, 'text/html', ''],
123
- '/folder/index.html' => [200, 'text/html', ''])
124
- @intranet.register_module(responder, [], responder.resources_dir)
125
- thread = Thread.new do
126
- @intranet.start
127
- end
128
- while @intranet.instance_variable_get(:@server).status != :Running
129
- end
130
-
131
- socket = TCPSocket.new('localhost', @intranet.port)
132
- socket.puts("GET /index.html HTTP/1.1\r\n" \
133
- "Host: localhost:#{@intranet.port}\r\n\r\n")
134
- expect(socket.gets).to include('HTTP/1.1 200 OK')
135
- socket.close
136
-
137
- socket = TCPSocket.new('localhost', @intranet.port)
138
- socket.puts("GET /folder/index.html HTTP/1.1\r\n" \
139
- "Host: localhost:#{@intranet.port}\r\n\r\n")
140
- expect(socket.gets).to include('HTTP/1.1 200 OK')
141
- socket.close
142
-
143
- socket = TCPSocket.new('localhost', @intranet.port)
144
- socket.puts("GET /folder/subdir/index.html HTTP/1.1\r\n" \
145
- "Host: localhost:#{@intranet.port}\r\n\r\n")
146
- expect(socket.gets).to include('HTTP/1.1 404 Not Found')
147
- socket.close
148
-
149
- socket = TCPSocket.new('localhost', @intranet.port)
150
- socket.puts("GET /design/home/style.css HTTP/1.1\r\n" \
151
- "Host: localhost:#{@intranet.port}\r\n\r\n")
152
- expect(socket.gets).to include('HTTP/1.1 200 OK')
153
- ensure
154
- socket.close
155
- Thread.kill(thread)
156
- thread.join
126
+ context 'when a valid module is registered' do
127
+ before(:each) do
128
+ @intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
129
+ responder = Intranet::TestResponder.new('/index.html' => [200, 'text/html', ''])
130
+ # Third argument of register_module() is optional, so we test both cases.
131
+ @intranet.register_module(responder, %w[responder], responder.resources_dir)
132
+ @intranet.register_module(responder, %w[resp onder])
133
+ @thread = Thread.new do
134
+ @intranet.start
135
+ end
136
+ while @intranet.instance_variable_get(:@server).status != :Running
157
137
  end
158
138
  end
159
- end
160
139
 
161
- context 'when a valid module is registered with a path' do
140
+ after(:each) do
141
+ Thread.kill(@thread)
142
+ @thread.join
143
+ end
144
+
162
145
  it 'should be used to serve URI relative to the module root' do
163
- begin
164
- @intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
165
- responder = Intranet::TestResponder.new('/index.html' => [200, 'text/html', ''])
166
- @intranet.register_module(responder, [], responder.resources_dir)
167
- @intranet.register_module(responder, %w[responder], responder.resources_dir)
168
- @intranet.register_module(responder, %w[resp onder], responder.resources_dir)
169
- thread = Thread.new do
170
- @intranet.start
171
- end
172
- while @intranet.instance_variable_get(:@server).status != :Running
173
- end
146
+ socket = TCPSocket.new('localhost', @intranet.port)
147
+ socket.puts("GET /responder/index.html HTTP/1.1\r\n" \
148
+ "Host: localhost:#{@intranet.port}\r\n\r\n")
149
+ expect(socket.gets).to include('HTTP/1.1 200 OK')
150
+ socket.close
174
151
 
175
- socket = TCPSocket.new('localhost', @intranet.port)
176
- socket.puts("GET /index.html HTTP/1.1\r\n" \
177
- "Host: localhost:#{@intranet.port}\r\n\r\n")
178
- expect(socket.gets).to include('HTTP/1.1 200 OK')
179
- socket.close
180
- socket = TCPSocket.new('localhost', @intranet.port)
181
- socket.puts("GET /design/home/style.css HTTP/1.1\r\n" \
182
- "Host: localhost:#{@intranet.port}\r\n\r\n")
183
- expect(socket.gets).to include('HTTP/1.1 200 OK')
184
- socket.close
152
+ socket = TCPSocket.new('localhost', @intranet.port)
153
+ socket.puts("GET /resp/onder/index.html HTTP/1.1\r\n" \
154
+ "Host: localhost:#{@intranet.port}\r\n\r\n")
155
+ expect(socket.gets).to include('HTTP/1.1 200 OK')
156
+ socket.close
185
157
 
186
- socket = TCPSocket.new('localhost', @intranet.port)
187
- socket.puts("GET /responder/index.html HTTP/1.1\r\n" \
188
- "Host: localhost:#{@intranet.port}\r\n\r\n")
189
- expect(socket.gets).to include('HTTP/1.1 200 OK')
190
- socket.close
191
- socket = TCPSocket.new('localhost', @intranet.port)
192
- socket.puts("GET /design/responder/style.css HTTP/1.1\r\n" \
193
- "Host: localhost:#{@intranet.port}\r\n\r\n")
194
- expect(socket.gets).to include('HTTP/1.1 200 OK')
195
- socket.close
158
+ socket = TCPSocket.new('localhost', @intranet.port)
159
+ socket.puts("GET /resp/onder/index2.html HTTP/1.1\r\n" \
160
+ "Host: localhost:#{@intranet.port}\r\n\r\n")
161
+ expect(socket.gets).to include('HTTP/1.1 404 Not Found')
162
+ socket.close
163
+ end
196
164
 
197
- socket = TCPSocket.new('localhost', @intranet.port)
198
- socket.puts("GET /resp/onder/index.html HTTP/1.1\r\n" \
199
- "Host: localhost:#{@intranet.port}\r\n\r\n")
200
- expect(socket.gets).to include('HTTP/1.1 200 OK')
201
- socket.close
202
- socket = TCPSocket.new('localhost', @intranet.port)
203
- socket.puts("GET /design/resp/onder/style.css HTTP/1.1\r\n" \
204
- "Host: localhost:#{@intranet.port}\r\n\r\n")
205
- expect(socket.gets).to include('HTTP/1.1 200 OK')
206
- ensure
207
- socket.close
208
- Thread.kill(thread)
209
- thread.join
210
- end
165
+ it 'should have its www/ directory available under the subfolder design/' do
166
+ socket = TCPSocket.new('localhost', @intranet.port)
167
+ socket.puts("GET /responder/design/style.css HTTP/1.1\r\n" \
168
+ "Host: localhost:#{@intranet.port}\r\n\r\n")
169
+ expect(socket.gets).to include('HTTP/1.1 200 OK')
170
+ socket.close
171
+
172
+ socket = TCPSocket.new('localhost', @intranet.port)
173
+ socket.puts("GET /resp/onder/design/style.css HTTP/1.1\r\n" \
174
+ "Host: localhost:#{@intranet.port}\r\n\r\n")
175
+ expect(socket.gets).to include('HTTP/1.1 200 OK')
176
+ socket.close
211
177
  end
212
178
  end
213
179
 
214
180
  context 'given a valid and registered module' do
215
- it 'should be called with the URL path and query' do
181
+ it 'should be called with the decoded URL path and query in UTF-8 encoding' do
216
182
  begin
217
183
  @intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
218
184
  responder = Intranet::TestResponder.new('/index.html' => [200, 'text/html', ''])
219
- @intranet.register_module(responder, [], responder.resources_dir)
185
+ @intranet.register_module(responder, ['responder'], responder.resources_dir)
220
186
  thread = Thread.new do
221
187
  @intranet.start
222
188
  end
@@ -224,25 +190,17 @@ RSpec.describe Intranet::Core do
224
190
  end
225
191
 
226
192
  socket = TCPSocket.new('localhost', @intranet.port)
227
- socket.puts("GET /query?var1=value1&var2=value2 HTTP/1.1\r\n" \
228
- "Host: localhost:#{@intranet.port}\r\n\r\n")
229
- expect(socket.gets).to include('HTTP/1.1 200 OK')
230
- while (line = socket.gets.chomp) # consume HTTP response headers
231
- break if line.empty?
232
- end
233
- line = socket.gets.chomp
234
- expect(line).to eql({ 'var1' => 'value1', 'var2' => 'value2' }.to_s)
235
- socket.close
236
-
237
- socket = TCPSocket.new('localhost', @intranet.port)
238
- socket.puts("GET /query?foo=bar&baz=boz HTTP/1.1\r\n" \
193
+ socket.puts("GET /responder/query%20t?var1=value1&var2=value2 HTTP/1.1\r\n" \
239
194
  "Host: localhost:#{@intranet.port}\r\n\r\n")
240
195
  expect(socket.gets).to include('HTTP/1.1 200 OK')
241
196
  while (line = socket.gets.chomp) # consume HTTP response headers
242
197
  break if line.empty?
243
198
  end
244
199
  line = socket.gets.chomp
245
- expect(line).to eql({ 'foo' => 'bar', 'baz' => 'boz' }.to_s)
200
+ expect(line).to eql(
201
+ 'PATH=/query t (UTF-8), ' \
202
+ 'QUERY={var1 (UTF-8) => value1 (UTF-8),var2 (UTF-8) => value2 (UTF-8)}'
203
+ )
246
204
  ensure
247
205
  socket.close
248
206
  Thread.kill(thread)
@@ -262,7 +220,7 @@ RSpec.describe Intranet::Core do
262
220
  ['/responder.css', 'nav.css'],
263
221
  ['module.js', '/js/interactive.js']
264
222
  )
265
- @intranet.register_module(responder, [], responder.resources_dir)
223
+ @intranet.register_module(responder, ['r'], responder.resources_dir)
266
224
  thread = Thread.new do
267
225
  @intranet.start
268
226
  end
@@ -270,7 +228,7 @@ RSpec.describe Intranet::Core do
270
228
  end
271
229
 
272
230
  socket = TCPSocket.new('localhost', @intranet.port)
273
- socket.puts("GET /index.html HTTP/1.1\r\nHost: localhost:#{@intranet.port}\r\n\r\n")
231
+ socket.puts("GET /r/index.html HTTP/1.1\r\nHost: localhost:#{@intranet.port}\r\n\r\n")
274
232
 
275
233
  # Return code: HTTP error 200
276
234
  expect(socket.gets).to include('HTTP/1.1 200 OK')
@@ -290,17 +248,17 @@ RSpec.describe Intranet::Core do
290
248
  expect(html).to match(%r{<body>.*<h1>.*#{hostname.capitalize}.*</h1>.*</body>}m)
291
249
  expect(html).to match(%r{<footer>.*#{hostname}.*</footer>}m)
292
250
 
293
- # Returned HTML document: includes all CSS dependencies
251
+ # Returned HTML document: includes all CSS dependencies, relative or absolute path
294
252
  expect(html).to match(%r{<link href='/responder.css' rel='stylesheet' type='text/css'})
295
- expect(html).to match(%r{<link href='nav.css' rel='stylesheet' type='text/css'})
253
+ expect(html).to match(%r{<link href='/r/nav.css' rel='stylesheet' type='text/css'})
296
254
 
297
255
  # Returned HTML document: includes all JS dependencies
298
- expect(html).to match(%r{<script defer='defer' src='module.js'></script>})
256
+ expect(html).to match(%r{<script defer='defer' src='/r/module.js'></script>})
299
257
  expect(html).to match(%r{<script defer='defer' src='/js/interactive.js'></script>})
300
258
 
301
259
  # Returned HTML document: includes Intranet Core name, version and URL
302
260
  expect(html).to match(
303
- %r{<footer>.*<a href='#{Intranet::Core::HOMEPAGE_URL}'.*>#{Intranet::Core::NAME}</a>.*#{Intranet::Core::VERSION}.*</footer>}m # rubocop:disable Metrics/LineLength
261
+ %r{<footer>.*<a href='#{Intranet::Core::HOMEPAGE_URL}'.*>#{Intranet::Core::NAME}</a>.*#{Intranet::Core::VERSION}.*</footer>}m # rubocop:disable Layout/LineLength
304
262
  )
305
263
 
306
264
  # Returned HTML document: includes all registered modules version name, version and URL
@@ -320,7 +278,7 @@ RSpec.describe Intranet::Core do
320
278
  '/index.html' => [206, 'text/html', { content: 'PARTIAL_CONTENT', title: 'MyTitle' }]
321
279
  )
322
280
  other_responder = Intranet::TestResponder.new({}, [], [], true)
323
- @intranet.register_module(responder, [], responder.resources_dir)
281
+ @intranet.register_module(responder, %w[r], responder.resources_dir)
324
282
  @intranet.register_module(responder, %w[dep_th1], responder.resources_dir)
325
283
  @intranet.register_module(responder, %w[depth2 res_p1], responder.resources_dir)
326
284
  @intranet.register_module(responder, %w[depth2 resp2], responder.resources_dir)
@@ -335,7 +293,7 @@ RSpec.describe Intranet::Core do
335
293
  end
336
294
 
337
295
  socket = TCPSocket.new('localhost', @intranet.port)
338
- socket.puts("GET /index.html HTTP/1.1\r\nHost: localhost:#{@intranet.port}\r\n\r\n")
296
+ socket.puts("GET /r/index.html HTTP/1.1\r\nHost: localhost:#{@intranet.port}\r\n\r\n")
339
297
 
340
298
  # Return code: HTTP error 200
341
299
  expect(socket.gets).to include('HTTP/1.1 200 OK')
@@ -367,12 +325,47 @@ RSpec.describe Intranet::Core do
367
325
  end
368
326
  end
369
327
 
328
+ describe '#home_url=' do
329
+ context 'given a relative URL' do
330
+ it 'should fail' do
331
+ @intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
332
+ expect { @intranet.home_url = 'foo/index.html' }.to raise_error ArgumentError
333
+ end
334
+ end
335
+
336
+ context 'given an absolute URL' do
337
+ it 'should set up a redirection from /index.html to the provided URL' do
338
+ begin
339
+ @intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
340
+ @intranet.home_url = '/responder/index.html'
341
+ thread = Thread.new do
342
+ @intranet.start
343
+ end
344
+ while @intranet.instance_variable_get(:@server).status != :Running
345
+ end
346
+
347
+ socket = TCPSocket.new('localhost', @intranet.port)
348
+ socket.puts("GET /index.html HTTP/1.1\r\nHost: localhost:#{@intranet.port}\r\n\r\n")
349
+ expect(socket.gets).to include('HTTP/1.1 307 Temporary Redirect')
350
+ while (line = socket.gets.chomp) # search the HTTP response for the 'Location' header
351
+ break if line.start_with?('Location:')
352
+ end
353
+ expect(line).to include("http://localhost:#{@intranet.port}/responder/index.html")
354
+ ensure
355
+ socket.close
356
+ Thread.kill(thread)
357
+ thread.join
358
+ end
359
+ end
360
+ end
361
+ end
362
+
370
363
  describe '#stop' do
371
364
  it 'should stop the web server and finalize all registered responders' do
372
365
  begin
373
366
  @intranet = described_class.new(Intranet::Logger.new(Intranet::Logger::FATAL))
374
367
  responder = Intranet::TestResponder.new('/index.html' => [200, 'text/html', 'CONTENT'])
375
- @intranet.register_module(responder, [], responder.resources_dir)
368
+ @intranet.register_module(responder, %w[r], responder.resources_dir)
376
369
  thread = Thread.new do
377
370
  @intranet.start
378
371
  end
@@ -380,7 +373,7 @@ RSpec.describe Intranet::Core do
380
373
  end
381
374
 
382
375
  socket = TCPSocket.new('localhost', @intranet.port)
383
- socket.puts("GET /index.html HTTP/1.1\r\nHost: localhost:#{@intranet.port}\r\n\r\n")
376
+ socket.puts("GET /r/index.html HTTP/1.1\r\nHost: localhost:#{@intranet.port}\r\n\r\n")
384
377
  expect(socket.gets).to include('HTTP/1.1 200 OK')
385
378
  expect(responder.finalized).to be false
386
379
  socket.close
@@ -38,12 +38,12 @@ module Intranet
38
38
  end
39
39
 
40
40
  def resources_dir
41
- __dir__
41
+ super
42
42
  end
43
43
 
44
44
  def generate_page(path, query)
45
- if path == '/query'
46
- [200, 'text/plain', query.to_s + "\r\n"]
45
+ if path.start_with?('/query')
46
+ [200, 'text/plain', dump_with_encoding(path, query)]
47
47
  else
48
48
  @responses.fetch(path)
49
49
  end
@@ -58,5 +58,13 @@ module Intranet
58
58
  def js_dependencies
59
59
  super + @extra_js
60
60
  end
61
+
62
+ private
63
+
64
+ def dump_with_encoding(path, query)
65
+ "PATH=#{path} (#{path.encoding}), QUERY={" +
66
+ query.map { |k, v| "#{k} (#{k.encoding}) => #{v} (#{v.encoding})" }.join(',') +
67
+ "}\r\n"
68
+ end
61
69
  end
62
70
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: intranet-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 2.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ebling Mis
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-02-03 00:00:00.000000000 Z
11
+ date: 2020-06-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: haml
@@ -114,14 +114,14 @@ dependencies:
114
114
  requirements:
115
115
  - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: 0.63.0
117
+ version: 0.81.0
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: 0.63.0
124
+ version: 0.81.0
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: simplecov
127
127
  requirement: !ruby/object:Gem::Requirement
@@ -189,7 +189,6 @@ files:
189
189
  - spec/intranet/logger_spec.rb
190
190
  - spec/spec_helper.rb
191
191
  - spec/test_responder/responder.rb
192
- - spec/test_responder/www/style.css
193
192
  homepage: https://rubygems.org/gems/intranet-core
194
193
  licenses:
195
194
  - MIT
@@ -211,7 +210,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
211
210
  version: '0'
212
211
  requirements: []
213
212
  rubyforge_project:
214
- rubygems_version: 2.5.2.1
213
+ rubygems_version: 2.7.6.2
215
214
  signing_key:
216
215
  specification_version: 4
217
216
  summary: Core component to build a custom intranet.
@@ -1,5 +0,0 @@
1
- /**
2
- * test_responder/www/style.css
3
- * Design for the Test Responder of the Intranet-Core.
4
- */
5
-