diff --git a/gns3server/controller/symbols.py b/gns3server/controller/symbols.py index 5e787fa7..9549507d 100644 --- a/gns3server/controller/symbols.py +++ b/gns3server/controller/symbols.py @@ -40,9 +40,10 @@ class Symbols: 'symbol_id': symbol_id, 'filename': file, 'builtin': True, - 'url': '/static/builtin_symbols/' + file }) self._symbols_path[symbol_id] = os.path.join(get_resource("symbols"), file) + symbols.sort(key=lambda x: x["filename"]) + #TODO: support ~/GNS3/symbols directory return symbols diff --git a/gns3server/handlers/__init__.py b/gns3server/handlers/__init__.py index 51b9fc07..e918c0a8 100644 --- a/gns3server/handlers/__init__.py +++ b/gns3server/handlers/__init__.py @@ -16,7 +16,6 @@ from gns3server.handlers.index_handler import IndexHandler -from gns3server.handlers.static_handler import StaticHandler from gns3server.handlers.api.controller import * diff --git a/gns3server/handlers/api/controller/symbol_handler.py b/gns3server/handlers/api/controller/symbol_handler.py index 1f5ddc6f..7ede100f 100644 --- a/gns3server/handlers/api/controller/symbol_handler.py +++ b/gns3server/handlers/api/controller/symbol_handler.py @@ -36,3 +36,17 @@ class SymbolHandler: controller = Controller.instance() response.json(controller.symbols.list()) + + @Route.get( + r"/symbols/{symbol_id:.+}/raw", + description="Get the symbol file", + status_codes={ + 200: "Symbol returned" + }) + def raw(request, response): + + controller = Controller.instance() + try: + yield from response.file(controller.symbols.get_path(request.match_info["symbol_id"])) + except KeyError: + response.set_status(404) diff --git a/gns3server/handlers/static_handler.py b/gns3server/handlers/static_handler.py deleted file mode 100644 index c8e09b48..00000000 --- a/gns3server/handlers/static_handler.py +++ /dev/null @@ -1,66 +0,0 @@ -# -# Copyright (C) 2016 GNS3 Technologies Inc. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os -import asyncio -import mimetypes -from aiohttp import hdrs - -from gns3server.web.route import Route -from gns3server.utils.get_resource import get_resource - - -class StaticHandler: - - @Route.get( - r"/static/{type}/{path:.+}", - description="Serve static content from various locations" - ) - def get(request, response): - type = request.match_info["type"] - # CLeanup the path for security - path = os.path.normpath(request.match_info["path"]).strip('/.') - if type == "builtin_symbols": - try: - yield from StaticHandler._serve_file(os.path.join(get_resource("symbols"), path), request, response) - except OSError: - response.set_status(404) - - @asyncio.coroutine - def _serve_file(path, request, response): - ct, encoding = mimetypes.guess_type(path) - if not ct: - ct = 'application/octet-stream' - if encoding: - response.headers[hdrs.CONTENT_ENCODING] = encoding - response.content_type = ct - - st = os.stat(path) - response.last_modified = st.st_mtime - response.content_length = st.st_size - - with open(path, 'rb') as fobj: - response.start(request) - chunk_size = 4096 - chunk = fobj.read(chunk_size) - while chunk: - response.write(chunk) - yield from response.drain() - chunk = fobj.read(chunk_size) - - if chunk: - response.write(chunk[:count]) - yield from response.drain() diff --git a/gns3server/web/response.py b/gns3server/web/response.py index 5d5a11ed..c057c6a9 100644 --- a/gns3server/web/response.py +++ b/gns3server/web/response.py @@ -17,11 +17,14 @@ import json import jsonschema +import aiohttp import aiohttp.web +import mimetypes import asyncio import logging -import sys import jinja2 +import sys +import os from ..utils.get_resource import get_resource from ..version import __version__ @@ -102,6 +105,35 @@ class Response(aiohttp.web.Response): raise aiohttp.web.HTTPBadRequest(text="{}".format(e)) self.body = json.dumps(answer, indent=4, sort_keys=True).encode('utf-8') + @asyncio.coroutine + def file(self, path): + """ + Return a file as a response + """ + ct, encoding = mimetypes.guess_type(path) + if not ct: + ct = 'application/octet-stream' + if encoding: + self.headers[aiohttp.hdrs.CONTENT_ENCODING] = encoding + self.content_type = ct + + st = os.stat(path) + self.last_modified = st.st_mtime + self.content_length = st.st_size + + with open(path, 'rb') as fobj: + self.start(self._request) + chunk_size = 4096 + chunk = fobj.read(chunk_size) + while chunk: + self.write(chunk) + yield from self.drain() + chunk = fobj.read(chunk_size) + + if chunk: + self.write(chunk[:count]) + yield from self.drain() + def redirect(self, url): """ Redirect to url diff --git a/tests/controller/test_symbols.py b/tests/controller/test_symbols.py index 94ef315d..563d0441 100644 --- a/tests/controller/test_symbols.py +++ b/tests/controller/test_symbols.py @@ -26,8 +26,12 @@ def test_list(): symbols = Symbols() assert { 'symbol_id': ':/symbols/firewall.svg', - 'url': '/static/builtin_symbols/firewall.svg', 'filename': 'firewall.svg', 'builtin': True } in symbols.list() assert symbols + + +def test_get_path(): + symbols = Symbols() + assert symbols.get_path(':/symbols/firewall.svg') == get_resource("symbols/firewall.svg") diff --git a/tests/handlers/api/controller/test_symbol.py b/tests/handlers/api/controller/test_symbol.py index 23c8d483..04483887 100644 --- a/tests/handlers/api/controller/test_symbol.py +++ b/tests/handlers/api/controller/test_symbol.py @@ -15,6 +15,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import urllib.parse + from gns3server.config import Config @@ -23,7 +25,17 @@ def test_symbols(http_controller): assert response.status == 200 assert { 'symbol_id': ':/symbols/firewall.svg', - 'url': '/static/builtin_symbols/firewall.svg', 'filename': 'firewall.svg', 'builtin': True } in response.json + + +def test_get(http_controller): + response = http_controller.get('/symbols/' + urllib.parse.quote(':/symbols/firewall.svg') + '/raw') + assert response.status == 200 + assert response.headers['CONTENT-LENGTH'] == '9381' + assert response.headers['CONTENT-TYPE'] == 'image/svg+xml' + assert '' in response.html + + response = http_controller.get('/symbols/404.png/raw') + assert response.status == 404 diff --git a/tests/handlers/test_static.py b/tests/handlers/test_static.py deleted file mode 100644 index 4ae8746f..00000000 --- a/tests/handlers/test_static.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2015 GNS3 Technologies Inc. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -import aiohttp -import os -from unittest.mock import patch - - -def test_get(http_root): - response = http_root.get('/static/builtin_symbols/firewall.svg') - assert response.status == 200 - assert response.headers['CONTENT-LENGTH'] == '9381' - assert response.headers['CONTENT-TYPE'] == 'image/svg+xml' - assert '' in response.html - - response = http_root.get('/static/builtin_symbols/../main.py') - assert response.status == 404 - - response = http_root.get('/static/builtin_symbols/404.png') - assert response.status == 404