1# Copyright (c) Twisted Matrix Laboratories.
2# See LICENSE for details.
3
4"""
5twisted.web.util and twisted.web.template merged to avoid cyclic deps
6"""
7
8import io
9import linecache
10import warnings
11from collections import OrderedDict
12from html import escape
13from typing import (
14    IO,
15    Any,
16    AnyStr,
17    Callable,
18    Dict,
19    List,
20    Mapping,
21    Optional,
22    Tuple,
23    Union,
24    cast,
25)
26from xml.sax import handler, make_parser
27from xml.sax.xmlreader import Locator
28
29from zope.interface import implementer
30
31from twisted.internet.defer import Deferred
32from twisted.logger import Logger
33from twisted.python import urlpath
34from twisted.python.failure import Failure
35from twisted.python.filepath import FilePath
36from twisted.python.reflect import fullyQualifiedName
37from twisted.web import resource
38from twisted.web._element import Element, renderer
39from twisted.web._flatten import Flattenable, flatten, flattenString
40from twisted.web._stan import CDATA, Comment, Tag, slot
41from twisted.web.iweb import IRenderable, IRequest, ITemplateLoader
42
43
44def _PRE(text):
45    """
46    Wraps <pre> tags around some text and HTML-escape it.
47
48    This is here since once twisted.web.html was deprecated it was hard to
49    migrate the html.PRE from current code to twisted.web.template.
50
51    For new code consider using twisted.web.template.
52
53    @return: Escaped text wrapped in <pre> tags.
54    @rtype: C{str}
55    """
56    return f"<pre>{escape(text)}</pre>"
57
58
59def redirectTo(URL: bytes, request: IRequest) -> bytes:
60    """
61    Generate a redirect to the given location.
62
63    @param URL: A L{bytes} giving the location to which to redirect.
64
65    @param request: The request object to use to generate the redirect.
66    @type request: L{IRequest<twisted.web.iweb.IRequest>} provider
67
68    @raise TypeError: If the type of C{URL} a L{str} instead of L{bytes}.
69
70    @return: A L{bytes} containing HTML which tries to convince the client
71        agent
72        to visit the new location even if it doesn't respect the I{FOUND}
73        response code.  This is intended to be returned from a render method,
74        eg::
75
76            def render_GET(self, request):
77                return redirectTo(b"http://example.com/", request)
78    """
79    if not isinstance(URL, bytes):
80        raise TypeError("URL must be bytes")
81    request.setHeader(b"Content-Type", b"text/html; charset=utf-8")
82    request.redirect(URL)
83    # FIXME: The URL should be HTML-escaped.
84    # https://twistedmatrix.com/trac/ticket/9839
85    content = b"""
86<html>
87    <head>
88        <meta http-equiv=\"refresh\" content=\"0;URL=%(url)s\">
89    </head>
90    <body bgcolor=\"#FFFFFF\" text=\"#000000\">
91    <a href=\"%(url)s\">click here</a>
92    </body>
93</html>
94""" % {
95        b"url": URL
96    }
97    return content
98
99
100class Redirect(resource.Resource):
101    """
102    Resource that redirects to a specific URL.
103
104    @ivar url: Redirect target URL to put in the I{Location} response header.
105    @type url: L{bytes}
106    """
107
108    isLeaf = True
109
110    def __init__(self, url: bytes):
111        super().__init__()
112        self.url = url
113
114    def render(self, request):
115        return redirectTo(self.url, request)
116
117    def getChild(self, name, request):
118        return self
119
120
121# FIXME: This is totally broken, see https://twistedmatrix.com/trac/ticket/9838
122class ChildRedirector(Redirect):
123    isLeaf = False
124
125    def __init__(self, url):
126        # XXX is this enough?
127        if (
128            (url.find("://") == -1)
129            and (not url.startswith(".."))
130            and (not url.startswith("/"))
131        ):
132            raise ValueError(
133                (
134                    "It seems you've given me a redirect (%s) that is a child of"
135                    " myself! That's not good, it'll cause an infinite redirect."
136                )
137                % url
138            )
139        Redirect.__init__(self, url)
140
141    def getChild(self, name, request):
142        newUrl = self.url
143        if not newUrl.endswith("/"):
144            newUrl += "/"
145        newUrl += name
146        return ChildRedirector(newUrl)
147
148
149class ParentRedirect(resource.Resource):
150    """
151    Redirect to the nearest directory and strip any query string.
152
153    This generates redirects like::
154
155        /              \u2192  /
156        /foo           \u2192  /
157        /foo?bar       \u2192  /
158        /foo/          \u2192  /foo/
159        /foo/bar       \u2192  /foo/
160        /foo/bar?baz   \u2192  /foo/
161
162    However, the generated I{Location} header contains an absolute URL rather
163    than a path.
164
165    The response is the same regardless of HTTP method.
166    """
167
168    isLeaf = 1
169
170    def render(self, request: IRequest) -> bytes:
171        """
172        Respond to all requests by redirecting to nearest directory.
173        """
174        here = str(urlpath.URLPath.fromRequest(request).here()).encode("ascii")
175        return redirectTo(here, request)
176
177
178class DeferredResource(resource.Resource):
179    """
180    I wrap up a Deferred that will eventually result in a Resource
181    object.
182    """
183
184    isLeaf = 1
185
186    def __init__(self, d):
187        resource.Resource.__init__(self)
188        self.d = d
189
190    def getChild(self, name, request):
191        return self
192
193    def render(self, request):
194        self.d.addCallback(self._cbChild, request).addErrback(self._ebChild, request)
195        from twisted.web.server import NOT_DONE_YET
196
197        return NOT_DONE_YET
198
199    def _cbChild(self, child, request):
200        request.render(resource.getChildForRequest(child, request))
201
202    def _ebChild(self, reason, request):
203        request.processingFailed(reason)
204
205
206class _SourceLineElement(Element):
207    """
208    L{_SourceLineElement} is an L{IRenderable} which can render a single line of
209    source code.
210
211    @ivar number: A C{int} giving the line number of the source code to be
212        rendered.
213    @ivar source: A C{str} giving the source code to be rendered.
214    """
215
216    def __init__(self, loader, number, source):
217        Element.__init__(self, loader)
218        self.number = number
219        self.source = source
220
221    @renderer
222    def sourceLine(self, request, tag):
223        """
224        Render the line of source as a child of C{tag}.
225        """
226        return tag(self.source.replace("  ", " \N{NO-BREAK SPACE}"))
227
228    @renderer
229    def lineNumber(self, request, tag):
230        """
231        Render the line number as a child of C{tag}.
232        """
233        return tag(str(self.number))
234
235
236class _SourceFragmentElement(Element):
237    """
238    L{_SourceFragmentElement} is an L{IRenderable} which can render several lines
239    of source code near the line number of a particular frame object.
240
241    @ivar frame: A L{Failure<twisted.python.failure.Failure>}-style frame object
242        for which to load a source line to render.  This is really a tuple
243        holding some information from a frame object.  See
244        L{Failure.frames<twisted.python.failure.Failure>} for specifics.
245    """
246
247    def __init__(self, loader, frame):
248        Element.__init__(self, loader)
249        self.frame = frame
250
251    def _getSourceLines(self):
252        """
253        Find the source line references by C{self.frame} and yield, in source
254        line order, it and the previous and following lines.
255
256        @return: A generator which yields two-tuples.  Each tuple gives a source
257            line number and the contents of that source line.
258        """
259        filename = self.frame[1]
260        lineNumber = self.frame[2]
261        for snipLineNumber in range(lineNumber - 1, lineNumber + 2):
262            yield (snipLineNumber, linecache.getline(filename, snipLineNumber).rstrip())
263
264    @renderer
265    def sourceLines(self, request, tag):
266        """
267        Render the source line indicated by C{self.frame} and several
268        surrounding lines.  The active line will be given a I{class} of
269        C{"snippetHighlightLine"}.  Other lines will be given a I{class} of
270        C{"snippetLine"}.
271        """
272        for (lineNumber, sourceLine) in self._getSourceLines():
273            newTag = tag.clone()
274            if lineNumber == self.frame[2]:
275                cssClass = "snippetHighlightLine"
276            else:
277                cssClass = "snippetLine"
278            loader = TagLoader(newTag(**{"class": cssClass}))
279            yield _SourceLineElement(loader, lineNumber, sourceLine)
280
281
282class _FrameElement(Element):
283    """
284    L{_FrameElement} is an L{IRenderable} which can render details about one
285    frame from a L{Failure<twisted.python.failure.Failure>}.
286
287    @ivar frame: A L{Failure<twisted.python.failure.Failure>}-style frame object
288        for which to load a source line to render.  This is really a tuple
289        holding some information from a frame object.  See
290        L{Failure.frames<twisted.python.failure.Failure>} for specifics.
291    """
292
293    def __init__(self, loader, frame):
294        Element.__init__(self, loader)
295        self.frame = frame
296
297    @renderer
298    def filename(self, request, tag):
299        """
300        Render the name of the file this frame references as a child of C{tag}.
301        """
302        return tag(self.frame[1])
303
304    @renderer
305    def lineNumber(self, request, tag):
306        """
307        Render the source line number this frame references as a child of
308        C{tag}.
309        """
310        return tag(str(self.frame[2]))
311
312    @renderer
313    def function(self, request, tag):
314        """
315        Render the function name this frame references as a child of C{tag}.
316        """
317        return tag(self.frame[0])
318
319    @renderer
320    def source(self, request, tag):
321        """
322        Render the source code surrounding the line this frame references,
323        replacing C{tag}.
324        """
325        return _SourceFragmentElement(TagLoader(tag), self.frame)
326
327
328class _StackElement(Element):
329    """
330    L{_StackElement} renders an L{IRenderable} which can render a list of frames.
331    """
332
333    def __init__(self, loader, stackFrames):
334        Element.__init__(self, loader)
335        self.stackFrames = stackFrames
336
337    @renderer
338    def frames(self, request, tag):
339        """
340        Render the list of frames in this L{_StackElement}, replacing C{tag}.
341        """
342        return [
343            _FrameElement(TagLoader(tag.clone()), frame) for frame in self.stackFrames
344        ]
345
346
347class _NSContext:
348    """
349    A mapping from XML namespaces onto their prefixes in the document.
350    """
351
352    def __init__(self, parent: Optional["_NSContext"] = None):
353        """
354        Pull out the parent's namespaces, if there's no parent then default to
355        XML.
356        """
357        self.parent = parent
358        if parent is not None:
359            self.nss: Dict[Optional[str], Optional[str]] = OrderedDict(parent.nss)
360        else:
361            self.nss = {"http://www.w3.org/XML/1998/namespace": "xml"}
362
363    def get(self, k: Optional[str], d: Optional[str] = None) -> Optional[str]:
364        """
365        Get a prefix for a namespace.
366
367        @param d: The default prefix value.
368        """
369        return self.nss.get(k, d)
370
371    def __setitem__(self, k: Optional[str], v: Optional[str]) -> None:
372        """
373        Proxy through to setting the prefix for the namespace.
374        """
375        self.nss.__setitem__(k, v)
376
377    def __getitem__(self, k: Optional[str]) -> Optional[str]:
378        """
379        Proxy through to getting the prefix for the namespace.
380        """
381        return self.nss.__getitem__(k)
382
383
384TEMPLATE_NAMESPACE = "http://twistedmatrix.com/ns/twisted.web.template/0.1"
385
386
387class _ToStan(handler.ContentHandler, handler.EntityResolver):
388    """
389    A SAX parser which converts an XML document to the Twisted STAN
390    Document Object Model.
391    """
392
393    def __init__(self, sourceFilename: Optional[str]):
394        """
395        @param sourceFilename: the filename the XML was loaded out of.
396        """
397        self.sourceFilename = sourceFilename
398        self.prefixMap = _NSContext()
399        self.inCDATA = False
400
401    def setDocumentLocator(self, locator: Locator) -> None:
402        """
403        Set the document locator, which knows about line and character numbers.
404        """
405        self.locator = locator
406
407    def startDocument(self) -> None:
408        """
409        Initialise the document.
410        """
411        # Depending on our active context, the element type can be Tag, slot
412        # or str. Since mypy doesn't understand that context, it would be
413        # a pain to not use Any here.
414        self.document: List[Any] = []
415        self.current = self.document
416        self.stack: List[Any] = []
417        self.xmlnsAttrs: List[Tuple[str, str]] = []
418
419    def endDocument(self) -> None:
420        """
421        Document ended.
422        """
423
424    def processingInstruction(self, target: str, data: str) -> None:
425        """
426        Processing instructions are ignored.
427        """
428
429    def startPrefixMapping(self, prefix: Optional[str], uri: str) -> None:
430        """
431        Set up the prefix mapping, which maps fully qualified namespace URIs
432        onto namespace prefixes.
433
434        This gets called before startElementNS whenever an C{xmlns} attribute
435        is seen.
436        """
437
438        self.prefixMap = _NSContext(self.prefixMap)
439        self.prefixMap[uri] = prefix
440
441        # Ignore the template namespace; we'll replace those during parsing.
442        if uri == TEMPLATE_NAMESPACE:
443            return
444
445        # Add to a list that will be applied once we have the element.
446        if prefix is None:
447            self.xmlnsAttrs.append(("xmlns", uri))
448        else:
449            self.xmlnsAttrs.append(("xmlns:%s" % prefix, uri))
450
451    def endPrefixMapping(self, prefix: Optional[str]) -> None:
452        """
453        "Pops the stack" on the prefix mapping.
454
455        Gets called after endElementNS.
456        """
457        parent = self.prefixMap.parent
458        assert parent is not None, "More prefix mapping ends than starts"
459        self.prefixMap = parent
460
461    def startElementNS(
462        self,
463        namespaceAndName: Tuple[str, str],
464        qname: Optional[str],
465        attrs: Mapping[Tuple[Optional[str], str], str],
466    ) -> None:
467        """
468        Gets called when we encounter a new xmlns attribute.
469
470        @param namespaceAndName: a (namespace, name) tuple, where name
471            determines which type of action to take, if the namespace matches
472            L{TEMPLATE_NAMESPACE}.
473        @param qname: ignored.
474        @param attrs: attributes on the element being started.
475        """
476
477        filename = self.sourceFilename
478        lineNumber = self.locator.getLineNumber()
479        columnNumber = self.locator.getColumnNumber()
480
481        ns, name = namespaceAndName
482        if ns == TEMPLATE_NAMESPACE:
483            if name == "transparent":
484                name = ""
485            elif name == "slot":
486                default: Optional[str]
487                try:
488                    # Try to get the default value for the slot
489                    default = attrs[(None, "default")]
490                except KeyError:
491                    # If there wasn't one, then use None to indicate no
492                    # default.
493                    default = None
494                sl = slot(
495                    attrs[(None, "name")],
496                    default=default,
497                    filename=filename,
498                    lineNumber=lineNumber,
499                    columnNumber=columnNumber,
500                )
501                self.stack.append(sl)
502                self.current.append(sl)
503                self.current = sl.children
504                return
505
506        render = None
507
508        attrs = OrderedDict(attrs)
509        for k, v in list(attrs.items()):
510            attrNS, justTheName = k
511            if attrNS != TEMPLATE_NAMESPACE:
512                continue
513            if justTheName == "render":
514                render = v
515                del attrs[k]
516
517        # nonTemplateAttrs is a dictionary mapping attributes that are *not* in
518        # TEMPLATE_NAMESPACE to their values.  Those in TEMPLATE_NAMESPACE were
519        # just removed from 'attrs' in the loop immediately above.  The key in
520        # nonTemplateAttrs is either simply the attribute name (if it was not
521        # specified as having a namespace in the template) or prefix:name,
522        # preserving the xml namespace prefix given in the document.
523
524        nonTemplateAttrs = OrderedDict()
525        for (attrNs, attrName), v in attrs.items():
526            nsPrefix = self.prefixMap.get(attrNs)
527            if nsPrefix is None:
528                attrKey = attrName
529            else:
530                attrKey = f"{nsPrefix}:{attrName}"
531            nonTemplateAttrs[attrKey] = v
532
533        if ns == TEMPLATE_NAMESPACE and name == "attr":
534            if not self.stack:
535                # TODO: define a better exception for this?
536                raise AssertionError(
537                    f"<{{{TEMPLATE_NAMESPACE}}}attr> as top-level element"
538                )
539            if "name" not in nonTemplateAttrs:
540                # TODO: same here
541                raise AssertionError(
542                    f"<{{{TEMPLATE_NAMESPACE}}}attr> requires a name attribute"
543                )
544            el = Tag(
545                "",
546                render=render,
547                filename=filename,
548                lineNumber=lineNumber,
549                columnNumber=columnNumber,
550            )
551            self.stack[-1].attributes[nonTemplateAttrs["name"]] = el
552            self.stack.append(el)
553            self.current = el.children
554            return
555
556        # Apply any xmlns attributes
557        if self.xmlnsAttrs:
558            nonTemplateAttrs.update(OrderedDict(self.xmlnsAttrs))
559            self.xmlnsAttrs = []
560
561        # Add the prefix that was used in the parsed template for non-template
562        # namespaces (which will not be consumed anyway).
563        if ns != TEMPLATE_NAMESPACE and ns is not None:
564            prefix = self.prefixMap[ns]
565            if prefix is not None:
566                name = f"{self.prefixMap[ns]}:{name}"
567        el = Tag(
568            name,
569            attributes=OrderedDict(
570                cast(Mapping[Union[bytes, str], str], nonTemplateAttrs)
571            ),
572            render=render,
573            filename=filename,
574            lineNumber=lineNumber,
575            columnNumber=columnNumber,
576        )
577        self.stack.append(el)
578        self.current.append(el)
579        self.current = el.children
580
581    def characters(self, ch: str) -> None:
582        """
583        Called when we receive some characters.  CDATA characters get passed
584        through as is.
585        """
586        if self.inCDATA:
587            self.stack[-1].append(ch)
588            return
589        self.current.append(ch)
590
591    def endElementNS(self, name: Tuple[str, str], qname: Optional[str]) -> None:
592        """
593        A namespace tag is closed.  Pop the stack, if there's anything left in
594        it, otherwise return to the document's namespace.
595        """
596        self.stack.pop()
597        if self.stack:
598            self.current = self.stack[-1].children
599        else:
600            self.current = self.document
601
602    def startDTD(self, name: str, publicId: str, systemId: str) -> None:
603        """
604        DTDs are ignored.
605        """
606
607    def endDTD(self, *args: object) -> None:
608        """
609        DTDs are ignored.
610        """
611
612    def startCDATA(self) -> None:
613        """
614        We're starting to be in a CDATA element, make a note of this.
615        """
616        self.inCDATA = True
617        self.stack.append([])
618
619    def endCDATA(self) -> None:
620        """
621        We're no longer in a CDATA element.  Collect up the characters we've
622        parsed and put them in a new CDATA object.
623        """
624        self.inCDATA = False
625        comment = "".join(self.stack.pop())
626        self.current.append(CDATA(comment))
627
628    def comment(self, content: str) -> None:
629        """
630        Add an XML comment which we've encountered.
631        """
632        self.current.append(Comment(content))
633
634
635def _flatsaxParse(fl: Union[IO[AnyStr], str]) -> List["Flattenable"]:
636    """
637    Perform a SAX parse of an XML document with the _ToStan class.
638
639    @param fl: The XML document to be parsed.
640
641    @return: a C{list} of Stan objects.
642    """
643    parser = make_parser()
644    parser.setFeature(handler.feature_validation, 0)
645    parser.setFeature(handler.feature_namespaces, 1)
646    parser.setFeature(handler.feature_external_ges, 0)
647    parser.setFeature(handler.feature_external_pes, 0)
648
649    s = _ToStan(getattr(fl, "name", None))
650    parser.setContentHandler(s)
651    parser.setEntityResolver(s)
652    parser.setProperty(handler.property_lexical_handler, s)
653
654    parser.parse(fl)
655
656    return s.document
657
658
659@implementer(ITemplateLoader)
660class XMLString:
661    """
662    An L{ITemplateLoader} that loads and parses XML from a string.
663    """
664
665    def __init__(self, s: Union[str, bytes]):
666        """
667        Run the parser on a L{io.StringIO} copy of the string.
668
669        @param s: The string from which to load the XML.
670        @type s: L{str}, or a UTF-8 encoded L{bytes}.
671        """
672        if not isinstance(s, str):
673            s = s.decode("utf8")
674
675        self._loadedTemplate: List["Flattenable"] = _flatsaxParse(io.StringIO(s))
676        """The loaded document."""
677
678    def load(self) -> List["Flattenable"]:
679        """
680        Return the document.
681
682        @return: the loaded document.
683        """
684        return self._loadedTemplate
685
686
687class FailureElement(Element):
688    """
689    L{FailureElement} is an L{IRenderable} which can render detailed information
690    about a L{Failure<twisted.python.failure.Failure>}.
691
692    @ivar failure: The L{Failure<twisted.python.failure.Failure>} instance which
693        will be rendered.
694
695    @since: 12.1
696    """
697
698    loader = XMLString(
699        """
700<div xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1">
701  <style type="text/css">
702    div.error {
703      color: red;
704      font-family: Verdana, Arial, helvetica, sans-serif;
705      font-weight: bold;
706    }
707
708    div {
709      font-family: Verdana, Arial, helvetica, sans-serif;
710    }
711
712    div.stackTrace {
713    }
714
715    div.frame {
716      padding: 1em;
717      background: white;
718      border-bottom: thin black dashed;
719    }
720
721    div.frame:first-child {
722      padding: 1em;
723      background: white;
724      border-top: thin black dashed;
725      border-bottom: thin black dashed;
726    }
727
728    div.location {
729    }
730
731    span.function {
732      font-weight: bold;
733      font-family: "Courier New", courier, monospace;
734    }
735
736    div.snippet {
737      margin-bottom: 0.5em;
738      margin-left: 1em;
739      background: #FFFFDD;
740    }
741
742    div.snippetHighlightLine {
743      color: red;
744    }
745
746    span.code {
747      font-family: "Courier New", courier, monospace;
748    }
749  </style>
750
751  <div class="error">
752    <span t:render="type" />: <span t:render="value" />
753  </div>
754  <div class="stackTrace" t:render="traceback">
755    <div class="frame" t:render="frames">
756      <div class="location">
757        <span t:render="filename" />:<span t:render="lineNumber" /> in
758        <span class="function" t:render="function" />
759      </div>
760      <div class="snippet" t:render="source">
761        <div t:render="sourceLines">
762          <span class="lineno" t:render="lineNumber" />
763          <code class="code" t:render="sourceLine" />
764        </div>
765      </div>
766    </div>
767  </div>
768  <div class="error">
769    <span t:render="type" />: <span t:render="value" />
770  </div>
771</div>
772"""
773    )
774
775    def __init__(self, failure, loader=None):
776        Element.__init__(self, loader)
777        self.failure = failure
778
779    @renderer
780    def type(self, request, tag):
781        """
782        Render the exception type as a child of C{tag}.
783        """
784        return tag(fullyQualifiedName(self.failure.type))
785
786    @renderer
787    def value(self, request, tag):
788        """
789        Render the exception value as a child of C{tag}.
790        """
791        return tag(str(self.failure.value).encode("utf8"))
792
793    @renderer
794    def traceback(self, request, tag):
795        """
796        Render all the frames in the wrapped
797        L{Failure<twisted.python.failure.Failure>}'s traceback stack, replacing
798        C{tag}.
799        """
800        return _StackElement(TagLoader(tag), self.failure.frames)
801
802
803def formatFailure(myFailure):
804    """
805    Construct an HTML representation of the given failure.
806
807    Consider using L{FailureElement} instead.
808
809    @type myFailure: L{Failure<twisted.python.failure.Failure>}
810
811    @rtype: L{bytes}
812    @return: A string containing the HTML representation of the given failure.
813    """
814    result = []
815    flattenString(None, FailureElement(myFailure)).addBoth(result.append)
816    if isinstance(result[0], bytes):
817        # Ensure the result string is all ASCII, for compatibility with the
818        # default encoding expected by browsers.
819        return result[0].decode("utf-8").encode("ascii", "xmlcharrefreplace")
820    result[0].raiseException()
821
822
823# Go read the definition of NOT_DONE_YET. For lulz. This is totally
824# equivalent. And this turns out to be necessary, because trying to import
825# NOT_DONE_YET in this module causes a circular import which we cannot escape
826# from. From which we cannot escape. Etc. glyph is okay with this solution for
827# now, and so am I, as long as this comment stays to explain to future
828# maintainers what it means. ~ C.
829#
830# See http://twistedmatrix.com/trac/ticket/5557 for progress on fixing this.
831NOT_DONE_YET = 1
832_moduleLog = Logger()
833
834
835@implementer(ITemplateLoader)
836class TagLoader:
837    """
838    An L{ITemplateLoader} that loads an existing flattenable object.
839    """
840
841    def __init__(self, tag: "Flattenable"):
842        """
843        @param tag: The object which will be loaded.
844        """
845
846        self.tag: "Flattenable" = tag
847        """The object which will be loaded."""
848
849    def load(self) -> List["Flattenable"]:
850        return [self.tag]
851
852
853@implementer(ITemplateLoader)
854class XMLFile:
855    """
856    An L{ITemplateLoader} that loads and parses XML from a file.
857    """
858
859    def __init__(self, path: FilePath):
860        """
861        Run the parser on a file.
862
863        @param path: The file from which to load the XML.
864        """
865        if not isinstance(path, FilePath):
866            warnings.warn(  # type: ignore[unreachable]
867                "Passing filenames or file objects to XMLFile is deprecated "
868                "since Twisted 12.1.  Pass a FilePath instead.",
869                category=DeprecationWarning,
870                stacklevel=2,
871            )
872
873        self._loadedTemplate: Optional[List["Flattenable"]] = None
874        """The loaded document, or L{None}, if not loaded."""
875
876        self._path: FilePath = path
877        """The file that is being loaded from."""
878
879    def _loadDoc(self) -> List["Flattenable"]:
880        """
881        Read and parse the XML.
882
883        @return: the loaded document.
884        """
885        if not isinstance(self._path, FilePath):
886            return _flatsaxParse(self._path)  # type: ignore[unreachable]
887        else:
888            with self._path.open("r") as f:
889                return _flatsaxParse(f)
890
891    def __repr__(self) -> str:
892        return f"<XMLFile of {self._path!r}>"
893
894    def load(self) -> List["Flattenable"]:
895        """
896        Return the document, first loading it if necessary.
897
898        @return: the loaded document.
899        """
900        if self._loadedTemplate is None:
901            self._loadedTemplate = self._loadDoc()
902        return self._loadedTemplate
903
904
905# Last updated October 2011, using W3Schools as a reference. Link:
906# http://www.w3schools.com/html5/html5_reference.asp
907# Note that <xmp> is explicitly omitted; its semantics do not work with
908# t.w.template and it is officially deprecated.
909VALID_HTML_TAG_NAMES = {
910    "a",
911    "abbr",
912    "acronym",
913    "address",
914    "applet",
915    "area",
916    "article",
917    "aside",
918    "audio",
919    "b",
920    "base",
921    "basefont",
922    "bdi",
923    "bdo",
924    "big",
925    "blockquote",
926    "body",
927    "br",
928    "button",
929    "canvas",
930    "caption",
931    "center",
932    "cite",
933    "code",
934    "col",
935    "colgroup",
936    "command",
937    "datalist",
938    "dd",
939    "del",
940    "details",
941    "dfn",
942    "dir",
943    "div",
944    "dl",
945    "dt",
946    "em",
947    "embed",
948    "fieldset",
949    "figcaption",
950    "figure",
951    "font",
952    "footer",
953    "form",
954    "frame",
955    "frameset",
956    "h1",
957    "h2",
958    "h3",
959    "h4",
960    "h5",
961    "h6",
962    "head",
963    "header",
964    "hgroup",
965    "hr",
966    "html",
967    "i",
968    "iframe",
969    "img",
970    "input",
971    "ins",
972    "isindex",
973    "keygen",
974    "kbd",
975    "label",
976    "legend",
977    "li",
978    "link",
979    "map",
980    "mark",
981    "menu",
982    "meta",
983    "meter",
984    "nav",
985    "noframes",
986    "noscript",
987    "object",
988    "ol",
989    "optgroup",
990    "option",
991    "output",
992    "p",
993    "param",
994    "pre",
995    "progress",
996    "q",
997    "rp",
998    "rt",
999    "ruby",
1000    "s",
1001    "samp",
1002    "script",
1003    "section",
1004    "select",
1005    "small",
1006    "source",
1007    "span",
1008    "strike",
1009    "strong",
1010    "style",
1011    "sub",
1012    "summary",
1013    "sup",
1014    "table",
1015    "tbody",
1016    "td",
1017    "textarea",
1018    "tfoot",
1019    "th",
1020    "thead",
1021    "time",
1022    "title",
1023    "tr",
1024    "tt",
1025    "u",
1026    "ul",
1027    "var",
1028    "video",
1029    "wbr",
1030}
1031
1032
1033class _TagFactory:
1034    """
1035    A factory for L{Tag} objects; the implementation of the L{tags} object.
1036
1037    This allows for the syntactic convenience of C{from twisted.web.html import
1038    tags; tags.a(href="linked-page.html")}, where 'a' can be basically any HTML
1039    tag.
1040
1041    The class is not exposed publicly because you only ever need one of these,
1042    and we already made it for you.
1043
1044    @see: L{tags}
1045    """
1046
1047    def __getattr__(self, tagName: str) -> Tag:
1048        if tagName == "transparent":
1049            return Tag("")
1050        # allow for E.del as E.del_
1051        tagName = tagName.rstrip("_")
1052        if tagName not in VALID_HTML_TAG_NAMES:
1053            raise AttributeError(f"unknown tag {tagName!r}")
1054        return Tag(tagName)
1055
1056
1057tags = _TagFactory()
1058
1059
1060def renderElement(
1061    request: IRequest,
1062    element: IRenderable,
1063    doctype: Optional[bytes] = b"<!DOCTYPE html>",
1064    _failElement: Optional[Callable[[Failure], "Element"]] = None,
1065) -> object:
1066    """
1067    Render an element or other L{IRenderable}.
1068
1069    @param request: The L{IRequest} being rendered to.
1070    @param element: An L{IRenderable} which will be rendered.
1071    @param doctype: A L{bytes} which will be written as the first line of
1072        the request, or L{None} to disable writing of a doctype.  The argument
1073        should not include a trailing newline and will default to the HTML5
1074        doctype C{'<!DOCTYPE html>'}.
1075
1076    @returns: NOT_DONE_YET
1077
1078    @since: 12.1
1079    """
1080    if doctype is not None:
1081        request.write(doctype)
1082        request.write(b"\n")
1083
1084    if _failElement is None:
1085        _failElement = FailureElement
1086
1087    d = flatten(request, element, request.write)
1088
1089    def eb(failure: Failure) -> Optional[Deferred[None]]:
1090        _moduleLog.failure(
1091            "An error occurred while rendering the response.", failure=failure
1092        )
1093        site = getattr(request, "site", None)
1094        if site is not None and site.displayTracebacks:
1095            assert _failElement is not None
1096            return flatten(request, _failElement(failure), request.write)
1097        else:
1098            request.write(
1099                b'<div style="font-size:800%;'
1100                b"background-color:#FFF;"
1101                b"color:#F00"
1102                b'">An error occurred while rendering the response.</div>'
1103            )
1104            return None
1105
1106    def finish(result: object, *, request: IRequest = request) -> object:
1107        request.finish()
1108        return result
1109
1110    d.addErrback(eb)
1111    d.addBoth(finish)
1112    return NOT_DONE_YET
1113