diff --git a/openlp/plugins/remotes/lib/httprouter.py b/openlp/plugins/remotes/lib/httprouter.py index 000d743cd..d2acc4ddf 100644 --- a/openlp/plugins/remotes/lib/httprouter.py +++ b/openlp/plugins/remotes/lib/httprouter.py @@ -150,6 +150,7 @@ class HttpRouter(RegistryProperties): self.routes = [ ('^/$', {'function': self.serve_file, 'secure': False}), ('^/(stage)$', {'function': self.serve_file, 'secure': False}), + ('^/(stage)/(.*)$', {'function': self.stages, 'secure': False}), ('^/(main)$', {'function': self.serve_file, 'secure': False}), (r'^/files/(.*)$', {'function': self.serve_file, 'secure': False}), (r'^/(\w+)/thumbnails([^/]+)?/(.*)$', {'function': self.serve_thumbnail, 'secure': False}), @@ -170,6 +171,7 @@ class HttpRouter(RegistryProperties): self.settings_section = 'remotes' self.translate() self.html_dir = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir), 'remotes', 'html') + self.config_dir = os.path.join(AppLocation.get_data_path(), 'stages') def do_post_processor(self): """ @@ -340,24 +342,32 @@ class HttpRouter(RegistryProperties): 'settings': translate('RemotePlugin.Mobile', 'Settings'), } - def serve_file(self, file_name=None): + def stages(self, url_path, file_name): """ - Send a file to the socket. For now, just a subset of file types and must be top level inside the html folder. - If subfolders requested return 404, easier for security for the present. + Allow Stage view to be delivered with custom views. - Ultimately for i18n, this could first look for xx/file.html before falling back to file.html. - where xx is the language, e.g. 'en' + :param url_path: base path of the URL. Not used but passed by caller + :param file_name: file name with path + :return: """ log.debug('serve file request %s' % file_name) - if not file_name: - file_name = 'index.html' - elif file_name == 'stage': - file_name = 'stage.html' - elif file_name == 'main': - file_name = 'main.html' - path = os.path.normpath(os.path.join(self.html_dir, file_name)) - if not path.startswith(self.html_dir): + parts = file_name.split('/') + if len(parts) == 1: + file_name = os.path.join(parts[0], 'stage.html') + elif len(parts) == 3: + file_name = os.path.join(parts[1], parts[2]) + path = os.path.normpath(os.path.join(self.config_dir, file_name)) + if not path.startswith(self.config_dir): return self.do_not_found() + return self._process_file(path) + + def _process_file(self, path): + """ + Common file processing code + + :param path: path to file to be loaded + :return: web resource to be loaded + """ content = None ext, content_type = self.get_content_type(path) file_handle = None @@ -380,10 +390,32 @@ class HttpRouter(RegistryProperties): self.end_headers() return content + def serve_file(self, file_name=None): + """ + Send a file to the socket. For now, just a subset of file types and must be top level inside the html folder. + If subfolders requested return 404, easier for security for the present. + + Ultimately for i18n, this could first look for xx/file.html before falling back to file.html. + where xx is the language, e.g. 'en' + """ + log.debug('serve file request %s' % file_name) + if not file_name: + file_name = 'index.html' + elif file_name == 'stage': + file_name = 'stage.html' + elif file_name == 'main': + file_name = 'main.html' + path = os.path.normpath(os.path.join(self.html_dir, file_name)) + if not path.startswith(self.html_dir): + return self.do_not_found() + return self._process_file(path) + def get_content_type(self, file_name): """ Examines the extension of the file and determines what the content_type should be, defaults to text/plain Returns the extension and the content_type + + :param file_name: name of file """ ext = os.path.splitext(file_name)[1] content_type = FILE_TYPES.get(ext, 'text/plain') @@ -392,6 +424,10 @@ class HttpRouter(RegistryProperties): def serve_thumbnail(self, controller_name=None, dimensions=None, file_name=None): """ Serve an image file. If not found return 404. + + :param file_name: file name to be served + :param dimensions: image size + :param controller_name: controller to be called """ log.debug('serve thumbnail %s/thumbnails%s/%s' % (controller_name, dimensions, file_name)) supported_controllers = ['presentations', 'images'] @@ -496,6 +532,8 @@ class HttpRouter(RegistryProperties): def controller_text(self, var): """ Perform an action on the slide controller. + + :param var: variable - not used """ log.debug("controller_text var = %s" % var) current_item = self.live_controller.service_item @@ -629,6 +667,8 @@ class HttpRouter(RegistryProperties): def go_live(self, plugin_name): """ Go live on an item of type ``plugin``. + + :param plugin_name: name of plugin """ try: request_id = json.loads(self.request_data)['request']['id'] @@ -642,6 +682,8 @@ class HttpRouter(RegistryProperties): def add_to_service(self, plugin_name): """ Add item of type ``plugin_name`` to the end of the service. + + :param plugin_name: name of plugin to be called """ try: request_id = json.loads(self.request_data)['request']['id'] diff --git a/openlp/plugins/remotes/lib/httpserver.py b/openlp/plugins/remotes/lib/httpserver.py index fa1348314..958088b00 100644 --- a/openlp/plugins/remotes/lib/httpserver.py +++ b/openlp/plugins/remotes/lib/httpserver.py @@ -167,7 +167,7 @@ class HTTPSServer(HTTPServer): local_data = AppLocation.get_directory(AppLocation.DataDir) self.socket = ssl.SSLSocket( sock=socket.socket(self.address_family, self.socket_type), - ssl_version=ssl.PROTOCOL_TLSv1, + ssl_version=ssl.PROTOCOL_TLSv1_2, certfile=os.path.join(local_data, 'remotes', 'openlp.crt'), keyfile=os.path.join(local_data, 'remotes', 'openlp.key'), server_side=True) diff --git a/tests/functional/openlp_plugins/remotes/test_router.py b/tests/functional/openlp_plugins/remotes/test_router.py index e62ee9c14..824303a8b 100644 --- a/tests/functional/openlp_plugins/remotes/test_router.py +++ b/tests/functional/openlp_plugins/remotes/test_router.py @@ -342,3 +342,39 @@ class TestRouter(TestCase, TestMixin): # THEN: service_manager.next_item() should have been called self.assertTrue(mocked_previous_item.called, 'previous_item() should have been called in service_manager') + + def remote_stage_personal_html_test(self): + """ + Test the stage url with a parameter after loaded a url/stage.html file + """ + # GIVEN: initial route + self.router.config_dir = '' + self.router.send_response = MagicMock() + self.router.send_header = MagicMock() + self.router.end_headers = MagicMock() + self.router.wfile = MagicMock() + self.router._process_file = MagicMock() + + # WHEN: I call stage with a suffix + self.router.stages('stages', 'trb') + + # THEN: we should use the specific stage file instance + self.router._process_file.assert_called_with(os.path.join('trb', 'stage.html')) + + def remote_stage_personal_css_test(self): + """ + Test the html with reference stages/trb/trb.css then loaded a stages/trb/trb.css file + """ + # GIVEN: initial route + self.router.config_dir = '' + self.router.send_response = MagicMock() + self.router.send_header = MagicMock() + self.router.end_headers = MagicMock() + self.router.wfile = MagicMock() + self.router._process_file = MagicMock() + + # WHEN: I call stage with a suffix + self.router.stages('stages', 'stages/trb/trb.css') + + # THEN: we should use the specific stage file instance + self.router._process_file.assert_called_with(os.path.join('trb', 'trb.css'))