Browse Source

Merge pull request #1023 from zauberzeug/base64-images

avoid treating base64 data as filename
Rodja Trappe 1 year ago
parent
commit
3e39a4decb

+ 3 - 2
nicegui/api_router.py

@@ -1,4 +1,5 @@
-from typing import Callable, Optional
+from pathlib import Path
+from typing import Callable, Optional, Union
 
 import fastapi
 
@@ -11,7 +12,7 @@ class APIRouter(fastapi.APIRouter):
              path: str, *,
              title: Optional[str] = None,
              viewport: Optional[str] = None,
-             favicon: Optional[str] = None,
+             favicon: Optional[Union[str, Path]] = None,
              dark: Optional[bool] = ...,
              response_timeout: float = 3.0,
              **kwargs,

+ 2 - 1
nicegui/elements/mixins/source_element.py

@@ -6,6 +6,7 @@ from typing_extensions import Self
 from ... import globals
 from ...binding import BindableProperty, bind, bind_from, bind_to
 from ...element import Element
+from ...helpers import is_file
 
 
 class SourceElement(Element):
@@ -13,7 +14,7 @@ class SourceElement(Element):
 
     def __init__(self, *, source: Union[str, Path], **kwargs: Any) -> None:
         super().__init__(**kwargs)
-        if Path(source).is_file():
+        if is_file(source):
             source = globals.app.add_static_file(local_file=source)
         self.source = source
         self._props['src'] = source

+ 17 - 13
nicegui/favicon.py

@@ -2,18 +2,19 @@ import base64
 import io
 import urllib.parse
 from pathlib import Path
-from typing import TYPE_CHECKING, Optional, Tuple
+from typing import TYPE_CHECKING, Optional, Tuple, Union
 
 from fastapi.responses import FileResponse, Response, StreamingResponse
 
 from . import __version__, globals
+from .helpers import is_file
 
 if TYPE_CHECKING:
     from .page import page
 
 
-def create_favicon_route(path: str, favicon: Optional[str]) -> None:
-    if favicon and Path(favicon).exists():
+def create_favicon_route(path: str, favicon: Optional[Union[str, Path]]) -> None:
+    if is_file(favicon):
         globals.app.add_route(f'{path}/favicon.ico', lambda _: FileResponse(favicon))
 
 
@@ -21,7 +22,7 @@ def get_favicon_url(page: 'page', prefix: str) -> str:
     favicon = page.favicon or globals.favicon
     if not favicon:
         return f'{prefix}/_nicegui/{__version__}/static/favicon.ico'
-    favicon = str(favicon)
+    favicon = str(favicon).strip()
     if is_remote_url(favicon):
         return favicon
     elif is_data_url(favicon):
@@ -37,15 +38,18 @@ def get_favicon_url(page: 'page', prefix: str) -> str:
 
 
 def get_favicon_response() -> Response:
-    if is_svg(globals.favicon):
-        return Response(globals.favicon, media_type='image/svg+xml')
-    elif is_data_url(globals.favicon):
-        media_type, bytes = data_url_to_bytes(globals.favicon)
+    if not globals.favicon:
+        raise ValueError(f'invalid favicon: {globals.favicon}')
+    favicon = str(globals.favicon).strip()
+    if is_svg(favicon):
+        return Response(favicon, media_type='image/svg+xml')
+    elif is_data_url(favicon):
+        media_type, bytes = data_url_to_bytes(favicon)
         return StreamingResponse(io.BytesIO(bytes), media_type=media_type)
-    elif is_char(globals.favicon):
-        return Response(char_to_svg(globals.favicon), media_type='image/svg+xml')
+    elif is_char(favicon):
+        return Response(char_to_svg(favicon), media_type='image/svg+xml')
     else:
-        raise ValueError(f'invalid favicon: {globals.favicon}')
+        raise ValueError(f'invalid favicon: {favicon}')
 
 
 def is_remote_url(favicon: str) -> bool:
@@ -89,6 +93,6 @@ def svg_to_data_url(svg: str) -> str:
 
 
 def data_url_to_bytes(data_url: str) -> Tuple[str, bytes]:
-    media_type, base64_image = data_url.split(",", 1)
-    media_type = media_type.split(":")[1].split(";")[0]
+    media_type, base64_image = data_url.split(',', 1)
+    media_type = media_type.split(':')[1].split(';')[0]
     return media_type, base64.b64decode(base64_image)

+ 2 - 1
nicegui/globals.py

@@ -3,6 +3,7 @@ import inspect
 import logging
 from contextlib import contextmanager
 from enum import Enum
+from pathlib import Path
 from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, Iterator, List, Optional, Union
 
 from socketio import AsyncServer
@@ -35,7 +36,7 @@ ui_run_has_been_called: bool = False
 reload: bool
 title: str
 viewport: str
-favicon: Optional[str]
+favicon: Optional[Union[str, Path]]
 dark: Optional[bool]
 language: Language
 binding_refresh_interval: float

+ 9 - 0
nicegui/helpers.py

@@ -38,6 +38,15 @@ def is_coroutine_function(object: Any) -> bool:
     return asyncio.iscoroutinefunction(object)
 
 
+def is_file(path: Optional[Union[str, Path]]) -> bool:
+    """Check if the path is a file that exists."""
+    if not path:
+        return False
+    elif isinstance(path, str) and path.strip().startswith('data:'):
+        return False  # NOTE: avoid passing data URLs to Path
+    return Path(path).is_file()
+
+
 def safe_invoke(func: Union[Callable[..., Any], Awaitable], client: Optional['Client'] = None) -> None:
     try:
         if isinstance(func, Awaitable):

+ 2 - 2
nicegui/nicegui.py

@@ -21,7 +21,7 @@ from .client import Client
 from .dependencies import js_components, js_dependencies
 from .element import Element
 from .error import error_content
-from .helpers import safe_invoke
+from .helpers import is_file, safe_invoke
 from .page import page
 
 globals.app = app = App(default_response_class=NiceGUIJSONResponse)
@@ -67,7 +67,7 @@ def handle_startup(with_welcome_message: bool = True) -> None:
                            '   if __name__ in {"__main__", "__mp_main__"}:\n'
                            'to allow for multiprocessing.')
     if globals.favicon:
-        if Path(globals.favicon).exists():
+        if is_file(globals.favicon):
             globals.app.add_route('/favicon.ico', lambda _: FileResponse(globals.favicon))
         else:
             globals.app.add_route('/favicon.ico', lambda _: favicon.get_favicon_response())

+ 3 - 2
nicegui/page.py

@@ -1,7 +1,8 @@
 import asyncio
 import inspect
 import time
-from typing import TYPE_CHECKING, Any, Callable, Optional
+from pathlib import Path
+from typing import TYPE_CHECKING, Any, Callable, Optional, Union
 
 from fastapi import Request, Response
 
@@ -20,7 +21,7 @@ class page:
                  path: str, *,
                  title: Optional[str] = None,
                  viewport: Optional[str] = None,
-                 favicon: Optional[str] = None,
+                 favicon: Optional[Union[str, Path]] = None,
                  dark: Optional[bool] = ...,
                  language: Language = ...,
                  response_timeout: float = 3.0,

+ 3 - 2
nicegui/run.py

@@ -3,7 +3,8 @@ import multiprocessing
 import os
 import socket
 import sys
-from typing import Any, List, Optional, Tuple
+from pathlib import Path
+from typing import Any, List, Optional, Tuple, Union
 
 import __main__
 import uvicorn
@@ -34,7 +35,7 @@ def run(*,
         port: int = 8080,
         title: str = 'NiceGUI',
         viewport: str = 'width=device-width, initial-scale=1',
-        favicon: Optional[str] = None,
+        favicon: Optional[Union[str, Path]] = None,
         dark: Optional[bool] = False,
         language: Language = 'en-US',
         binding_refresh_interval: float = 0.1,

+ 3 - 2
nicegui/run_with.py

@@ -1,4 +1,5 @@
-from typing import Optional
+from pathlib import Path
+from typing import Optional, Union
 
 from fastapi import FastAPI
 
@@ -12,7 +13,7 @@ def run_with(
     app: FastAPI, *,
     title: str = 'NiceGUI',
     viewport: str = 'width=device-width, initial-scale=1',
-    favicon: Optional[str] = None,
+    favicon: Optional[Union[str, Path]] = None,
     dark: Optional[bool] = False,
     language: Language = 'en-US',
     binding_refresh_interval: float = 0.1,