RAHHH
This commit is contained in:
12
backend/lib/python3.12/site-packages/twisted/web/__init__.py
Normal file
12
backend/lib/python3.12/site-packages/twisted/web/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
||||
# -*- test-case-name: twisted.web.test -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Twisted Web: HTTP clients and servers, plus tools for implementing them.
|
||||
|
||||
Contains a L{web server<twisted.web.server>} (including an
|
||||
L{HTTP implementation<twisted.web.http>}, a
|
||||
L{resource model<twisted.web.resource>}), and
|
||||
a L{web client<twisted.web.client>}.
|
||||
"""
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
68
backend/lib/python3.12/site-packages/twisted/web/_abnf.py
Normal file
68
backend/lib/python3.12/site-packages/twisted/web/_abnf.py
Normal file
@@ -0,0 +1,68 @@
|
||||
# -*- test-case-name: twisted.web.test.test_abnf -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tools for pedantically processing the HTTP protocol.
|
||||
"""
|
||||
|
||||
|
||||
def _istoken(b: bytes) -> bool:
|
||||
"""
|
||||
Is the string a token per RFC 9110 section 5.6.2?
|
||||
"""
|
||||
for c in b:
|
||||
if c not in (
|
||||
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" # ALPHA
|
||||
b"0123456789" # DIGIT
|
||||
b"!#$%&'*+-.^_`|~"
|
||||
):
|
||||
return False
|
||||
return b != b""
|
||||
|
||||
|
||||
def _decint(data: bytes) -> int:
|
||||
"""
|
||||
Parse a decimal integer of the form C{1*DIGIT}, i.e. consisting only of
|
||||
decimal digits. The integer may be embedded in whitespace (space and
|
||||
horizontal tab). This differs from the built-in L{int()} function by
|
||||
disallowing a leading C{+} character and various forms of whitespace
|
||||
(note that we sanitize linear whitespace in header values in
|
||||
L{twisted.web.http_headers.Headers}).
|
||||
|
||||
@param data: Value to parse.
|
||||
|
||||
@returns: A non-negative integer.
|
||||
|
||||
@raises ValueError: When I{value} contains non-decimal characters.
|
||||
"""
|
||||
data = data.strip(b" \t")
|
||||
if not data.isdigit():
|
||||
raise ValueError(f"Value contains non-decimal digits: {data!r}")
|
||||
return int(data)
|
||||
|
||||
|
||||
def _ishexdigits(b: bytes) -> bool:
|
||||
"""
|
||||
Is the string case-insensitively hexidecimal?
|
||||
|
||||
It must be composed of one or more characters in the ranges a-f, A-F
|
||||
and 0-9.
|
||||
"""
|
||||
for c in b:
|
||||
if c not in b"0123456789abcdefABCDEF":
|
||||
return False
|
||||
return b != b""
|
||||
|
||||
|
||||
def _hexint(b: bytes) -> int:
|
||||
"""
|
||||
Decode a hexadecimal integer.
|
||||
|
||||
Unlike L{int(b, 16)}, this raises L{ValueError} when the integer has
|
||||
a prefix like C{b'0x'}, C{b'+'}, or C{b'-'}, which is desirable when
|
||||
parsing network protocols.
|
||||
"""
|
||||
if not _ishexdigits(b):
|
||||
raise ValueError(b)
|
||||
return int(b, 16)
|
||||
@@ -0,0 +1,7 @@
|
||||
# -*- test-case-name: twisted.web.test.test_httpauth -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
HTTP header-based authentication migrated from web2
|
||||
"""
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,58 @@
|
||||
# -*- test-case-name: twisted.web.test.test_httpauth -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
HTTP BASIC authentication.
|
||||
|
||||
@see: U{http://tools.ietf.org/html/rfc1945}
|
||||
@see: U{http://tools.ietf.org/html/rfc2616}
|
||||
@see: U{http://tools.ietf.org/html/rfc2617}
|
||||
"""
|
||||
|
||||
|
||||
import binascii
|
||||
|
||||
from zope.interface import implementer
|
||||
|
||||
from twisted.cred import credentials, error
|
||||
from twisted.web.iweb import ICredentialFactory
|
||||
|
||||
|
||||
@implementer(ICredentialFactory)
|
||||
class BasicCredentialFactory:
|
||||
"""
|
||||
Credential Factory for HTTP Basic Authentication
|
||||
|
||||
@type authenticationRealm: L{bytes}
|
||||
@ivar authenticationRealm: The HTTP authentication realm which will be issued in
|
||||
challenges.
|
||||
"""
|
||||
|
||||
scheme = b"basic"
|
||||
|
||||
def __init__(self, authenticationRealm):
|
||||
self.authenticationRealm = authenticationRealm
|
||||
|
||||
def getChallenge(self, request):
|
||||
"""
|
||||
Return a challenge including the HTTP authentication realm with which
|
||||
this factory was created.
|
||||
"""
|
||||
return {"realm": self.authenticationRealm}
|
||||
|
||||
def decode(self, response, request):
|
||||
"""
|
||||
Parse the base64-encoded, colon-separated username and password into a
|
||||
L{credentials.UsernamePassword} instance.
|
||||
"""
|
||||
try:
|
||||
creds = binascii.a2b_base64(response + b"===")
|
||||
except binascii.Error:
|
||||
raise error.LoginFailed("Invalid credentials")
|
||||
|
||||
creds = creds.split(b":", 1)
|
||||
if len(creds) == 2:
|
||||
return credentials.UsernamePassword(*creds)
|
||||
else:
|
||||
raise error.LoginFailed("Invalid credentials")
|
||||
@@ -0,0 +1,56 @@
|
||||
# -*- test-case-name: twisted.web.test.test_httpauth -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Implementation of RFC2617: HTTP Digest Authentication
|
||||
|
||||
@see: U{http://www.faqs.org/rfcs/rfc2617.html}
|
||||
"""
|
||||
|
||||
|
||||
from zope.interface import implementer
|
||||
|
||||
from twisted.cred import credentials
|
||||
from twisted.web.iweb import ICredentialFactory
|
||||
|
||||
|
||||
@implementer(ICredentialFactory)
|
||||
class DigestCredentialFactory:
|
||||
"""
|
||||
Wrapper for L{digest.DigestCredentialFactory} that implements the
|
||||
L{ICredentialFactory} interface.
|
||||
"""
|
||||
|
||||
scheme = b"digest"
|
||||
|
||||
def __init__(self, algorithm, authenticationRealm):
|
||||
"""
|
||||
Create the digest credential factory that this object wraps.
|
||||
"""
|
||||
self.digest = credentials.DigestCredentialFactory(
|
||||
algorithm, authenticationRealm
|
||||
)
|
||||
|
||||
def getChallenge(self, request):
|
||||
"""
|
||||
Generate the challenge for use in the WWW-Authenticate header
|
||||
|
||||
@param request: The L{IRequest} to with access was denied and for the
|
||||
response to which this challenge is being generated.
|
||||
|
||||
@return: The L{dict} that can be used to generate a WWW-Authenticate
|
||||
header.
|
||||
"""
|
||||
return self.digest.getChallenge(request.getClientAddress().host)
|
||||
|
||||
def decode(self, response, request):
|
||||
"""
|
||||
Create a L{twisted.cred.credentials.DigestedCredentials} object
|
||||
from the given response and request.
|
||||
|
||||
@see: L{ICredentialFactory.decode}
|
||||
"""
|
||||
return self.digest.decode(
|
||||
response, request.method, request.getClientAddress().host
|
||||
)
|
||||
@@ -0,0 +1,236 @@
|
||||
# -*- test-case-name: twisted.web.test.test_httpauth -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
A guard implementation which supports HTTP header-based authentication
|
||||
schemes.
|
||||
|
||||
If no I{Authorization} header is supplied, an anonymous login will be
|
||||
attempted by using a L{Anonymous} credentials object. If such a header is
|
||||
supplied and does not contain allowed credentials, or if anonymous login is
|
||||
denied, a 401 will be sent in the response along with I{WWW-Authenticate}
|
||||
headers for each of the allowed authentication schemes.
|
||||
"""
|
||||
|
||||
|
||||
from zope.interface import implementer
|
||||
|
||||
from twisted.cred import error
|
||||
from twisted.cred.credentials import Anonymous
|
||||
from twisted.logger import Logger
|
||||
from twisted.python.components import proxyForInterface
|
||||
from twisted.web import util
|
||||
from twisted.web.resource import IResource, _UnsafeErrorPage
|
||||
|
||||
|
||||
@implementer(IResource)
|
||||
class UnauthorizedResource:
|
||||
"""
|
||||
Simple IResource to escape Resource dispatch
|
||||
"""
|
||||
|
||||
isLeaf = True
|
||||
|
||||
def __init__(self, factories):
|
||||
self._credentialFactories = factories
|
||||
|
||||
def render(self, request):
|
||||
"""
|
||||
Send www-authenticate headers to the client
|
||||
"""
|
||||
|
||||
def ensureBytes(s):
|
||||
return s.encode("ascii") if isinstance(s, str) else s
|
||||
|
||||
def generateWWWAuthenticate(scheme, challenge):
|
||||
lst = []
|
||||
for k, v in challenge.items():
|
||||
k = ensureBytes(k)
|
||||
v = ensureBytes(v)
|
||||
lst.append(k + b"=" + quoteString(v))
|
||||
return b" ".join([scheme, b", ".join(lst)])
|
||||
|
||||
def quoteString(s):
|
||||
return b'"' + s.replace(b"\\", rb"\\").replace(b'"', rb"\"") + b'"'
|
||||
|
||||
request.setResponseCode(401)
|
||||
for fact in self._credentialFactories:
|
||||
challenge = fact.getChallenge(request)
|
||||
request.responseHeaders.addRawHeader(
|
||||
b"www-authenticate", generateWWWAuthenticate(fact.scheme, challenge)
|
||||
)
|
||||
if request.method == b"HEAD":
|
||||
return b""
|
||||
return b"Unauthorized"
|
||||
|
||||
def getChildWithDefault(self, path, request):
|
||||
"""
|
||||
Disable resource dispatch
|
||||
"""
|
||||
return self
|
||||
|
||||
def putChild(self, path, child):
|
||||
# IResource.putChild
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
@implementer(IResource)
|
||||
class HTTPAuthSessionWrapper:
|
||||
"""
|
||||
Wrap a portal, enforcing supported header-based authentication schemes.
|
||||
|
||||
@ivar _portal: The L{Portal} which will be used to retrieve L{IResource}
|
||||
avatars.
|
||||
|
||||
@ivar _credentialFactories: A list of L{ICredentialFactory} providers which
|
||||
will be used to decode I{Authorization} headers into L{ICredentials}
|
||||
providers.
|
||||
"""
|
||||
|
||||
isLeaf = False
|
||||
_log = Logger()
|
||||
|
||||
def __init__(self, portal, credentialFactories):
|
||||
"""
|
||||
Initialize a session wrapper
|
||||
|
||||
@type portal: C{Portal}
|
||||
@param portal: The portal that will authenticate the remote client
|
||||
|
||||
@type credentialFactories: C{Iterable}
|
||||
@param credentialFactories: The portal that will authenticate the
|
||||
remote client based on one submitted C{ICredentialFactory}
|
||||
"""
|
||||
self._portal = portal
|
||||
self._credentialFactories = credentialFactories
|
||||
|
||||
def _authorizedResource(self, request):
|
||||
"""
|
||||
Get the L{IResource} which the given request is authorized to receive.
|
||||
If the proper authorization headers are present, the resource will be
|
||||
requested from the portal. If not, an anonymous login attempt will be
|
||||
made.
|
||||
"""
|
||||
authheader = request.getHeader(b"authorization")
|
||||
if not authheader:
|
||||
return util.DeferredResource(self._login(Anonymous()))
|
||||
|
||||
factory, respString = self._selectParseHeader(authheader)
|
||||
if factory is None:
|
||||
return UnauthorizedResource(self._credentialFactories)
|
||||
try:
|
||||
credentials = factory.decode(respString, request)
|
||||
except error.LoginFailed:
|
||||
return UnauthorizedResource(self._credentialFactories)
|
||||
except BaseException:
|
||||
self._log.failure("Unexpected failure from credentials factory")
|
||||
return _UnsafeErrorPage(500, "Internal Error", "")
|
||||
else:
|
||||
return util.DeferredResource(self._login(credentials))
|
||||
|
||||
def render(self, request):
|
||||
"""
|
||||
Find the L{IResource} avatar suitable for the given request, if
|
||||
possible, and render it. Otherwise, perhaps render an error page
|
||||
requiring authorization or describing an internal server failure.
|
||||
"""
|
||||
return self._authorizedResource(request).render(request)
|
||||
|
||||
def getChildWithDefault(self, path, request):
|
||||
"""
|
||||
Inspect the Authorization HTTP header, and return a deferred which,
|
||||
when fired after successful authentication, will return an authorized
|
||||
C{Avatar}. On authentication failure, an C{UnauthorizedResource} will
|
||||
be returned, essentially halting further dispatch on the wrapped
|
||||
resource and all children
|
||||
"""
|
||||
# Don't consume any segments of the request - this class should be
|
||||
# transparent!
|
||||
request.postpath.insert(0, request.prepath.pop())
|
||||
return self._authorizedResource(request)
|
||||
|
||||
def _login(self, credentials):
|
||||
"""
|
||||
Get the L{IResource} avatar for the given credentials.
|
||||
|
||||
@return: A L{Deferred} which will be called back with an L{IResource}
|
||||
avatar or which will errback if authentication fails.
|
||||
"""
|
||||
d = self._portal.login(credentials, None, IResource)
|
||||
d.addCallbacks(self._loginSucceeded, self._loginFailed)
|
||||
return d
|
||||
|
||||
def _loginSucceeded(self, args):
|
||||
"""
|
||||
Handle login success by wrapping the resulting L{IResource} avatar
|
||||
so that the C{logout} callback will be invoked when rendering is
|
||||
complete.
|
||||
"""
|
||||
interface, avatar, logout = args
|
||||
|
||||
class ResourceWrapper(proxyForInterface(IResource, "resource")):
|
||||
"""
|
||||
Wrap an L{IResource} so that whenever it or a child of it
|
||||
completes rendering, the cred logout hook will be invoked.
|
||||
|
||||
An assumption is made here that exactly one L{IResource} from
|
||||
among C{avatar} and all of its children will be rendered. If
|
||||
more than one is rendered, C{logout} will be invoked multiple
|
||||
times and probably earlier than desired.
|
||||
"""
|
||||
|
||||
def getChildWithDefault(self, name, request):
|
||||
"""
|
||||
Pass through the lookup to the wrapped resource, wrapping
|
||||
the result in L{ResourceWrapper} to ensure C{logout} is
|
||||
called when rendering of the child is complete.
|
||||
"""
|
||||
return ResourceWrapper(self.resource.getChildWithDefault(name, request))
|
||||
|
||||
def render(self, request):
|
||||
"""
|
||||
Hook into response generation so that when rendering has
|
||||
finished completely (with or without error), C{logout} is
|
||||
called.
|
||||
"""
|
||||
request.notifyFinish().addBoth(lambda ign: logout())
|
||||
return super().render(request)
|
||||
|
||||
return ResourceWrapper(avatar)
|
||||
|
||||
def _loginFailed(self, result):
|
||||
"""
|
||||
Handle login failure by presenting either another challenge (for
|
||||
expected authentication/authorization-related failures) or a server
|
||||
error page (for anything else).
|
||||
"""
|
||||
if result.check(error.Unauthorized, error.LoginFailed):
|
||||
return UnauthorizedResource(self._credentialFactories)
|
||||
else:
|
||||
self._log.failure(
|
||||
"HTTPAuthSessionWrapper.getChildWithDefault encountered "
|
||||
"unexpected error",
|
||||
failure=result,
|
||||
)
|
||||
return _UnsafeErrorPage(500, "Internal Error", "")
|
||||
|
||||
def _selectParseHeader(self, header):
|
||||
"""
|
||||
Choose an C{ICredentialFactory} from C{_credentialFactories}
|
||||
suitable to use to decode the given I{Authenticate} header.
|
||||
|
||||
@return: A two-tuple of a factory and the remaining portion of the
|
||||
header value to be decoded or a two-tuple of L{None} if no
|
||||
factory can decode the header value.
|
||||
"""
|
||||
elements = header.split(b" ")
|
||||
scheme = elements[0].lower()
|
||||
for fact in self._credentialFactories:
|
||||
if fact.scheme == scheme:
|
||||
return (fact, b" ".join(elements[1:]))
|
||||
return (None, None)
|
||||
|
||||
def putChild(self, path, child):
|
||||
# IResource.putChild
|
||||
raise NotImplementedError()
|
||||
200
backend/lib/python3.12/site-packages/twisted/web/_element.py
Normal file
200
backend/lib/python3.12/site-packages/twisted/web/_element.py
Normal file
@@ -0,0 +1,200 @@
|
||||
# -*- test-case-name: twisted.web.test.test_template -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
import itertools
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Callable,
|
||||
List,
|
||||
Optional,
|
||||
TypeVar,
|
||||
Union,
|
||||
overload,
|
||||
)
|
||||
|
||||
from zope.interface import implementer
|
||||
|
||||
from twisted.web.error import (
|
||||
MissingRenderMethod,
|
||||
MissingTemplateLoader,
|
||||
UnexposedMethodError,
|
||||
)
|
||||
from twisted.web.iweb import IRenderable, IRequest, ITemplateLoader
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from twisted.web.template import Flattenable, Tag
|
||||
|
||||
|
||||
T = TypeVar("T")
|
||||
_Tc = TypeVar("_Tc", bound=Callable[..., object])
|
||||
|
||||
|
||||
class Expose:
|
||||
"""
|
||||
Helper for exposing methods for various uses using a simple decorator-style
|
||||
callable.
|
||||
|
||||
Instances of this class can be called with one or more functions as
|
||||
positional arguments. The names of these functions will be added to a list
|
||||
on the class object of which they are methods.
|
||||
"""
|
||||
|
||||
def __call__(self, f: _Tc, /, *funcObjs: Callable[..., object]) -> _Tc:
|
||||
"""
|
||||
Add one or more functions to the set of exposed functions.
|
||||
|
||||
This is a way to declare something about a class definition, similar to
|
||||
L{zope.interface.implementer}. Use it like this::
|
||||
|
||||
magic = Expose('perform extra magic')
|
||||
class Foo(Bar):
|
||||
def twiddle(self, x, y):
|
||||
...
|
||||
def frob(self, a, b):
|
||||
...
|
||||
magic(twiddle, frob)
|
||||
|
||||
Later you can query the object::
|
||||
|
||||
aFoo = Foo()
|
||||
magic.get(aFoo, 'twiddle')(x=1, y=2)
|
||||
|
||||
The call to C{get} will fail if the name it is given has not been
|
||||
exposed using C{magic}.
|
||||
|
||||
@param funcObjs: One or more function objects which will be exposed to
|
||||
the client.
|
||||
|
||||
@return: The first of C{funcObjs}.
|
||||
"""
|
||||
for fObj in itertools.chain([f], funcObjs):
|
||||
exposedThrough: List[Expose] = getattr(fObj, "exposedThrough", [])
|
||||
exposedThrough.append(self)
|
||||
setattr(fObj, "exposedThrough", exposedThrough)
|
||||
return f
|
||||
|
||||
_nodefault = object()
|
||||
|
||||
@overload
|
||||
def get(self, instance: object, methodName: str) -> Callable[..., Any]:
|
||||
...
|
||||
|
||||
@overload
|
||||
def get(
|
||||
self, instance: object, methodName: str, default: T
|
||||
) -> Union[Callable[..., Any], T]:
|
||||
...
|
||||
|
||||
def get(
|
||||
self, instance: object, methodName: str, default: object = _nodefault
|
||||
) -> object:
|
||||
"""
|
||||
Retrieve an exposed method with the given name from the given instance.
|
||||
|
||||
@raise UnexposedMethodError: Raised if C{default} is not specified and
|
||||
there is no exposed method with the given name.
|
||||
|
||||
@return: A callable object for the named method assigned to the given
|
||||
instance.
|
||||
"""
|
||||
method = getattr(instance, methodName, None)
|
||||
exposedThrough = getattr(method, "exposedThrough", [])
|
||||
if self not in exposedThrough:
|
||||
if default is self._nodefault:
|
||||
raise UnexposedMethodError(self, methodName)
|
||||
return default
|
||||
return method
|
||||
|
||||
|
||||
def exposer(thunk: Callable[..., object]) -> Expose:
|
||||
expose = Expose()
|
||||
expose.__doc__ = thunk.__doc__
|
||||
return expose
|
||||
|
||||
|
||||
@exposer
|
||||
def renderer() -> None:
|
||||
"""
|
||||
Decorate with L{renderer} to use methods as template render directives.
|
||||
|
||||
For example::
|
||||
|
||||
class Foo(Element):
|
||||
@renderer
|
||||
def twiddle(self, request, tag):
|
||||
return tag('Hello, world.')
|
||||
|
||||
<div xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1">
|
||||
<span t:render="twiddle" />
|
||||
</div>
|
||||
|
||||
Will result in this final output::
|
||||
|
||||
<div>
|
||||
<span>Hello, world.</span>
|
||||
</div>
|
||||
"""
|
||||
|
||||
|
||||
@implementer(IRenderable)
|
||||
class Element:
|
||||
"""
|
||||
Base for classes which can render part of a page.
|
||||
|
||||
An Element is a renderer that can be embedded in a stan document and can
|
||||
hook its template (from the loader) up to render methods.
|
||||
|
||||
An Element might be used to encapsulate the rendering of a complex piece of
|
||||
data which is to be displayed in multiple different contexts. The Element
|
||||
allows the rendering logic to be easily re-used in different ways.
|
||||
|
||||
Element returns render methods which are registered using
|
||||
L{twisted.web._element.renderer}. For example::
|
||||
|
||||
class Menu(Element):
|
||||
@renderer
|
||||
def items(self, request, tag):
|
||||
....
|
||||
|
||||
Render methods are invoked with two arguments: first, the
|
||||
L{twisted.web.http.Request} being served and second, the tag object which
|
||||
"invoked" the render method.
|
||||
|
||||
@ivar loader: The factory which will be used to load documents to
|
||||
return from C{render}.
|
||||
"""
|
||||
|
||||
loader: Optional[ITemplateLoader] = None
|
||||
|
||||
def __init__(self, loader: Optional[ITemplateLoader] = None):
|
||||
if loader is not None:
|
||||
self.loader = loader
|
||||
|
||||
def lookupRenderMethod(
|
||||
self, name: str
|
||||
) -> Callable[[Optional[IRequest], "Tag"], "Flattenable"]:
|
||||
"""
|
||||
Look up and return the named render method.
|
||||
"""
|
||||
method = renderer.get(self, name, None)
|
||||
if method is None:
|
||||
raise MissingRenderMethod(self, name)
|
||||
return method
|
||||
|
||||
def render(self, request: Optional[IRequest]) -> "Flattenable":
|
||||
"""
|
||||
Implement L{IRenderable} to allow one L{Element} to be embedded in
|
||||
another's template or rendering output.
|
||||
|
||||
(This will simply load the template from the C{loader}; when used in a
|
||||
template, the flattening engine will keep track of this object
|
||||
separately as the object to lookup renderers on and call
|
||||
L{Element.renderer} to look them up. The resulting object from this
|
||||
method is not directly associated with this L{Element}.)
|
||||
"""
|
||||
loader = self.loader
|
||||
if loader is None:
|
||||
raise MissingTemplateLoader(self)
|
||||
return loader.load()
|
||||
486
backend/lib/python3.12/site-packages/twisted/web/_flatten.py
Normal file
486
backend/lib/python3.12/site-packages/twisted/web/_flatten.py
Normal file
@@ -0,0 +1,486 @@
|
||||
# -*- test-case-name: twisted.web.test.test_flatten,twisted.web.test.test_template -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Context-free flattener/serializer for rendering Python objects, possibly
|
||||
complex or arbitrarily nested, as strings.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from inspect import iscoroutine
|
||||
from io import BytesIO
|
||||
from sys import exc_info
|
||||
from traceback import extract_tb
|
||||
from types import GeneratorType
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
Coroutine,
|
||||
Generator,
|
||||
List,
|
||||
Mapping,
|
||||
Optional,
|
||||
Sequence,
|
||||
Tuple,
|
||||
TypeVar,
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
|
||||
from twisted.internet.defer import Deferred, ensureDeferred
|
||||
from twisted.python.compat import nativeString
|
||||
from twisted.python.failure import Failure
|
||||
from twisted.web._stan import CDATA, CharRef, Comment, Tag, slot, voidElements
|
||||
from twisted.web.error import FlattenerError, UnfilledSlot, UnsupportedType
|
||||
from twisted.web.iweb import IRenderable, IRequest
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
FlattenableRecursive = Any
|
||||
"""
|
||||
For documentation purposes, read C{FlattenableRecursive} as L{Flattenable}.
|
||||
However, since mypy doesn't support recursive type definitions (yet?),
|
||||
we'll put Any in the actual definition.
|
||||
"""
|
||||
|
||||
Flattenable = Union[
|
||||
bytes,
|
||||
str,
|
||||
slot,
|
||||
CDATA,
|
||||
Comment,
|
||||
Tag,
|
||||
Tuple[FlattenableRecursive, ...],
|
||||
List[FlattenableRecursive],
|
||||
Generator[FlattenableRecursive, None, None],
|
||||
CharRef,
|
||||
Deferred[FlattenableRecursive],
|
||||
Coroutine[Deferred[FlattenableRecursive], object, FlattenableRecursive],
|
||||
IRenderable,
|
||||
]
|
||||
"""
|
||||
Type alias containing all types that can be flattened by L{flatten()}.
|
||||
"""
|
||||
|
||||
# The maximum number of bytes to synchronously accumulate in the flattener
|
||||
# buffer before delivering them onwards.
|
||||
BUFFER_SIZE = 2**16
|
||||
|
||||
|
||||
def escapeForContent(data: Union[bytes, str]) -> bytes:
|
||||
"""
|
||||
Escape some character or UTF-8 byte data for inclusion in an HTML or XML
|
||||
document, by replacing metacharacters (C{&<>}) with their entity
|
||||
equivalents (C{&<>}).
|
||||
|
||||
This is used as an input to L{_flattenElement}'s C{dataEscaper} parameter.
|
||||
|
||||
@param data: The string to escape.
|
||||
|
||||
@return: The quoted form of C{data}. If C{data} is L{str}, return a utf-8
|
||||
encoded string.
|
||||
"""
|
||||
if isinstance(data, str):
|
||||
data = data.encode("utf-8")
|
||||
data = data.replace(b"&", b"&").replace(b"<", b"<").replace(b">", b">")
|
||||
return data
|
||||
|
||||
|
||||
def attributeEscapingDoneOutside(data: Union[bytes, str]) -> bytes:
|
||||
"""
|
||||
Escape some character or UTF-8 byte data for inclusion in the top level of
|
||||
an attribute. L{attributeEscapingDoneOutside} actually passes the data
|
||||
through unchanged, because L{writeWithAttributeEscaping} handles the
|
||||
quoting of the text within attributes outside the generator returned by
|
||||
L{_flattenElement}; this is used as the C{dataEscaper} argument to that
|
||||
L{_flattenElement} call so that that generator does not redundantly escape
|
||||
its text output.
|
||||
|
||||
@param data: The string to escape.
|
||||
|
||||
@return: The string, unchanged, except for encoding.
|
||||
"""
|
||||
if isinstance(data, str):
|
||||
return data.encode("utf-8")
|
||||
return data
|
||||
|
||||
|
||||
def writeWithAttributeEscaping(
|
||||
write: Callable[[bytes], object]
|
||||
) -> Callable[[bytes], None]:
|
||||
"""
|
||||
Decorate a C{write} callable so that all output written is properly quoted
|
||||
for inclusion within an XML attribute value.
|
||||
|
||||
If a L{Tag <twisted.web.template.Tag>} C{x} is flattened within the context
|
||||
of the contents of another L{Tag <twisted.web.template.Tag>} C{y}, the
|
||||
metacharacters (C{<>&"}) delimiting C{x} should be passed through
|
||||
unchanged, but the textual content of C{x} should still be quoted, as
|
||||
usual. For example: C{<y><x>&</x></y>}. That is the default behavior
|
||||
of L{_flattenElement} when L{escapeForContent} is passed as the
|
||||
C{dataEscaper}.
|
||||
|
||||
However, when a L{Tag <twisted.web.template.Tag>} C{x} is flattened within
|
||||
the context of an I{attribute} of another L{Tag <twisted.web.template.Tag>}
|
||||
C{y}, then the metacharacters delimiting C{x} should be quoted so that it
|
||||
can be parsed from the attribute's value. In the DOM itself, this is not a
|
||||
valid thing to do, but given that renderers and slots may be freely moved
|
||||
around in a L{twisted.web.template} template, it is a condition which may
|
||||
arise in a document and must be handled in a way which produces valid
|
||||
output. So, for example, you should be able to get C{<y attr="<x />"
|
||||
/>}. This should also be true for other XML/HTML meta-constructs such as
|
||||
comments and CDATA, so if you were to serialize a L{comment
|
||||
<twisted.web.template.Comment>} in an attribute you should get C{<y
|
||||
attr="<-- comment -->" />}. Therefore in order to capture these
|
||||
meta-characters, flattening is done with C{write} callable that is wrapped
|
||||
with L{writeWithAttributeEscaping}.
|
||||
|
||||
The final case, and hopefully the much more common one as compared to
|
||||
serializing L{Tag <twisted.web.template.Tag>} and arbitrary L{IRenderable}
|
||||
objects within an attribute, is to serialize a simple string, and those
|
||||
should be passed through for L{writeWithAttributeEscaping} to quote
|
||||
without applying a second, redundant level of quoting.
|
||||
|
||||
@param write: A callable which will be invoked with the escaped L{bytes}.
|
||||
|
||||
@return: A callable that writes data with escaping.
|
||||
"""
|
||||
|
||||
def _write(data: bytes) -> None:
|
||||
write(escapeForContent(data).replace(b'"', b"""))
|
||||
|
||||
return _write
|
||||
|
||||
|
||||
def escapedCDATA(data: Union[bytes, str]) -> bytes:
|
||||
"""
|
||||
Escape CDATA for inclusion in a document.
|
||||
|
||||
@param data: The string to escape.
|
||||
|
||||
@return: The quoted form of C{data}. If C{data} is unicode, return a utf-8
|
||||
encoded string.
|
||||
"""
|
||||
if isinstance(data, str):
|
||||
data = data.encode("utf-8")
|
||||
return data.replace(b"]]>", b"]]]]><![CDATA[>")
|
||||
|
||||
|
||||
def escapedComment(data: Union[bytes, str]) -> bytes:
|
||||
"""
|
||||
Within comments the sequence C{-->} can be mistaken as the end of the comment.
|
||||
To ensure consistent parsing and valid output the sequence is replaced with C{-->}.
|
||||
Furthermore, whitespace is added when a comment ends in a dash. This is done to break
|
||||
the connection of the ending C{-} with the closing C{-->}.
|
||||
|
||||
@param data: The string to escape.
|
||||
|
||||
@return: The quoted form of C{data}. If C{data} is unicode, return a utf-8
|
||||
encoded string.
|
||||
"""
|
||||
if isinstance(data, str):
|
||||
data = data.encode("utf-8")
|
||||
data = data.replace(b"-->", b"-->")
|
||||
if data and data[-1:] == b"-":
|
||||
data += b" "
|
||||
return data
|
||||
|
||||
|
||||
def _getSlotValue(
|
||||
name: str,
|
||||
slotData: Sequence[Optional[Mapping[str, Flattenable]]],
|
||||
default: Optional[Flattenable] = None,
|
||||
) -> Flattenable:
|
||||
"""
|
||||
Find the value of the named slot in the given stack of slot data.
|
||||
"""
|
||||
for slotFrame in reversed(slotData):
|
||||
if slotFrame is not None and name in slotFrame:
|
||||
return slotFrame[name]
|
||||
else:
|
||||
if default is not None:
|
||||
return default
|
||||
raise UnfilledSlot(name)
|
||||
|
||||
|
||||
def _fork(d: Deferred[T]) -> Deferred[T]:
|
||||
"""
|
||||
Create a new L{Deferred} based on C{d} that will fire and fail with C{d}'s
|
||||
result or error, but will not modify C{d}'s callback type.
|
||||
"""
|
||||
d2: Deferred[T] = Deferred(lambda _: d.cancel())
|
||||
|
||||
def callback(result: T) -> T:
|
||||
d2.callback(result)
|
||||
return result
|
||||
|
||||
def errback(failure: Failure) -> Failure:
|
||||
d2.errback(failure)
|
||||
return failure
|
||||
|
||||
d.addCallbacks(callback, errback)
|
||||
return d2
|
||||
|
||||
|
||||
def _flattenElement(
|
||||
request: Optional[IRequest],
|
||||
root: Flattenable,
|
||||
write: Callable[[bytes], object],
|
||||
slotData: List[Optional[Mapping[str, Flattenable]]],
|
||||
renderFactory: Optional[IRenderable],
|
||||
dataEscaper: Callable[[Union[bytes, str]], bytes],
|
||||
# This is annotated as Generator[T, None, None] instead of Iterator[T]
|
||||
# because mypy does not consider an Iterator to be an instance of
|
||||
# GeneratorType.
|
||||
) -> Generator[Union[Generator[Any, Any, Any], Deferred[Flattenable]], None, None]:
|
||||
"""
|
||||
Make C{root} slightly more flat by yielding all its immediate contents as
|
||||
strings, deferreds or generators that are recursive calls to itself.
|
||||
|
||||
@param request: A request object which will be passed to
|
||||
L{IRenderable.render}.
|
||||
|
||||
@param root: An object to be made flatter. This may be of type C{unicode},
|
||||
L{str}, L{slot}, L{Tag <twisted.web.template.Tag>}, L{tuple}, L{list},
|
||||
L{types.GeneratorType}, L{Deferred}, or an object that implements
|
||||
L{IRenderable}.
|
||||
|
||||
@param write: A callable which will be invoked with each L{bytes} produced
|
||||
by flattening C{root}.
|
||||
|
||||
@param slotData: A L{list} of L{dict} mapping L{str} slot names to data
|
||||
with which those slots will be replaced.
|
||||
|
||||
@param renderFactory: If not L{None}, an object that provides
|
||||
L{IRenderable}.
|
||||
|
||||
@param dataEscaper: A 1-argument callable which takes L{bytes} or
|
||||
L{unicode} and returns L{bytes}, quoted as appropriate for the
|
||||
rendering context. This is really only one of two values:
|
||||
L{attributeEscapingDoneOutside} or L{escapeForContent}, depending on
|
||||
whether the rendering context is within an attribute or not. See the
|
||||
explanation in L{writeWithAttributeEscaping}.
|
||||
|
||||
@return: An iterator that eventually writes L{bytes} to C{write}.
|
||||
It can yield other iterators or L{Deferred}s; if it yields another
|
||||
iterator, the caller will iterate it; if it yields a L{Deferred},
|
||||
the result of that L{Deferred} will be another generator, in which
|
||||
case it is iterated. See L{_flattenTree} for the trampoline that
|
||||
consumes said values.
|
||||
"""
|
||||
|
||||
def keepGoing(
|
||||
newRoot: Flattenable,
|
||||
dataEscaper: Callable[[Union[bytes, str]], bytes] = dataEscaper,
|
||||
renderFactory: Optional[IRenderable] = renderFactory,
|
||||
write: Callable[[bytes], object] = write,
|
||||
) -> Generator[Union[Flattenable, Deferred[Flattenable]], None, None]:
|
||||
return _flattenElement(
|
||||
request, newRoot, write, slotData, renderFactory, dataEscaper
|
||||
)
|
||||
|
||||
def keepGoingAsync(result: Deferred[Flattenable]) -> Deferred[Flattenable]:
|
||||
return result.addCallback(keepGoing)
|
||||
|
||||
if isinstance(root, (bytes, str)):
|
||||
write(dataEscaper(root))
|
||||
elif isinstance(root, slot):
|
||||
slotValue = _getSlotValue(root.name, slotData, root.default)
|
||||
yield keepGoing(slotValue)
|
||||
elif isinstance(root, CDATA):
|
||||
write(b"<![CDATA[")
|
||||
write(escapedCDATA(root.data))
|
||||
write(b"]]>")
|
||||
elif isinstance(root, Comment):
|
||||
write(b"<!--")
|
||||
write(escapedComment(root.data))
|
||||
write(b"-->")
|
||||
elif isinstance(root, Tag):
|
||||
slotData.append(root.slotData)
|
||||
rendererName = root.render
|
||||
if rendererName is not None:
|
||||
if renderFactory is None:
|
||||
raise ValueError(
|
||||
f'Tag wants to be rendered by method "{rendererName}" '
|
||||
f"but is not contained in any IRenderable"
|
||||
)
|
||||
rootClone = root.clone(False)
|
||||
rootClone.render = None
|
||||
renderMethod = renderFactory.lookupRenderMethod(rendererName)
|
||||
result = renderMethod(request, rootClone)
|
||||
yield keepGoing(result)
|
||||
slotData.pop()
|
||||
return
|
||||
|
||||
if not root.tagName:
|
||||
yield keepGoing(root.children)
|
||||
return
|
||||
|
||||
write(b"<")
|
||||
if isinstance(root.tagName, str):
|
||||
tagName = root.tagName.encode("ascii")
|
||||
else:
|
||||
tagName = root.tagName
|
||||
write(tagName)
|
||||
for k, v in root.attributes.items():
|
||||
if isinstance(k, str):
|
||||
k = k.encode("ascii")
|
||||
write(b" " + k + b'="')
|
||||
# Serialize the contents of the attribute, wrapping the results of
|
||||
# that serialization so that _everything_ is quoted.
|
||||
yield keepGoing(
|
||||
v, attributeEscapingDoneOutside, write=writeWithAttributeEscaping(write)
|
||||
)
|
||||
write(b'"')
|
||||
if root.children or nativeString(tagName) not in voidElements:
|
||||
write(b">")
|
||||
# Regardless of whether we're in an attribute or not, switch back
|
||||
# to the escapeForContent dataEscaper. The contents of a tag must
|
||||
# be quoted no matter what; in the top-level document, just so
|
||||
# they're valid, and if they're within an attribute, they have to
|
||||
# be quoted so that after applying the *un*-quoting required to re-
|
||||
# parse the tag within the attribute, all the quoting is still
|
||||
# correct.
|
||||
yield keepGoing(root.children, escapeForContent)
|
||||
write(b"</" + tagName + b">")
|
||||
else:
|
||||
write(b" />")
|
||||
|
||||
elif isinstance(root, (tuple, list, GeneratorType)):
|
||||
for element in root:
|
||||
yield keepGoing(element)
|
||||
elif isinstance(root, CharRef):
|
||||
escaped = "&#%d;" % (root.ordinal,)
|
||||
write(escaped.encode("ascii"))
|
||||
elif isinstance(root, Deferred):
|
||||
yield keepGoingAsync(_fork(root))
|
||||
elif iscoroutine(root):
|
||||
yield keepGoingAsync(
|
||||
Deferred.fromCoroutine(
|
||||
cast(Coroutine[Deferred[Flattenable], object, Flattenable], root)
|
||||
)
|
||||
)
|
||||
elif IRenderable.providedBy(root):
|
||||
result = root.render(request)
|
||||
yield keepGoing(result, renderFactory=root)
|
||||
else:
|
||||
raise UnsupportedType(root)
|
||||
|
||||
|
||||
async def _flattenTree(
|
||||
request: Optional[IRequest], root: Flattenable, write: Callable[[bytes], object]
|
||||
) -> None:
|
||||
"""
|
||||
Make C{root} into an iterable of L{bytes} and L{Deferred} by doing a depth
|
||||
first traversal of the tree.
|
||||
|
||||
@param request: A request object which will be passed to
|
||||
L{IRenderable.render}.
|
||||
|
||||
@param root: An object to be made flatter. This may be of type C{unicode},
|
||||
L{bytes}, L{slot}, L{Tag <twisted.web.template.Tag>}, L{tuple},
|
||||
L{list}, L{types.GeneratorType}, L{Deferred}, or something providing
|
||||
L{IRenderable}.
|
||||
|
||||
@param write: A callable which will be invoked with each L{bytes} produced
|
||||
by flattening C{root}.
|
||||
|
||||
@return: A C{Deferred}-returning coroutine that resolves to C{None}.
|
||||
"""
|
||||
buf = []
|
||||
bufSize = 0
|
||||
|
||||
# Accumulate some bytes up to the buffer size so that we don't annoy the
|
||||
# upstream writer with a million tiny string.
|
||||
def bufferedWrite(bs: bytes) -> None:
|
||||
nonlocal bufSize
|
||||
buf.append(bs)
|
||||
bufSize += len(bs)
|
||||
if bufSize >= BUFFER_SIZE:
|
||||
flushBuffer()
|
||||
|
||||
# Deliver the buffered content to the upstream writer as a single string.
|
||||
# This is how a "big enough" buffer gets delivered, how a buffer of any
|
||||
# size is delivered before execution is suspended to wait for an
|
||||
# asynchronous value, and how anything left in the buffer when we're
|
||||
# finished is delivered.
|
||||
def flushBuffer() -> None:
|
||||
nonlocal bufSize
|
||||
if bufSize > 0:
|
||||
write(b"".join(buf))
|
||||
del buf[:]
|
||||
bufSize = 0
|
||||
|
||||
stack: List[Generator[Any, Any, Any]] = [
|
||||
_flattenElement(request, root, bufferedWrite, [], None, escapeForContent)
|
||||
]
|
||||
|
||||
while stack:
|
||||
try:
|
||||
element = next(stack[-1])
|
||||
if isinstance(element, Deferred):
|
||||
# Before suspending flattening for an unknown amount of time,
|
||||
# flush whatever data we have collected so far.
|
||||
flushBuffer()
|
||||
element = await element
|
||||
except StopIteration:
|
||||
stack.pop()
|
||||
except Exception as e:
|
||||
roots = []
|
||||
for generator in stack:
|
||||
if generator.gi_frame is not None:
|
||||
roots.append(generator.gi_frame.f_locals["root"])
|
||||
stack.pop()
|
||||
raise FlattenerError(e, roots, extract_tb(exc_info()[2]))
|
||||
else:
|
||||
stack.append(element)
|
||||
|
||||
# Flush any data that remains in the buffer before finishing.
|
||||
flushBuffer()
|
||||
|
||||
|
||||
def flatten(
|
||||
request: Optional[IRequest], root: Flattenable, write: Callable[[bytes], object]
|
||||
) -> Deferred[None]:
|
||||
"""
|
||||
Incrementally write out a string representation of C{root} using C{write}.
|
||||
|
||||
In order to create a string representation, C{root} will be decomposed into
|
||||
simpler objects which will themselves be decomposed and so on until strings
|
||||
or objects which can easily be converted to strings are encountered.
|
||||
|
||||
@param request: A request object which will be passed to the C{render}
|
||||
method of any L{IRenderable} provider which is encountered.
|
||||
|
||||
@param root: An object to be made flatter. This may be of type L{str},
|
||||
L{bytes}, L{slot}, L{Tag <twisted.web.template.Tag>}, L{tuple},
|
||||
L{list}, L{types.GeneratorType}, L{Deferred}, or something that
|
||||
provides L{IRenderable}.
|
||||
|
||||
@param write: A callable which will be invoked with each L{bytes} produced
|
||||
by flattening C{root}.
|
||||
|
||||
@return: A L{Deferred} which will be called back with C{None} when C{root}
|
||||
has been completely flattened into C{write} or which will be errbacked
|
||||
if an unexpected exception occurs.
|
||||
"""
|
||||
return ensureDeferred(_flattenTree(request, root, write))
|
||||
|
||||
|
||||
def flattenString(request: Optional[IRequest], root: Flattenable) -> Deferred[bytes]:
|
||||
"""
|
||||
Collate a string representation of C{root} into a single string.
|
||||
|
||||
This is basically gluing L{flatten} to an L{io.BytesIO} and returning
|
||||
the results. See L{flatten} for the exact meanings of C{request} and
|
||||
C{root}.
|
||||
|
||||
@return: A L{Deferred} which will be called back with a single UTF-8 encoded
|
||||
string as its result when C{root} has been completely flattened or which
|
||||
will be errbacked if an unexpected exception occurs.
|
||||
"""
|
||||
io = BytesIO()
|
||||
d = flatten(request, root, io.write)
|
||||
d.addCallback(lambda _: io.getvalue())
|
||||
return cast(Deferred[bytes], d)
|
||||
1290
backend/lib/python3.12/site-packages/twisted/web/_http2.py
Normal file
1290
backend/lib/python3.12/site-packages/twisted/web/_http2.py
Normal file
File diff suppressed because it is too large
Load Diff
1759
backend/lib/python3.12/site-packages/twisted/web/_newclient.py
Normal file
1759
backend/lib/python3.12/site-packages/twisted/web/_newclient.py
Normal file
File diff suppressed because it is too large
Load Diff
112
backend/lib/python3.12/site-packages/twisted/web/_responses.py
Normal file
112
backend/lib/python3.12/site-packages/twisted/web/_responses.py
Normal file
@@ -0,0 +1,112 @@
|
||||
# -*- test-case-name: twisted.web.test.test_http -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
HTTP response code definitions.
|
||||
"""
|
||||
|
||||
|
||||
_CONTINUE = 100
|
||||
SWITCHING = 101
|
||||
|
||||
OK = 200
|
||||
CREATED = 201
|
||||
ACCEPTED = 202
|
||||
NON_AUTHORITATIVE_INFORMATION = 203
|
||||
NO_CONTENT = 204
|
||||
RESET_CONTENT = 205
|
||||
PARTIAL_CONTENT = 206
|
||||
MULTI_STATUS = 207
|
||||
|
||||
MULTIPLE_CHOICE = 300
|
||||
MOVED_PERMANENTLY = 301
|
||||
FOUND = 302
|
||||
SEE_OTHER = 303
|
||||
NOT_MODIFIED = 304
|
||||
USE_PROXY = 305
|
||||
TEMPORARY_REDIRECT = 307
|
||||
PERMANENT_REDIRECT = 308
|
||||
|
||||
BAD_REQUEST = 400
|
||||
UNAUTHORIZED = 401
|
||||
PAYMENT_REQUIRED = 402
|
||||
FORBIDDEN = 403
|
||||
NOT_FOUND = 404
|
||||
NOT_ALLOWED = 405
|
||||
NOT_ACCEPTABLE = 406
|
||||
PROXY_AUTH_REQUIRED = 407
|
||||
REQUEST_TIMEOUT = 408
|
||||
CONFLICT = 409
|
||||
GONE = 410
|
||||
LENGTH_REQUIRED = 411
|
||||
PRECONDITION_FAILED = 412
|
||||
REQUEST_ENTITY_TOO_LARGE = 413
|
||||
REQUEST_URI_TOO_LONG = 414
|
||||
UNSUPPORTED_MEDIA_TYPE = 415
|
||||
REQUESTED_RANGE_NOT_SATISFIABLE = 416
|
||||
EXPECTATION_FAILED = 417
|
||||
IM_A_TEAPOT = 418
|
||||
|
||||
INTERNAL_SERVER_ERROR = 500
|
||||
NOT_IMPLEMENTED = 501
|
||||
BAD_GATEWAY = 502
|
||||
SERVICE_UNAVAILABLE = 503
|
||||
GATEWAY_TIMEOUT = 504
|
||||
HTTP_VERSION_NOT_SUPPORTED = 505
|
||||
INSUFFICIENT_STORAGE_SPACE = 507
|
||||
NOT_EXTENDED = 510
|
||||
|
||||
RESPONSES = {
|
||||
# 100
|
||||
_CONTINUE: b"Continue",
|
||||
SWITCHING: b"Switching Protocols",
|
||||
# 200
|
||||
OK: b"OK",
|
||||
CREATED: b"Created",
|
||||
ACCEPTED: b"Accepted",
|
||||
NON_AUTHORITATIVE_INFORMATION: b"Non-Authoritative Information",
|
||||
NO_CONTENT: b"No Content",
|
||||
RESET_CONTENT: b"Reset Content.",
|
||||
PARTIAL_CONTENT: b"Partial Content",
|
||||
MULTI_STATUS: b"Multi-Status",
|
||||
# 300
|
||||
MULTIPLE_CHOICE: b"Multiple Choices",
|
||||
MOVED_PERMANENTLY: b"Moved Permanently",
|
||||
FOUND: b"Found",
|
||||
SEE_OTHER: b"See Other",
|
||||
NOT_MODIFIED: b"Not Modified",
|
||||
USE_PROXY: b"Use Proxy",
|
||||
# 306 not defined??
|
||||
TEMPORARY_REDIRECT: b"Temporary Redirect",
|
||||
PERMANENT_REDIRECT: b"Permanent Redirect",
|
||||
# 400
|
||||
BAD_REQUEST: b"Bad Request",
|
||||
UNAUTHORIZED: b"Unauthorized",
|
||||
PAYMENT_REQUIRED: b"Payment Required",
|
||||
FORBIDDEN: b"Forbidden",
|
||||
NOT_FOUND: b"Not Found",
|
||||
NOT_ALLOWED: b"Method Not Allowed",
|
||||
NOT_ACCEPTABLE: b"Not Acceptable",
|
||||
PROXY_AUTH_REQUIRED: b"Proxy Authentication Required",
|
||||
REQUEST_TIMEOUT: b"Request Time-out",
|
||||
CONFLICT: b"Conflict",
|
||||
GONE: b"Gone",
|
||||
LENGTH_REQUIRED: b"Length Required",
|
||||
PRECONDITION_FAILED: b"Precondition Failed",
|
||||
REQUEST_ENTITY_TOO_LARGE: b"Request Entity Too Large",
|
||||
REQUEST_URI_TOO_LONG: b"Request-URI Too Long",
|
||||
UNSUPPORTED_MEDIA_TYPE: b"Unsupported Media Type",
|
||||
REQUESTED_RANGE_NOT_SATISFIABLE: b"Requested Range not satisfiable",
|
||||
EXPECTATION_FAILED: b"Expectation Failed",
|
||||
IM_A_TEAPOT: b"I'm a teapot",
|
||||
# 500
|
||||
INTERNAL_SERVER_ERROR: b"Internal Server Error",
|
||||
NOT_IMPLEMENTED: b"Not Implemented",
|
||||
BAD_GATEWAY: b"Bad Gateway",
|
||||
SERVICE_UNAVAILABLE: b"Service Unavailable",
|
||||
GATEWAY_TIMEOUT: b"Gateway Time-out",
|
||||
HTTP_VERSION_NOT_SUPPORTED: b"HTTP Version not supported",
|
||||
INSUFFICIENT_STORAGE_SPACE: b"Insufficient Storage Space",
|
||||
NOT_EXTENDED: b"Not Extended",
|
||||
}
|
||||
360
backend/lib/python3.12/site-packages/twisted/web/_stan.py
Normal file
360
backend/lib/python3.12/site-packages/twisted/web/_stan.py
Normal file
@@ -0,0 +1,360 @@
|
||||
# -*- test-case-name: twisted.web.test.test_stan -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
An s-expression-like syntax for expressing xml in pure python.
|
||||
|
||||
Stan tags allow you to build XML documents using Python.
|
||||
|
||||
Stan is a DOM, or Document Object Model, implemented using basic Python types
|
||||
and functions called "flatteners". A flattener is a function that knows how to
|
||||
turn an object of a specific type into something that is closer to an HTML
|
||||
string. Stan differs from the W3C DOM by not being as cumbersome and heavy
|
||||
weight. Since the object model is built using simple python types such as lists,
|
||||
strings, and dictionaries, the API is simpler and constructing a DOM less
|
||||
cumbersome.
|
||||
|
||||
@var voidElements: the names of HTML 'U{void
|
||||
elements<http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html#void-elements>}';
|
||||
those which can't have contents and can therefore be self-closing in the
|
||||
output.
|
||||
"""
|
||||
|
||||
|
||||
from inspect import iscoroutine, isgenerator
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional, Union
|
||||
from warnings import warn
|
||||
|
||||
import attr
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from twisted.web.template import Flattenable
|
||||
|
||||
|
||||
@attr.s(unsafe_hash=False, eq=False, auto_attribs=True)
|
||||
class slot:
|
||||
"""
|
||||
Marker for markup insertion in a template.
|
||||
"""
|
||||
|
||||
name: str
|
||||
"""
|
||||
The name of this slot.
|
||||
|
||||
The key which must be used in L{Tag.fillSlots} to fill it.
|
||||
"""
|
||||
|
||||
children: List["Tag"] = attr.ib(init=False, factory=list)
|
||||
"""
|
||||
The L{Tag} objects included in this L{slot}'s template.
|
||||
"""
|
||||
|
||||
default: Optional["Flattenable"] = None
|
||||
"""
|
||||
The default contents of this slot, if it is left unfilled.
|
||||
|
||||
If this is L{None}, an L{UnfilledSlot} will be raised, rather than
|
||||
L{None} actually being used.
|
||||
"""
|
||||
|
||||
filename: Optional[str] = None
|
||||
"""
|
||||
The name of the XML file from which this tag was parsed.
|
||||
|
||||
If it was not parsed from an XML file, L{None}.
|
||||
"""
|
||||
|
||||
lineNumber: Optional[int] = None
|
||||
"""
|
||||
The line number on which this tag was encountered in the XML file
|
||||
from which it was parsed.
|
||||
|
||||
If it was not parsed from an XML file, L{None}.
|
||||
"""
|
||||
|
||||
columnNumber: Optional[int] = None
|
||||
"""
|
||||
The column number at which this tag was encountered in the XML file
|
||||
from which it was parsed.
|
||||
|
||||
If it was not parsed from an XML file, L{None}.
|
||||
"""
|
||||
|
||||
|
||||
@attr.s(unsafe_hash=False, eq=False, repr=False, auto_attribs=True)
|
||||
class Tag:
|
||||
"""
|
||||
A L{Tag} represents an XML tags with a tag name, attributes, and children.
|
||||
A L{Tag} can be constructed using the special L{twisted.web.template.tags}
|
||||
object, or it may be constructed directly with a tag name. L{Tag}s have a
|
||||
special method, C{__call__}, which makes representing trees of XML natural
|
||||
using pure python syntax.
|
||||
"""
|
||||
|
||||
tagName: Union[bytes, str]
|
||||
"""
|
||||
The name of the represented element.
|
||||
|
||||
For a tag like C{<div></div>}, this would be C{"div"}.
|
||||
"""
|
||||
|
||||
attributes: Dict[Union[bytes, str], "Flattenable"] = attr.ib(factory=dict)
|
||||
"""The attributes of the element."""
|
||||
|
||||
children: List["Flattenable"] = attr.ib(factory=list)
|
||||
"""The contents of this C{Tag}."""
|
||||
|
||||
render: Optional[str] = None
|
||||
"""
|
||||
The name of the render method to use for this L{Tag}.
|
||||
|
||||
This name will be looked up at render time by the
|
||||
L{twisted.web.template.Element} doing the rendering,
|
||||
via L{twisted.web.template.Element.lookupRenderMethod},
|
||||
to determine which method to call.
|
||||
"""
|
||||
|
||||
filename: Optional[str] = None
|
||||
"""
|
||||
The name of the XML file from which this tag was parsed.
|
||||
|
||||
If it was not parsed from an XML file, L{None}.
|
||||
"""
|
||||
|
||||
lineNumber: Optional[int] = None
|
||||
"""
|
||||
The line number on which this tag was encountered in the XML file
|
||||
from which it was parsed.
|
||||
|
||||
If it was not parsed from an XML file, L{None}.
|
||||
"""
|
||||
|
||||
columnNumber: Optional[int] = None
|
||||
"""
|
||||
The column number at which this tag was encountered in the XML file
|
||||
from which it was parsed.
|
||||
|
||||
If it was not parsed from an XML file, L{None}.
|
||||
"""
|
||||
|
||||
slotData: Optional[Dict[str, "Flattenable"]] = attr.ib(init=False, default=None)
|
||||
"""
|
||||
The data which can fill slots.
|
||||
|
||||
If present, a dictionary mapping slot names to renderable values.
|
||||
The values in this dict might be anything that can be present as
|
||||
the child of a L{Tag}: strings, lists, L{Tag}s, generators, etc.
|
||||
"""
|
||||
|
||||
def fillSlots(self, **slots: "Flattenable") -> "Tag":
|
||||
"""
|
||||
Remember the slots provided at this position in the DOM.
|
||||
|
||||
During the rendering of children of this node, slots with names in
|
||||
C{slots} will be rendered as their corresponding values.
|
||||
|
||||
@return: C{self}. This enables the idiom C{return tag.fillSlots(...)} in
|
||||
renderers.
|
||||
"""
|
||||
if self.slotData is None:
|
||||
self.slotData = {}
|
||||
self.slotData.update(slots)
|
||||
return self
|
||||
|
||||
def __call__(self, *children: "Flattenable", **kw: "Flattenable") -> "Tag":
|
||||
"""
|
||||
Add children and change attributes on this tag.
|
||||
|
||||
This is implemented using __call__ because it then allows the natural
|
||||
syntax::
|
||||
|
||||
table(tr1, tr2, width="100%", height="50%", border="1")
|
||||
|
||||
Children may be other tag instances, strings, functions, or any other
|
||||
object which has a registered flatten.
|
||||
|
||||
Attributes may be 'transparent' tag instances (so that
|
||||
C{a(href=transparent(data="foo", render=myhrefrenderer))} works),
|
||||
strings, functions, or any other object which has a registered
|
||||
flattener.
|
||||
|
||||
If the attribute is a python keyword, such as 'class', you can add an
|
||||
underscore to the name, like 'class_'.
|
||||
|
||||
There is one special keyword argument, 'render', which will be used as
|
||||
the name of the renderer and saved as the 'render' attribute of this
|
||||
instance, rather than the DOM 'render' attribute in the attributes
|
||||
dictionary.
|
||||
"""
|
||||
self.children.extend(children)
|
||||
|
||||
for k, v in kw.items():
|
||||
if k[-1] == "_":
|
||||
k = k[:-1]
|
||||
|
||||
if k == "render":
|
||||
if not isinstance(v, str):
|
||||
raise TypeError(
|
||||
f'Value for "render" attribute must be str, got {v!r}'
|
||||
)
|
||||
self.render = v
|
||||
else:
|
||||
self.attributes[k] = v
|
||||
return self
|
||||
|
||||
def _clone(self, obj: "Flattenable", deep: bool) -> "Flattenable":
|
||||
"""
|
||||
Clone a C{Flattenable} object; used by L{Tag.clone}.
|
||||
|
||||
Note that both lists and tuples are cloned into lists.
|
||||
|
||||
@param obj: an object with a clone method, a list or tuple, or something
|
||||
which should be immutable.
|
||||
|
||||
@param deep: whether to continue cloning child objects; i.e. the
|
||||
contents of lists, the sub-tags within a tag.
|
||||
|
||||
@return: a clone of C{obj}.
|
||||
"""
|
||||
if hasattr(obj, "clone"):
|
||||
return obj.clone(deep)
|
||||
elif isinstance(obj, (list, tuple)):
|
||||
return [self._clone(x, deep) for x in obj]
|
||||
elif isgenerator(obj):
|
||||
warn(
|
||||
"Cloning a Tag which contains a generator is unsafe, "
|
||||
"since the generator can be consumed only once; "
|
||||
"this is deprecated since Twisted 21.7.0 and will raise "
|
||||
"an exception in the future",
|
||||
DeprecationWarning,
|
||||
)
|
||||
return obj
|
||||
elif iscoroutine(obj):
|
||||
warn(
|
||||
"Cloning a Tag which contains a coroutine is unsafe, "
|
||||
"since the coroutine can run only once; "
|
||||
"this is deprecated since Twisted 21.7.0 and will raise "
|
||||
"an exception in the future",
|
||||
DeprecationWarning,
|
||||
)
|
||||
return obj
|
||||
else:
|
||||
return obj
|
||||
|
||||
def clone(self, deep: bool = True) -> "Tag":
|
||||
"""
|
||||
Return a clone of this tag. If deep is True, clone all of this tag's
|
||||
children. Otherwise, just shallow copy the children list without copying
|
||||
the children themselves.
|
||||
"""
|
||||
if deep:
|
||||
newchildren = [self._clone(x, True) for x in self.children]
|
||||
else:
|
||||
newchildren = self.children[:]
|
||||
newattrs = self.attributes.copy()
|
||||
for key in newattrs.keys():
|
||||
newattrs[key] = self._clone(newattrs[key], True)
|
||||
|
||||
newslotdata = None
|
||||
if self.slotData:
|
||||
newslotdata = self.slotData.copy()
|
||||
for key in newslotdata:
|
||||
newslotdata[key] = self._clone(newslotdata[key], True)
|
||||
|
||||
newtag = Tag(
|
||||
self.tagName,
|
||||
attributes=newattrs,
|
||||
children=newchildren,
|
||||
render=self.render,
|
||||
filename=self.filename,
|
||||
lineNumber=self.lineNumber,
|
||||
columnNumber=self.columnNumber,
|
||||
)
|
||||
newtag.slotData = newslotdata
|
||||
|
||||
return newtag
|
||||
|
||||
def clear(self) -> "Tag":
|
||||
"""
|
||||
Clear any existing children from this tag.
|
||||
"""
|
||||
self.children = []
|
||||
return self
|
||||
|
||||
def __repr__(self) -> str:
|
||||
rstr = ""
|
||||
if self.attributes:
|
||||
rstr += ", attributes=%r" % self.attributes
|
||||
if self.children:
|
||||
rstr += ", children=%r" % self.children
|
||||
return f"Tag({self.tagName!r}{rstr})"
|
||||
|
||||
|
||||
voidElements = (
|
||||
"img",
|
||||
"br",
|
||||
"hr",
|
||||
"base",
|
||||
"meta",
|
||||
"link",
|
||||
"param",
|
||||
"area",
|
||||
"input",
|
||||
"col",
|
||||
"basefont",
|
||||
"isindex",
|
||||
"frame",
|
||||
"command",
|
||||
"embed",
|
||||
"keygen",
|
||||
"source",
|
||||
"track",
|
||||
"wbs",
|
||||
)
|
||||
|
||||
|
||||
@attr.s(unsafe_hash=False, eq=False, repr=False, auto_attribs=True)
|
||||
class CDATA:
|
||||
"""
|
||||
A C{<![CDATA[]]>} block from a template. Given a separate representation in
|
||||
the DOM so that they may be round-tripped through rendering without losing
|
||||
information.
|
||||
"""
|
||||
|
||||
data: str
|
||||
"""The data between "C{<![CDATA[}" and "C{]]>}"."""
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"CDATA({self.data!r})"
|
||||
|
||||
|
||||
@attr.s(unsafe_hash=False, eq=False, repr=False, auto_attribs=True)
|
||||
class Comment:
|
||||
"""
|
||||
A C{<!-- -->} comment from a template. Given a separate representation in
|
||||
the DOM so that they may be round-tripped through rendering without losing
|
||||
information.
|
||||
"""
|
||||
|
||||
data: str
|
||||
"""The data between "C{<!--}" and "C{-->}"."""
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Comment({self.data!r})"
|
||||
|
||||
|
||||
@attr.s(unsafe_hash=False, eq=False, repr=False, auto_attribs=True)
|
||||
class CharRef:
|
||||
"""
|
||||
A numeric character reference. Given a separate representation in the DOM
|
||||
so that non-ASCII characters may be output as pure ASCII.
|
||||
|
||||
@since: 12.0
|
||||
"""
|
||||
|
||||
ordinal: int
|
||||
"""The ordinal value of the unicode character to which this object refers."""
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "CharRef(%d)" % (self.ordinal,)
|
||||
1084
backend/lib/python3.12/site-packages/twisted/web/_template_util.py
Normal file
1084
backend/lib/python3.12/site-packages/twisted/web/_template_util.py
Normal file
File diff suppressed because it is too large
Load Diff
1815
backend/lib/python3.12/site-packages/twisted/web/client.py
Normal file
1815
backend/lib/python3.12/site-packages/twisted/web/client.py
Normal file
File diff suppressed because it is too large
Load Diff
27
backend/lib/python3.12/site-packages/twisted/web/demo.py
Normal file
27
backend/lib/python3.12/site-packages/twisted/web/demo.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
I am a simple test resource.
|
||||
"""
|
||||
|
||||
|
||||
from twisted.web import static
|
||||
|
||||
|
||||
class Test(static.Data):
|
||||
isLeaf = True
|
||||
|
||||
def __init__(self):
|
||||
static.Data.__init__(
|
||||
self,
|
||||
b"""
|
||||
<html>
|
||||
<head><title>Twisted Web Demo</title><head>
|
||||
<body>
|
||||
Hello! This is a Twisted Web test page.
|
||||
</body>
|
||||
</html>
|
||||
""",
|
||||
"text/html",
|
||||
)
|
||||
390
backend/lib/python3.12/site-packages/twisted/web/distrib.py
Normal file
390
backend/lib/python3.12/site-packages/twisted/web/distrib.py
Normal file
@@ -0,0 +1,390 @@
|
||||
# -*- test-case-name: twisted.web.test.test_distrib -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Distributed web servers.
|
||||
|
||||
This is going to have to be refactored so that argument parsing is done
|
||||
by each subprocess and not by the main web server (i.e. GET, POST etc.).
|
||||
"""
|
||||
|
||||
import copy
|
||||
import os
|
||||
import sys
|
||||
|
||||
try:
|
||||
import pwd
|
||||
except ImportError:
|
||||
pwd = None # type: ignore[assignment]
|
||||
from io import BytesIO
|
||||
from xml.dom.minidom import getDOMImplementation
|
||||
|
||||
from twisted.internet import address, reactor
|
||||
from twisted.logger import Logger
|
||||
from twisted.persisted import styles
|
||||
from twisted.spread import pb
|
||||
from twisted.spread.banana import SIZE_LIMIT
|
||||
from twisted.web import http, resource, server, static, util
|
||||
from twisted.web.http_headers import Headers
|
||||
|
||||
|
||||
class _ReferenceableProducerWrapper(pb.Referenceable):
|
||||
def __init__(self, producer):
|
||||
self.producer = producer
|
||||
|
||||
def remote_resumeProducing(self):
|
||||
self.producer.resumeProducing()
|
||||
|
||||
def remote_pauseProducing(self):
|
||||
self.producer.pauseProducing()
|
||||
|
||||
def remote_stopProducing(self):
|
||||
self.producer.stopProducing()
|
||||
|
||||
|
||||
class Request(pb.RemoteCopy, server.Request):
|
||||
"""
|
||||
A request which was received by a L{ResourceSubscription} and sent via
|
||||
PB to a distributed node.
|
||||
"""
|
||||
|
||||
def setCopyableState(self, state):
|
||||
"""
|
||||
Initialize this L{twisted.web.distrib.Request} based on the copied
|
||||
state so that it closely resembles a L{twisted.web.server.Request}.
|
||||
"""
|
||||
for k in "host", "client":
|
||||
tup = state[k]
|
||||
addrdesc = {"INET": "TCP", "UNIX": "UNIX"}[tup[0]]
|
||||
addr = {
|
||||
"TCP": lambda: address.IPv4Address(addrdesc, tup[1], tup[2]),
|
||||
"UNIX": lambda: address.UNIXAddress(tup[1]),
|
||||
}[addrdesc]()
|
||||
state[k] = addr
|
||||
state["requestHeaders"] = Headers(dict(state["requestHeaders"]))
|
||||
pb.RemoteCopy.setCopyableState(self, state)
|
||||
# Emulate the local request interface --
|
||||
self.content = BytesIO(self.content_data)
|
||||
self.finish = self.remote.remoteMethod("finish")
|
||||
self.setHeader = self.remote.remoteMethod("setHeader")
|
||||
self.addCookie = self.remote.remoteMethod("addCookie")
|
||||
self.setETag = self.remote.remoteMethod("setETag")
|
||||
self.setResponseCode = self.remote.remoteMethod("setResponseCode")
|
||||
self.setLastModified = self.remote.remoteMethod("setLastModified")
|
||||
|
||||
# To avoid failing if a resource tries to write a very long string
|
||||
# all at once, this one will be handled slightly differently.
|
||||
self._write = self.remote.remoteMethod("write")
|
||||
|
||||
def write(self, bytes):
|
||||
"""
|
||||
Write the given bytes to the response body.
|
||||
|
||||
@param bytes: The bytes to write. If this is longer than 640k, it
|
||||
will be split up into smaller pieces.
|
||||
"""
|
||||
start = 0
|
||||
end = SIZE_LIMIT
|
||||
while True:
|
||||
self._write(bytes[start:end])
|
||||
start += SIZE_LIMIT
|
||||
end += SIZE_LIMIT
|
||||
if start >= len(bytes):
|
||||
break
|
||||
|
||||
def registerProducer(self, producer, streaming):
|
||||
self.remote.callRemote(
|
||||
"registerProducer", _ReferenceableProducerWrapper(producer), streaming
|
||||
).addErrback(self.fail)
|
||||
|
||||
def unregisterProducer(self):
|
||||
self.remote.callRemote("unregisterProducer").addErrback(self.fail)
|
||||
|
||||
def fail(self, failure):
|
||||
self._log.failure("", failure=failure)
|
||||
|
||||
|
||||
pb.setUnjellyableForClass(server.Request, Request)
|
||||
|
||||
|
||||
class Issue:
|
||||
_log = Logger()
|
||||
|
||||
def __init__(self, request):
|
||||
self.request = request
|
||||
|
||||
def finished(self, result):
|
||||
if result is not server.NOT_DONE_YET:
|
||||
assert isinstance(result, str), "return value not a string"
|
||||
self.request.write(result)
|
||||
self.request.finish()
|
||||
|
||||
def failed(self, failure):
|
||||
# XXX: Argh. FIXME.
|
||||
failure = str(failure)
|
||||
self.request.write(
|
||||
resource._UnsafeErrorPage(
|
||||
http.INTERNAL_SERVER_ERROR,
|
||||
"Server Connection Lost",
|
||||
# GHSA-vg46-2rrj-3647 note: _PRE does HTML-escape the input.
|
||||
"Connection to distributed server lost:" + util._PRE(failure),
|
||||
).render(self.request)
|
||||
)
|
||||
self.request.finish()
|
||||
self._log.info(failure)
|
||||
|
||||
|
||||
class ResourceSubscription(resource.Resource):
|
||||
isLeaf = 1
|
||||
waiting = 0
|
||||
_log = Logger()
|
||||
|
||||
def __init__(self, host, port):
|
||||
resource.Resource.__init__(self)
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.pending = []
|
||||
self.publisher = None
|
||||
|
||||
def __getstate__(self):
|
||||
"""Get persistent state for this ResourceSubscription."""
|
||||
# When I unserialize,
|
||||
state = copy.copy(self.__dict__)
|
||||
# Publisher won't be connected...
|
||||
state["publisher"] = None
|
||||
# I won't be making a connection
|
||||
state["waiting"] = 0
|
||||
# There will be no pending requests.
|
||||
state["pending"] = []
|
||||
return state
|
||||
|
||||
def connected(self, publisher):
|
||||
"""I've connected to a publisher; I'll now send all my requests."""
|
||||
self._log.info("connected to publisher")
|
||||
publisher.broker.notifyOnDisconnect(self.booted)
|
||||
self.publisher = publisher
|
||||
self.waiting = 0
|
||||
for request in self.pending:
|
||||
self.render(request)
|
||||
self.pending = []
|
||||
|
||||
def notConnected(self, msg):
|
||||
"""I can't connect to a publisher; I'll now reply to all pending
|
||||
requests.
|
||||
"""
|
||||
self._log.info("could not connect to distributed web service: {msg}", msg=msg)
|
||||
self.waiting = 0
|
||||
self.publisher = None
|
||||
for request in self.pending:
|
||||
request.write("Unable to connect to distributed server.")
|
||||
request.finish()
|
||||
self.pending = []
|
||||
|
||||
def booted(self):
|
||||
self.notConnected("connection dropped")
|
||||
|
||||
def render(self, request):
|
||||
"""Render this request, from my server.
|
||||
|
||||
This will always be asynchronous, and therefore return NOT_DONE_YET.
|
||||
It spins off a request to the pb client, and either adds it to the list
|
||||
of pending issues or requests it immediately, depending on if the
|
||||
client is already connected.
|
||||
"""
|
||||
if not self.publisher:
|
||||
self.pending.append(request)
|
||||
if not self.waiting:
|
||||
self.waiting = 1
|
||||
bf = pb.PBClientFactory()
|
||||
timeout = 10
|
||||
if self.host == "unix":
|
||||
reactor.connectUNIX(self.port, bf, timeout)
|
||||
else:
|
||||
reactor.connectTCP(self.host, self.port, bf, timeout)
|
||||
d = bf.getRootObject()
|
||||
d.addCallbacks(self.connected, self.notConnected)
|
||||
|
||||
else:
|
||||
i = Issue(request)
|
||||
self.publisher.callRemote("request", request).addCallbacks(
|
||||
i.finished, i.failed
|
||||
)
|
||||
return server.NOT_DONE_YET
|
||||
|
||||
|
||||
class ResourcePublisher(pb.Root, styles.Versioned):
|
||||
"""
|
||||
L{ResourcePublisher} exposes a remote API which can be used to respond
|
||||
to request.
|
||||
|
||||
@ivar site: The site which will be used for resource lookup.
|
||||
@type site: L{twisted.web.server.Site}
|
||||
"""
|
||||
|
||||
_log = Logger()
|
||||
|
||||
def __init__(self, site):
|
||||
self.site = site
|
||||
|
||||
persistenceVersion = 2
|
||||
|
||||
def upgradeToVersion2(self):
|
||||
self.application.authorizer.removeIdentity("web")
|
||||
del self.application.services[self.serviceName]
|
||||
del self.serviceName
|
||||
del self.application
|
||||
del self.perspectiveName
|
||||
|
||||
def getPerspectiveNamed(self, name):
|
||||
return self
|
||||
|
||||
def remote_request(self, request):
|
||||
"""
|
||||
Look up the resource for the given request and render it.
|
||||
"""
|
||||
res = self.site.getResourceFor(request)
|
||||
self._log.info(request)
|
||||
result = res.render(request)
|
||||
if result is not server.NOT_DONE_YET:
|
||||
request.write(result)
|
||||
request.finish()
|
||||
return server.NOT_DONE_YET
|
||||
|
||||
|
||||
class UserDirectory(resource.Resource):
|
||||
"""
|
||||
A resource which lists available user resources and serves them as
|
||||
children.
|
||||
|
||||
@ivar _pwd: An object like L{pwd} which is used to enumerate users and
|
||||
their home directories.
|
||||
"""
|
||||
|
||||
userDirName = "public_html"
|
||||
userSocketName = ".twistd-web-pb"
|
||||
|
||||
template = """
|
||||
<html>
|
||||
<head>
|
||||
<title>twisted.web.distrib.UserDirectory</title>
|
||||
<style>
|
||||
|
||||
a
|
||||
{
|
||||
font-family: Lucida, Verdana, Helvetica, Arial, sans-serif;
|
||||
color: #369;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
th
|
||||
{
|
||||
font-family: Lucida, Verdana, Helvetica, Arial, sans-serif;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
pre, code
|
||||
{
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
}
|
||||
|
||||
p, body, td, ol, ul, menu, blockquote, div
|
||||
{
|
||||
font-family: Lucida, Verdana, Helvetica, Arial, sans-serif;
|
||||
color: #000;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>twisted.web.distrib.UserDirectory</h1>
|
||||
|
||||
%(users)s
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
def __init__(self, userDatabase=None):
|
||||
resource.Resource.__init__(self)
|
||||
if userDatabase is None:
|
||||
userDatabase = pwd
|
||||
self._pwd = userDatabase
|
||||
|
||||
def _users(self):
|
||||
"""
|
||||
Return a list of two-tuples giving links to user resources and text to
|
||||
associate with those links.
|
||||
"""
|
||||
users = []
|
||||
for user in self._pwd.getpwall():
|
||||
name, passwd, uid, gid, gecos, dir, shell = user
|
||||
realname = gecos.split(",")[0]
|
||||
if not realname:
|
||||
realname = name
|
||||
if os.path.exists(os.path.join(dir, self.userDirName)):
|
||||
users.append((name, realname + " (file)"))
|
||||
twistdsock = os.path.join(dir, self.userSocketName)
|
||||
if os.path.exists(twistdsock):
|
||||
linkName = name + ".twistd"
|
||||
users.append((linkName, realname + " (twistd)"))
|
||||
return users
|
||||
|
||||
def render_GET(self, request):
|
||||
"""
|
||||
Render as HTML a listing of all known users with links to their
|
||||
personal resources.
|
||||
"""
|
||||
|
||||
domImpl = getDOMImplementation()
|
||||
newDoc = domImpl.createDocument(None, "ul", None)
|
||||
listing = newDoc.documentElement
|
||||
for link, text in self._users():
|
||||
linkElement = newDoc.createElement("a")
|
||||
linkElement.setAttribute("href", link + "/")
|
||||
textNode = newDoc.createTextNode(text)
|
||||
linkElement.appendChild(textNode)
|
||||
item = newDoc.createElement("li")
|
||||
item.appendChild(linkElement)
|
||||
listing.appendChild(item)
|
||||
|
||||
htmlDoc = self.template % ({"users": listing.toxml()})
|
||||
return htmlDoc.encode("utf-8")
|
||||
|
||||
def getChild(self, name, request):
|
||||
if name == b"":
|
||||
return self
|
||||
|
||||
td = b".twistd"
|
||||
|
||||
if name.endswith(td):
|
||||
username = name[: -len(td)]
|
||||
sub = 1
|
||||
else:
|
||||
username = name
|
||||
sub = 0
|
||||
try:
|
||||
# Decode using the filesystem encoding to reverse a transformation
|
||||
# done in the pwd module.
|
||||
(
|
||||
pw_name,
|
||||
pw_passwd,
|
||||
pw_uid,
|
||||
pw_gid,
|
||||
pw_gecos,
|
||||
pw_dir,
|
||||
pw_shell,
|
||||
) = self._pwd.getpwnam(username.decode(sys.getfilesystemencoding()))
|
||||
except KeyError:
|
||||
return resource._UnsafeNoResource()
|
||||
if sub:
|
||||
twistdsock = os.path.join(pw_dir, self.userSocketName)
|
||||
rs = ResourceSubscription("unix", twistdsock)
|
||||
self.putChild(name, rs)
|
||||
return rs
|
||||
else:
|
||||
path = os.path.join(pw_dir, self.userDirName)
|
||||
if not os.path.exists(path):
|
||||
return resource._UnsafeNoResource()
|
||||
return static.File(path)
|
||||
313
backend/lib/python3.12/site-packages/twisted/web/domhelpers.py
Normal file
313
backend/lib/python3.12/site-packages/twisted/web/domhelpers.py
Normal file
@@ -0,0 +1,313 @@
|
||||
# -*- test-case-name: twisted.web.test.test_domhelpers -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
A library for performing interesting tasks with DOM objects.
|
||||
|
||||
This module is now deprecated.
|
||||
"""
|
||||
import warnings
|
||||
from io import StringIO
|
||||
|
||||
from incremental import Version, getVersionString
|
||||
|
||||
from twisted.web import microdom
|
||||
from twisted.web.microdom import escape, getElementsByTagName, unescape
|
||||
|
||||
warningString = "twisted.web.domhelpers was deprecated at {}".format(
|
||||
getVersionString(Version("Twisted", 23, 10, 0))
|
||||
)
|
||||
warnings.warn(warningString, DeprecationWarning, stacklevel=3)
|
||||
|
||||
|
||||
# These modules are imported here as a shortcut.
|
||||
escape
|
||||
getElementsByTagName
|
||||
|
||||
|
||||
class NodeLookupError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def substitute(request, node, subs):
|
||||
"""
|
||||
Look through the given node's children for strings, and
|
||||
attempt to do string substitution with the given parameter.
|
||||
"""
|
||||
for child in node.childNodes:
|
||||
if hasattr(child, "nodeValue") and child.nodeValue:
|
||||
child.replaceData(0, len(child.nodeValue), child.nodeValue % subs)
|
||||
substitute(request, child, subs)
|
||||
|
||||
|
||||
def _get(node, nodeId, nodeAttrs=("id", "class", "model", "pattern")):
|
||||
"""
|
||||
(internal) Get a node with the specified C{nodeId} as any of the C{class},
|
||||
C{id} or C{pattern} attributes.
|
||||
"""
|
||||
|
||||
if hasattr(node, "hasAttributes") and node.hasAttributes():
|
||||
for nodeAttr in nodeAttrs:
|
||||
if str(node.getAttribute(nodeAttr)) == nodeId:
|
||||
return node
|
||||
if node.hasChildNodes():
|
||||
if hasattr(node.childNodes, "length"):
|
||||
length = node.childNodes.length
|
||||
else:
|
||||
length = len(node.childNodes)
|
||||
for childNum in range(length):
|
||||
result = _get(node.childNodes[childNum], nodeId)
|
||||
if result:
|
||||
return result
|
||||
|
||||
|
||||
def get(node, nodeId):
|
||||
"""
|
||||
Get a node with the specified C{nodeId} as any of the C{class},
|
||||
C{id} or C{pattern} attributes. If there is no such node, raise
|
||||
L{NodeLookupError}.
|
||||
"""
|
||||
result = _get(node, nodeId)
|
||||
if result:
|
||||
return result
|
||||
raise NodeLookupError(nodeId)
|
||||
|
||||
|
||||
def getIfExists(node, nodeId):
|
||||
"""
|
||||
Get a node with the specified C{nodeId} as any of the C{class},
|
||||
C{id} or C{pattern} attributes. If there is no such node, return
|
||||
L{None}.
|
||||
"""
|
||||
return _get(node, nodeId)
|
||||
|
||||
|
||||
def getAndClear(node, nodeId):
|
||||
"""Get a node with the specified C{nodeId} as any of the C{class},
|
||||
C{id} or C{pattern} attributes. If there is no such node, raise
|
||||
L{NodeLookupError}. Remove all child nodes before returning.
|
||||
"""
|
||||
result = get(node, nodeId)
|
||||
if result:
|
||||
clearNode(result)
|
||||
return result
|
||||
|
||||
|
||||
def clearNode(node):
|
||||
"""
|
||||
Remove all children from the given node.
|
||||
"""
|
||||
node.childNodes[:] = []
|
||||
|
||||
|
||||
def locateNodes(nodeList, key, value, noNesting=1):
|
||||
"""
|
||||
Find subnodes in the given node where the given attribute
|
||||
has the given value.
|
||||
"""
|
||||
returnList = []
|
||||
if not isinstance(nodeList, type([])):
|
||||
return locateNodes(nodeList.childNodes, key, value, noNesting)
|
||||
for childNode in nodeList:
|
||||
if not hasattr(childNode, "getAttribute"):
|
||||
continue
|
||||
if str(childNode.getAttribute(key)) == value:
|
||||
returnList.append(childNode)
|
||||
if noNesting:
|
||||
continue
|
||||
returnList.extend(locateNodes(childNode, key, value, noNesting))
|
||||
return returnList
|
||||
|
||||
|
||||
def superSetAttribute(node, key, value):
|
||||
if not hasattr(node, "setAttribute"):
|
||||
return
|
||||
node.setAttribute(key, value)
|
||||
if node.hasChildNodes():
|
||||
for child in node.childNodes:
|
||||
superSetAttribute(child, key, value)
|
||||
|
||||
|
||||
def superPrependAttribute(node, key, value):
|
||||
if not hasattr(node, "setAttribute"):
|
||||
return
|
||||
old = node.getAttribute(key)
|
||||
if old:
|
||||
node.setAttribute(key, value + "/" + old)
|
||||
else:
|
||||
node.setAttribute(key, value)
|
||||
if node.hasChildNodes():
|
||||
for child in node.childNodes:
|
||||
superPrependAttribute(child, key, value)
|
||||
|
||||
|
||||
def superAppendAttribute(node, key, value):
|
||||
if not hasattr(node, "setAttribute"):
|
||||
return
|
||||
old = node.getAttribute(key)
|
||||
if old:
|
||||
node.setAttribute(key, old + "/" + value)
|
||||
else:
|
||||
node.setAttribute(key, value)
|
||||
if node.hasChildNodes():
|
||||
for child in node.childNodes:
|
||||
superAppendAttribute(child, key, value)
|
||||
|
||||
|
||||
def gatherTextNodes(iNode, dounescape=0, joinWith=""):
|
||||
"""Visit each child node and collect its text data, if any, into a string.
|
||||
For example::
|
||||
>>> doc=microdom.parseString('<a>1<b>2<c>3</c>4</b></a>')
|
||||
>>> gatherTextNodes(doc.documentElement)
|
||||
'1234'
|
||||
With dounescape=1, also convert entities back into normal characters.
|
||||
@return: the gathered nodes as a single string
|
||||
@rtype: str"""
|
||||
gathered = []
|
||||
gathered_append = gathered.append
|
||||
slice = [iNode]
|
||||
while len(slice) > 0:
|
||||
c = slice.pop(0)
|
||||
if hasattr(c, "nodeValue") and c.nodeValue is not None:
|
||||
if dounescape:
|
||||
val = unescape(c.nodeValue)
|
||||
else:
|
||||
val = c.nodeValue
|
||||
gathered_append(val)
|
||||
slice[:0] = c.childNodes
|
||||
return joinWith.join(gathered)
|
||||
|
||||
|
||||
class RawText(microdom.Text):
|
||||
"""This is an evil and horrible speed hack. Basically, if you have a big
|
||||
chunk of XML that you want to insert into the DOM, but you don't want to
|
||||
incur the cost of parsing it, you can construct one of these and insert it
|
||||
into the DOM. This will most certainly only work with microdom as the API
|
||||
for converting nodes to xml is different in every DOM implementation.
|
||||
|
||||
This could be improved by making this class a Lazy parser, so if you
|
||||
inserted this into the DOM and then later actually tried to mutate this
|
||||
node, it would be parsed then.
|
||||
"""
|
||||
|
||||
def writexml(
|
||||
self,
|
||||
writer,
|
||||
indent="",
|
||||
addindent="",
|
||||
newl="",
|
||||
strip=0,
|
||||
nsprefixes=None,
|
||||
namespace=None,
|
||||
):
|
||||
writer.write(f"{indent}{self.data}{newl}")
|
||||
|
||||
|
||||
def findNodes(parent, matcher, accum=None):
|
||||
if accum is None:
|
||||
accum = []
|
||||
if not parent.hasChildNodes():
|
||||
return accum
|
||||
for child in parent.childNodes:
|
||||
# print child, child.nodeType, child.nodeName
|
||||
if matcher(child):
|
||||
accum.append(child)
|
||||
findNodes(child, matcher, accum)
|
||||
return accum
|
||||
|
||||
|
||||
def findNodesShallowOnMatch(parent, matcher, recurseMatcher, accum=None):
|
||||
if accum is None:
|
||||
accum = []
|
||||
if not parent.hasChildNodes():
|
||||
return accum
|
||||
for child in parent.childNodes:
|
||||
# print child, child.nodeType, child.nodeName
|
||||
if matcher(child):
|
||||
accum.append(child)
|
||||
if recurseMatcher(child):
|
||||
findNodesShallowOnMatch(child, matcher, recurseMatcher, accum)
|
||||
return accum
|
||||
|
||||
|
||||
def findNodesShallow(parent, matcher, accum=None):
|
||||
if accum is None:
|
||||
accum = []
|
||||
if not parent.hasChildNodes():
|
||||
return accum
|
||||
for child in parent.childNodes:
|
||||
if matcher(child):
|
||||
accum.append(child)
|
||||
else:
|
||||
findNodes(child, matcher, accum)
|
||||
return accum
|
||||
|
||||
|
||||
def findElementsWithAttributeShallow(parent, attribute):
|
||||
"""
|
||||
Return an iterable of the elements which are direct children of C{parent}
|
||||
and which have the C{attribute} attribute.
|
||||
"""
|
||||
return findNodesShallow(
|
||||
parent,
|
||||
lambda n: getattr(n, "tagName", None) is not None and n.hasAttribute(attribute),
|
||||
)
|
||||
|
||||
|
||||
def findElements(parent, matcher):
|
||||
"""
|
||||
Return an iterable of the elements which are children of C{parent} for
|
||||
which the predicate C{matcher} returns true.
|
||||
"""
|
||||
return findNodes(
|
||||
parent,
|
||||
lambda n, matcher=matcher: getattr(n, "tagName", None) is not None
|
||||
and matcher(n),
|
||||
)
|
||||
|
||||
|
||||
def findElementsWithAttribute(parent, attribute, value=None):
|
||||
if value:
|
||||
return findElements(
|
||||
parent,
|
||||
lambda n, attribute=attribute, value=value: n.hasAttribute(attribute)
|
||||
and n.getAttribute(attribute) == value,
|
||||
)
|
||||
else:
|
||||
return findElements(
|
||||
parent, lambda n, attribute=attribute: n.hasAttribute(attribute)
|
||||
)
|
||||
|
||||
|
||||
def findNodesNamed(parent, name):
|
||||
return findNodes(parent, lambda n, name=name: n.nodeName == name)
|
||||
|
||||
|
||||
def writeNodeData(node, oldio):
|
||||
for subnode in node.childNodes:
|
||||
if hasattr(subnode, "data"):
|
||||
oldio.write("" + subnode.data)
|
||||
else:
|
||||
writeNodeData(subnode, oldio)
|
||||
|
||||
|
||||
def getNodeText(node):
|
||||
oldio = StringIO()
|
||||
writeNodeData(node, oldio)
|
||||
return oldio.getvalue()
|
||||
|
||||
|
||||
def getParents(node):
|
||||
l = []
|
||||
while node:
|
||||
l.append(node)
|
||||
node = node.parentNode
|
||||
return l
|
||||
|
||||
|
||||
def namedChildren(parent, nodeName):
|
||||
"""namedChildren(parent, nodeName) -> children (not descendants) of parent
|
||||
that have tagName == nodeName
|
||||
"""
|
||||
return [n for n in parent.childNodes if getattr(n, "tagName", "") == nodeName]
|
||||
442
backend/lib/python3.12/site-packages/twisted/web/error.py
Normal file
442
backend/lib/python3.12/site-packages/twisted/web/error.py
Normal file
@@ -0,0 +1,442 @@
|
||||
# -*- test-case-name: twisted.web.test.test_error -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Exception definitions for L{twisted.web}.
|
||||
"""
|
||||
|
||||
__all__ = [
|
||||
"Error",
|
||||
"PageRedirect",
|
||||
"InfiniteRedirection",
|
||||
"RenderError",
|
||||
"MissingRenderMethod",
|
||||
"MissingTemplateLoader",
|
||||
"UnexposedMethodError",
|
||||
"UnfilledSlot",
|
||||
"UnsupportedType",
|
||||
"FlattenerError",
|
||||
"RedirectWithNoLocation",
|
||||
]
|
||||
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import Optional, Union, cast
|
||||
|
||||
from twisted.python.compat import nativeString
|
||||
from twisted.web._responses import RESPONSES
|
||||
|
||||
|
||||
def _codeToMessage(code: Union[int, bytes]) -> Optional[bytes]:
|
||||
"""
|
||||
Returns the response message corresponding to an HTTP code, or None
|
||||
if the code is unknown or unrecognized.
|
||||
|
||||
@param code: HTTP status code, for example C{http.NOT_FOUND}.
|
||||
|
||||
@return: A string message or none
|
||||
"""
|
||||
try:
|
||||
return RESPONSES.get(int(code))
|
||||
except (ValueError, AttributeError):
|
||||
return None
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
"""
|
||||
A basic HTTP error.
|
||||
|
||||
@ivar status: Refers to an HTTP status code, for example C{http.NOT_FOUND}.
|
||||
|
||||
@param message: A short error message, for example "NOT FOUND".
|
||||
|
||||
@ivar response: A complete HTML document for an error page.
|
||||
"""
|
||||
|
||||
status: bytes
|
||||
message: Optional[bytes]
|
||||
response: Optional[bytes]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
code: Union[int, bytes],
|
||||
message: Optional[bytes] = None,
|
||||
response: Optional[bytes] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Initializes a basic exception.
|
||||
|
||||
@type code: L{bytes} or L{int}
|
||||
@param code: Refers to an HTTP status code (for example, 200) either as
|
||||
an integer or a bytestring representing such. If no C{message} is
|
||||
given, C{code} is mapped to a descriptive bytestring that is used
|
||||
instead.
|
||||
|
||||
@type message: L{bytes}
|
||||
@param message: A short error message, for example C{b"NOT FOUND"}.
|
||||
|
||||
@type response: L{bytes}
|
||||
@param response: A complete HTML document for an error page.
|
||||
"""
|
||||
|
||||
message = message or _codeToMessage(code)
|
||||
|
||||
Exception.__init__(self, code, message, response)
|
||||
|
||||
if isinstance(code, int):
|
||||
# If we're given an int, convert it to a bytestring
|
||||
# downloadPage gives a bytes, Agent gives an int, and it worked by
|
||||
# accident previously, so just make it keep working.
|
||||
code = b"%d" % (code,)
|
||||
elif len(code) != 3 or not code.isdigit():
|
||||
# Status codes must be 3 digits. See
|
||||
# https://httpwg.org/specs/rfc9110.html#status.code.extensibility
|
||||
raise ValueError(f"Not a valid HTTP status code: {code!r}")
|
||||
|
||||
self.status = code
|
||||
self.message = message
|
||||
self.response = response
|
||||
|
||||
def __str__(self) -> str:
|
||||
s = self.status
|
||||
if self.message:
|
||||
s += b" " + self.message
|
||||
return nativeString(s)
|
||||
|
||||
|
||||
class PageRedirect(Error):
|
||||
"""
|
||||
A request resulted in an HTTP redirect.
|
||||
|
||||
@ivar location: The location of the redirect which was not followed.
|
||||
"""
|
||||
|
||||
location: Optional[bytes]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
code: Union[int, bytes],
|
||||
message: Optional[bytes] = None,
|
||||
response: Optional[bytes] = None,
|
||||
location: Optional[bytes] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Initializes a page redirect exception.
|
||||
|
||||
@type code: L{bytes}
|
||||
@param code: Refers to an HTTP status code, for example
|
||||
C{http.NOT_FOUND}. If no C{message} is given, C{code} is mapped to a
|
||||
descriptive string that is used instead.
|
||||
|
||||
@type message: L{bytes}
|
||||
@param message: A short error message, for example C{b"NOT FOUND"}.
|
||||
|
||||
@type response: L{bytes}
|
||||
@param response: A complete HTML document for an error page.
|
||||
|
||||
@type location: L{bytes}
|
||||
@param location: The location response-header field value. It is an
|
||||
absolute URI used to redirect the receiver to a location other than
|
||||
the Request-URI so the request can be completed.
|
||||
"""
|
||||
Error.__init__(self, code, message, response)
|
||||
if self.message and location:
|
||||
self.message = self.message + b" to " + location
|
||||
self.location = location
|
||||
|
||||
|
||||
class InfiniteRedirection(Error):
|
||||
"""
|
||||
HTTP redirection is occurring endlessly.
|
||||
|
||||
@ivar location: The first URL in the series of redirections which was
|
||||
not followed.
|
||||
"""
|
||||
|
||||
location: Optional[bytes]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
code: Union[int, bytes],
|
||||
message: Optional[bytes] = None,
|
||||
response: Optional[bytes] = None,
|
||||
location: Optional[bytes] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Initializes an infinite redirection exception.
|
||||
|
||||
@param code: Refers to an HTTP status code, for example
|
||||
C{http.NOT_FOUND}. If no C{message} is given, C{code} is mapped to a
|
||||
descriptive string that is used instead.
|
||||
|
||||
@param message: A short error message, for example C{b"NOT FOUND"}.
|
||||
|
||||
@param response: A complete HTML document for an error page.
|
||||
|
||||
@param location: The location response-header field value. It is an
|
||||
absolute URI used to redirect the receiver to a location other than
|
||||
the Request-URI so the request can be completed.
|
||||
"""
|
||||
Error.__init__(self, code, message, response)
|
||||
if self.message and location:
|
||||
self.message = self.message + b" to " + location
|
||||
self.location = location
|
||||
|
||||
|
||||
class RedirectWithNoLocation(Error):
|
||||
"""
|
||||
Exception passed to L{ResponseFailed} if we got a redirect without a
|
||||
C{Location} header field.
|
||||
|
||||
@type uri: L{bytes}
|
||||
@ivar uri: The URI which failed to give a proper location header
|
||||
field.
|
||||
|
||||
@since: 11.1
|
||||
"""
|
||||
|
||||
message: bytes
|
||||
uri: bytes
|
||||
|
||||
def __init__(self, code: Union[bytes, int], message: bytes, uri: bytes) -> None:
|
||||
"""
|
||||
Initializes a page redirect exception when no location is given.
|
||||
|
||||
@type code: L{bytes}
|
||||
@param code: Refers to an HTTP status code, for example
|
||||
C{http.NOT_FOUND}. If no C{message} is given, C{code} is mapped to
|
||||
a descriptive string that is used instead.
|
||||
|
||||
@type message: L{bytes}
|
||||
@param message: A short error message.
|
||||
|
||||
@type uri: L{bytes}
|
||||
@param uri: The URI which failed to give a proper location header
|
||||
field.
|
||||
"""
|
||||
Error.__init__(self, code, message)
|
||||
self.message = self.message + b" to " + uri
|
||||
self.uri = uri
|
||||
|
||||
|
||||
class UnsupportedMethod(Exception):
|
||||
"""
|
||||
Raised by a resource when faced with a strange request method.
|
||||
|
||||
RFC 2616 (HTTP 1.1) gives us two choices when faced with this situation:
|
||||
If the type of request is known to us, but not allowed for the requested
|
||||
resource, respond with NOT_ALLOWED. Otherwise, if the request is something
|
||||
we don't know how to deal with in any case, respond with NOT_IMPLEMENTED.
|
||||
|
||||
When this exception is raised by a Resource's render method, the server
|
||||
will make the appropriate response.
|
||||
|
||||
This exception's first argument MUST be a sequence of the methods the
|
||||
resource *does* support.
|
||||
"""
|
||||
|
||||
allowedMethods = ()
|
||||
|
||||
def __init__(self, allowedMethods, *args):
|
||||
Exception.__init__(self, allowedMethods, *args)
|
||||
self.allowedMethods = allowedMethods
|
||||
|
||||
if not isinstance(allowedMethods, Sequence):
|
||||
raise TypeError(
|
||||
"First argument must be a sequence of supported methods, "
|
||||
"but my first argument is not a sequence."
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"Expected one of {self.allowedMethods!r}"
|
||||
|
||||
|
||||
class SchemeNotSupported(Exception):
|
||||
"""
|
||||
The scheme of a URI was not one of the supported values.
|
||||
"""
|
||||
|
||||
|
||||
class RenderError(Exception):
|
||||
"""
|
||||
Base exception class for all errors which can occur during template
|
||||
rendering.
|
||||
"""
|
||||
|
||||
|
||||
class MissingRenderMethod(RenderError):
|
||||
"""
|
||||
Tried to use a render method which does not exist.
|
||||
|
||||
@ivar element: The element which did not have the render method.
|
||||
@ivar renderName: The name of the renderer which could not be found.
|
||||
"""
|
||||
|
||||
def __init__(self, element, renderName):
|
||||
RenderError.__init__(self, element, renderName)
|
||||
self.element = element
|
||||
self.renderName = renderName
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "{!r}: {!r} had no render method named {!r}".format(
|
||||
self.__class__.__name__,
|
||||
self.element,
|
||||
self.renderName,
|
||||
)
|
||||
|
||||
|
||||
class MissingTemplateLoader(RenderError):
|
||||
"""
|
||||
L{MissingTemplateLoader} is raised when trying to render an Element without
|
||||
a template loader, i.e. a C{loader} attribute.
|
||||
|
||||
@ivar element: The Element which did not have a document factory.
|
||||
"""
|
||||
|
||||
def __init__(self, element):
|
||||
RenderError.__init__(self, element)
|
||||
self.element = element
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.__class__.__name__!r}: {self.element!r} had no loader"
|
||||
|
||||
|
||||
class UnexposedMethodError(Exception):
|
||||
"""
|
||||
Raised on any attempt to get a method which has not been exposed.
|
||||
"""
|
||||
|
||||
|
||||
class UnfilledSlot(Exception):
|
||||
"""
|
||||
During flattening, a slot with no associated data was encountered.
|
||||
"""
|
||||
|
||||
|
||||
class UnsupportedType(Exception):
|
||||
"""
|
||||
During flattening, an object of a type which cannot be flattened was
|
||||
encountered.
|
||||
"""
|
||||
|
||||
|
||||
class ExcessiveBufferingError(Exception):
|
||||
"""
|
||||
The HTTP/2 protocol has been forced to buffer an excessive amount of
|
||||
outbound data, and has therefore closed the connection and dropped all
|
||||
outbound data.
|
||||
"""
|
||||
|
||||
|
||||
class FlattenerError(Exception):
|
||||
"""
|
||||
An error occurred while flattening an object.
|
||||
|
||||
@ivar _roots: A list of the objects on the flattener's stack at the time
|
||||
the unflattenable object was encountered. The first element is least
|
||||
deeply nested object and the last element is the most deeply nested.
|
||||
"""
|
||||
|
||||
def __init__(self, exception, roots, traceback):
|
||||
self._exception = exception
|
||||
self._roots = roots
|
||||
self._traceback = traceback
|
||||
Exception.__init__(self, exception, roots, traceback)
|
||||
|
||||
def _formatRoot(self, obj):
|
||||
"""
|
||||
Convert an object from C{self._roots} to a string suitable for
|
||||
inclusion in a render-traceback (like a normal Python traceback, but
|
||||
can include "frame" source locations which are not in Python source
|
||||
files).
|
||||
|
||||
@param obj: Any object which can be a render step I{root}.
|
||||
Typically, L{Tag}s, strings, and other simple Python types.
|
||||
|
||||
@return: A string representation of C{obj}.
|
||||
@rtype: L{str}
|
||||
"""
|
||||
# There's a circular dependency between this class and 'Tag', although
|
||||
# only for an isinstance() check.
|
||||
from twisted.web.template import Tag
|
||||
|
||||
if isinstance(obj, (bytes, str)):
|
||||
# It's somewhat unlikely that there will ever be a str in the roots
|
||||
# list. However, something like a MemoryError during a str.replace
|
||||
# call (eg, replacing " with ") could possibly cause this.
|
||||
# Likewise, UTF-8 encoding a unicode string to a byte string might
|
||||
# fail like this.
|
||||
if len(obj) > 40:
|
||||
if isinstance(obj, str):
|
||||
ellipsis = "<...>"
|
||||
else:
|
||||
ellipsis = b"<...>"
|
||||
return ascii(obj[:20] + ellipsis + obj[-20:])
|
||||
else:
|
||||
return ascii(obj)
|
||||
elif isinstance(obj, Tag):
|
||||
if obj.filename is None:
|
||||
return "Tag <" + obj.tagName + ">"
|
||||
else:
|
||||
return 'File "%s", line %d, column %d, in "%s"' % (
|
||||
obj.filename,
|
||||
obj.lineNumber,
|
||||
obj.columnNumber,
|
||||
obj.tagName,
|
||||
)
|
||||
else:
|
||||
return ascii(obj)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""
|
||||
Present a string representation which includes a template traceback, so
|
||||
we can tell where this error occurred in the template, as well as in
|
||||
Python.
|
||||
"""
|
||||
# Avoid importing things unnecessarily until we actually need them;
|
||||
# since this is an 'error' module we should be extra paranoid about
|
||||
# that.
|
||||
from traceback import format_list
|
||||
|
||||
if self._roots:
|
||||
roots = (
|
||||
" " + "\n ".join([self._formatRoot(r) for r in self._roots]) + "\n"
|
||||
)
|
||||
else:
|
||||
roots = ""
|
||||
if self._traceback:
|
||||
traceback = (
|
||||
"\n".join(
|
||||
[
|
||||
line
|
||||
for entry in format_list(self._traceback)
|
||||
for line in entry.splitlines()
|
||||
]
|
||||
)
|
||||
+ "\n"
|
||||
)
|
||||
else:
|
||||
traceback = ""
|
||||
return cast(
|
||||
str,
|
||||
(
|
||||
"Exception while flattening:\n"
|
||||
+ roots
|
||||
+ traceback
|
||||
+ self._exception.__class__.__name__
|
||||
+ ": "
|
||||
+ str(self._exception)
|
||||
+ "\n"
|
||||
),
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return repr(self)
|
||||
|
||||
|
||||
class UnsupportedSpecialHeader(Exception):
|
||||
"""
|
||||
A HTTP/2 request was received that contained a HTTP/2 pseudo-header field
|
||||
that is not recognised by Twisted.
|
||||
"""
|
||||
21
backend/lib/python3.12/site-packages/twisted/web/guard.py
Normal file
21
backend/lib/python3.12/site-packages/twisted/web/guard.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# -*- test-case-name: twisted.web.test.test_httpauth -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Resource traversal integration with L{twisted.cred} to allow for
|
||||
authentication and authorization of HTTP requests.
|
||||
"""
|
||||
|
||||
|
||||
from twisted.web._auth.basic import BasicCredentialFactory
|
||||
from twisted.web._auth.digest import DigestCredentialFactory
|
||||
|
||||
# Expose HTTP authentication classes here.
|
||||
from twisted.web._auth.wrapper import HTTPAuthSessionWrapper
|
||||
|
||||
__all__ = [
|
||||
"HTTPAuthSessionWrapper",
|
||||
"BasicCredentialFactory",
|
||||
"DigestCredentialFactory",
|
||||
]
|
||||
56
backend/lib/python3.12/site-packages/twisted/web/html.py
Normal file
56
backend/lib/python3.12/site-packages/twisted/web/html.py
Normal file
@@ -0,0 +1,56 @@
|
||||
# -*- test-case-name: twisted.web.test.test_html -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
|
||||
"""I hold HTML generation helpers.
|
||||
"""
|
||||
|
||||
from html import escape
|
||||
from io import StringIO
|
||||
|
||||
from incremental import Version
|
||||
|
||||
from twisted.python import log
|
||||
from twisted.python.deprecate import deprecated
|
||||
|
||||
|
||||
@deprecated(Version("Twisted", 15, 3, 0), replacement="twisted.web.template")
|
||||
def PRE(text):
|
||||
"Wrap <pre> tags around some text and HTML-escape it."
|
||||
return "<pre>" + escape(text) + "</pre>"
|
||||
|
||||
|
||||
@deprecated(Version("Twisted", 15, 3, 0), replacement="twisted.web.template")
|
||||
def UL(lst):
|
||||
io = StringIO()
|
||||
io.write("<ul>\n")
|
||||
for el in lst:
|
||||
io.write("<li> %s</li>\n" % el)
|
||||
io.write("</ul>")
|
||||
return io.getvalue()
|
||||
|
||||
|
||||
@deprecated(Version("Twisted", 15, 3, 0), replacement="twisted.web.template")
|
||||
def linkList(lst):
|
||||
io = StringIO()
|
||||
io.write("<ul>\n")
|
||||
for hr, el in lst:
|
||||
io.write(f'<li> <a href="{hr}">{el}</a></li>\n')
|
||||
io.write("</ul>")
|
||||
return io.getvalue()
|
||||
|
||||
|
||||
@deprecated(Version("Twisted", 15, 3, 0), replacement="twisted.web.template")
|
||||
def output(func, *args, **kw):
|
||||
"""output(func, *args, **kw) -> html string
|
||||
Either return the result of a function (which presumably returns an
|
||||
HTML-legal string) or a sparse HTMLized error message and a message
|
||||
in the server log.
|
||||
"""
|
||||
try:
|
||||
return func(*args, **kw)
|
||||
except BaseException:
|
||||
log.msg(f"Error calling {func!r}:")
|
||||
log.err()
|
||||
return PRE("An error occurred.")
|
||||
3440
backend/lib/python3.12/site-packages/twisted/web/http.py
Normal file
3440
backend/lib/python3.12/site-packages/twisted/web/http.py
Normal file
File diff suppressed because it is too large
Load Diff
284
backend/lib/python3.12/site-packages/twisted/web/http_headers.py
Normal file
284
backend/lib/python3.12/site-packages/twisted/web/http_headers.py
Normal file
@@ -0,0 +1,284 @@
|
||||
# -*- test-case-name: twisted.web.test.test_http_headers -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
An API for storing HTTP header names and values.
|
||||
"""
|
||||
|
||||
from typing import (
|
||||
AnyStr,
|
||||
ClassVar,
|
||||
Dict,
|
||||
Iterator,
|
||||
List,
|
||||
Mapping,
|
||||
Optional,
|
||||
Sequence,
|
||||
Tuple,
|
||||
TypeVar,
|
||||
Union,
|
||||
overload,
|
||||
)
|
||||
|
||||
from twisted.python.compat import cmp, comparable
|
||||
from twisted.web._abnf import _istoken
|
||||
|
||||
|
||||
class InvalidHeaderName(ValueError):
|
||||
"""
|
||||
HTTP header names must be tokens, per RFC 9110 section 5.1.
|
||||
"""
|
||||
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
|
||||
def _sanitizeLinearWhitespace(headerComponent: bytes) -> bytes:
|
||||
r"""
|
||||
Replace linear whitespace (C{\n}, C{\r\n}, C{\r}) in a header
|
||||
value with a single space.
|
||||
|
||||
@param headerComponent: The header value to sanitize.
|
||||
|
||||
@return: The sanitized header value.
|
||||
"""
|
||||
return b" ".join(headerComponent.splitlines())
|
||||
|
||||
|
||||
@comparable
|
||||
class Headers:
|
||||
"""
|
||||
Stores HTTP headers in a key and multiple value format.
|
||||
|
||||
When passed L{str}, header names (e.g. 'Content-Type')
|
||||
are encoded using ISO-8859-1 and header values (e.g.
|
||||
'text/html;charset=utf-8') are encoded using UTF-8. Some methods that return
|
||||
values will return them in the same type as the name given.
|
||||
|
||||
If the header keys or values cannot be encoded or decoded using the rules
|
||||
above, using just L{bytes} arguments to the methods of this class will
|
||||
ensure no decoding or encoding is done, and L{Headers} will treat the keys
|
||||
and values as opaque byte strings.
|
||||
|
||||
@ivar _rawHeaders: A L{dict} mapping header names as L{bytes} to L{list}s of
|
||||
header values as L{bytes}.
|
||||
"""
|
||||
|
||||
__slots__ = ["_rawHeaders"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
rawHeaders: Optional[Mapping[AnyStr, Sequence[AnyStr]]] = None,
|
||||
) -> None:
|
||||
self._rawHeaders: Dict[bytes, List[bytes]] = {}
|
||||
if rawHeaders is not None:
|
||||
for name, values in rawHeaders.items():
|
||||
self.setRawHeaders(name, values)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""
|
||||
Return a string fully describing the headers set on this object.
|
||||
"""
|
||||
return "{}({!r})".format(
|
||||
self.__class__.__name__,
|
||||
self._rawHeaders,
|
||||
)
|
||||
|
||||
def __cmp__(self, other):
|
||||
"""
|
||||
Define L{Headers} instances as being equal to each other if they have
|
||||
the same raw headers.
|
||||
"""
|
||||
if isinstance(other, Headers):
|
||||
return cmp(
|
||||
sorted(self._rawHeaders.items()), sorted(other._rawHeaders.items())
|
||||
)
|
||||
return NotImplemented
|
||||
|
||||
def copy(self):
|
||||
"""
|
||||
Return a copy of itself with the same headers set.
|
||||
|
||||
@return: A new L{Headers}
|
||||
"""
|
||||
return self.__class__(self._rawHeaders)
|
||||
|
||||
def hasHeader(self, name: AnyStr) -> bool:
|
||||
"""
|
||||
Check for the existence of a given header.
|
||||
|
||||
@param name: The name of the HTTP header to check for.
|
||||
|
||||
@return: C{True} if the header exists, otherwise C{False}.
|
||||
"""
|
||||
return _nameEncoder.encode(name) in self._rawHeaders
|
||||
|
||||
def removeHeader(self, name: AnyStr) -> None:
|
||||
"""
|
||||
Remove the named header from this header object.
|
||||
|
||||
@param name: The name of the HTTP header to remove.
|
||||
|
||||
@return: L{None}
|
||||
"""
|
||||
self._rawHeaders.pop(_nameEncoder.encode(name), None)
|
||||
|
||||
def setRawHeaders(
|
||||
self, name: Union[str, bytes], values: Sequence[Union[str, bytes]]
|
||||
) -> None:
|
||||
"""
|
||||
Sets the raw representation of the given header.
|
||||
|
||||
@param name: The name of the HTTP header to set the values for.
|
||||
|
||||
@param values: A list of strings each one being a header value of
|
||||
the given name.
|
||||
|
||||
@raise TypeError: Raised if C{values} is not a sequence of L{bytes}
|
||||
or L{str}, or if C{name} is not L{bytes} or L{str}.
|
||||
|
||||
@return: L{None}
|
||||
"""
|
||||
_name = _nameEncoder.encode(name)
|
||||
encodedValues: List[bytes] = []
|
||||
for v in values:
|
||||
if isinstance(v, str):
|
||||
_v = v.encode("utf8")
|
||||
else:
|
||||
_v = v
|
||||
encodedValues.append(_sanitizeLinearWhitespace(_v))
|
||||
|
||||
self._rawHeaders[_name] = encodedValues
|
||||
|
||||
def addRawHeader(self, name: Union[str, bytes], value: Union[str, bytes]) -> None:
|
||||
"""
|
||||
Add a new raw value for the given header.
|
||||
|
||||
@param name: The name of the header for which to set the value.
|
||||
|
||||
@param value: The value to set for the named header.
|
||||
"""
|
||||
self._rawHeaders.setdefault(_nameEncoder.encode(name), []).append(
|
||||
_sanitizeLinearWhitespace(
|
||||
value.encode("utf8") if isinstance(value, str) else value
|
||||
)
|
||||
)
|
||||
|
||||
@overload
|
||||
def getRawHeaders(self, name: AnyStr) -> Optional[Sequence[AnyStr]]:
|
||||
...
|
||||
|
||||
@overload
|
||||
def getRawHeaders(self, name: AnyStr, default: _T) -> Union[Sequence[AnyStr], _T]:
|
||||
...
|
||||
|
||||
def getRawHeaders(
|
||||
self, name: AnyStr, default: Optional[_T] = None
|
||||
) -> Union[Sequence[AnyStr], Optional[_T]]:
|
||||
"""
|
||||
Returns a sequence of headers matching the given name as the raw string
|
||||
given.
|
||||
|
||||
@param name: The name of the HTTP header to get the values of.
|
||||
|
||||
@param default: The value to return if no header with the given C{name}
|
||||
exists.
|
||||
|
||||
@return: If the named header is present, a sequence of its
|
||||
values. Otherwise, C{default}.
|
||||
"""
|
||||
encodedName = _nameEncoder.encode(name)
|
||||
values = self._rawHeaders.get(encodedName, [])
|
||||
if not values:
|
||||
return default
|
||||
|
||||
if isinstance(name, str):
|
||||
return [v.decode("utf8") for v in values]
|
||||
return values
|
||||
|
||||
def getAllRawHeaders(self) -> Iterator[Tuple[bytes, Sequence[bytes]]]:
|
||||
"""
|
||||
Return an iterator of key, value pairs of all headers contained in this
|
||||
object, as L{bytes}. The keys are capitalized in canonical
|
||||
capitalization.
|
||||
"""
|
||||
return iter(self._rawHeaders.items())
|
||||
|
||||
|
||||
class _NameEncoder:
|
||||
"""
|
||||
C{_NameEncoder} converts HTTP header names to L{bytes} and canonicalizies
|
||||
their capitalization.
|
||||
|
||||
@cvar _caseMappings: A L{dict} that maps conventionally-capitalized
|
||||
header names to their canonicalized representation, for headers with
|
||||
unconventional capitalization.
|
||||
|
||||
@cvar _canonicalHeaderCache: A L{dict} that maps header names to their
|
||||
canonicalized representation.
|
||||
"""
|
||||
|
||||
__slots__ = ("_canonicalHeaderCache",)
|
||||
_canonicalHeaderCache: Dict[Union[bytes, str], bytes]
|
||||
|
||||
_caseMappings: ClassVar[Dict[bytes, bytes]] = {
|
||||
b"Content-Md5": b"Content-MD5",
|
||||
b"Dnt": b"DNT",
|
||||
b"Etag": b"ETag",
|
||||
b"P3p": b"P3P",
|
||||
b"Te": b"TE",
|
||||
b"Www-Authenticate": b"WWW-Authenticate",
|
||||
b"X-Xss-Protection": b"X-XSS-Protection",
|
||||
}
|
||||
|
||||
_MAX_CACHED_HEADERS: ClassVar[int] = 10_000
|
||||
|
||||
def __init__(self):
|
||||
self._canonicalHeaderCache = {}
|
||||
|
||||
def encode(self, name: Union[str, bytes]) -> bytes:
|
||||
"""
|
||||
Encode the name of a header (eg 'Content-Type') to an ISO-8859-1
|
||||
bytestring if required. It will be canonicalized to Http-Header-Case.
|
||||
|
||||
@raises InvalidHeaderName:
|
||||
If the header name contains invalid characters like whitespace
|
||||
or NUL.
|
||||
|
||||
@param name: An HTTP header name
|
||||
|
||||
@return: C{name}, encoded if required, in Header-Case
|
||||
"""
|
||||
if canonicalName := self._canonicalHeaderCache.get(name):
|
||||
return canonicalName
|
||||
|
||||
bytes_name = name.encode("iso-8859-1") if isinstance(name, str) else name
|
||||
|
||||
if not _istoken(bytes_name):
|
||||
raise InvalidHeaderName(bytes_name)
|
||||
|
||||
result = b"-".join([word.capitalize() for word in bytes_name.split(b"-")])
|
||||
|
||||
# Some headers have special capitalization:
|
||||
if result in self._caseMappings:
|
||||
result = self._caseMappings[result]
|
||||
|
||||
# In general, we should only see a very small number of header
|
||||
# variations in the real world, so caching them is fine. However, an
|
||||
# attacker could generate infinite header variations to fill up RAM, so
|
||||
# we cap how many we cache. The performance degradation from lack of
|
||||
# caching won't be that bad, and legit traffic won't hit it.
|
||||
if len(self._canonicalHeaderCache) < self._MAX_CACHED_HEADERS:
|
||||
self._canonicalHeaderCache[name] = result
|
||||
|
||||
return result
|
||||
|
||||
|
||||
_nameEncoder = _NameEncoder()
|
||||
"""
|
||||
The global name encoder.
|
||||
"""
|
||||
|
||||
|
||||
__all__ = ["Headers"]
|
||||
831
backend/lib/python3.12/site-packages/twisted/web/iweb.py
Normal file
831
backend/lib/python3.12/site-packages/twisted/web/iweb.py
Normal file
@@ -0,0 +1,831 @@
|
||||
# -*- test-case-name: twisted.web.test -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Interface definitions for L{twisted.web}.
|
||||
|
||||
@var UNKNOWN_LENGTH: An opaque object which may be used as the value of
|
||||
L{IBodyProducer.length} to indicate that the length of the entity
|
||||
body is not known in advance.
|
||||
"""
|
||||
|
||||
from typing import TYPE_CHECKING, Callable, List, Optional
|
||||
|
||||
from zope.interface import Attribute, Interface
|
||||
|
||||
from twisted.cred.credentials import IUsernameDigestHash
|
||||
from twisted.internet.defer import Deferred
|
||||
from twisted.internet.interfaces import IPushProducer
|
||||
from twisted.web.http_headers import Headers
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from twisted.web.template import Flattenable, Tag
|
||||
|
||||
|
||||
class IRequest(Interface):
|
||||
"""
|
||||
An HTTP request.
|
||||
|
||||
@since: 9.0
|
||||
"""
|
||||
|
||||
method = Attribute("A L{bytes} giving the HTTP method that was used.")
|
||||
uri = Attribute(
|
||||
"A L{bytes} giving the full encoded URI which was requested (including"
|
||||
" query arguments)."
|
||||
)
|
||||
path = Attribute(
|
||||
"A L{bytes} giving the encoded query path of the request URI (not "
|
||||
"including query arguments)."
|
||||
)
|
||||
args = Attribute(
|
||||
"A mapping of decoded query argument names as L{bytes} to "
|
||||
"corresponding query argument values as L{list}s of L{bytes}. "
|
||||
"For example, for a URI with C{foo=bar&foo=baz&quux=spam} "
|
||||
"for its query part, C{args} will be C{{b'foo': [b'bar', b'baz'], "
|
||||
"b'quux': [b'spam']}}."
|
||||
)
|
||||
|
||||
prepath = Attribute(
|
||||
"The URL path segments which have been processed during resource "
|
||||
"traversal, as a list of L{bytes}."
|
||||
)
|
||||
|
||||
postpath = Attribute(
|
||||
"The URL path segments which have not (yet) been processed "
|
||||
"during resource traversal, as a list of L{bytes}."
|
||||
)
|
||||
|
||||
requestHeaders = Attribute(
|
||||
"A L{http_headers.Headers} instance giving all received HTTP request "
|
||||
"headers."
|
||||
)
|
||||
|
||||
content = Attribute(
|
||||
"A file-like object giving the request body. This may be a file on "
|
||||
"disk, an L{io.BytesIO}, or some other type. The implementation is "
|
||||
"free to decide on a per-request basis."
|
||||
)
|
||||
|
||||
responseHeaders = Attribute(
|
||||
"A L{http_headers.Headers} instance holding all HTTP response "
|
||||
"headers to be sent."
|
||||
)
|
||||
|
||||
def getHeader(key):
|
||||
"""
|
||||
Get an HTTP request header.
|
||||
|
||||
@type key: L{bytes} or L{str}
|
||||
@param key: The name of the header to get the value of.
|
||||
|
||||
@rtype: L{bytes} or L{str} or L{None}
|
||||
@return: The value of the specified header, or L{None} if that header
|
||||
was not present in the request. The string type of the result
|
||||
matches the type of C{key}.
|
||||
"""
|
||||
|
||||
def getCookie(key):
|
||||
"""
|
||||
Get a cookie that was sent from the network.
|
||||
|
||||
@type key: L{bytes}
|
||||
@param key: The name of the cookie to get.
|
||||
|
||||
@rtype: L{bytes} or L{None}
|
||||
@returns: The value of the specified cookie, or L{None} if that cookie
|
||||
was not present in the request.
|
||||
"""
|
||||
|
||||
def getAllHeaders():
|
||||
"""
|
||||
Return dictionary mapping the names of all received headers to the last
|
||||
value received for each.
|
||||
|
||||
Since this method does not return all header information,
|
||||
C{requestHeaders.getAllRawHeaders()} may be preferred.
|
||||
"""
|
||||
|
||||
def getRequestHostname():
|
||||
"""
|
||||
Get the hostname that the HTTP client passed in to the request.
|
||||
|
||||
This will either use the C{Host:} header (if it is available; which,
|
||||
for a spec-compliant request, it will be) or the IP address of the host
|
||||
we are listening on if the header is unavailable.
|
||||
|
||||
@note: This is the I{host portion} of the requested resource, which
|
||||
means that:
|
||||
|
||||
1. it might be an IPv4 or IPv6 address, not just a DNS host
|
||||
name,
|
||||
|
||||
2. there's no guarantee it's even a I{valid} host name or IP
|
||||
address, since the C{Host:} header may be malformed,
|
||||
|
||||
3. it does not include the port number.
|
||||
|
||||
@returns: the requested hostname
|
||||
|
||||
@rtype: L{bytes}
|
||||
"""
|
||||
|
||||
def getHost():
|
||||
"""
|
||||
Get my originally requesting transport's host.
|
||||
|
||||
@return: An L{IAddress<twisted.internet.interfaces.IAddress>}.
|
||||
"""
|
||||
|
||||
def getClientAddress():
|
||||
"""
|
||||
Return the address of the client who submitted this request.
|
||||
|
||||
The address may not be a network address. Callers must check
|
||||
its type before using it.
|
||||
|
||||
@since: 18.4
|
||||
|
||||
@return: the client's address.
|
||||
@rtype: an L{IAddress} provider.
|
||||
"""
|
||||
|
||||
def getClientIP():
|
||||
"""
|
||||
Return the IP address of the client who submitted this request.
|
||||
|
||||
This method is B{deprecated}. See L{getClientAddress} instead.
|
||||
|
||||
@returns: the client IP address or L{None} if the request was submitted
|
||||
over a transport where IP addresses do not make sense.
|
||||
@rtype: L{str} or L{None}
|
||||
"""
|
||||
|
||||
def getUser():
|
||||
"""
|
||||
Return the HTTP user sent with this request, if any.
|
||||
|
||||
If no user was supplied, return the empty string.
|
||||
|
||||
@returns: the HTTP user, if any
|
||||
@rtype: L{str}
|
||||
"""
|
||||
|
||||
def getPassword():
|
||||
"""
|
||||
Return the HTTP password sent with this request, if any.
|
||||
|
||||
If no password was supplied, return the empty string.
|
||||
|
||||
@returns: the HTTP password, if any
|
||||
@rtype: L{str}
|
||||
"""
|
||||
|
||||
def isSecure():
|
||||
"""
|
||||
Return True if this request is using a secure transport.
|
||||
|
||||
Normally this method returns True if this request's HTTPChannel
|
||||
instance is using a transport that implements ISSLTransport.
|
||||
|
||||
This will also return True if setHost() has been called
|
||||
with ssl=True.
|
||||
|
||||
@returns: True if this request is secure
|
||||
@rtype: C{bool}
|
||||
"""
|
||||
|
||||
def getSession(sessionInterface=None):
|
||||
"""
|
||||
Look up the session associated with this request or create a new one if
|
||||
there is not one.
|
||||
|
||||
@return: The L{Session} instance identified by the session cookie in
|
||||
the request, or the C{sessionInterface} component of that session
|
||||
if C{sessionInterface} is specified.
|
||||
"""
|
||||
|
||||
def URLPath():
|
||||
"""
|
||||
@return: A L{URLPath<twisted.python.urlpath.URLPath>} instance
|
||||
which identifies the URL for which this request is.
|
||||
"""
|
||||
|
||||
def prePathURL():
|
||||
"""
|
||||
At any time during resource traversal or resource rendering,
|
||||
returns an absolute URL to the most nested resource which has
|
||||
yet been reached.
|
||||
|
||||
@see: {twisted.web.server.Request.prepath}
|
||||
|
||||
@return: An absolute URL.
|
||||
@rtype: L{bytes}
|
||||
"""
|
||||
|
||||
def rememberRootURL():
|
||||
"""
|
||||
Remember the currently-processed part of the URL for later
|
||||
recalling.
|
||||
"""
|
||||
|
||||
def getRootURL():
|
||||
"""
|
||||
Get a previously-remembered URL.
|
||||
|
||||
@return: An absolute URL.
|
||||
@rtype: L{bytes}
|
||||
"""
|
||||
|
||||
# Methods for outgoing response
|
||||
def finish():
|
||||
"""
|
||||
Indicate that the response to this request is complete.
|
||||
"""
|
||||
|
||||
def write(data):
|
||||
"""
|
||||
Write some data to the body of the response to this request. Response
|
||||
headers are written the first time this method is called, after which
|
||||
new response headers may not be added.
|
||||
|
||||
@param data: Bytes of the response body.
|
||||
@type data: L{bytes}
|
||||
"""
|
||||
|
||||
def addCookie(
|
||||
k,
|
||||
v,
|
||||
expires=None,
|
||||
domain=None,
|
||||
path=None,
|
||||
max_age=None,
|
||||
comment=None,
|
||||
secure=None,
|
||||
):
|
||||
"""
|
||||
Set an outgoing HTTP cookie.
|
||||
|
||||
In general, you should consider using sessions instead of cookies, see
|
||||
L{twisted.web.server.Request.getSession} and the
|
||||
L{twisted.web.server.Session} class for details.
|
||||
"""
|
||||
|
||||
def setResponseCode(code, message=None):
|
||||
"""
|
||||
Set the HTTP response code.
|
||||
|
||||
@type code: L{int}
|
||||
@type message: L{bytes}
|
||||
"""
|
||||
|
||||
def setHeader(k, v):
|
||||
"""
|
||||
Set an HTTP response header. Overrides any previously set values for
|
||||
this header.
|
||||
|
||||
@type k: L{bytes} or L{str}
|
||||
@param k: The name of the header for which to set the value.
|
||||
|
||||
@type v: L{bytes} or L{str}
|
||||
@param v: The value to set for the named header. A L{str} will be
|
||||
UTF-8 encoded, which may not interoperable with other
|
||||
implementations. Avoid passing non-ASCII characters if possible.
|
||||
"""
|
||||
|
||||
def redirect(url):
|
||||
"""
|
||||
Utility function that does a redirect.
|
||||
|
||||
The request should have finish() called after this.
|
||||
"""
|
||||
|
||||
def setLastModified(when):
|
||||
"""
|
||||
Set the C{Last-Modified} time for the response to this request.
|
||||
|
||||
If I am called more than once, I ignore attempts to set Last-Modified
|
||||
earlier, only replacing the Last-Modified time if it is to a later
|
||||
value.
|
||||
|
||||
If I am a conditional request, I may modify my response code to
|
||||
L{NOT_MODIFIED<http.NOT_MODIFIED>} if appropriate for the time given.
|
||||
|
||||
@param when: The last time the resource being returned was modified, in
|
||||
seconds since the epoch.
|
||||
@type when: L{int} or L{float}
|
||||
|
||||
@return: If I am a C{If-Modified-Since} conditional request and the time
|
||||
given is not newer than the condition, I return
|
||||
L{CACHED<http.CACHED>} to indicate that you should write no body.
|
||||
Otherwise, I return a false value.
|
||||
"""
|
||||
|
||||
def setETag(etag):
|
||||
"""
|
||||
Set an C{entity tag} for the outgoing response.
|
||||
|
||||
That's "entity tag" as in the HTTP/1.1 I{ETag} header, "used for
|
||||
comparing two or more entities from the same requested resource."
|
||||
|
||||
If I am a conditional request, I may modify my response code to
|
||||
L{NOT_MODIFIED<http.NOT_MODIFIED>} or
|
||||
L{PRECONDITION_FAILED<http.PRECONDITION_FAILED>}, if appropriate for the
|
||||
tag given.
|
||||
|
||||
@param etag: The entity tag for the resource being returned.
|
||||
@type etag: L{str}
|
||||
|
||||
@return: If I am a C{If-None-Match} conditional request and the tag
|
||||
matches one in the request, I return L{CACHED<http.CACHED>} to
|
||||
indicate that you should write no body. Otherwise, I return a
|
||||
false value.
|
||||
"""
|
||||
|
||||
def setHost(host, port, ssl=0):
|
||||
"""
|
||||
Change the host and port the request thinks it's using.
|
||||
|
||||
This method is useful for working with reverse HTTP proxies (e.g. both
|
||||
Squid and Apache's mod_proxy can do this), when the address the HTTP
|
||||
client is using is different than the one we're listening on.
|
||||
|
||||
For example, Apache may be listening on https://www.example.com, and
|
||||
then forwarding requests to http://localhost:8080, but we don't want
|
||||
HTML produced by Twisted to say 'http://localhost:8080', they should
|
||||
say 'https://www.example.com', so we do::
|
||||
|
||||
request.setHost('www.example.com', 443, ssl=1)
|
||||
"""
|
||||
|
||||
|
||||
class INonQueuedRequestFactory(Interface):
|
||||
"""
|
||||
A factory of L{IRequest} objects that does not take a ``queued`` parameter.
|
||||
"""
|
||||
|
||||
def __call__(channel):
|
||||
"""
|
||||
Create an L{IRequest} that is operating on the given channel. There
|
||||
must only be one L{IRequest} object processing at any given time on a
|
||||
channel.
|
||||
|
||||
@param channel: A L{twisted.web.http.HTTPChannel} object.
|
||||
@type channel: L{twisted.web.http.HTTPChannel}
|
||||
|
||||
@return: A request object.
|
||||
@rtype: L{IRequest}
|
||||
"""
|
||||
|
||||
|
||||
class IAccessLogFormatter(Interface):
|
||||
"""
|
||||
An object which can represent an HTTP request as a line of text for
|
||||
inclusion in an access log file.
|
||||
"""
|
||||
|
||||
def __call__(timestamp, request):
|
||||
"""
|
||||
Generate a line for the access log.
|
||||
|
||||
@param timestamp: The time at which the request was completed in the
|
||||
standard format for access logs.
|
||||
@type timestamp: L{unicode}
|
||||
|
||||
@param request: The request object about which to log.
|
||||
@type request: L{twisted.web.server.Request}
|
||||
|
||||
@return: One line describing the request without a trailing newline.
|
||||
@rtype: L{unicode}
|
||||
"""
|
||||
|
||||
|
||||
class ICredentialFactory(Interface):
|
||||
"""
|
||||
A credential factory defines a way to generate a particular kind of
|
||||
authentication challenge and a way to interpret the responses to these
|
||||
challenges. It creates
|
||||
L{ICredentials<twisted.cred.credentials.ICredentials>} providers from
|
||||
responses. These objects will be used with L{twisted.cred} to authenticate
|
||||
an authorize requests.
|
||||
"""
|
||||
|
||||
scheme = Attribute(
|
||||
"A L{str} giving the name of the authentication scheme with which "
|
||||
"this factory is associated. For example, C{'basic'} or C{'digest'}."
|
||||
)
|
||||
|
||||
def getChallenge(request):
|
||||
"""
|
||||
Generate a new challenge to be sent to a client.
|
||||
|
||||
@type request: L{twisted.web.http.Request}
|
||||
@param request: The request the response to which this challenge will
|
||||
be included.
|
||||
|
||||
@rtype: L{dict}
|
||||
@return: A mapping from L{str} challenge fields to associated L{str}
|
||||
values.
|
||||
"""
|
||||
|
||||
def decode(response, request):
|
||||
"""
|
||||
Create a credentials object from the given response.
|
||||
|
||||
@type response: L{str}
|
||||
@param response: scheme specific response string
|
||||
|
||||
@type request: L{twisted.web.http.Request}
|
||||
@param request: The request being processed (from which the response
|
||||
was taken).
|
||||
|
||||
@raise twisted.cred.error.LoginFailed: If the response is invalid.
|
||||
|
||||
@rtype: L{twisted.cred.credentials.ICredentials} provider
|
||||
@return: The credentials represented by the given response.
|
||||
"""
|
||||
|
||||
|
||||
class IBodyProducer(IPushProducer):
|
||||
"""
|
||||
Objects which provide L{IBodyProducer} write bytes to an object which
|
||||
provides L{IConsumer<twisted.internet.interfaces.IConsumer>} by calling its
|
||||
C{write} method repeatedly.
|
||||
|
||||
L{IBodyProducer} providers may start producing as soon as they have an
|
||||
L{IConsumer<twisted.internet.interfaces.IConsumer>} provider. That is, they
|
||||
should not wait for a C{resumeProducing} call to begin writing data.
|
||||
|
||||
L{IConsumer.unregisterProducer<twisted.internet.interfaces.IConsumer.unregisterProducer>}
|
||||
must not be called. Instead, the
|
||||
L{Deferred<twisted.internet.defer.Deferred>} returned from C{startProducing}
|
||||
must be fired when all bytes have been written.
|
||||
|
||||
L{IConsumer.write<twisted.internet.interfaces.IConsumer.write>} may
|
||||
synchronously invoke any of C{pauseProducing}, C{resumeProducing}, or
|
||||
C{stopProducing}. These methods must be implemented with this in mind.
|
||||
|
||||
@since: 9.0
|
||||
"""
|
||||
|
||||
# Despite the restrictions above and the additional requirements of
|
||||
# stopProducing documented below, this interface still needs to be an
|
||||
# IPushProducer subclass. Providers of it will be passed to IConsumer
|
||||
# providers which only know about IPushProducer and IPullProducer, not
|
||||
# about this interface. This interface needs to remain close enough to one
|
||||
# of those interfaces for consumers to work with it.
|
||||
|
||||
length = Attribute(
|
||||
"""
|
||||
C{length} is a L{int} indicating how many bytes in total this
|
||||
L{IBodyProducer} will write to the consumer or L{UNKNOWN_LENGTH}
|
||||
if this is not known in advance.
|
||||
"""
|
||||
)
|
||||
|
||||
def startProducing(consumer):
|
||||
"""
|
||||
Start producing to the given
|
||||
L{IConsumer<twisted.internet.interfaces.IConsumer>} provider.
|
||||
|
||||
@return: A L{Deferred<twisted.internet.defer.Deferred>} which stops
|
||||
production of data when L{Deferred.cancel} is called, and which
|
||||
fires with L{None} when all bytes have been produced or with a
|
||||
L{Failure<twisted.python.failure.Failure>} if there is any problem
|
||||
before all bytes have been produced.
|
||||
"""
|
||||
|
||||
def stopProducing():
|
||||
"""
|
||||
In addition to the standard behavior of
|
||||
L{IProducer.stopProducing<twisted.internet.interfaces.IProducer.stopProducing>}
|
||||
(stop producing data), make sure the
|
||||
L{Deferred<twisted.internet.defer.Deferred>} returned by
|
||||
C{startProducing} is never fired.
|
||||
"""
|
||||
|
||||
|
||||
class IRenderable(Interface):
|
||||
"""
|
||||
An L{IRenderable} is an object that may be rendered by the
|
||||
L{twisted.web.template} templating system.
|
||||
"""
|
||||
|
||||
def lookupRenderMethod(
|
||||
name: str,
|
||||
) -> Callable[[Optional[IRequest], "Tag"], "Flattenable"]:
|
||||
"""
|
||||
Look up and return the render method associated with the given name.
|
||||
|
||||
@param name: The value of a render directive encountered in the
|
||||
document returned by a call to L{IRenderable.render}.
|
||||
|
||||
@return: A two-argument callable which will be invoked with the request
|
||||
being responded to and the tag object on which the render directive
|
||||
was encountered.
|
||||
"""
|
||||
|
||||
def render(request: Optional[IRequest]) -> "Flattenable":
|
||||
"""
|
||||
Get the document for this L{IRenderable}.
|
||||
|
||||
@param request: The request in response to which this method is being
|
||||
invoked.
|
||||
|
||||
@return: An object which can be flattened.
|
||||
"""
|
||||
|
||||
|
||||
class ITemplateLoader(Interface):
|
||||
"""
|
||||
A loader for templates; something usable as a value for
|
||||
L{twisted.web.template.Element}'s C{loader} attribute.
|
||||
"""
|
||||
|
||||
def load() -> List["Flattenable"]:
|
||||
"""
|
||||
Load a template suitable for rendering.
|
||||
|
||||
@return: a L{list} of flattenable objects, such as byte and unicode
|
||||
strings, L{twisted.web.template.Element}s and L{IRenderable} providers.
|
||||
"""
|
||||
|
||||
|
||||
class IResponse(Interface):
|
||||
"""
|
||||
An object representing an HTTP response received from an HTTP server.
|
||||
|
||||
@since: 11.1
|
||||
"""
|
||||
|
||||
version = Attribute(
|
||||
"A three-tuple describing the protocol and protocol version "
|
||||
"of the response. The first element is of type L{str}, the second "
|
||||
"and third are of type L{int}. For example, C{(b'HTTP', 1, 1)}."
|
||||
)
|
||||
|
||||
code = Attribute("The HTTP status code of this response, as a L{int}.")
|
||||
|
||||
phrase = Attribute("The HTTP reason phrase of this response, as a L{str}.")
|
||||
|
||||
headers = Attribute("The HTTP response L{Headers} of this response.")
|
||||
|
||||
length = Attribute(
|
||||
"The L{int} number of bytes expected to be in the body of this "
|
||||
"response or L{UNKNOWN_LENGTH} if the server did not indicate how "
|
||||
"many bytes to expect. For I{HEAD} responses, this will be 0; if "
|
||||
"the response includes a I{Content-Length} header, it will be "
|
||||
"available in C{headers}."
|
||||
)
|
||||
|
||||
request = Attribute("The L{IClientRequest} that resulted in this response.")
|
||||
|
||||
previousResponse = Attribute(
|
||||
"The previous L{IResponse} from a redirect, or L{None} if there was no "
|
||||
"previous response. This can be used to walk the response or request "
|
||||
"history for redirections."
|
||||
)
|
||||
|
||||
def deliverBody(protocol):
|
||||
"""
|
||||
Register an L{IProtocol<twisted.internet.interfaces.IProtocol>} provider
|
||||
to receive the response body.
|
||||
|
||||
The protocol will be connected to a transport which provides
|
||||
L{IPushProducer}. The protocol's C{connectionLost} method will be
|
||||
called with:
|
||||
|
||||
- L{ResponseDone}, which indicates that all bytes from the response
|
||||
have been successfully delivered.
|
||||
|
||||
- L{PotentialDataLoss}, which indicates that it cannot be determined
|
||||
if the entire response body has been delivered. This only occurs
|
||||
when making requests to HTTP servers which do not set
|
||||
I{Content-Length} or a I{Transfer-Encoding} in the response.
|
||||
|
||||
- L{ResponseFailed}, which indicates that some bytes from the response
|
||||
were lost. The C{reasons} attribute of the exception may provide
|
||||
more specific indications as to why.
|
||||
"""
|
||||
|
||||
def setPreviousResponse(response):
|
||||
"""
|
||||
Set the reference to the previous L{IResponse}.
|
||||
|
||||
The value of the previous response can be read via
|
||||
L{IResponse.previousResponse}.
|
||||
"""
|
||||
|
||||
|
||||
class _IRequestEncoder(Interface):
|
||||
"""
|
||||
An object encoding data passed to L{IRequest.write}, for example for
|
||||
compression purpose.
|
||||
|
||||
@since: 12.3
|
||||
"""
|
||||
|
||||
def encode(data):
|
||||
"""
|
||||
Encode the data given and return the result.
|
||||
|
||||
@param data: The content to encode.
|
||||
@type data: L{str}
|
||||
|
||||
@return: The encoded data.
|
||||
@rtype: L{str}
|
||||
"""
|
||||
|
||||
def finish():
|
||||
"""
|
||||
Callback called when the request is closing.
|
||||
|
||||
@return: If necessary, the pending data accumulated from previous
|
||||
C{encode} calls.
|
||||
@rtype: L{str}
|
||||
"""
|
||||
|
||||
|
||||
class _IRequestEncoderFactory(Interface):
|
||||
"""
|
||||
A factory for returing L{_IRequestEncoder} instances.
|
||||
|
||||
@since: 12.3
|
||||
"""
|
||||
|
||||
def encoderForRequest(request):
|
||||
"""
|
||||
If applicable, returns a L{_IRequestEncoder} instance which will encode
|
||||
the request.
|
||||
"""
|
||||
|
||||
|
||||
class IClientRequest(Interface):
|
||||
"""
|
||||
An object representing an HTTP request to make to an HTTP server.
|
||||
|
||||
@since: 13.1
|
||||
"""
|
||||
|
||||
method = Attribute(
|
||||
"The HTTP method for this request, as L{bytes}. For example: "
|
||||
"C{b'GET'}, C{b'HEAD'}, C{b'POST'}, etc."
|
||||
)
|
||||
|
||||
absoluteURI = Attribute(
|
||||
"The absolute URI of the requested resource, as L{bytes}; or L{None} "
|
||||
"if the absolute URI cannot be determined."
|
||||
)
|
||||
|
||||
headers = Attribute(
|
||||
"Headers to be sent to the server, as "
|
||||
"a L{twisted.web.http_headers.Headers} instance."
|
||||
)
|
||||
|
||||
|
||||
class IAgent(Interface):
|
||||
"""
|
||||
An agent makes HTTP requests.
|
||||
|
||||
The way in which requests are issued is left up to each implementation.
|
||||
Some may issue them directly to the server indicated by the net location
|
||||
portion of the request URL. Others may use a proxy specified by system
|
||||
configuration.
|
||||
|
||||
Processing of responses is also left very widely specified. An
|
||||
implementation may perform no special handling of responses, or it may
|
||||
implement redirect following or content negotiation, it may implement a
|
||||
cookie store or automatically respond to authentication challenges. It may
|
||||
implement many other unforeseen behaviors as well.
|
||||
|
||||
It is also intended that L{IAgent} implementations be composable. An
|
||||
implementation which provides cookie handling features should re-use an
|
||||
implementation that provides connection pooling and this combination could
|
||||
be used by an implementation which adds content negotiation functionality.
|
||||
Some implementations will be completely self-contained, such as those which
|
||||
actually perform the network operations to send and receive requests, but
|
||||
most or all other implementations should implement a small number of new
|
||||
features (perhaps one new feature) and delegate the rest of the
|
||||
request/response machinery to another implementation.
|
||||
|
||||
This allows for great flexibility in the behavior an L{IAgent} will
|
||||
provide. For example, an L{IAgent} with web browser-like behavior could be
|
||||
obtained by combining a number of (hypothetical) implementations::
|
||||
|
||||
baseAgent = Agent(reactor)
|
||||
decode = ContentDecoderAgent(baseAgent, [(b"gzip", GzipDecoder())])
|
||||
cookie = CookieAgent(decode, diskStore.cookie)
|
||||
authenticate = AuthenticateAgent(
|
||||
cookie, [diskStore.credentials, GtkAuthInterface()])
|
||||
cache = CacheAgent(authenticate, diskStore.cache)
|
||||
redirect = BrowserLikeRedirectAgent(cache, limit=10)
|
||||
|
||||
doSomeRequests(cache)
|
||||
"""
|
||||
|
||||
def request(
|
||||
method: bytes,
|
||||
uri: bytes,
|
||||
headers: Optional[Headers] = None,
|
||||
bodyProducer: Optional[IBodyProducer] = None,
|
||||
) -> Deferred[IResponse]:
|
||||
"""
|
||||
Request the resource at the given location.
|
||||
|
||||
@param method: The request method to use, such as C{b"GET"}, C{b"HEAD"},
|
||||
C{b"PUT"}, C{b"POST"}, etc.
|
||||
|
||||
@param uri: The location of the resource to request. This should be an
|
||||
absolute URI but some implementations may support relative URIs
|
||||
(with absolute or relative paths). I{HTTP} and I{HTTPS} are the
|
||||
schemes most likely to be supported but others may be as well.
|
||||
|
||||
@param headers: The headers to send with the request (or L{None} to
|
||||
send no extra headers). An implementation may add its own headers
|
||||
to this (for example for client identification or content
|
||||
negotiation).
|
||||
|
||||
@param bodyProducer: An object which can generate bytes to make up the
|
||||
body of this request (for example, the properly encoded contents of
|
||||
a file for a file upload). Or, L{None} if the request is to have
|
||||
no body.
|
||||
|
||||
@return: A L{Deferred} that fires with an L{IResponse} provider when
|
||||
the header of the response has been received (regardless of the
|
||||
response status code) or with a L{Failure} if there is any problem
|
||||
which prevents that response from being received (including
|
||||
problems that prevent the request from being sent).
|
||||
"""
|
||||
|
||||
|
||||
class IPolicyForHTTPS(Interface):
|
||||
"""
|
||||
An L{IPolicyForHTTPS} provides a policy for verifying the certificates of
|
||||
HTTPS connections, in the form of a L{client connection creator
|
||||
<twisted.internet.interfaces.IOpenSSLClientConnectionCreator>} per network
|
||||
location.
|
||||
|
||||
@since: 14.0
|
||||
"""
|
||||
|
||||
def creatorForNetloc(hostname, port):
|
||||
"""
|
||||
Create a L{client connection creator
|
||||
<twisted.internet.interfaces.IOpenSSLClientConnectionCreator>}
|
||||
appropriate for the given URL "netloc"; i.e. hostname and port number
|
||||
pair.
|
||||
|
||||
@param hostname: The name of the requested remote host.
|
||||
@type hostname: L{bytes}
|
||||
|
||||
@param port: The number of the requested remote port.
|
||||
@type port: L{int}
|
||||
|
||||
@return: A client connection creator expressing the security
|
||||
requirements for the given remote host.
|
||||
@rtype: L{client connection creator
|
||||
<twisted.internet.interfaces.IOpenSSLClientConnectionCreator>}
|
||||
"""
|
||||
|
||||
|
||||
class IAgentEndpointFactory(Interface):
|
||||
"""
|
||||
An L{IAgentEndpointFactory} provides a way of constructing an endpoint
|
||||
used for outgoing Agent requests. This is useful in the case of needing to
|
||||
proxy outgoing connections, or to otherwise vary the transport used.
|
||||
|
||||
@since: 15.0
|
||||
"""
|
||||
|
||||
def endpointForURI(uri):
|
||||
"""
|
||||
Construct and return an L{IStreamClientEndpoint} for the outgoing
|
||||
request's connection.
|
||||
|
||||
@param uri: The URI of the request.
|
||||
@type uri: L{twisted.web.client.URI}
|
||||
|
||||
@return: An endpoint which will have its C{connect} method called to
|
||||
issue the request.
|
||||
@rtype: an L{IStreamClientEndpoint} provider
|
||||
|
||||
@raises twisted.internet.error.SchemeNotSupported: If the given
|
||||
URI's scheme cannot be handled by this factory.
|
||||
"""
|
||||
|
||||
|
||||
UNKNOWN_LENGTH = "twisted.web.iweb.UNKNOWN_LENGTH"
|
||||
|
||||
__all__ = [
|
||||
"IUsernameDigestHash",
|
||||
"ICredentialFactory",
|
||||
"IRequest",
|
||||
"IBodyProducer",
|
||||
"IRenderable",
|
||||
"IResponse",
|
||||
"_IRequestEncoder",
|
||||
"_IRequestEncoderFactory",
|
||||
"IClientRequest",
|
||||
"UNKNOWN_LENGTH",
|
||||
]
|
||||
1217
backend/lib/python3.12/site-packages/twisted/web/microdom.py
Normal file
1217
backend/lib/python3.12/site-packages/twisted/web/microdom.py
Normal file
File diff suppressed because it is too large
Load Diff
1
backend/lib/python3.12/site-packages/twisted/web/newsfragments/.gitignore
vendored
Normal file
1
backend/lib/python3.12/site-packages/twisted/web/newsfragments/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!.gitignore
|
||||
134
backend/lib/python3.12/site-packages/twisted/web/pages.py
Normal file
134
backend/lib/python3.12/site-packages/twisted/web/pages.py
Normal file
@@ -0,0 +1,134 @@
|
||||
# -*- test-case-name: twisted.web.test.test_pages -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Utility implementations of L{IResource}.
|
||||
"""
|
||||
|
||||
__all__ = (
|
||||
"errorPage",
|
||||
"notFound",
|
||||
"forbidden",
|
||||
)
|
||||
|
||||
from typing import cast
|
||||
|
||||
from twisted.web import http
|
||||
from twisted.web.iweb import IRenderable, IRequest
|
||||
from twisted.web.resource import IResource, Resource
|
||||
from twisted.web.template import renderElement, tags
|
||||
|
||||
|
||||
class _ErrorPage(Resource):
|
||||
"""
|
||||
L{_ErrorPage} is a resource that responds to all requests with a particular
|
||||
(parameterized) HTTP status code and an HTML body containing some
|
||||
descriptive text. This is useful for rendering simple error pages.
|
||||
|
||||
@see: L{twisted.web.pages.errorPage}
|
||||
|
||||
@ivar _code: An integer HTTP status code which will be used for the
|
||||
response.
|
||||
|
||||
@ivar _brief: A short string which will be included in the response body as
|
||||
the page title.
|
||||
|
||||
@ivar _detail: A longer string which will be included in the response body.
|
||||
"""
|
||||
|
||||
def __init__(self, code: int, brief: str, detail: str) -> None:
|
||||
super().__init__()
|
||||
self._code: int = code
|
||||
self._brief: str = brief
|
||||
self._detail: str = detail
|
||||
|
||||
def render(self, request: IRequest) -> object:
|
||||
"""
|
||||
Respond to all requests with the given HTTP status code and an HTML
|
||||
document containing the explanatory strings.
|
||||
"""
|
||||
request.setResponseCode(self._code)
|
||||
request.setHeader(b"content-type", b"text/html; charset=utf-8")
|
||||
return renderElement(
|
||||
request,
|
||||
# cast because the type annotations here seem off; Tag isn't an
|
||||
# IRenderable but also probably should be? See
|
||||
# https://github.com/twisted/twisted/issues/4982
|
||||
cast(
|
||||
IRenderable,
|
||||
tags.html(
|
||||
tags.head(tags.title(f"{self._code} - {self._brief}")),
|
||||
tags.body(tags.h1(self._brief), tags.p(self._detail)),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
def getChild(self, path: bytes, request: IRequest) -> Resource:
|
||||
"""
|
||||
Handle all requests for which L{_ErrorPage} lacks a child by returning
|
||||
this error page.
|
||||
|
||||
@param path: A path segment.
|
||||
|
||||
@param request: HTTP request
|
||||
"""
|
||||
return self
|
||||
|
||||
|
||||
def errorPage(code: int, brief: str, detail: str) -> _ErrorPage:
|
||||
"""
|
||||
Build a resource that responds to all requests with a particular HTTP
|
||||
status code and an HTML body containing some descriptive text. This is
|
||||
useful for rendering simple error pages.
|
||||
|
||||
The resource dynamically handles all paths below it. Use
|
||||
L{IResource.putChild()} to override a specific path.
|
||||
|
||||
@param code: An integer HTTP status code which will be used for the
|
||||
response.
|
||||
|
||||
@param brief: A short string which will be included in the response
|
||||
body as the page title.
|
||||
|
||||
@param detail: A longer string which will be included in the
|
||||
response body.
|
||||
|
||||
@returns: An L{IResource}
|
||||
"""
|
||||
return _ErrorPage(code, brief, detail)
|
||||
|
||||
|
||||
def notFound(
|
||||
brief: str = "No Such Resource",
|
||||
message: str = "Sorry. No luck finding that resource.",
|
||||
) -> IResource:
|
||||
"""
|
||||
Generate an L{IResource} with a 404 Not Found status code.
|
||||
|
||||
@see: L{twisted.web.pages.errorPage}
|
||||
|
||||
@param brief: A short string displayed as the page title.
|
||||
|
||||
@param brief: A longer string displayed in the page body.
|
||||
|
||||
@returns: An L{IResource}
|
||||
"""
|
||||
return _ErrorPage(http.NOT_FOUND, brief, message)
|
||||
|
||||
|
||||
def forbidden(
|
||||
brief: str = "Forbidden Resource", message: str = "Sorry, resource is forbidden."
|
||||
) -> IResource:
|
||||
"""
|
||||
Generate an L{IResource} with a 403 Forbidden status code.
|
||||
|
||||
@see: L{twisted.web.pages.errorPage}
|
||||
|
||||
@param brief: A short string displayed as the page title.
|
||||
|
||||
@param brief: A longer string displayed in the page body.
|
||||
|
||||
@returns: An L{IResource}
|
||||
"""
|
||||
return _ErrorPage(http.FORBIDDEN, brief, message)
|
||||
296
backend/lib/python3.12/site-packages/twisted/web/proxy.py
Normal file
296
backend/lib/python3.12/site-packages/twisted/web/proxy.py
Normal file
@@ -0,0 +1,296 @@
|
||||
# -*- test-case-name: twisted.web.test.test_proxy -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Simplistic HTTP proxy support.
|
||||
|
||||
This comes in two main variants - the Proxy and the ReverseProxy.
|
||||
|
||||
When a Proxy is in use, a browser trying to connect to a server (say,
|
||||
www.yahoo.com) will be intercepted by the Proxy, and the proxy will covertly
|
||||
connect to the server, and return the result.
|
||||
|
||||
When a ReverseProxy is in use, the client connects directly to the ReverseProxy
|
||||
(say, www.yahoo.com) which farms off the request to one of a pool of servers,
|
||||
and returns the result.
|
||||
|
||||
Normally, a Proxy is used on the client end of an Internet connection, while a
|
||||
ReverseProxy is used on the server end.
|
||||
"""
|
||||
|
||||
from urllib.parse import quote as urlquote, urlparse, urlunparse
|
||||
|
||||
from twisted.internet import reactor
|
||||
from twisted.internet.protocol import ClientFactory
|
||||
from twisted.web.http import _QUEUED_SENTINEL, HTTPChannel, HTTPClient, Request
|
||||
from twisted.web.resource import Resource
|
||||
from twisted.web.server import NOT_DONE_YET
|
||||
|
||||
|
||||
class ProxyClient(HTTPClient):
|
||||
"""
|
||||
Used by ProxyClientFactory to implement a simple web proxy.
|
||||
|
||||
@ivar _finished: A flag which indicates whether or not the original request
|
||||
has been finished yet.
|
||||
"""
|
||||
|
||||
_finished = False
|
||||
|
||||
def __init__(self, command, rest, version, headers, data, father):
|
||||
self.father = father
|
||||
self.command = command
|
||||
self.rest = rest
|
||||
if b"proxy-connection" in headers:
|
||||
del headers[b"proxy-connection"]
|
||||
headers[b"connection"] = b"close"
|
||||
headers.pop(b"keep-alive", None)
|
||||
self.headers = headers
|
||||
self.data = data
|
||||
|
||||
def connectionMade(self):
|
||||
self.sendCommand(self.command, self.rest)
|
||||
for header, value in self.headers.items():
|
||||
self.sendHeader(header, value)
|
||||
self.endHeaders()
|
||||
self.transport.write(self.data)
|
||||
|
||||
def handleStatus(self, version, code, message):
|
||||
self.father.setResponseCode(int(code), message)
|
||||
|
||||
def handleHeader(self, key, value):
|
||||
# t.web.server.Request sets default values for these headers in its
|
||||
# 'process' method. When these headers are received from the remote
|
||||
# server, they ought to override the defaults, rather than append to
|
||||
# them.
|
||||
if key.lower() in [b"server", b"date", b"content-type"]:
|
||||
self.father.responseHeaders.setRawHeaders(key, [value])
|
||||
else:
|
||||
self.father.responseHeaders.addRawHeader(key, value)
|
||||
|
||||
def handleResponsePart(self, buffer):
|
||||
self.father.write(buffer)
|
||||
|
||||
def handleResponseEnd(self):
|
||||
"""
|
||||
Finish the original request, indicating that the response has been
|
||||
completely written to it, and disconnect the outgoing transport.
|
||||
"""
|
||||
if not self._finished:
|
||||
self._finished = True
|
||||
self.father.finish()
|
||||
self.transport.loseConnection()
|
||||
|
||||
|
||||
class ProxyClientFactory(ClientFactory):
|
||||
"""
|
||||
Used by ProxyRequest to implement a simple web proxy.
|
||||
"""
|
||||
|
||||
# Type is wrong. See: https://twistedmatrix.com/trac/ticket/10006
|
||||
protocol = ProxyClient # type: ignore[assignment]
|
||||
|
||||
def __init__(self, command, rest, version, headers, data, father):
|
||||
self.father = father
|
||||
self.command = command
|
||||
self.rest = rest
|
||||
self.headers = headers
|
||||
self.data = data
|
||||
self.version = version
|
||||
|
||||
def buildProtocol(self, addr):
|
||||
return self.protocol(
|
||||
self.command, self.rest, self.version, self.headers, self.data, self.father
|
||||
)
|
||||
|
||||
def clientConnectionFailed(self, connector, reason):
|
||||
"""
|
||||
Report a connection failure in a response to the incoming request as
|
||||
an error.
|
||||
"""
|
||||
self.father.setResponseCode(501, b"Gateway error")
|
||||
self.father.responseHeaders.addRawHeader(b"Content-Type", b"text/html")
|
||||
self.father.write(b"<H1>Could not connect</H1>")
|
||||
self.father.finish()
|
||||
|
||||
|
||||
class ProxyRequest(Request):
|
||||
"""
|
||||
Used by Proxy to implement a simple web proxy.
|
||||
|
||||
@ivar reactor: the reactor used to create connections.
|
||||
@type reactor: object providing L{twisted.internet.interfaces.IReactorTCP}
|
||||
"""
|
||||
|
||||
protocols = {b"http": ProxyClientFactory}
|
||||
ports = {b"http": 80}
|
||||
|
||||
def __init__(self, channel, queued=_QUEUED_SENTINEL, reactor=reactor):
|
||||
Request.__init__(self, channel, queued)
|
||||
self.reactor = reactor
|
||||
|
||||
def process(self):
|
||||
parsed = urlparse(self.uri)
|
||||
protocol = parsed[0]
|
||||
host = parsed[1].decode("ascii")
|
||||
port = self.ports[protocol]
|
||||
if ":" in host:
|
||||
host, port = host.split(":")
|
||||
port = int(port)
|
||||
rest = urlunparse((b"", b"") + parsed[2:])
|
||||
if not rest:
|
||||
rest = rest + b"/"
|
||||
class_ = self.protocols[protocol]
|
||||
headers = self.getAllHeaders().copy()
|
||||
if b"host" not in headers:
|
||||
headers[b"host"] = host.encode("ascii")
|
||||
self.content.seek(0, 0)
|
||||
s = self.content.read()
|
||||
clientFactory = class_(self.method, rest, self.clientproto, headers, s, self)
|
||||
self.reactor.connectTCP(host, port, clientFactory)
|
||||
|
||||
|
||||
class Proxy(HTTPChannel):
|
||||
"""
|
||||
This class implements a simple web proxy.
|
||||
|
||||
Since it inherits from L{twisted.web.http.HTTPChannel}, to use it you
|
||||
should do something like this::
|
||||
|
||||
from twisted.web import http
|
||||
f = http.HTTPFactory()
|
||||
f.protocol = Proxy
|
||||
|
||||
Make the HTTPFactory a listener on a port as per usual, and you have
|
||||
a fully-functioning web proxy!
|
||||
"""
|
||||
|
||||
requestFactory = ProxyRequest
|
||||
|
||||
|
||||
class ReverseProxyRequest(Request):
|
||||
"""
|
||||
Used by ReverseProxy to implement a simple reverse proxy.
|
||||
|
||||
@ivar proxyClientFactoryClass: a proxy client factory class, used to create
|
||||
new connections.
|
||||
@type proxyClientFactoryClass: L{ClientFactory}
|
||||
|
||||
@ivar reactor: the reactor used to create connections.
|
||||
@type reactor: object providing L{twisted.internet.interfaces.IReactorTCP}
|
||||
"""
|
||||
|
||||
proxyClientFactoryClass = ProxyClientFactory
|
||||
|
||||
def __init__(self, channel, queued=_QUEUED_SENTINEL, reactor=reactor):
|
||||
Request.__init__(self, channel, queued)
|
||||
self.reactor = reactor
|
||||
|
||||
def process(self):
|
||||
"""
|
||||
Handle this request by connecting to the proxied server and forwarding
|
||||
it there, then forwarding the response back as the response to this
|
||||
request.
|
||||
"""
|
||||
self.requestHeaders.setRawHeaders(b"host", [self.factory.host.encode("ascii")])
|
||||
clientFactory = self.proxyClientFactoryClass(
|
||||
self.method,
|
||||
self.uri,
|
||||
self.clientproto,
|
||||
self.getAllHeaders(),
|
||||
self.content.read(),
|
||||
self,
|
||||
)
|
||||
self.reactor.connectTCP(self.factory.host, self.factory.port, clientFactory)
|
||||
|
||||
|
||||
class ReverseProxy(HTTPChannel):
|
||||
"""
|
||||
Implements a simple reverse proxy.
|
||||
|
||||
For details of usage, see the file examples/reverse-proxy.py.
|
||||
"""
|
||||
|
||||
requestFactory = ReverseProxyRequest
|
||||
|
||||
|
||||
class ReverseProxyResource(Resource):
|
||||
"""
|
||||
Resource that renders the results gotten from another server
|
||||
|
||||
Put this resource in the tree to cause everything below it to be relayed
|
||||
to a different server.
|
||||
|
||||
@ivar proxyClientFactoryClass: a proxy client factory class, used to create
|
||||
new connections.
|
||||
@type proxyClientFactoryClass: L{ClientFactory}
|
||||
|
||||
@ivar reactor: the reactor used to create connections.
|
||||
@type reactor: object providing L{twisted.internet.interfaces.IReactorTCP}
|
||||
"""
|
||||
|
||||
proxyClientFactoryClass = ProxyClientFactory
|
||||
|
||||
def __init__(self, host, port, path, reactor=reactor):
|
||||
"""
|
||||
@param host: the host of the web server to proxy.
|
||||
@type host: C{str}
|
||||
|
||||
@param port: the port of the web server to proxy.
|
||||
@type port: C{port}
|
||||
|
||||
@param path: the base path to fetch data from. Note that you shouldn't
|
||||
put any trailing slashes in it, it will be added automatically in
|
||||
request. For example, if you put B{/foo}, a request on B{/bar} will
|
||||
be proxied to B{/foo/bar}. Any required encoding of special
|
||||
characters (such as " " or "/") should have been done already.
|
||||
|
||||
@type path: C{bytes}
|
||||
"""
|
||||
Resource.__init__(self)
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.path = path
|
||||
self.reactor = reactor
|
||||
|
||||
def getChild(self, path, request):
|
||||
"""
|
||||
Create and return a proxy resource with the same proxy configuration
|
||||
as this one, except that its path also contains the segment given by
|
||||
C{path} at the end.
|
||||
"""
|
||||
return ReverseProxyResource(
|
||||
self.host,
|
||||
self.port,
|
||||
self.path + b"/" + urlquote(path, safe=b"").encode("utf-8"),
|
||||
self.reactor,
|
||||
)
|
||||
|
||||
def render(self, request):
|
||||
"""
|
||||
Render a request by forwarding it to the proxied server.
|
||||
"""
|
||||
# RFC 2616 tells us that we can omit the port if it's the default port,
|
||||
# but we have to provide it otherwise
|
||||
if self.port == 80:
|
||||
host = self.host
|
||||
else:
|
||||
host = "%s:%d" % (self.host, self.port)
|
||||
request.requestHeaders.setRawHeaders(b"host", [host.encode("ascii")])
|
||||
request.content.seek(0, 0)
|
||||
qs = urlparse(request.uri)[4]
|
||||
if qs:
|
||||
rest = self.path + b"?" + qs
|
||||
else:
|
||||
rest = self.path
|
||||
clientFactory = self.proxyClientFactoryClass(
|
||||
request.method,
|
||||
rest,
|
||||
request.clientproto,
|
||||
request.getAllHeaders(),
|
||||
request.content.read(),
|
||||
request,
|
||||
)
|
||||
self.reactor.connectTCP(self.host, self.port, clientFactory)
|
||||
return NOT_DONE_YET
|
||||
460
backend/lib/python3.12/site-packages/twisted/web/resource.py
Normal file
460
backend/lib/python3.12/site-packages/twisted/web/resource.py
Normal file
@@ -0,0 +1,460 @@
|
||||
# -*- test-case-name: twisted.web.test.test_web, twisted.web.test.test_resource -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Implementation of the lowest-level Resource class.
|
||||
|
||||
See L{twisted.web.pages} for some utility implementations.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
__all__ = [
|
||||
"IResource",
|
||||
"getChildForRequest",
|
||||
"Resource",
|
||||
"ErrorPage",
|
||||
"NoResource",
|
||||
"ForbiddenResource",
|
||||
"EncodingResourceWrapper",
|
||||
]
|
||||
|
||||
import warnings
|
||||
from typing import Sequence
|
||||
|
||||
from zope.interface import Attribute, Interface, implementer
|
||||
|
||||
from incremental import Version
|
||||
|
||||
from twisted.python.compat import nativeString
|
||||
from twisted.python.components import proxyForInterface
|
||||
from twisted.python.deprecate import deprecated
|
||||
from twisted.python.reflect import prefixedMethodNames
|
||||
from twisted.web._responses import FORBIDDEN, NOT_FOUND
|
||||
from twisted.web.error import UnsupportedMethod
|
||||
|
||||
|
||||
class IResource(Interface):
|
||||
"""
|
||||
A web resource.
|
||||
"""
|
||||
|
||||
isLeaf = Attribute(
|
||||
"""
|
||||
Signal if this IResource implementor is a "leaf node" or not. If True,
|
||||
getChildWithDefault will not be called on this Resource.
|
||||
"""
|
||||
)
|
||||
|
||||
def getChildWithDefault(name, request):
|
||||
"""
|
||||
Return a child with the given name for the given request.
|
||||
This is the external interface used by the Resource publishing
|
||||
machinery. If implementing IResource without subclassing
|
||||
Resource, it must be provided. However, if subclassing Resource,
|
||||
getChild overridden instead.
|
||||
|
||||
@param name: A single path component from a requested URL. For example,
|
||||
a request for I{http://example.com/foo/bar} will result in calls to
|
||||
this method with C{b"foo"} and C{b"bar"} as values for this
|
||||
argument.
|
||||
@type name: C{bytes}
|
||||
|
||||
@param request: A representation of all of the information about the
|
||||
request that is being made for this child.
|
||||
@type request: L{twisted.web.server.Request}
|
||||
"""
|
||||
|
||||
def putChild(path: bytes, child: "IResource") -> None:
|
||||
"""
|
||||
Put a child L{IResource} implementor at the given path.
|
||||
|
||||
@param path: A single path component, to be interpreted relative to the
|
||||
path this resource is found at, at which to put the given child.
|
||||
For example, if resource A can be found at I{http://example.com/foo}
|
||||
then a call like C{A.putChild(b"bar", B)} will make resource B
|
||||
available at I{http://example.com/foo/bar}.
|
||||
|
||||
The path component is I{not} URL-encoded -- pass C{b'foo bar'}
|
||||
rather than C{b'foo%20bar'}.
|
||||
"""
|
||||
|
||||
def render(request):
|
||||
"""
|
||||
Render a request. This is called on the leaf resource for a request.
|
||||
|
||||
@return: Either C{server.NOT_DONE_YET} to indicate an asynchronous or a
|
||||
C{bytes} instance to write as the response to the request. If
|
||||
C{NOT_DONE_YET} is returned, at some point later (for example, in a
|
||||
Deferred callback) call C{request.write(b"<html>")} to write data to
|
||||
the request, and C{request.finish()} to send the data to the
|
||||
browser.
|
||||
|
||||
@raise twisted.web.error.UnsupportedMethod: If the HTTP verb
|
||||
requested is not supported by this resource.
|
||||
"""
|
||||
|
||||
|
||||
def getChildForRequest(resource, request):
|
||||
"""
|
||||
Traverse resource tree to find who will handle the request.
|
||||
"""
|
||||
while request.postpath and not resource.isLeaf:
|
||||
pathElement = request.postpath.pop(0)
|
||||
request.prepath.append(pathElement)
|
||||
resource = resource.getChildWithDefault(pathElement, request)
|
||||
return resource
|
||||
|
||||
|
||||
@implementer(IResource)
|
||||
class Resource:
|
||||
"""
|
||||
Define a web-accessible resource.
|
||||
|
||||
This serves two main purposes: one is to provide a standard representation
|
||||
for what HTTP specification calls an 'entity', and the other is to provide
|
||||
an abstract directory structure for URL retrieval.
|
||||
"""
|
||||
|
||||
entityType = IResource
|
||||
allowedMethods: Sequence[bytes]
|
||||
|
||||
server = None
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Initialize.
|
||||
"""
|
||||
self.children = {}
|
||||
|
||||
isLeaf = 0
|
||||
|
||||
### Abstract Collection Interface
|
||||
|
||||
def listStaticNames(self):
|
||||
return list(self.children.keys())
|
||||
|
||||
def listStaticEntities(self):
|
||||
return list(self.children.items())
|
||||
|
||||
def listNames(self):
|
||||
return list(self.listStaticNames()) + self.listDynamicNames()
|
||||
|
||||
def listEntities(self):
|
||||
return list(self.listStaticEntities()) + self.listDynamicEntities()
|
||||
|
||||
def listDynamicNames(self):
|
||||
return []
|
||||
|
||||
def listDynamicEntities(self, request=None):
|
||||
return []
|
||||
|
||||
def getStaticEntity(self, name):
|
||||
return self.children.get(name)
|
||||
|
||||
def getDynamicEntity(self, name, request):
|
||||
if name not in self.children:
|
||||
return self.getChild(name, request)
|
||||
else:
|
||||
return None
|
||||
|
||||
def delEntity(self, name):
|
||||
del self.children[name]
|
||||
|
||||
def reallyPutEntity(self, name, entity):
|
||||
self.children[name] = entity
|
||||
|
||||
# Concrete HTTP interface
|
||||
|
||||
def getChild(self, path, request):
|
||||
"""
|
||||
Retrieve a 'child' resource from me.
|
||||
|
||||
Implement this to create dynamic resource generation -- resources which
|
||||
are always available may be registered with self.putChild().
|
||||
|
||||
This will not be called if the class-level variable 'isLeaf' is set in
|
||||
your subclass; instead, the 'postpath' attribute of the request will be
|
||||
left as a list of the remaining path elements.
|
||||
|
||||
For example, the URL /foo/bar/baz will normally be::
|
||||
|
||||
| site.resource.getChild('foo').getChild('bar').getChild('baz').
|
||||
|
||||
However, if the resource returned by 'bar' has isLeaf set to true, then
|
||||
the getChild call will never be made on it.
|
||||
|
||||
Parameters and return value have the same meaning and requirements as
|
||||
those defined by L{IResource.getChildWithDefault}.
|
||||
"""
|
||||
return _UnsafeNoResource()
|
||||
|
||||
def getChildWithDefault(self, path, request):
|
||||
"""
|
||||
Retrieve a static or dynamically generated child resource from me.
|
||||
|
||||
First checks if a resource was added manually by putChild, and then
|
||||
call getChild to check for dynamic resources. Only override if you want
|
||||
to affect behaviour of all child lookups, rather than just dynamic
|
||||
ones.
|
||||
|
||||
This will check to see if I have a pre-registered child resource of the
|
||||
given name, and call getChild if I do not.
|
||||
|
||||
@see: L{IResource.getChildWithDefault}
|
||||
"""
|
||||
if path in self.children:
|
||||
return self.children[path]
|
||||
return self.getChild(path, request)
|
||||
|
||||
def getChildForRequest(self, request):
|
||||
"""
|
||||
Deprecated in favor of L{getChildForRequest}.
|
||||
|
||||
@see: L{twisted.web.resource.getChildForRequest}.
|
||||
"""
|
||||
warnings.warn(
|
||||
"Please use module level getChildForRequest.", DeprecationWarning, 2
|
||||
)
|
||||
return getChildForRequest(self, request)
|
||||
|
||||
def putChild(self, path: bytes, child: IResource) -> None:
|
||||
"""
|
||||
Register a static child.
|
||||
|
||||
You almost certainly don't want '/' in your path. If you
|
||||
intended to have the root of a folder, e.g. /foo/, you want
|
||||
path to be ''.
|
||||
|
||||
@param path: A single path component.
|
||||
|
||||
@param child: The child resource to register.
|
||||
|
||||
@see: L{IResource.putChild}
|
||||
"""
|
||||
if not isinstance(path, bytes):
|
||||
raise TypeError(f"Path segment must be bytes, but {path!r} is {type(path)}")
|
||||
|
||||
self.children[path] = child
|
||||
# IResource is incomplete and doesn't mention this server attribute, see
|
||||
# https://github.com/twisted/twisted/issues/11717
|
||||
child.server = self.server # type: ignore[attr-defined]
|
||||
|
||||
def render(self, request):
|
||||
"""
|
||||
Render a given resource. See L{IResource}'s render method.
|
||||
|
||||
I delegate to methods of self with the form 'render_METHOD'
|
||||
where METHOD is the HTTP that was used to make the
|
||||
request. Examples: render_GET, render_HEAD, render_POST, and
|
||||
so on. Generally you should implement those methods instead of
|
||||
overriding this one.
|
||||
|
||||
render_METHOD methods are expected to return a byte string which will be
|
||||
the rendered page, unless the return value is C{server.NOT_DONE_YET}, in
|
||||
which case it is this class's responsibility to write the results using
|
||||
C{request.write(data)} and then call C{request.finish()}.
|
||||
|
||||
Old code that overrides render() directly is likewise expected
|
||||
to return a byte string or NOT_DONE_YET.
|
||||
|
||||
@see: L{IResource.render}
|
||||
"""
|
||||
m = getattr(self, "render_" + nativeString(request.method), None)
|
||||
if not m:
|
||||
try:
|
||||
allowedMethods = self.allowedMethods
|
||||
except AttributeError:
|
||||
allowedMethods = _computeAllowedMethods(self)
|
||||
raise UnsupportedMethod(allowedMethods)
|
||||
return m(request)
|
||||
|
||||
def render_HEAD(self, request):
|
||||
"""
|
||||
Default handling of HEAD method.
|
||||
|
||||
I just return self.render_GET(request). When method is HEAD,
|
||||
the framework will handle this correctly.
|
||||
"""
|
||||
return self.render_GET(request)
|
||||
|
||||
|
||||
def _computeAllowedMethods(resource):
|
||||
"""
|
||||
Compute the allowed methods on a C{Resource} based on defined render_FOO
|
||||
methods. Used when raising C{UnsupportedMethod} but C{Resource} does
|
||||
not define C{allowedMethods} attribute.
|
||||
"""
|
||||
allowedMethods = []
|
||||
for name in prefixedMethodNames(resource.__class__, "render_"):
|
||||
# Potentially there should be an API for encode('ascii') in this
|
||||
# situation - an API for taking a Python native string (bytes on Python
|
||||
# 2, text on Python 3) and returning a socket-compatible string type.
|
||||
allowedMethods.append(name.encode("ascii"))
|
||||
return allowedMethods
|
||||
|
||||
|
||||
class _UnsafeErrorPageBase(Resource):
|
||||
"""
|
||||
Base class for deprecated error page resources.
|
||||
|
||||
@ivar template: A native string which will have a dictionary interpolated
|
||||
into it to generate the response body. The dictionary has the following
|
||||
keys:
|
||||
|
||||
- C{"code"}: The status code passed to L{_UnsafeErrorPage.__init__}.
|
||||
- C{"brief"}: The brief description passed to
|
||||
L{_UnsafeErrorPage.__init__}.
|
||||
- C{"detail"}: The detailed description passed to
|
||||
L{_UnsafeErrorPage.__init__}.
|
||||
|
||||
@ivar code: An integer status code which will be used for the response.
|
||||
@type code: C{int}
|
||||
|
||||
@ivar brief: A short string which will be included in the response body as
|
||||
the page title.
|
||||
@type brief: C{str}
|
||||
|
||||
@ivar detail: A longer string which will be included in the response body.
|
||||
@type detail: C{str}
|
||||
"""
|
||||
|
||||
template = """
|
||||
<html>
|
||||
<head><title>%(code)s - %(brief)s</title></head>
|
||||
<body>
|
||||
<h1>%(brief)s</h1>
|
||||
<p>%(detail)s</p>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
def __init__(self, status, brief, detail):
|
||||
Resource.__init__(self)
|
||||
self.code = status
|
||||
self.brief = brief
|
||||
self.detail = detail
|
||||
|
||||
def render(self, request):
|
||||
request.setResponseCode(self.code)
|
||||
request.setHeader(b"content-type", b"text/html; charset=utf-8")
|
||||
interpolated = self.template % dict(
|
||||
code=self.code, brief=self.brief, detail=self.detail
|
||||
)
|
||||
if isinstance(interpolated, str):
|
||||
return interpolated.encode("utf-8")
|
||||
return interpolated
|
||||
|
||||
def getChild(self, chnam, request):
|
||||
return self
|
||||
|
||||
|
||||
class _UnsafeErrorPage(_UnsafeErrorPageBase):
|
||||
"""
|
||||
L{_UnsafeErrorPage}, publicly available via the deprecated alias
|
||||
C{ErrorPage}, is a resource which responds with a particular
|
||||
(parameterized) status and a body consisting of HTML containing some
|
||||
descriptive text. This is useful for rendering simple error pages.
|
||||
|
||||
Deprecated in Twisted 22.10.0 because it permits HTML injection; use
|
||||
L{twisted.web.pages.errorPage} instead.
|
||||
"""
|
||||
|
||||
@deprecated(
|
||||
Version("Twisted", 22, 10, 0),
|
||||
"Use twisted.web.pages.errorPage instead, which properly escapes HTML.",
|
||||
)
|
||||
def __init__(self, status, brief, detail):
|
||||
_UnsafeErrorPageBase.__init__(self, status, brief, detail)
|
||||
|
||||
|
||||
class _UnsafeNoResource(_UnsafeErrorPageBase):
|
||||
"""
|
||||
L{_UnsafeNoResource}, publicly available via the deprecated alias
|
||||
C{NoResource}, is a specialization of L{_UnsafeErrorPage} which
|
||||
returns the HTTP response code I{NOT FOUND}.
|
||||
|
||||
Deprecated in Twisted 22.10.0 because it permits HTML injection; use
|
||||
L{twisted.web.pages.notFound} instead.
|
||||
"""
|
||||
|
||||
@deprecated(
|
||||
Version("Twisted", 22, 10, 0),
|
||||
"Use twisted.web.pages.notFound instead, which properly escapes HTML.",
|
||||
)
|
||||
def __init__(self, message="Sorry. No luck finding that resource."):
|
||||
_UnsafeErrorPageBase.__init__(self, NOT_FOUND, "No Such Resource", message)
|
||||
|
||||
|
||||
class _UnsafeForbiddenResource(_UnsafeErrorPageBase):
|
||||
"""
|
||||
L{_UnsafeForbiddenResource}, publicly available via the deprecated alias
|
||||
C{ForbiddenResource} is a specialization of L{_UnsafeErrorPage} which
|
||||
returns the I{FORBIDDEN} HTTP response code.
|
||||
|
||||
Deprecated in Twisted 22.10.0 because it permits HTML injection; use
|
||||
L{twisted.web.pages.forbidden} instead.
|
||||
"""
|
||||
|
||||
@deprecated(
|
||||
Version("Twisted", 22, 10, 0),
|
||||
"Use twisted.web.pages.forbidden instead, which properly escapes HTML.",
|
||||
)
|
||||
def __init__(self, message="Sorry, resource is forbidden."):
|
||||
_UnsafeErrorPageBase.__init__(self, FORBIDDEN, "Forbidden Resource", message)
|
||||
|
||||
|
||||
# Deliberately undocumented public aliases. See GHSA-vg46-2rrj-3647.
|
||||
ErrorPage = _UnsafeErrorPage
|
||||
NoResource = _UnsafeNoResource
|
||||
ForbiddenResource = _UnsafeForbiddenResource
|
||||
|
||||
|
||||
class _IEncodingResource(Interface):
|
||||
"""
|
||||
A resource which knows about L{_IRequestEncoderFactory}.
|
||||
|
||||
@since: 12.3
|
||||
"""
|
||||
|
||||
def getEncoder(request):
|
||||
"""
|
||||
Parse the request and return an encoder if applicable, using
|
||||
L{_IRequestEncoderFactory.encoderForRequest}.
|
||||
|
||||
@return: A L{_IRequestEncoder}, or L{None}.
|
||||
"""
|
||||
|
||||
|
||||
@implementer(_IEncodingResource)
|
||||
class EncodingResourceWrapper(proxyForInterface(IResource)): # type: ignore[misc]
|
||||
"""
|
||||
Wrap a L{IResource}, potentially applying an encoding to the response body
|
||||
generated.
|
||||
|
||||
Note that the returned children resources won't be wrapped, so you have to
|
||||
explicitly wrap them if you want the encoding to be applied.
|
||||
|
||||
@ivar encoders: A list of
|
||||
L{_IRequestEncoderFactory<twisted.web.iweb._IRequestEncoderFactory>}
|
||||
returning L{_IRequestEncoder<twisted.web.iweb._IRequestEncoder>} that
|
||||
may transform the data passed to C{Request.write}. The list must be
|
||||
sorted in order of priority: the first encoder factory handling the
|
||||
request will prevent the others from doing the same.
|
||||
@type encoders: C{list}.
|
||||
|
||||
@since: 12.3
|
||||
"""
|
||||
|
||||
def __init__(self, original, encoders):
|
||||
super().__init__(original)
|
||||
self._encoders = encoders
|
||||
|
||||
def getEncoder(self, request):
|
||||
"""
|
||||
Browser the list of encoders looking for one applicable encoder.
|
||||
"""
|
||||
for encoderFactory in self._encoders:
|
||||
encoder = encoderFactory.encoderForRequest(request)
|
||||
if encoder is not None:
|
||||
return encoder
|
||||
55
backend/lib/python3.12/site-packages/twisted/web/rewrite.py
Normal file
55
backend/lib/python3.12/site-packages/twisted/web/rewrite.py
Normal file
@@ -0,0 +1,55 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
#
|
||||
from twisted.web import resource
|
||||
|
||||
|
||||
class RewriterResource(resource.Resource):
|
||||
def __init__(self, orig, *rewriteRules):
|
||||
resource.Resource.__init__(self)
|
||||
self.resource = orig
|
||||
self.rewriteRules = list(rewriteRules)
|
||||
|
||||
def _rewrite(self, request):
|
||||
for rewriteRule in self.rewriteRules:
|
||||
rewriteRule(request)
|
||||
|
||||
def getChild(self, path, request):
|
||||
request.postpath.insert(0, path)
|
||||
request.prepath.pop()
|
||||
self._rewrite(request)
|
||||
path = request.postpath.pop(0)
|
||||
request.prepath.append(path)
|
||||
return self.resource.getChildWithDefault(path, request)
|
||||
|
||||
def render(self, request):
|
||||
self._rewrite(request)
|
||||
return self.resource.render(request)
|
||||
|
||||
|
||||
def tildeToUsers(request):
|
||||
if request.postpath and request.postpath[0][:1] == "~":
|
||||
request.postpath[:1] = ["users", request.postpath[0][1:]]
|
||||
request.path = "/" + "/".join(request.prepath + request.postpath)
|
||||
|
||||
|
||||
def alias(aliasPath, sourcePath):
|
||||
"""
|
||||
I am not a very good aliaser. But I'm the best I can be. If I'm
|
||||
aliasing to a Resource that generates links, and it uses any parts
|
||||
of request.prepath to do so, the links will not be relative to the
|
||||
aliased path, but rather to the aliased-to path. That I can't
|
||||
alias static.File directory listings that nicely. However, I can
|
||||
still be useful, as many resources will play nice.
|
||||
"""
|
||||
sourcePath = sourcePath.split("/")
|
||||
aliasPath = aliasPath.split("/")
|
||||
|
||||
def rewriter(request):
|
||||
if request.postpath[: len(aliasPath)] == aliasPath:
|
||||
after = request.postpath[len(aliasPath) :]
|
||||
request.postpath = sourcePath + after
|
||||
request.path = "/" + "/".join(request.prepath + request.postpath)
|
||||
|
||||
return rewriter
|
||||
193
backend/lib/python3.12/site-packages/twisted/web/script.py
Normal file
193
backend/lib/python3.12/site-packages/twisted/web/script.py
Normal file
@@ -0,0 +1,193 @@
|
||||
# -*- test-case-name: twisted.web.test.test_script -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
I contain PythonScript, which is a very simple python script resource.
|
||||
"""
|
||||
|
||||
|
||||
import os
|
||||
import traceback
|
||||
from io import StringIO
|
||||
|
||||
from twisted import copyright
|
||||
from twisted.python.compat import execfile, networkString
|
||||
from twisted.python.filepath import _coerceToFilesystemEncoding
|
||||
from twisted.web import http, resource, server, static, util
|
||||
|
||||
rpyNoResource = """<p>You forgot to assign to the variable "resource" in your script. For example:</p>
|
||||
<pre>
|
||||
# MyCoolWebApp.rpy
|
||||
|
||||
import mygreatresource
|
||||
|
||||
resource = mygreatresource.MyGreatResource()
|
||||
</pre>
|
||||
"""
|
||||
|
||||
|
||||
class AlreadyCached(Exception):
|
||||
"""
|
||||
This exception is raised when a path has already been cached.
|
||||
"""
|
||||
|
||||
|
||||
class CacheScanner:
|
||||
def __init__(self, path, registry):
|
||||
self.path = path
|
||||
self.registry = registry
|
||||
self.doCache = 0
|
||||
|
||||
def cache(self):
|
||||
c = self.registry.getCachedPath(self.path)
|
||||
if c is not None:
|
||||
raise AlreadyCached(c)
|
||||
self.recache()
|
||||
|
||||
def recache(self):
|
||||
self.doCache = 1
|
||||
|
||||
|
||||
noRsrc = resource._UnsafeErrorPage(500, "Whoops! Internal Error", rpyNoResource)
|
||||
|
||||
|
||||
def ResourceScript(path, registry):
|
||||
"""
|
||||
I am a normal py file which must define a 'resource' global, which should
|
||||
be an instance of (a subclass of) web.resource.Resource; it will be
|
||||
renderred.
|
||||
"""
|
||||
cs = CacheScanner(path, registry)
|
||||
glob = {
|
||||
"__file__": _coerceToFilesystemEncoding("", path),
|
||||
"resource": noRsrc,
|
||||
"registry": registry,
|
||||
"cache": cs.cache,
|
||||
"recache": cs.recache,
|
||||
}
|
||||
try:
|
||||
execfile(path, glob, glob)
|
||||
except AlreadyCached as ac:
|
||||
return ac.args[0]
|
||||
rsrc = glob["resource"]
|
||||
if cs.doCache and rsrc is not noRsrc:
|
||||
registry.cachePath(path, rsrc)
|
||||
return rsrc
|
||||
|
||||
|
||||
def ResourceTemplate(path, registry):
|
||||
from quixote import ptl_compile
|
||||
|
||||
glob = {
|
||||
"__file__": _coerceToFilesystemEncoding("", path),
|
||||
"resource": resource._UnsafeErrorPage(
|
||||
500, "Whoops! Internal Error", rpyNoResource
|
||||
),
|
||||
"registry": registry,
|
||||
}
|
||||
|
||||
with open(path) as f: # Not closed by quixote as of 2.9.1
|
||||
e = ptl_compile.compile_template(f, path)
|
||||
code = compile(e, "<source>", "exec")
|
||||
eval(code, glob, glob)
|
||||
return glob["resource"]
|
||||
|
||||
|
||||
class ResourceScriptWrapper(resource.Resource):
|
||||
def __init__(self, path, registry=None):
|
||||
resource.Resource.__init__(self)
|
||||
self.path = path
|
||||
self.registry = registry or static.Registry()
|
||||
|
||||
def render(self, request):
|
||||
res = ResourceScript(self.path, self.registry)
|
||||
return res.render(request)
|
||||
|
||||
def getChildWithDefault(self, path, request):
|
||||
res = ResourceScript(self.path, self.registry)
|
||||
return res.getChildWithDefault(path, request)
|
||||
|
||||
|
||||
class ResourceScriptDirectory(resource.Resource):
|
||||
"""
|
||||
L{ResourceScriptDirectory} is a resource which serves scripts from a
|
||||
filesystem directory. File children of a L{ResourceScriptDirectory} will
|
||||
be served using L{ResourceScript}. Directory children will be served using
|
||||
another L{ResourceScriptDirectory}.
|
||||
|
||||
@ivar path: A C{str} giving the filesystem path in which children will be
|
||||
looked up.
|
||||
|
||||
@ivar registry: A L{static.Registry} instance which will be used to decide
|
||||
how to interpret scripts found as children of this resource.
|
||||
"""
|
||||
|
||||
def __init__(self, pathname, registry=None):
|
||||
resource.Resource.__init__(self)
|
||||
self.path = pathname
|
||||
self.registry = registry or static.Registry()
|
||||
|
||||
def getChild(self, path, request):
|
||||
fn = os.path.join(self.path, path)
|
||||
|
||||
if os.path.isdir(fn):
|
||||
return ResourceScriptDirectory(fn, self.registry)
|
||||
if os.path.exists(fn):
|
||||
return ResourceScript(fn, self.registry)
|
||||
return resource._UnsafeNoResource()
|
||||
|
||||
def render(self, request):
|
||||
return resource._UnsafeNoResource().render(request)
|
||||
|
||||
|
||||
class PythonScript(resource.Resource):
|
||||
"""
|
||||
I am an extremely simple dynamic resource; an embedded python script.
|
||||
|
||||
This will execute a file (usually of the extension '.epy') as Python code,
|
||||
internal to the webserver.
|
||||
"""
|
||||
|
||||
isLeaf = True
|
||||
|
||||
def __init__(self, filename, registry):
|
||||
"""
|
||||
Initialize me with a script name.
|
||||
"""
|
||||
self.filename = filename
|
||||
self.registry = registry
|
||||
|
||||
def render(self, request):
|
||||
"""
|
||||
Render me to a web client.
|
||||
|
||||
Load my file, execute it in a special namespace (with 'request' and
|
||||
'__file__' global vars) and finish the request. Output to the web-page
|
||||
will NOT be handled with print - standard output goes to the log - but
|
||||
with request.write.
|
||||
"""
|
||||
request.setHeader(
|
||||
b"x-powered-by", networkString("Twisted/%s" % copyright.version)
|
||||
)
|
||||
namespace = {
|
||||
"request": request,
|
||||
"__file__": _coerceToFilesystemEncoding("", self.filename),
|
||||
"registry": self.registry,
|
||||
}
|
||||
try:
|
||||
execfile(self.filename, namespace, namespace)
|
||||
except OSError as e:
|
||||
if e.errno == 2: # file not found
|
||||
request.setResponseCode(http.NOT_FOUND)
|
||||
request.write(
|
||||
resource._UnsafeNoResource("File not found.").render(request)
|
||||
)
|
||||
except BaseException:
|
||||
io = StringIO()
|
||||
traceback.print_exc(file=io)
|
||||
output = util._PRE(io.getvalue())
|
||||
output = output.encode("utf8")
|
||||
request.write(output)
|
||||
request.finish()
|
||||
return server.NOT_DONE_YET
|
||||
891
backend/lib/python3.12/site-packages/twisted/web/server.py
Normal file
891
backend/lib/python3.12/site-packages/twisted/web/server.py
Normal file
@@ -0,0 +1,891 @@
|
||||
# -*- test-case-name: twisted.web.test.test_web -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
This is a web server which integrates with the twisted.internet infrastructure.
|
||||
|
||||
@var NOT_DONE_YET: A token value which L{twisted.web.resource.IResource.render}
|
||||
implementations can return to indicate that the application will later call
|
||||
C{.write} and C{.finish} to complete the request, and that the HTTP
|
||||
connection should be left open.
|
||||
@type NOT_DONE_YET: Opaque; do not depend on any particular type for this
|
||||
value.
|
||||
"""
|
||||
|
||||
import copy
|
||||
import os
|
||||
import re
|
||||
import zlib
|
||||
from binascii import hexlify
|
||||
from html import escape
|
||||
from typing import List, Optional
|
||||
from urllib.parse import quote as _quote
|
||||
|
||||
from zope.interface import implementer
|
||||
|
||||
from twisted import copyright
|
||||
from twisted.internet import address, interfaces
|
||||
from twisted.internet.error import AlreadyCalled, AlreadyCancelled
|
||||
from twisted.logger import Logger
|
||||
from twisted.python import components, failure, reflect
|
||||
from twisted.python.compat import nativeString, networkString
|
||||
from twisted.spread.pb import Copyable, ViewPoint
|
||||
from twisted.web import http, iweb, resource, util
|
||||
from twisted.web.error import UnsupportedMethod
|
||||
from twisted.web.http import (
|
||||
NO_CONTENT,
|
||||
NOT_MODIFIED,
|
||||
HTTPFactory,
|
||||
Request as _HTTPRequest,
|
||||
datetimeToString,
|
||||
unquote,
|
||||
)
|
||||
|
||||
NOT_DONE_YET = 1
|
||||
|
||||
__all__ = [
|
||||
"supportedMethods",
|
||||
"Request",
|
||||
"Session",
|
||||
"Site",
|
||||
"version",
|
||||
"NOT_DONE_YET",
|
||||
"GzipEncoderFactory",
|
||||
]
|
||||
|
||||
# Support for other methods may be implemented on a per-resource basis.
|
||||
supportedMethods = (b"GET", b"HEAD", b"POST")
|
||||
|
||||
|
||||
def quote(string, *args, **kwargs):
|
||||
return _quote(string.decode("charmap"), *args, **kwargs).encode("charmap")
|
||||
|
||||
|
||||
def _addressToTuple(addr):
|
||||
if isinstance(addr, address.IPv4Address):
|
||||
return ("INET", addr.host, addr.port)
|
||||
elif isinstance(addr, address.UNIXAddress):
|
||||
return ("UNIX", addr.name)
|
||||
else:
|
||||
return tuple(addr)
|
||||
|
||||
|
||||
@implementer(iweb.IRequest)
|
||||
class Request(Copyable, http.Request, components.Componentized):
|
||||
"""
|
||||
An HTTP request.
|
||||
|
||||
@ivar defaultContentType: A L{bytes} giving the default I{Content-Type}
|
||||
value to send in responses if no other value is set. L{None} disables
|
||||
the default.
|
||||
|
||||
@ivar _insecureSession: The L{Session} object representing state that will
|
||||
be transmitted over plain-text HTTP.
|
||||
|
||||
@ivar _secureSession: The L{Session} object representing the state that
|
||||
will be transmitted only over HTTPS.
|
||||
"""
|
||||
|
||||
defaultContentType: Optional[bytes] = b"text/html"
|
||||
site = None
|
||||
appRootURL = None
|
||||
prepath: Optional[List[bytes]] = None
|
||||
postpath: Optional[List[bytes]] = None
|
||||
__pychecker__ = "unusednames=issuer"
|
||||
_inFakeHead = False
|
||||
_encoder = None
|
||||
_log = Logger()
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
_HTTPRequest.__init__(self, *args, **kw)
|
||||
components.Componentized.__init__(self)
|
||||
|
||||
def getStateToCopyFor(self, issuer):
|
||||
x = self.__dict__.copy()
|
||||
del x["transport"]
|
||||
# XXX refactor this attribute out; it's from protocol
|
||||
# del x['server']
|
||||
del x["channel"]
|
||||
del x["content"]
|
||||
del x["site"]
|
||||
self.content.seek(0, 0)
|
||||
x["content_data"] = self.content.read()
|
||||
x["remote"] = ViewPoint(issuer, self)
|
||||
|
||||
# Address objects aren't jellyable
|
||||
x["host"] = _addressToTuple(x["host"])
|
||||
x["client"] = _addressToTuple(x["client"])
|
||||
|
||||
# Header objects also aren't jellyable.
|
||||
x["requestHeaders"] = list(x["requestHeaders"].getAllRawHeaders())
|
||||
|
||||
return x
|
||||
|
||||
# HTML generation helpers
|
||||
|
||||
def sibLink(self, name):
|
||||
"""
|
||||
Return the text that links to a sibling of the requested resource.
|
||||
|
||||
@param name: The sibling resource
|
||||
@type name: C{bytes}
|
||||
|
||||
@return: A relative URL.
|
||||
@rtype: C{bytes}
|
||||
"""
|
||||
if self.postpath:
|
||||
return (len(self.postpath) * b"../") + name
|
||||
else:
|
||||
return name
|
||||
|
||||
def childLink(self, name):
|
||||
"""
|
||||
Return the text that links to a child of the requested resource.
|
||||
|
||||
@param name: The child resource
|
||||
@type name: C{bytes}
|
||||
|
||||
@return: A relative URL.
|
||||
@rtype: C{bytes}
|
||||
"""
|
||||
lpp = len(self.postpath)
|
||||
if lpp > 1:
|
||||
return ((lpp - 1) * b"../") + name
|
||||
elif lpp == 1:
|
||||
return name
|
||||
else: # lpp == 0
|
||||
if len(self.prepath) and self.prepath[-1]:
|
||||
return self.prepath[-1] + b"/" + name
|
||||
else:
|
||||
return name
|
||||
|
||||
def gotLength(self, length):
|
||||
"""
|
||||
Called when HTTP channel got length of content in this request.
|
||||
|
||||
This method is not intended for users.
|
||||
|
||||
@param length: The length of the request body, as indicated by the
|
||||
request headers. L{None} if the request headers do not indicate a
|
||||
length.
|
||||
"""
|
||||
try:
|
||||
getContentFile = self.channel.site.getContentFile
|
||||
except AttributeError:
|
||||
_HTTPRequest.gotLength(self, length)
|
||||
else:
|
||||
self.content = getContentFile(length)
|
||||
|
||||
def process(self):
|
||||
"""
|
||||
Process a request.
|
||||
|
||||
Find the addressed resource in this request's L{Site},
|
||||
and call L{self.render()<Request.render()>} with it.
|
||||
|
||||
@see: L{Site.getResourceFor()}
|
||||
"""
|
||||
|
||||
# get site from channel
|
||||
self.site = self.channel.site
|
||||
|
||||
# set various default headers
|
||||
self.setHeader(b"Server", version)
|
||||
self.setHeader(b"Date", datetimeToString())
|
||||
|
||||
# Resource Identification
|
||||
self.prepath = []
|
||||
self.postpath = list(map(unquote, self.path[1:].split(b"/")))
|
||||
|
||||
# Short-circuit for requests whose path is '*'.
|
||||
if self.path == b"*":
|
||||
self._handleStar()
|
||||
return
|
||||
|
||||
try:
|
||||
resrc = self.site.getResourceFor(self)
|
||||
if resource._IEncodingResource.providedBy(resrc):
|
||||
encoder = resrc.getEncoder(self)
|
||||
if encoder is not None:
|
||||
self._encoder = encoder
|
||||
self.render(resrc)
|
||||
except BaseException:
|
||||
self.processingFailed(failure.Failure())
|
||||
|
||||
def write(self, data):
|
||||
"""
|
||||
Write data to the transport (if not responding to a HEAD request).
|
||||
|
||||
@param data: A string to write to the response.
|
||||
@type data: L{bytes}
|
||||
"""
|
||||
if not self.startedWriting:
|
||||
# Before doing the first write, check to see if a default
|
||||
# Content-Type header should be supplied. We omit it on
|
||||
# NOT_MODIFIED and NO_CONTENT responses. We also omit it if there
|
||||
# is a Content-Length header set to 0, as empty bodies don't need
|
||||
# a content-type.
|
||||
needsCT = self.code not in (NOT_MODIFIED, NO_CONTENT)
|
||||
contentType = self.responseHeaders.getRawHeaders(b"Content-Type")
|
||||
contentLength = self.responseHeaders.getRawHeaders(b"Content-Length")
|
||||
contentLengthZero = contentLength and (contentLength[0] == b"0")
|
||||
|
||||
if (
|
||||
needsCT
|
||||
and contentType is None
|
||||
and self.defaultContentType is not None
|
||||
and not contentLengthZero
|
||||
):
|
||||
self.responseHeaders.setRawHeaders(
|
||||
b"Content-Type", [self.defaultContentType]
|
||||
)
|
||||
|
||||
# Only let the write happen if we're not generating a HEAD response by
|
||||
# faking out the request method. Note, if we are doing that,
|
||||
# startedWriting will never be true, and the above logic may run
|
||||
# multiple times. It will only actually change the responseHeaders
|
||||
# once though, so it's still okay.
|
||||
if not self._inFakeHead:
|
||||
if self._encoder:
|
||||
data = self._encoder.encode(data)
|
||||
_HTTPRequest.write(self, data)
|
||||
|
||||
def finish(self):
|
||||
"""
|
||||
Override L{twisted.web.http.Request.finish} for possible encoding.
|
||||
"""
|
||||
if self._encoder:
|
||||
data = self._encoder.finish()
|
||||
if data:
|
||||
_HTTPRequest.write(self, data)
|
||||
return _HTTPRequest.finish(self)
|
||||
|
||||
def render(self, resrc):
|
||||
"""
|
||||
Ask a resource to render itself.
|
||||
|
||||
If the resource does not support the requested method,
|
||||
generate a C{NOT IMPLEMENTED} or C{NOT ALLOWED} response.
|
||||
|
||||
@param resrc: The resource to render.
|
||||
@type resrc: L{twisted.web.resource.IResource}
|
||||
|
||||
@see: L{IResource.render()<twisted.web.resource.IResource.render()>}
|
||||
"""
|
||||
try:
|
||||
body = resrc.render(self)
|
||||
except UnsupportedMethod as e:
|
||||
allowedMethods = e.allowedMethods
|
||||
if (self.method == b"HEAD") and (b"GET" in allowedMethods):
|
||||
# We must support HEAD (RFC 2616, 5.1.1). If the
|
||||
# resource doesn't, fake it by giving the resource
|
||||
# a 'GET' request and then return only the headers,
|
||||
# not the body.
|
||||
self._log.info(
|
||||
"Using GET to fake a HEAD request for {resrc}", resrc=resrc
|
||||
)
|
||||
self.method = b"GET"
|
||||
self._inFakeHead = True
|
||||
body = resrc.render(self)
|
||||
|
||||
if body is NOT_DONE_YET:
|
||||
self._log.info(
|
||||
"Tried to fake a HEAD request for {resrc}, but "
|
||||
"it got away from me.",
|
||||
resrc=resrc,
|
||||
)
|
||||
# Oh well, I guess we won't include the content length.
|
||||
else:
|
||||
self.setHeader(b"Content-Length", b"%d" % (len(body),))
|
||||
|
||||
self._inFakeHead = False
|
||||
self.method = b"HEAD"
|
||||
self.write(b"")
|
||||
self.finish()
|
||||
return
|
||||
|
||||
if self.method in (supportedMethods):
|
||||
# We MUST include an Allow header
|
||||
# (RFC 2616, 10.4.6 and 14.7)
|
||||
self.setHeader(b"Allow", b", ".join(allowedMethods))
|
||||
s = (
|
||||
"""Your browser approached me (at %(URI)s) with"""
|
||||
""" the method "%(method)s". I only allow"""
|
||||
""" the method%(plural)s %(allowed)s here."""
|
||||
% {
|
||||
"URI": escape(nativeString(self.uri)),
|
||||
"method": nativeString(self.method),
|
||||
"plural": ((len(allowedMethods) > 1) and "s") or "",
|
||||
"allowed": ", ".join([nativeString(x) for x in allowedMethods]),
|
||||
}
|
||||
)
|
||||
epage = resource._UnsafeErrorPage(
|
||||
http.NOT_ALLOWED, "Method Not Allowed", s
|
||||
)
|
||||
body = epage.render(self)
|
||||
else:
|
||||
epage = resource._UnsafeErrorPage(
|
||||
http.NOT_IMPLEMENTED,
|
||||
"Huh?",
|
||||
"I don't know how to treat a %s request."
|
||||
% (escape(self.method.decode("charmap")),),
|
||||
)
|
||||
body = epage.render(self)
|
||||
# end except UnsupportedMethod
|
||||
|
||||
if body is NOT_DONE_YET:
|
||||
return
|
||||
if not isinstance(body, bytes):
|
||||
body = resource._UnsafeErrorPage(
|
||||
http.INTERNAL_SERVER_ERROR,
|
||||
"Request did not return bytes",
|
||||
"Request: "
|
||||
# GHSA-vg46-2rrj-3647 note: _PRE does HTML-escape the input.
|
||||
+ util._PRE(reflect.safe_repr(self))
|
||||
+ "<br />"
|
||||
+ "Resource: "
|
||||
+ util._PRE(reflect.safe_repr(resrc))
|
||||
+ "<br />"
|
||||
+ "Value: "
|
||||
+ util._PRE(reflect.safe_repr(body)),
|
||||
).render(self)
|
||||
|
||||
if self.method == b"HEAD":
|
||||
if len(body) > 0:
|
||||
# This is a Bad Thing (RFC 2616, 9.4)
|
||||
self._log.info(
|
||||
"Warning: HEAD request {slf} for resource {resrc} is"
|
||||
" returning a message body. I think I'll eat it.",
|
||||
slf=self,
|
||||
resrc=resrc,
|
||||
)
|
||||
self.setHeader(b"Content-Length", b"%d" % (len(body),))
|
||||
self.write(b"")
|
||||
else:
|
||||
self.setHeader(b"Content-Length", b"%d" % (len(body),))
|
||||
self.write(body)
|
||||
self.finish()
|
||||
|
||||
def processingFailed(self, reason):
|
||||
"""
|
||||
Finish this request with an indication that processing failed and
|
||||
possibly display a traceback.
|
||||
|
||||
@param reason: Reason this request has failed.
|
||||
@type reason: L{twisted.python.failure.Failure}
|
||||
|
||||
@return: The reason passed to this method.
|
||||
@rtype: L{twisted.python.failure.Failure}
|
||||
"""
|
||||
self._log.failure("", failure=reason)
|
||||
if self.site.displayTracebacks:
|
||||
body = (
|
||||
b"<html><head><title>web.Server Traceback"
|
||||
b" (most recent call last)</title></head>"
|
||||
b"<body><b>web.Server Traceback"
|
||||
b" (most recent call last):</b>\n\n"
|
||||
+ util.formatFailure(reason)
|
||||
+ b"\n\n</body></html>\n"
|
||||
)
|
||||
else:
|
||||
body = (
|
||||
b"<html><head><title>Processing Failed"
|
||||
b"</title></head><body>"
|
||||
b"<b>Processing Failed</b></body></html>"
|
||||
)
|
||||
|
||||
self.setResponseCode(http.INTERNAL_SERVER_ERROR)
|
||||
self.setHeader(b"Content-Type", b"text/html")
|
||||
self.setHeader(b"Content-Length", b"%d" % (len(body),))
|
||||
self.write(body)
|
||||
self.finish()
|
||||
return reason
|
||||
|
||||
def view_write(self, issuer, data):
|
||||
"""Remote version of write; same interface."""
|
||||
self.write(data)
|
||||
|
||||
def view_finish(self, issuer):
|
||||
"""Remote version of finish; same interface."""
|
||||
self.finish()
|
||||
|
||||
def view_addCookie(self, issuer, k, v, **kwargs):
|
||||
"""Remote version of addCookie; same interface."""
|
||||
self.addCookie(k, v, **kwargs)
|
||||
|
||||
def view_setHeader(self, issuer, k, v):
|
||||
"""Remote version of setHeader; same interface."""
|
||||
self.setHeader(k, v)
|
||||
|
||||
def view_setLastModified(self, issuer, when):
|
||||
"""Remote version of setLastModified; same interface."""
|
||||
self.setLastModified(when)
|
||||
|
||||
def view_setETag(self, issuer, tag):
|
||||
"""Remote version of setETag; same interface."""
|
||||
self.setETag(tag)
|
||||
|
||||
def view_setResponseCode(self, issuer, code, message=None):
|
||||
"""
|
||||
Remote version of setResponseCode; same interface.
|
||||
"""
|
||||
self.setResponseCode(code, message)
|
||||
|
||||
def view_registerProducer(self, issuer, producer, streaming):
|
||||
"""Remote version of registerProducer; same interface.
|
||||
(requires a remote producer.)
|
||||
"""
|
||||
self.registerProducer(_RemoteProducerWrapper(producer), streaming)
|
||||
|
||||
def view_unregisterProducer(self, issuer):
|
||||
self.unregisterProducer()
|
||||
|
||||
### these calls remain local
|
||||
|
||||
_secureSession = None
|
||||
_insecureSession = None
|
||||
|
||||
@property
|
||||
def session(self):
|
||||
"""
|
||||
If a session has already been created or looked up with
|
||||
L{Request.getSession}, this will return that object. (This will always
|
||||
be the session that matches the security of the request; so if
|
||||
C{forceNotSecure} is used on a secure request, this will not return
|
||||
that session.)
|
||||
|
||||
@return: the session attribute
|
||||
@rtype: L{Session} or L{None}
|
||||
"""
|
||||
if self.isSecure():
|
||||
return self._secureSession
|
||||
else:
|
||||
return self._insecureSession
|
||||
|
||||
def getSession(self, sessionInterface=None, forceNotSecure=False):
|
||||
"""
|
||||
Check if there is a session cookie, and if not, create it.
|
||||
|
||||
By default, the cookie with be secure for HTTPS requests and not secure
|
||||
for HTTP requests. If for some reason you need access to the insecure
|
||||
cookie from a secure request you can set C{forceNotSecure = True}.
|
||||
|
||||
@param forceNotSecure: Should we retrieve a session that will be
|
||||
transmitted over HTTP, even if this L{Request} was delivered over
|
||||
HTTPS?
|
||||
@type forceNotSecure: L{bool}
|
||||
"""
|
||||
# Make sure we aren't creating a secure session on a non-secure page
|
||||
secure = self.isSecure() and not forceNotSecure
|
||||
|
||||
if not secure:
|
||||
cookieString = b"TWISTED_SESSION"
|
||||
sessionAttribute = "_insecureSession"
|
||||
else:
|
||||
cookieString = b"TWISTED_SECURE_SESSION"
|
||||
sessionAttribute = "_secureSession"
|
||||
|
||||
session = getattr(self, sessionAttribute)
|
||||
|
||||
if session is not None:
|
||||
# We have a previously created session.
|
||||
try:
|
||||
# Refresh the session, to keep it alive.
|
||||
session.touch()
|
||||
except (AlreadyCalled, AlreadyCancelled):
|
||||
# Session has already expired.
|
||||
session = None
|
||||
|
||||
if session is None:
|
||||
# No session was created yet for this request.
|
||||
cookiename = b"_".join([cookieString] + self.sitepath)
|
||||
sessionCookie = self.getCookie(cookiename)
|
||||
if sessionCookie:
|
||||
try:
|
||||
session = self.site.getSession(sessionCookie)
|
||||
except KeyError:
|
||||
pass
|
||||
# if it still hasn't been set, fix it up.
|
||||
if not session:
|
||||
session = self.site.makeSession()
|
||||
self.addCookie(cookiename, session.uid, path=b"/", secure=secure)
|
||||
|
||||
setattr(self, sessionAttribute, session)
|
||||
|
||||
if sessionInterface:
|
||||
return session.getComponent(sessionInterface)
|
||||
|
||||
return session
|
||||
|
||||
def _prePathURL(self, prepath):
|
||||
port = self.getHost().port
|
||||
if self.isSecure():
|
||||
default = 443
|
||||
else:
|
||||
default = 80
|
||||
if port == default:
|
||||
hostport = ""
|
||||
else:
|
||||
hostport = ":%d" % port
|
||||
prefix = networkString(
|
||||
"http%s://%s%s/"
|
||||
% (
|
||||
self.isSecure() and "s" or "",
|
||||
nativeString(self.getRequestHostname()),
|
||||
hostport,
|
||||
)
|
||||
)
|
||||
path = b"/".join([quote(segment, safe=b"") for segment in prepath])
|
||||
return prefix + path
|
||||
|
||||
def prePathURL(self):
|
||||
return self._prePathURL(self.prepath)
|
||||
|
||||
def URLPath(self):
|
||||
from twisted.python import urlpath
|
||||
|
||||
return urlpath.URLPath.fromRequest(self)
|
||||
|
||||
def rememberRootURL(self):
|
||||
"""
|
||||
Remember the currently-processed part of the URL for later
|
||||
recalling.
|
||||
"""
|
||||
url = self._prePathURL(self.prepath[:-1])
|
||||
self.appRootURL = url
|
||||
|
||||
def getRootURL(self):
|
||||
"""
|
||||
Get a previously-remembered URL.
|
||||
|
||||
@return: An absolute URL.
|
||||
@rtype: L{bytes}
|
||||
"""
|
||||
return self.appRootURL
|
||||
|
||||
def _handleStar(self):
|
||||
"""
|
||||
Handle receiving a request whose path is '*'.
|
||||
|
||||
RFC 7231 defines an OPTIONS * request as being something that a client
|
||||
can send as a low-effort way to probe server capabilities or readiness.
|
||||
Rather than bother the user with this, we simply fast-path it back to
|
||||
an empty 200 OK. Any non-OPTIONS verb gets a 405 Method Not Allowed
|
||||
telling the client they can only use OPTIONS.
|
||||
"""
|
||||
if self.method == b"OPTIONS":
|
||||
self.setResponseCode(http.OK)
|
||||
else:
|
||||
self.setResponseCode(http.NOT_ALLOWED)
|
||||
self.setHeader(b"Allow", b"OPTIONS")
|
||||
|
||||
# RFC 7231 says we MUST set content-length 0 when responding to this
|
||||
# with no body.
|
||||
self.setHeader(b"Content-Length", b"0")
|
||||
self.finish()
|
||||
|
||||
|
||||
@implementer(iweb._IRequestEncoderFactory)
|
||||
class GzipEncoderFactory:
|
||||
"""
|
||||
@cvar compressLevel: The compression level used by the compressor, default
|
||||
to 9 (highest).
|
||||
|
||||
@since: 12.3
|
||||
"""
|
||||
|
||||
_gzipCheckRegex = re.compile(rb"(:?^|[\s,])gzip(:?$|[\s,])")
|
||||
compressLevel = 9
|
||||
|
||||
def encoderForRequest(self, request):
|
||||
"""
|
||||
Check the headers if the client accepts gzip encoding, and encodes the
|
||||
request if so.
|
||||
"""
|
||||
acceptHeaders = b",".join(
|
||||
request.requestHeaders.getRawHeaders(b"Accept-Encoding", [])
|
||||
)
|
||||
if self._gzipCheckRegex.search(acceptHeaders):
|
||||
encoding = request.responseHeaders.getRawHeaders(b"Content-Encoding")
|
||||
if encoding:
|
||||
encoding = b",".join(encoding + [b"gzip"])
|
||||
else:
|
||||
encoding = b"gzip"
|
||||
|
||||
request.responseHeaders.setRawHeaders(b"Content-Encoding", [encoding])
|
||||
return _GzipEncoder(self.compressLevel, request)
|
||||
|
||||
|
||||
@implementer(iweb._IRequestEncoder)
|
||||
class _GzipEncoder:
|
||||
"""
|
||||
An encoder which supports gzip.
|
||||
|
||||
@ivar _zlibCompressor: The zlib compressor instance used to compress the
|
||||
stream.
|
||||
|
||||
@ivar _request: A reference to the originating request.
|
||||
|
||||
@since: 12.3
|
||||
"""
|
||||
|
||||
_zlibCompressor = None
|
||||
|
||||
def __init__(self, compressLevel, request):
|
||||
self._zlibCompressor = zlib.compressobj(
|
||||
compressLevel, zlib.DEFLATED, 16 + zlib.MAX_WBITS
|
||||
)
|
||||
self._request = request
|
||||
|
||||
def encode(self, data):
|
||||
"""
|
||||
Write to the request, automatically compressing data on the fly.
|
||||
"""
|
||||
if not self._request.startedWriting:
|
||||
# Remove the content-length header, we can't honor it
|
||||
# because we compress on the fly.
|
||||
self._request.responseHeaders.removeHeader(b"Content-Length")
|
||||
return self._zlibCompressor.compress(data)
|
||||
|
||||
def finish(self):
|
||||
"""
|
||||
Finish handling the request request, flushing any data from the zlib
|
||||
buffer.
|
||||
"""
|
||||
remain = self._zlibCompressor.flush()
|
||||
self._zlibCompressor = None
|
||||
return remain
|
||||
|
||||
|
||||
class _RemoteProducerWrapper:
|
||||
def __init__(self, remote):
|
||||
self.resumeProducing = remote.remoteMethod("resumeProducing")
|
||||
self.pauseProducing = remote.remoteMethod("pauseProducing")
|
||||
self.stopProducing = remote.remoteMethod("stopProducing")
|
||||
|
||||
|
||||
class Session(components.Componentized):
|
||||
"""
|
||||
A user's session with a system.
|
||||
|
||||
This utility class contains no functionality, but is used to
|
||||
represent a session.
|
||||
|
||||
@ivar site: The L{Site} that generated the session.
|
||||
@type site: L{Site}
|
||||
|
||||
@ivar uid: A unique identifier for the session.
|
||||
@type uid: L{bytes}
|
||||
|
||||
@ivar _reactor: An object providing L{IReactorTime} to use for scheduling
|
||||
expiration.
|
||||
|
||||
@ivar sessionTimeout: Time after last modification the session will expire,
|
||||
in seconds.
|
||||
@type sessionTimeout: L{float}
|
||||
|
||||
@ivar lastModified: Time the C{touch()} method was last called (or time the
|
||||
session was created). A UNIX timestamp as returned by
|
||||
L{IReactorTime.seconds()}.
|
||||
@type lastModified: L{float}
|
||||
"""
|
||||
|
||||
sessionTimeout = 900
|
||||
|
||||
_expireCall = None
|
||||
|
||||
def __init__(self, site, uid, reactor=None):
|
||||
"""
|
||||
Initialize a session with a unique ID for that session.
|
||||
|
||||
@param reactor: L{IReactorTime} used to schedule expiration of the
|
||||
session. If C{None}, the reactor associated with I{site} is used.
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
if reactor is None:
|
||||
reactor = site.reactor
|
||||
self._reactor = reactor
|
||||
|
||||
self.site = site
|
||||
self.uid = uid
|
||||
self.expireCallbacks = []
|
||||
self.touch()
|
||||
self.sessionNamespaces = {}
|
||||
|
||||
def startCheckingExpiration(self):
|
||||
"""
|
||||
Start expiration tracking.
|
||||
|
||||
@return: L{None}
|
||||
"""
|
||||
self._expireCall = self._reactor.callLater(self.sessionTimeout, self.expire)
|
||||
|
||||
def notifyOnExpire(self, callback):
|
||||
"""
|
||||
Call this callback when the session expires or logs out.
|
||||
"""
|
||||
self.expireCallbacks.append(callback)
|
||||
|
||||
def expire(self):
|
||||
"""
|
||||
Expire/logout of the session.
|
||||
"""
|
||||
del self.site.sessions[self.uid]
|
||||
for c in self.expireCallbacks:
|
||||
c()
|
||||
self.expireCallbacks = []
|
||||
if self._expireCall and self._expireCall.active():
|
||||
self._expireCall.cancel()
|
||||
# Break reference cycle.
|
||||
self._expireCall = None
|
||||
|
||||
def touch(self):
|
||||
"""
|
||||
Mark the session as modified, which resets expiration timer.
|
||||
"""
|
||||
self.lastModified = self._reactor.seconds()
|
||||
if self._expireCall is not None:
|
||||
self._expireCall.reset(self.sessionTimeout)
|
||||
|
||||
|
||||
version = networkString(f"TwistedWeb/{copyright.version}")
|
||||
|
||||
|
||||
@implementer(interfaces.IProtocolNegotiationFactory)
|
||||
class Site(HTTPFactory):
|
||||
"""
|
||||
A web site: manage log, sessions, and resources.
|
||||
|
||||
@ivar requestFactory: A factory which is called with (channel)
|
||||
and creates L{Request} instances. Default to L{Request}.
|
||||
|
||||
@ivar displayTracebacks: If set, unhandled exceptions raised during
|
||||
rendering are returned to the client as HTML. Default to C{False}.
|
||||
|
||||
@ivar sessionFactory: factory for sessions objects. Default to L{Session}.
|
||||
|
||||
@ivar sessions: Mapping of session IDs to objects returned by
|
||||
C{sessionFactory}.
|
||||
@type sessions: L{dict} mapping L{bytes} to L{Session} given the default
|
||||
C{sessionFactory}
|
||||
|
||||
@ivar counter: The number of sessions that have been generated.
|
||||
@type counter: L{int}
|
||||
|
||||
@ivar sessionCheckTime: Deprecated and unused. See
|
||||
L{Session.sessionTimeout} instead.
|
||||
"""
|
||||
|
||||
counter = 0
|
||||
requestFactory = Request
|
||||
displayTracebacks = False
|
||||
sessionFactory = Session
|
||||
sessionCheckTime = 1800
|
||||
_entropy = os.urandom
|
||||
|
||||
def __init__(self, resource, requestFactory=None, *args, **kwargs):
|
||||
"""
|
||||
@param resource: The root of the resource hierarchy. All request
|
||||
traversal for requests received by this factory will begin at this
|
||||
resource.
|
||||
@type resource: L{IResource} provider
|
||||
@param requestFactory: Overwrite for default requestFactory.
|
||||
@type requestFactory: C{callable} or C{class}.
|
||||
|
||||
@see: L{twisted.web.http.HTTPFactory.__init__}
|
||||
"""
|
||||
super().__init__(*args, **kwargs)
|
||||
self.sessions = {}
|
||||
self.resource = resource
|
||||
if requestFactory is not None:
|
||||
self.requestFactory = requestFactory
|
||||
|
||||
def _openLogFile(self, path):
|
||||
from twisted.python import logfile
|
||||
|
||||
return logfile.LogFile(os.path.basename(path), os.path.dirname(path))
|
||||
|
||||
def __getstate__(self):
|
||||
d = self.__dict__.copy()
|
||||
d["sessions"] = {}
|
||||
return d
|
||||
|
||||
def _mkuid(self):
|
||||
"""
|
||||
(internal) Generate an opaque, unique ID for a user's session.
|
||||
"""
|
||||
self.counter = self.counter + 1
|
||||
return hexlify(self._entropy(32))
|
||||
|
||||
def makeSession(self):
|
||||
"""
|
||||
Generate a new Session instance, and store it for future reference.
|
||||
"""
|
||||
uid = self._mkuid()
|
||||
session = self.sessions[uid] = self.sessionFactory(self, uid)
|
||||
session.startCheckingExpiration()
|
||||
return session
|
||||
|
||||
def getSession(self, uid):
|
||||
"""
|
||||
Get a previously generated session.
|
||||
|
||||
@param uid: Unique ID of the session.
|
||||
@type uid: L{bytes}.
|
||||
|
||||
@raise KeyError: If the session is not found.
|
||||
"""
|
||||
return self.sessions[uid]
|
||||
|
||||
def buildProtocol(self, addr):
|
||||
"""
|
||||
Generate a channel attached to this site.
|
||||
"""
|
||||
channel = super().buildProtocol(addr)
|
||||
channel.requestFactory = self.requestFactory
|
||||
channel.site = self
|
||||
return channel
|
||||
|
||||
isLeaf = 0
|
||||
|
||||
def render(self, request):
|
||||
"""
|
||||
Redirect because a Site is always a directory.
|
||||
"""
|
||||
request.redirect(request.prePathURL() + b"/")
|
||||
request.finish()
|
||||
|
||||
def getChildWithDefault(self, pathEl, request):
|
||||
"""
|
||||
Emulate a resource's getChild method.
|
||||
"""
|
||||
request.site = self
|
||||
return self.resource.getChildWithDefault(pathEl, request)
|
||||
|
||||
def getResourceFor(self, request):
|
||||
"""
|
||||
Get a resource for a request.
|
||||
|
||||
This iterates through the resource hierarchy, calling
|
||||
getChildWithDefault on each resource it finds for a path element,
|
||||
stopping when it hits an element where isLeaf is true.
|
||||
"""
|
||||
request.site = self
|
||||
# Sitepath is used to determine cookie names between distributed
|
||||
# servers and disconnected sites.
|
||||
request.sitepath = copy.copy(request.prepath)
|
||||
return resource.getChildForRequest(self.resource, request)
|
||||
|
||||
# IProtocolNegotiationFactory
|
||||
def acceptableProtocols(self):
|
||||
"""
|
||||
Protocols this server can speak.
|
||||
"""
|
||||
baseProtocols = [b"http/1.1"]
|
||||
|
||||
if http.H2_ENABLED:
|
||||
baseProtocols.insert(0, b"h2")
|
||||
|
||||
return baseProtocols
|
||||
1078
backend/lib/python3.12/site-packages/twisted/web/static.py
Normal file
1078
backend/lib/python3.12/site-packages/twisted/web/static.py
Normal file
File diff suppressed because it is too large
Load Diff
644
backend/lib/python3.12/site-packages/twisted/web/sux.py
Normal file
644
backend/lib/python3.12/site-packages/twisted/web/sux.py
Normal file
@@ -0,0 +1,644 @@
|
||||
# -*- test-case-name: twisted.web.test.test_xml -*-
|
||||
#
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
|
||||
"""
|
||||
*S*mall, *U*ncomplicated *X*ML.
|
||||
|
||||
This is a very simple implementation of XML/HTML as a network
|
||||
protocol. It is not at all clever. Its main features are that it
|
||||
does not:
|
||||
|
||||
- support namespaces
|
||||
- mung mnemonic entity references
|
||||
- validate
|
||||
- perform *any* external actions (such as fetching URLs or writing files)
|
||||
under *any* circumstances
|
||||
- has lots and lots of horrible hacks for supporting broken HTML (as an
|
||||
option, they're not on by default).
|
||||
"""
|
||||
|
||||
|
||||
from twisted.internet.protocol import Protocol
|
||||
from twisted.python.reflect import prefixedMethodNames
|
||||
|
||||
# Elements of the three-tuples in the state table.
|
||||
BEGIN_HANDLER = 0
|
||||
DO_HANDLER = 1
|
||||
END_HANDLER = 2
|
||||
|
||||
identChars = ".-_:"
|
||||
lenientIdentChars = identChars + ";+#/%~"
|
||||
|
||||
|
||||
def nop(*args, **kw):
|
||||
"Do nothing."
|
||||
|
||||
|
||||
def unionlist(*args):
|
||||
l = []
|
||||
for x in args:
|
||||
l.extend(x)
|
||||
d = {x: 1 for x in l}
|
||||
return d.keys()
|
||||
|
||||
|
||||
def zipfndict(*args, **kw):
|
||||
default = kw.get("default", nop)
|
||||
d = {}
|
||||
for key in unionlist(*(fndict.keys() for fndict in args)):
|
||||
d[key] = tuple(x.get(key, default) for x in args)
|
||||
return d
|
||||
|
||||
|
||||
def prefixedMethodClassDict(clazz, prefix):
|
||||
return {
|
||||
name: getattr(clazz, prefix + name)
|
||||
for name in prefixedMethodNames(clazz, prefix)
|
||||
}
|
||||
|
||||
|
||||
def prefixedMethodObjDict(obj, prefix):
|
||||
return {
|
||||
name: getattr(obj, prefix + name)
|
||||
for name in prefixedMethodNames(obj.__class__, prefix)
|
||||
}
|
||||
|
||||
|
||||
class ParseError(Exception):
|
||||
def __init__(self, filename, line, col, message):
|
||||
self.filename = filename
|
||||
self.line = line
|
||||
self.col = col
|
||||
self.message = message
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.filename}:{self.line}:{self.col}: {self.message}"
|
||||
|
||||
|
||||
class XMLParser(Protocol):
|
||||
state = None
|
||||
encodings = None
|
||||
filename = "<xml />"
|
||||
beExtremelyLenient = 0
|
||||
_prepend = None
|
||||
|
||||
# _leadingBodyData will sometimes be set before switching to the
|
||||
# 'bodydata' state, when we "accidentally" read a byte of bodydata
|
||||
# in a different state.
|
||||
_leadingBodyData = None
|
||||
|
||||
def connectionMade(self):
|
||||
self.lineno = 1
|
||||
self.colno = 0
|
||||
self.encodings = []
|
||||
|
||||
def saveMark(self):
|
||||
"""Get the line number and column of the last character parsed"""
|
||||
# This gets replaced during dataReceived, restored afterwards
|
||||
return (self.lineno, self.colno)
|
||||
|
||||
def _parseError(self, message):
|
||||
raise ParseError(*((self.filename,) + self.saveMark() + (message,)))
|
||||
|
||||
def _buildStateTable(self):
|
||||
"""Return a dictionary of begin, do, end state function tuples"""
|
||||
# _buildStateTable leaves something to be desired but it does what it
|
||||
# does.. probably slowly, so I'm doing some evil caching so it doesn't
|
||||
# get called more than once per class.
|
||||
stateTable = getattr(self.__class__, "__stateTable", None)
|
||||
if stateTable is None:
|
||||
stateTable = self.__class__.__stateTable = zipfndict(
|
||||
*(
|
||||
prefixedMethodObjDict(self, prefix)
|
||||
for prefix in ("begin_", "do_", "end_")
|
||||
)
|
||||
)
|
||||
return stateTable
|
||||
|
||||
def _decode(self, data):
|
||||
if "UTF-16" in self.encodings or "UCS-2" in self.encodings:
|
||||
assert not len(data) & 1, "UTF-16 must come in pairs for now"
|
||||
if self._prepend:
|
||||
data = self._prepend + data
|
||||
for encoding in self.encodings:
|
||||
data = str(data, encoding)
|
||||
return data
|
||||
|
||||
def maybeBodyData(self):
|
||||
if self.endtag:
|
||||
return "bodydata"
|
||||
|
||||
# Get ready for fun! We're going to allow
|
||||
# <script>if (foo < bar)</script> to work!
|
||||
# We do this by making everything between <script> and
|
||||
# </script> a Text
|
||||
# BUT <script src="foo"> will be special-cased to do regular,
|
||||
# lenient behavior, because those may not have </script>
|
||||
# -radix
|
||||
|
||||
if self.tagName == "script" and "src" not in self.tagAttributes:
|
||||
# we do this ourselves rather than having begin_waitforendscript
|
||||
# because that can get called multiple times and we don't want
|
||||
# bodydata to get reset other than the first time.
|
||||
self.begin_bodydata(None)
|
||||
return "waitforendscript"
|
||||
return "bodydata"
|
||||
|
||||
def dataReceived(self, data):
|
||||
stateTable = self._buildStateTable()
|
||||
if not self.state:
|
||||
# all UTF-16 starts with this string
|
||||
if data.startswith((b"\xff\xfe", b"\xfe\xff")):
|
||||
self._prepend = data[0:2]
|
||||
self.encodings.append("UTF-16")
|
||||
data = data[2:]
|
||||
self.state = "begin"
|
||||
if self.encodings:
|
||||
data = self._decode(data)
|
||||
else:
|
||||
data = data.decode("utf-8")
|
||||
# bring state, lineno, colno into local scope
|
||||
lineno, colno = self.lineno, self.colno
|
||||
curState = self.state
|
||||
# replace saveMark with a nested scope function
|
||||
_saveMark = self.saveMark
|
||||
|
||||
def saveMark():
|
||||
return (lineno, colno)
|
||||
|
||||
self.saveMark = saveMark
|
||||
# fetch functions from the stateTable
|
||||
beginFn, doFn, endFn = stateTable[curState]
|
||||
try:
|
||||
for byte in data:
|
||||
# do newline stuff
|
||||
if byte == "\n":
|
||||
lineno += 1
|
||||
colno = 0
|
||||
else:
|
||||
colno += 1
|
||||
newState = doFn(byte)
|
||||
if newState is not None and newState != curState:
|
||||
# this is the endFn from the previous state
|
||||
endFn()
|
||||
curState = newState
|
||||
beginFn, doFn, endFn = stateTable[curState]
|
||||
beginFn(byte)
|
||||
finally:
|
||||
self.saveMark = _saveMark
|
||||
self.lineno, self.colno = lineno, colno
|
||||
# state doesn't make sense if there's an exception..
|
||||
self.state = curState
|
||||
|
||||
def connectionLost(self, reason):
|
||||
"""
|
||||
End the last state we were in.
|
||||
"""
|
||||
stateTable = self._buildStateTable()
|
||||
stateTable[self.state][END_HANDLER]()
|
||||
|
||||
# state methods
|
||||
|
||||
def do_begin(self, byte):
|
||||
if byte.isspace():
|
||||
return
|
||||
if byte != "<":
|
||||
if self.beExtremelyLenient:
|
||||
self._leadingBodyData = byte
|
||||
return "bodydata"
|
||||
self._parseError(f"First char of document [{byte!r}] wasn't <")
|
||||
return "tagstart"
|
||||
|
||||
def begin_comment(self, byte):
|
||||
self.commentbuf = ""
|
||||
|
||||
def do_comment(self, byte):
|
||||
self.commentbuf += byte
|
||||
if self.commentbuf.endswith("-->"):
|
||||
self.gotComment(self.commentbuf[:-3])
|
||||
return "bodydata"
|
||||
|
||||
def begin_tagstart(self, byte):
|
||||
self.tagName = "" # name of the tag
|
||||
self.tagAttributes = {} # attributes of the tag
|
||||
self.termtag = 0 # is the tag self-terminating
|
||||
self.endtag = 0
|
||||
|
||||
def do_tagstart(self, byte):
|
||||
if byte.isalnum() or byte in identChars:
|
||||
self.tagName += byte
|
||||
if self.tagName == "!--":
|
||||
return "comment"
|
||||
elif byte.isspace():
|
||||
if self.tagName:
|
||||
if self.endtag:
|
||||
# properly strict thing to do here is probably to only
|
||||
# accept whitespace
|
||||
return "waitforgt"
|
||||
return "attrs"
|
||||
else:
|
||||
self._parseError("Whitespace before tag-name")
|
||||
elif byte == ">":
|
||||
if self.endtag:
|
||||
self.gotTagEnd(self.tagName)
|
||||
return "bodydata"
|
||||
else:
|
||||
self.gotTagStart(self.tagName, {})
|
||||
return (
|
||||
(not self.beExtremelyLenient) and "bodydata" or self.maybeBodyData()
|
||||
)
|
||||
elif byte == "/":
|
||||
if self.tagName:
|
||||
return "afterslash"
|
||||
else:
|
||||
self.endtag = 1
|
||||
elif byte in "!?":
|
||||
if self.tagName:
|
||||
if not self.beExtremelyLenient:
|
||||
self._parseError("Invalid character in tag-name")
|
||||
else:
|
||||
self.tagName += byte
|
||||
self.termtag = 1
|
||||
elif byte == "[":
|
||||
if self.tagName == "!":
|
||||
return "expectcdata"
|
||||
else:
|
||||
self._parseError("Invalid '[' in tag-name")
|
||||
else:
|
||||
if self.beExtremelyLenient:
|
||||
self.bodydata = "<"
|
||||
return "unentity"
|
||||
self._parseError("Invalid tag character: %r" % byte)
|
||||
|
||||
def begin_unentity(self, byte):
|
||||
self.bodydata += byte
|
||||
|
||||
def do_unentity(self, byte):
|
||||
self.bodydata += byte
|
||||
return "bodydata"
|
||||
|
||||
def end_unentity(self):
|
||||
self.gotText(self.bodydata)
|
||||
|
||||
def begin_expectcdata(self, byte):
|
||||
self.cdatabuf = byte
|
||||
|
||||
def do_expectcdata(self, byte):
|
||||
self.cdatabuf += byte
|
||||
cdb = self.cdatabuf
|
||||
cd = "[CDATA["
|
||||
if len(cd) > len(cdb):
|
||||
if cd.startswith(cdb):
|
||||
return
|
||||
elif self.beExtremelyLenient:
|
||||
## WHAT THE CRAP!? MSWord9 generates HTML that includes these
|
||||
## bizarre <![if !foo]> <![endif]> chunks, so I've gotta ignore
|
||||
## 'em as best I can. this should really be a separate parse
|
||||
## state but I don't even have any idea what these _are_.
|
||||
return "waitforgt"
|
||||
else:
|
||||
self._parseError("Mal-formed CDATA header")
|
||||
if cd == cdb:
|
||||
self.cdatabuf = ""
|
||||
return "cdata"
|
||||
self._parseError("Mal-formed CDATA header")
|
||||
|
||||
def do_cdata(self, byte):
|
||||
self.cdatabuf += byte
|
||||
if self.cdatabuf.endswith("]]>"):
|
||||
self.cdatabuf = self.cdatabuf[:-3]
|
||||
return "bodydata"
|
||||
|
||||
def end_cdata(self):
|
||||
self.gotCData(self.cdatabuf)
|
||||
self.cdatabuf = ""
|
||||
|
||||
def do_attrs(self, byte):
|
||||
if byte.isalnum() or byte in identChars:
|
||||
# XXX FIXME really handle !DOCTYPE at some point
|
||||
if self.tagName == "!DOCTYPE":
|
||||
return "doctype"
|
||||
if self.tagName[0] in "!?":
|
||||
return "waitforgt"
|
||||
return "attrname"
|
||||
elif byte.isspace():
|
||||
return
|
||||
elif byte == ">":
|
||||
self.gotTagStart(self.tagName, self.tagAttributes)
|
||||
return (not self.beExtremelyLenient) and "bodydata" or self.maybeBodyData()
|
||||
elif byte == "/":
|
||||
return "afterslash"
|
||||
elif self.beExtremelyLenient:
|
||||
# discard and move on? Only case I've seen of this so far was:
|
||||
# <foo bar="baz"">
|
||||
return
|
||||
self._parseError("Unexpected character: %r" % byte)
|
||||
|
||||
def begin_doctype(self, byte):
|
||||
self.doctype = byte
|
||||
|
||||
def do_doctype(self, byte):
|
||||
if byte == ">":
|
||||
return "bodydata"
|
||||
self.doctype += byte
|
||||
|
||||
def end_doctype(self):
|
||||
self.gotDoctype(self.doctype)
|
||||
self.doctype = None
|
||||
|
||||
def do_waitforgt(self, byte):
|
||||
if byte == ">":
|
||||
if self.endtag or not self.beExtremelyLenient:
|
||||
return "bodydata"
|
||||
return self.maybeBodyData()
|
||||
|
||||
def begin_attrname(self, byte):
|
||||
self.attrname = byte
|
||||
self._attrname_termtag = 0
|
||||
|
||||
def do_attrname(self, byte):
|
||||
if byte.isalnum() or byte in identChars:
|
||||
self.attrname += byte
|
||||
return
|
||||
elif byte == "=":
|
||||
return "beforeattrval"
|
||||
elif byte.isspace():
|
||||
return "beforeeq"
|
||||
elif self.beExtremelyLenient:
|
||||
if byte in "\"'":
|
||||
return "attrval"
|
||||
if byte in lenientIdentChars or byte.isalnum():
|
||||
self.attrname += byte
|
||||
return
|
||||
if byte == "/":
|
||||
self._attrname_termtag = 1
|
||||
return
|
||||
if byte == ">":
|
||||
self.attrval = "True"
|
||||
self.tagAttributes[self.attrname] = self.attrval
|
||||
self.gotTagStart(self.tagName, self.tagAttributes)
|
||||
if self._attrname_termtag:
|
||||
self.gotTagEnd(self.tagName)
|
||||
return "bodydata"
|
||||
return self.maybeBodyData()
|
||||
# something is really broken. let's leave this attribute where it
|
||||
# is and move on to the next thing
|
||||
return
|
||||
self._parseError(f"Invalid attribute name: {self.attrname!r} {byte!r}")
|
||||
|
||||
def do_beforeattrval(self, byte):
|
||||
if byte in "\"'":
|
||||
return "attrval"
|
||||
elif byte.isspace():
|
||||
return
|
||||
elif self.beExtremelyLenient:
|
||||
if byte in lenientIdentChars or byte.isalnum():
|
||||
return "messyattr"
|
||||
if byte == ">":
|
||||
self.attrval = "True"
|
||||
self.tagAttributes[self.attrname] = self.attrval
|
||||
self.gotTagStart(self.tagName, self.tagAttributes)
|
||||
return self.maybeBodyData()
|
||||
if byte == "\\":
|
||||
# I saw this in actual HTML once:
|
||||
# <font size=\"3\"><sup>SM</sup></font>
|
||||
return
|
||||
self._parseError(
|
||||
"Invalid initial attribute value: %r; Attribute values must be quoted."
|
||||
% byte
|
||||
)
|
||||
|
||||
attrname = ""
|
||||
attrval = ""
|
||||
|
||||
def begin_beforeeq(self, byte):
|
||||
self._beforeeq_termtag = 0
|
||||
|
||||
def do_beforeeq(self, byte):
|
||||
if byte == "=":
|
||||
return "beforeattrval"
|
||||
elif byte.isspace():
|
||||
return
|
||||
elif self.beExtremelyLenient:
|
||||
if byte.isalnum() or byte in identChars:
|
||||
self.attrval = "True"
|
||||
self.tagAttributes[self.attrname] = self.attrval
|
||||
return "attrname"
|
||||
elif byte == ">":
|
||||
self.attrval = "True"
|
||||
self.tagAttributes[self.attrname] = self.attrval
|
||||
self.gotTagStart(self.tagName, self.tagAttributes)
|
||||
if self._beforeeq_termtag:
|
||||
self.gotTagEnd(self.tagName)
|
||||
return "bodydata"
|
||||
return self.maybeBodyData()
|
||||
elif byte == "/":
|
||||
self._beforeeq_termtag = 1
|
||||
return
|
||||
self._parseError("Invalid attribute")
|
||||
|
||||
def begin_attrval(self, byte):
|
||||
self.quotetype = byte
|
||||
self.attrval = ""
|
||||
|
||||
def do_attrval(self, byte):
|
||||
if byte == self.quotetype:
|
||||
return "attrs"
|
||||
self.attrval += byte
|
||||
|
||||
def end_attrval(self):
|
||||
self.tagAttributes[self.attrname] = self.attrval
|
||||
self.attrname = self.attrval = ""
|
||||
|
||||
def begin_messyattr(self, byte):
|
||||
self.attrval = byte
|
||||
|
||||
def do_messyattr(self, byte):
|
||||
if byte.isspace():
|
||||
return "attrs"
|
||||
elif byte == ">":
|
||||
endTag = 0
|
||||
if self.attrval.endswith("/"):
|
||||
endTag = 1
|
||||
self.attrval = self.attrval[:-1]
|
||||
self.tagAttributes[self.attrname] = self.attrval
|
||||
self.gotTagStart(self.tagName, self.tagAttributes)
|
||||
if endTag:
|
||||
self.gotTagEnd(self.tagName)
|
||||
return "bodydata"
|
||||
return self.maybeBodyData()
|
||||
else:
|
||||
self.attrval += byte
|
||||
|
||||
def end_messyattr(self):
|
||||
if self.attrval:
|
||||
self.tagAttributes[self.attrname] = self.attrval
|
||||
|
||||
def begin_afterslash(self, byte):
|
||||
self._after_slash_closed = 0
|
||||
|
||||
def do_afterslash(self, byte):
|
||||
# this state is only after a self-terminating slash, e.g. <foo/>
|
||||
if self._after_slash_closed:
|
||||
self._parseError("Mal-formed") # XXX When does this happen??
|
||||
if byte != ">":
|
||||
if self.beExtremelyLenient:
|
||||
return
|
||||
else:
|
||||
self._parseError("No data allowed after '/'")
|
||||
self._after_slash_closed = 1
|
||||
self.gotTagStart(self.tagName, self.tagAttributes)
|
||||
self.gotTagEnd(self.tagName)
|
||||
# don't need maybeBodyData here because there better not be
|
||||
# any javascript code after a <script/>... we'll see :(
|
||||
return "bodydata"
|
||||
|
||||
def begin_bodydata(self, byte):
|
||||
if self._leadingBodyData:
|
||||
self.bodydata = self._leadingBodyData
|
||||
del self._leadingBodyData
|
||||
else:
|
||||
self.bodydata = ""
|
||||
|
||||
def do_bodydata(self, byte):
|
||||
if byte == "<":
|
||||
return "tagstart"
|
||||
if byte == "&":
|
||||
return "entityref"
|
||||
self.bodydata += byte
|
||||
|
||||
def end_bodydata(self):
|
||||
self.gotText(self.bodydata)
|
||||
self.bodydata = ""
|
||||
|
||||
def do_waitforendscript(self, byte):
|
||||
if byte == "<":
|
||||
return "waitscriptendtag"
|
||||
self.bodydata += byte
|
||||
|
||||
def begin_waitscriptendtag(self, byte):
|
||||
self.temptagdata = ""
|
||||
self.tagName = ""
|
||||
self.endtag = 0
|
||||
|
||||
def do_waitscriptendtag(self, byte):
|
||||
# 1 enforce / as first byte read
|
||||
# 2 enforce following bytes to be subset of "script" until
|
||||
# tagName == "script"
|
||||
# 2a when that happens, gotText(self.bodydata) and gotTagEnd(self.tagName)
|
||||
# 3 spaces can happen anywhere, they're ignored
|
||||
# e.g. < / script >
|
||||
# 4 anything else causes all data I've read to be moved to the
|
||||
# bodydata, and switch back to waitforendscript state
|
||||
|
||||
# If it turns out this _isn't_ a </script>, we need to
|
||||
# remember all the data we've been through so we can append it
|
||||
# to bodydata
|
||||
self.temptagdata += byte
|
||||
|
||||
# 1
|
||||
if byte == "/":
|
||||
self.endtag = True
|
||||
elif not self.endtag:
|
||||
self.bodydata += "<" + self.temptagdata
|
||||
return "waitforendscript"
|
||||
# 2
|
||||
elif byte.isalnum() or byte in identChars:
|
||||
self.tagName += byte
|
||||
if not "script".startswith(self.tagName):
|
||||
self.bodydata += "<" + self.temptagdata
|
||||
return "waitforendscript"
|
||||
elif self.tagName == "script":
|
||||
self.gotText(self.bodydata)
|
||||
self.gotTagEnd(self.tagName)
|
||||
return "waitforgt"
|
||||
# 3
|
||||
elif byte.isspace():
|
||||
return "waitscriptendtag"
|
||||
# 4
|
||||
else:
|
||||
self.bodydata += "<" + self.temptagdata
|
||||
return "waitforendscript"
|
||||
|
||||
def begin_entityref(self, byte):
|
||||
self.erefbuf = ""
|
||||
self.erefextra = "" # extra bit for lenient mode
|
||||
|
||||
def do_entityref(self, byte):
|
||||
if byte.isspace() or byte == "<":
|
||||
if self.beExtremelyLenient:
|
||||
# '&foo' probably was '&foo'
|
||||
if self.erefbuf and self.erefbuf != "amp":
|
||||
self.erefextra = self.erefbuf
|
||||
self.erefbuf = "amp"
|
||||
if byte == "<":
|
||||
return "tagstart"
|
||||
else:
|
||||
self.erefextra += byte
|
||||
return "spacebodydata"
|
||||
self._parseError("Bad entity reference")
|
||||
elif byte != ";":
|
||||
self.erefbuf += byte
|
||||
else:
|
||||
return "bodydata"
|
||||
|
||||
def end_entityref(self):
|
||||
self.gotEntityReference(self.erefbuf)
|
||||
|
||||
# hacky support for space after & in entityref in beExtremelyLenient
|
||||
# state should only happen in that case
|
||||
def begin_spacebodydata(self, byte):
|
||||
self.bodydata = self.erefextra
|
||||
self.erefextra = None
|
||||
|
||||
do_spacebodydata = do_bodydata
|
||||
end_spacebodydata = end_bodydata
|
||||
|
||||
# Sorta SAX-ish API
|
||||
|
||||
def gotTagStart(self, name, attributes):
|
||||
"""Encountered an opening tag.
|
||||
|
||||
Default behaviour is to print."""
|
||||
print("begin", name, attributes)
|
||||
|
||||
def gotText(self, data):
|
||||
"""Encountered text
|
||||
|
||||
Default behaviour is to print."""
|
||||
print("text:", repr(data))
|
||||
|
||||
def gotEntityReference(self, entityRef):
|
||||
"""Encountered mnemonic entity reference
|
||||
|
||||
Default behaviour is to print."""
|
||||
print("entityRef: &%s;" % entityRef)
|
||||
|
||||
def gotComment(self, comment):
|
||||
"""Encountered comment.
|
||||
|
||||
Default behaviour is to ignore."""
|
||||
pass
|
||||
|
||||
def gotCData(self, cdata):
|
||||
"""Encountered CDATA
|
||||
|
||||
Default behaviour is to call the gotText method"""
|
||||
self.gotText(cdata)
|
||||
|
||||
def gotDoctype(self, doctype):
|
||||
"""Encountered DOCTYPE
|
||||
|
||||
This is really grotty: it basically just gives you everything between
|
||||
'<!DOCTYPE' and '>' as an argument.
|
||||
"""
|
||||
print("!DOCTYPE", repr(doctype))
|
||||
|
||||
def gotTagEnd(self, name):
|
||||
"""Encountered closing tag
|
||||
|
||||
Default behaviour is to print."""
|
||||
print("end", name)
|
||||
322
backend/lib/python3.12/site-packages/twisted/web/tap.py
Normal file
322
backend/lib/python3.12/site-packages/twisted/web/tap.py
Normal file
@@ -0,0 +1,322 @@
|
||||
# -*- test-case-name: twisted.web.test.test_tap -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Support for creating a service which runs a web server.
|
||||
"""
|
||||
|
||||
|
||||
import os
|
||||
import warnings
|
||||
|
||||
import incremental
|
||||
|
||||
from twisted.application import service, strports
|
||||
from twisted.internet import interfaces, reactor
|
||||
from twisted.python import deprecate, reflect, threadpool, usage
|
||||
from twisted.spread import pb
|
||||
from twisted.web import demo, distrib, resource, script, server, static, twcgi, wsgi
|
||||
|
||||
|
||||
class Options(usage.Options):
|
||||
"""
|
||||
Define the options accepted by the I{twistd web} plugin.
|
||||
"""
|
||||
|
||||
synopsis = "[web options]"
|
||||
|
||||
optParameters = [
|
||||
["logfile", "l", None, "Path to web CLF (Combined Log Format) log file."],
|
||||
[
|
||||
"certificate",
|
||||
"c",
|
||||
"server.pem",
|
||||
"(DEPRECATED: use --listen) " "SSL certificate to use for HTTPS. ",
|
||||
],
|
||||
[
|
||||
"privkey",
|
||||
"k",
|
||||
"server.pem",
|
||||
"(DEPRECATED: use --listen) " "SSL certificate to use for HTTPS.",
|
||||
],
|
||||
]
|
||||
|
||||
optFlags = [
|
||||
[
|
||||
"notracebacks",
|
||||
"n",
|
||||
(
|
||||
"(DEPRECATED: Tracebacks are disabled by default. "
|
||||
"See --enable-tracebacks to turn them on."
|
||||
),
|
||||
],
|
||||
[
|
||||
"display-tracebacks",
|
||||
"",
|
||||
(
|
||||
"Show uncaught exceptions during rendering tracebacks to "
|
||||
"the client. WARNING: This may be a security risk and "
|
||||
"expose private data!"
|
||||
),
|
||||
],
|
||||
]
|
||||
|
||||
optFlags.append(
|
||||
[
|
||||
"personal",
|
||||
"",
|
||||
"Instead of generating a webserver, generate a "
|
||||
"ResourcePublisher which listens on the port given by "
|
||||
"--listen, or ~/%s " % (distrib.UserDirectory.userSocketName,)
|
||||
+ "if --listen is not specified.",
|
||||
]
|
||||
)
|
||||
|
||||
compData = usage.Completions(
|
||||
optActions={
|
||||
"logfile": usage.CompleteFiles("*.log"),
|
||||
"certificate": usage.CompleteFiles("*.pem"),
|
||||
"privkey": usage.CompleteFiles("*.pem"),
|
||||
}
|
||||
)
|
||||
|
||||
longdesc = """\
|
||||
This starts a webserver. If you specify no arguments, it will be a
|
||||
demo webserver that has the Test class from twisted.web.demo in it."""
|
||||
|
||||
def __init__(self):
|
||||
usage.Options.__init__(self)
|
||||
self["indexes"] = []
|
||||
self["root"] = None
|
||||
self["extraHeaders"] = []
|
||||
self["ports"] = []
|
||||
self["port"] = self["https"] = None
|
||||
|
||||
def opt_port(self, port):
|
||||
"""
|
||||
(DEPRECATED: use --listen)
|
||||
Strports description of port to start the server on
|
||||
"""
|
||||
msg = deprecate.getDeprecationWarningString(
|
||||
self.opt_port, incremental.Version("Twisted", 18, 4, 0)
|
||||
)
|
||||
warnings.warn(msg, category=DeprecationWarning, stacklevel=2)
|
||||
self["port"] = port
|
||||
|
||||
opt_p = opt_port
|
||||
|
||||
def opt_https(self, port):
|
||||
"""
|
||||
(DEPRECATED: use --listen)
|
||||
Port to listen on for Secure HTTP.
|
||||
"""
|
||||
msg = deprecate.getDeprecationWarningString(
|
||||
self.opt_https, incremental.Version("Twisted", 18, 4, 0)
|
||||
)
|
||||
warnings.warn(msg, category=DeprecationWarning, stacklevel=2)
|
||||
self["https"] = port
|
||||
|
||||
def opt_listen(self, port):
|
||||
"""
|
||||
Add an strports description of port to start the server on.
|
||||
[default: tcp:8080]
|
||||
"""
|
||||
self["ports"].append(port)
|
||||
|
||||
def opt_index(self, indexName):
|
||||
"""
|
||||
Add the name of a file used to check for directory indexes.
|
||||
[default: index, index.html]
|
||||
"""
|
||||
self["indexes"].append(indexName)
|
||||
|
||||
opt_i = opt_index
|
||||
|
||||
def opt_user(self):
|
||||
"""
|
||||
Makes a server with ~/public_html and ~/.twistd-web-pb support for
|
||||
users.
|
||||
"""
|
||||
self["root"] = distrib.UserDirectory()
|
||||
|
||||
opt_u = opt_user
|
||||
|
||||
def opt_path(self, path):
|
||||
"""
|
||||
<path> is either a specific file or a directory to be set as the root
|
||||
of the web server. Use this if you have a directory full of HTML, cgi,
|
||||
epy, or rpy files or any other files that you want to be served up raw.
|
||||
"""
|
||||
self["root"] = static.File(os.path.abspath(path))
|
||||
self["root"].processors = {
|
||||
".epy": script.PythonScript,
|
||||
".rpy": script.ResourceScript,
|
||||
}
|
||||
self["root"].processors[".cgi"] = twcgi.CGIScript
|
||||
|
||||
def opt_processor(self, proc):
|
||||
"""
|
||||
`ext=class' where `class' is added as a Processor for files ending
|
||||
with `ext'.
|
||||
"""
|
||||
if not isinstance(self["root"], static.File):
|
||||
raise usage.UsageError("You can only use --processor after --path.")
|
||||
ext, klass = proc.split("=", 1)
|
||||
self["root"].processors[ext] = reflect.namedClass(klass)
|
||||
|
||||
def opt_class(self, className):
|
||||
"""
|
||||
Create a Resource subclass with a zero-argument constructor.
|
||||
"""
|
||||
classObj = reflect.namedClass(className)
|
||||
self["root"] = classObj()
|
||||
|
||||
def opt_resource_script(self, name):
|
||||
"""
|
||||
An .rpy file to be used as the root resource of the webserver.
|
||||
"""
|
||||
self["root"] = script.ResourceScriptWrapper(name)
|
||||
|
||||
def opt_wsgi(self, name):
|
||||
"""
|
||||
The FQPN of a WSGI application object to serve as the root resource of
|
||||
the webserver.
|
||||
"""
|
||||
try:
|
||||
application = reflect.namedAny(name)
|
||||
except (AttributeError, ValueError):
|
||||
raise usage.UsageError(f"No such WSGI application: {name!r}")
|
||||
pool = threadpool.ThreadPool()
|
||||
reactor.callWhenRunning(pool.start)
|
||||
reactor.addSystemEventTrigger("after", "shutdown", pool.stop)
|
||||
self["root"] = wsgi.WSGIResource(reactor, pool, application)
|
||||
|
||||
def opt_mime_type(self, defaultType):
|
||||
"""
|
||||
Specify the default mime-type for static files.
|
||||
"""
|
||||
if not isinstance(self["root"], static.File):
|
||||
raise usage.UsageError("You can only use --mime_type after --path.")
|
||||
self["root"].defaultType = defaultType
|
||||
|
||||
opt_m = opt_mime_type
|
||||
|
||||
def opt_allow_ignore_ext(self):
|
||||
"""
|
||||
Specify whether or not a request for 'foo' should return 'foo.ext'
|
||||
"""
|
||||
if not isinstance(self["root"], static.File):
|
||||
raise usage.UsageError(
|
||||
"You can only use --allow_ignore_ext " "after --path."
|
||||
)
|
||||
self["root"].ignoreExt("*")
|
||||
|
||||
def opt_ignore_ext(self, ext):
|
||||
"""
|
||||
Specify an extension to ignore. These will be processed in order.
|
||||
"""
|
||||
if not isinstance(self["root"], static.File):
|
||||
raise usage.UsageError("You can only use --ignore_ext " "after --path.")
|
||||
self["root"].ignoreExt(ext)
|
||||
|
||||
def opt_add_header(self, header):
|
||||
"""
|
||||
Specify an additional header to be included in all responses. Specified
|
||||
as "HeaderName: HeaderValue".
|
||||
"""
|
||||
name, value = header.split(":", 1)
|
||||
self["extraHeaders"].append((name.strip(), value.strip()))
|
||||
|
||||
def postOptions(self):
|
||||
"""
|
||||
Set up conditional defaults and check for dependencies.
|
||||
|
||||
If SSL is not available but an HTTPS server was configured, raise a
|
||||
L{UsageError} indicating that this is not possible.
|
||||
|
||||
If no server port was supplied, select a default appropriate for the
|
||||
other options supplied.
|
||||
"""
|
||||
if self["port"] is not None:
|
||||
self["ports"].append(self["port"])
|
||||
if self["https"] is not None:
|
||||
try:
|
||||
reflect.namedModule("OpenSSL.SSL")
|
||||
except ImportError:
|
||||
raise usage.UsageError("SSL support not installed")
|
||||
sslStrport = "ssl:port={}:privateKey={}:certKey={}".format(
|
||||
self["https"],
|
||||
self["privkey"],
|
||||
self["certificate"],
|
||||
)
|
||||
self["ports"].append(sslStrport)
|
||||
if len(self["ports"]) == 0:
|
||||
if self["personal"]:
|
||||
path = os.path.expanduser(
|
||||
os.path.join("~", distrib.UserDirectory.userSocketName)
|
||||
)
|
||||
self["ports"].append("unix:" + path)
|
||||
else:
|
||||
self["ports"].append("tcp:8080")
|
||||
|
||||
|
||||
def makePersonalServerFactory(site):
|
||||
"""
|
||||
Create and return a factory which will respond to I{distrib} requests
|
||||
against the given site.
|
||||
|
||||
@type site: L{twisted.web.server.Site}
|
||||
@rtype: L{twisted.internet.protocol.Factory}
|
||||
"""
|
||||
return pb.PBServerFactory(distrib.ResourcePublisher(site))
|
||||
|
||||
|
||||
class _AddHeadersResource(resource.Resource):
|
||||
def __init__(self, originalResource, headers):
|
||||
self._originalResource = originalResource
|
||||
self._headers = headers
|
||||
|
||||
def getChildWithDefault(self, name, request):
|
||||
for k, v in self._headers:
|
||||
request.responseHeaders.addRawHeader(k, v)
|
||||
return self._originalResource.getChildWithDefault(name, request)
|
||||
|
||||
|
||||
def makeService(config):
|
||||
s = service.MultiService()
|
||||
if config["root"]:
|
||||
root = config["root"]
|
||||
if config["indexes"]:
|
||||
config["root"].indexNames = config["indexes"]
|
||||
else:
|
||||
# This really ought to be web.Admin or something
|
||||
root = demo.Test()
|
||||
|
||||
if isinstance(root, static.File):
|
||||
root.registry.setComponent(interfaces.IServiceCollection, s)
|
||||
|
||||
if config["extraHeaders"]:
|
||||
root = _AddHeadersResource(root, config["extraHeaders"])
|
||||
|
||||
if config["logfile"]:
|
||||
site = server.Site(root, logPath=config["logfile"])
|
||||
else:
|
||||
site = server.Site(root)
|
||||
|
||||
if config["display-tracebacks"]:
|
||||
site.displayTracebacks = True
|
||||
|
||||
# Deprecate --notracebacks/-n
|
||||
if config["notracebacks"]:
|
||||
msg = deprecate._getDeprecationWarningString(
|
||||
"--notracebacks", incremental.Version("Twisted", 19, 7, 0)
|
||||
)
|
||||
warnings.warn(msg, category=DeprecationWarning, stacklevel=2)
|
||||
|
||||
if config["personal"]:
|
||||
site = makePersonalServerFactory(site)
|
||||
for port in config["ports"]:
|
||||
svc = strports.service(port, site)
|
||||
svc.setServiceParent(s)
|
||||
return s
|
||||
60
backend/lib/python3.12/site-packages/twisted/web/template.py
Normal file
60
backend/lib/python3.12/site-packages/twisted/web/template.py
Normal file
@@ -0,0 +1,60 @@
|
||||
# -*- test-case-name: twisted.web.test.test_template -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
HTML rendering for twisted.web.
|
||||
|
||||
@var VALID_HTML_TAG_NAMES: A list of recognized HTML tag names, used by the
|
||||
L{tag} object.
|
||||
|
||||
@var TEMPLATE_NAMESPACE: The XML namespace used to identify attributes and
|
||||
elements used by the templating system, which should be removed from the
|
||||
final output document.
|
||||
|
||||
@var tags: A convenience object which can produce L{Tag} objects on demand via
|
||||
attribute access. For example: C{tags.div} is equivalent to C{Tag("div")}.
|
||||
Tags not specified in L{VALID_HTML_TAG_NAMES} will result in an
|
||||
L{AttributeError}.
|
||||
"""
|
||||
|
||||
|
||||
__all__ = [
|
||||
"TEMPLATE_NAMESPACE",
|
||||
"VALID_HTML_TAG_NAMES",
|
||||
"Element",
|
||||
"Flattenable",
|
||||
"TagLoader",
|
||||
"XMLString",
|
||||
"XMLFile",
|
||||
"renderer",
|
||||
"flatten",
|
||||
"flattenString",
|
||||
"tags",
|
||||
"Comment",
|
||||
"CDATA",
|
||||
"Tag",
|
||||
"slot",
|
||||
"CharRef",
|
||||
"renderElement",
|
||||
]
|
||||
|
||||
from ._stan import CharRef
|
||||
from ._template_util import (
|
||||
CDATA,
|
||||
TEMPLATE_NAMESPACE,
|
||||
VALID_HTML_TAG_NAMES,
|
||||
Comment,
|
||||
Element,
|
||||
Flattenable,
|
||||
Tag,
|
||||
TagLoader,
|
||||
XMLFile,
|
||||
XMLString,
|
||||
flatten,
|
||||
flattenString,
|
||||
renderElement,
|
||||
renderer,
|
||||
slot,
|
||||
tags,
|
||||
)
|
||||
@@ -0,0 +1,6 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for L{twisted.web}.
|
||||
"""
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user