fix: strip XML prolog from the text/html fallback body
This commit is contained in:
parent
3d9239b7a5
commit
12956a5034
3 changed files with 40 additions and 2 deletions
|
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
||||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||||
from urllib.parse import parse_qs, urlsplit
|
from urllib.parse import parse_qs, urlsplit
|
||||||
|
|
||||||
from ..negotiate import FORMAT_WML, content_type_for, negotiate
|
from ..negotiate import FORMAT_HTML, FORMAT_WML, content_type_for, negotiate
|
||||||
from ..site import NotFound, RawFile, Redirect, Site, WmlCard
|
from ..site import NotFound, RawFile, Redirect, Site, WmlCard
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -55,7 +55,12 @@ class SmolwebHTTPHandler(BaseHTTPRequestHandler):
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
body = resource.wml_index if fmt == FORMAT_WML else resource.xhtml
|
if fmt == FORMAT_WML:
|
||||||
|
body = resource.wml_index
|
||||||
|
elif fmt == FORMAT_HTML:
|
||||||
|
body = resource.html
|
||||||
|
else:
|
||||||
|
body = resource.xhtml
|
||||||
self._send(
|
self._send(
|
||||||
200,
|
200,
|
||||||
content_type_for(fmt),
|
content_type_for(fmt),
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import mimetypes
|
import mimetypes
|
||||||
import posixpath
|
import posixpath
|
||||||
|
import re
|
||||||
import threading
|
import threading
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
@ -25,6 +26,15 @@ from wapdown.conversion.core import strip_frontmatter as wapdown_strip_frontmatt
|
||||||
from wapdown.parsers.markdown import CARD_BREAK_PATTERN
|
from wapdown.parsers.markdown import CARD_BREAK_PATTERN
|
||||||
|
|
||||||
|
|
||||||
|
# XHTML-MP's leading `<?xml ...?>` processing instruction is valid XML but
|
||||||
|
# not valid HTML5: a browser parsing it as text/html (the fallback for a
|
||||||
|
# client that didn't ask for application/vnd.wap.xhtml+xml) tolerates it as
|
||||||
|
# a bogus comment and still renders the page, but logs a console warning.
|
||||||
|
# Stripped only for that fallback -- real XHTML-MP and WAP 2.0 clients get
|
||||||
|
# the untouched, fully valid XML document.
|
||||||
|
_XML_PROLOG_RE = re.compile(rb"^<\?xml[^>]*\?>\r?\n?")
|
||||||
|
|
||||||
|
|
||||||
class NotFound(Exception):
|
class NotFound(Exception):
|
||||||
"""No resource exists at the requested path."""
|
"""No resource exists at the requested path."""
|
||||||
|
|
||||||
|
|
@ -48,6 +58,7 @@ class Document:
|
||||||
|
|
||||||
gemtext: bytes
|
gemtext: bytes
|
||||||
xhtml: bytes
|
xhtml: bytes
|
||||||
|
html: bytes # xhtml with the XML prolog stripped, for the text/html fallback
|
||||||
wml_index: bytes # the deck served at the document's own URL
|
wml_index: bytes # the deck served at the document's own URL
|
||||||
wml_cards: Dict[str, bytes] = field(default_factory=dict) # deck_per_card only
|
wml_cards: Dict[str, bytes] = field(default_factory=dict) # deck_per_card only
|
||||||
cache_control: Optional[int] = None
|
cache_control: Optional[int] = None
|
||||||
|
|
@ -235,6 +246,7 @@ class Site:
|
||||||
xhtml = wapdown_convert(
|
xhtml = wapdown_convert(
|
||||||
wap_body, config=config, renderer_name="xhtmlmp", base_path=base_path
|
wap_body, config=config, renderer_name="xhtmlmp", base_path=base_path
|
||||||
).encode("utf-8")
|
).encode("utf-8")
|
||||||
|
html = _XML_PROLOG_RE.sub(b"", xhtml, count=1)
|
||||||
|
|
||||||
wml_output = wapdown_convert(
|
wml_output = wapdown_convert(
|
||||||
wap_body, config=config, renderer_name="wml", base_path=base_path
|
wap_body, config=config, renderer_name="wml", base_path=base_path
|
||||||
|
|
@ -244,6 +256,7 @@ class Site:
|
||||||
return Document(
|
return Document(
|
||||||
gemtext=gemtext,
|
gemtext=gemtext,
|
||||||
xhtml=xhtml,
|
xhtml=xhtml,
|
||||||
|
html=html,
|
||||||
wml_index=wml_index,
|
wml_index=wml_index,
|
||||||
wml_cards=wml_cards,
|
wml_cards=wml_cards,
|
||||||
cache_control=config.cache_control,
|
cache_control=config.cache_control,
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,26 @@ class TestWmlCardUrls:
|
||||||
assert b"{.card" not in doc.xhtml
|
assert b"{.card" not in doc.xhtml
|
||||||
|
|
||||||
|
|
||||||
|
class TestHtmlFallbackHasNoXmlProlog:
|
||||||
|
def test_html_field_has_no_prolog(self, site: Site):
|
||||||
|
# A browser parsing this as text/html (rather than XML) tolerates
|
||||||
|
# a leading <?xml ...?> as a bogus comment and still renders the
|
||||||
|
# page, but logs a console warning -- so the text/html fallback
|
||||||
|
# must not carry it.
|
||||||
|
doc = site.resolve("/")
|
||||||
|
assert not doc.html.startswith(b"<?xml")
|
||||||
|
assert doc.html.startswith(b"<!DOCTYPE html")
|
||||||
|
|
||||||
|
def test_xhtml_field_keeps_the_prolog(self, site: Site):
|
||||||
|
# Real XHTML-MP/WAP 2.0 clients still need well-formed XML.
|
||||||
|
doc = site.resolve("/")
|
||||||
|
assert doc.xhtml.startswith(b'<?xml version="1.0"')
|
||||||
|
|
||||||
|
def test_html_and_xhtml_are_otherwise_identical(self, site: Site):
|
||||||
|
doc = site.resolve("/")
|
||||||
|
assert doc.html == doc.xhtml[doc.xhtml.index(b"<!DOCTYPE"):]
|
||||||
|
|
||||||
|
|
||||||
class TestLiveReload:
|
class TestLiveReload:
|
||||||
def test_edited_file_is_rerendered(self, tmp_path: Path):
|
def test_edited_file_is_rerendered(self, tmp_path: Path):
|
||||||
target = tmp_path / "index.md"
|
target = tmp_path / "index.md"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue