mirror of
https://github.com/pablorevilla-meshtastic/meshview.git
synced 2026-10-31 03:01:58 +01:00
Keep /api/lang and the static page route inside their directories (#173)
* Update CONTRIBUTING.md
* Update CONTRIBUTING.md
* Keep /api/lang and the static page route inside their directories
Two routes built a filename from request input without checking where it
ended up:
- /api/lang?lang=... joined the value onto the lang directory and added
".json". os.path.join discards the directory when the value is an
absolute path, so ?lang=/some/where/file returned /some/where/file.json.
Any .json file the server process can read was served.
- /{page} appended ".html" and read static/<page>. aiohttp passes an
encoded slash (%2F) through a single path segment and unquotes it, so
/..%2F..%2F..%2Fx read ../../../x.html relative to the static directory.
The lang route now only accepts a plain language code (en, es, pt-BR, ...)
and falls back to English otherwise, exactly as it already did for unknown
languages. The page route resolves the final path and requires it to be a
file inside meshview/static, the same check /docs/{doc} already does.
Co-Authored-By: Claude Mythos 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Pablo Revilla <pablorevilla@gmail.com>
Co-authored-by: Nathan <nathan@stonekitty.net>
Co-authored-by: Claude Mythos 5.1 <noreply@anthropic.com>
This commit is contained in:
2 files changed
+13
-2
No files matched your search
+5
-2
@@ -241,8 +241,11 @@ async def serve_page(request):
|
||||
if not page.endswith(".html"):
|
||||
page = f"{page}.html"
|
||||
|
||||
html_file = pathlib.Path(__file__).parent / "static" / page
|
||||
if not html_file.exists():
|
||||
# aiohttp lets an encoded slash (%2F) through a single path segment, so resolve the
|
||||
# final path and make sure it is still inside the static directory.
|
||||
static_root = (pathlib.Path(__file__).parent / "static").resolve()
|
||||
html_file = (static_root / page).resolve()
|
||||
if static_root not in html_file.parents or not html_file.is_file():
|
||||
raise web.HTTPNotFound(text=f"Page '{page}' not found")
|
||||
|
||||
content = html_file.read_text(encoding="utf-8")
|
||||
|
||||
@@ -5,6 +5,7 @@ import json
|
||||
import logging
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
|
||||
from aiohttp import web
|
||||
from sqlalchemy import func, select
|
||||
@@ -37,6 +38,9 @@ _LANG_CACHE = {}
|
||||
# Create dedicated route table for API endpoints
|
||||
routes = web.RouteTableDef()
|
||||
|
||||
# Language codes are short identifiers such as "en", "es", "pt-BR".
|
||||
LANG_CODE_RE = re.compile(r"^[A-Za-z]{2,3}(?:[-_][A-Za-z0-9]{2,8})?$")
|
||||
|
||||
|
||||
def _config_bool(section: str, key: str, default: bool = False) -> bool:
|
||||
return str(CONFIG.get(section, {}).get(key, default)).lower() in ("1", "true", "yes", "on")
|
||||
@@ -779,6 +783,10 @@ async def api_lang(request):
|
||||
lang_code = request.query.get("lang") or CONFIG.get("site", {}).get("language", "en")
|
||||
section = request.query.get("section")
|
||||
|
||||
# The code names a file, so it must stay a plain language code. Anything else (a path,
|
||||
# an absolute filename) falls back to English just like an unknown language does.
|
||||
if not LANG_CODE_RE.match(lang_code):
|
||||
lang_code = "en"
|
||||
lang_file = os.path.join(LANG_DIR, f"{lang_code}.json")
|
||||
if not os.path.exists(lang_file):
|
||||
lang_file = os.path.join(LANG_DIR, "en.json")
|
||||
|
||||
Reference in new issue
Block a user