diff options
author | shmel1k <shmel1k@ydb.tech> | 2023-11-26 18:16:14 +0300 |
---|---|---|
committer | shmel1k <shmel1k@ydb.tech> | 2023-11-26 18:43:30 +0300 |
commit | b8cf9e88f4c5c64d9406af533d8948deb050d695 (patch) | |
tree | 218eb61fb3c3b96ec08b4d8cdfef383104a87d63 /contrib/python/Twisted/py3/twisted/web/twcgi.py | |
parent | 523f645a83a0ec97a0332dbc3863bb354c92a328 (diff) | |
download | ydb-b8cf9e88f4c5c64d9406af533d8948deb050d695.tar.gz |
add kikimr_configure
Diffstat (limited to 'contrib/python/Twisted/py3/twisted/web/twcgi.py')
-rw-r--r-- | contrib/python/Twisted/py3/twisted/web/twcgi.py | 343 |
1 files changed, 343 insertions, 0 deletions
diff --git a/contrib/python/Twisted/py3/twisted/web/twcgi.py b/contrib/python/Twisted/py3/twisted/web/twcgi.py new file mode 100644 index 0000000000..fcf831108b --- /dev/null +++ b/contrib/python/Twisted/py3/twisted/web/twcgi.py @@ -0,0 +1,343 @@ +# -*- test-case-name: twisted.web.test.test_cgi -*- +# Copyright (c) Twisted Matrix Laboratories. +# See LICENSE for details. + + +""" +I hold resource classes and helper classes that deal with CGI scripts. +""" + +# System Imports +import os +import urllib +from typing import AnyStr + +# Twisted Imports +from twisted.internet import protocol +from twisted.logger import Logger +from twisted.python import filepath +from twisted.spread import pb +from twisted.web import http, resource, server, static + + +class CGIDirectory(resource.Resource, filepath.FilePath[AnyStr]): + def __init__(self, pathname): + resource.Resource.__init__(self) + filepath.FilePath.__init__(self, pathname) + + def getChild(self, path, request): + fnp = self.child(path) + if not fnp.exists(): + return static.File.childNotFound + elif fnp.isdir(): + return CGIDirectory(fnp.path) + else: + return CGIScript(fnp.path) + + def render(self, request): + notFound = resource.NoResource( + "CGI directories do not support directory listing." + ) + return notFound.render(request) + + +class CGIScript(resource.Resource): + """ + L{CGIScript} is a resource which runs child processes according to the CGI + specification. + + The implementation is complex due to the fact that it requires asynchronous + IPC with an external process with an unpleasant protocol. + """ + + isLeaf = 1 + + def __init__(self, filename, registry=None, reactor=None): + """ + Initialize, with the name of a CGI script file. + """ + self.filename = filename + if reactor is None: + # This installs a default reactor, if None was installed before. + # We do a late import here, so that importing the current module + # won't directly trigger installing a default reactor. + from twisted.internet import reactor + self._reactor = reactor + + def render(self, request): + """ + Do various things to conform to the CGI specification. + + I will set up the usual slew of environment variables, then spin off a + process. + + @type request: L{twisted.web.http.Request} + @param request: An HTTP request. + """ + scriptName = b"/" + b"/".join(request.prepath) + serverName = request.getRequestHostname().split(b":")[0] + env = { + "SERVER_SOFTWARE": server.version, + "SERVER_NAME": serverName, + "GATEWAY_INTERFACE": "CGI/1.1", + "SERVER_PROTOCOL": request.clientproto, + "SERVER_PORT": str(request.getHost().port), + "REQUEST_METHOD": request.method, + "SCRIPT_NAME": scriptName, + "SCRIPT_FILENAME": self.filename, + "REQUEST_URI": request.uri, + } + + ip = request.getClientAddress().host + if ip is not None: + env["REMOTE_ADDR"] = ip + pp = request.postpath + if pp: + env["PATH_INFO"] = "/" + "/".join(pp) + + if hasattr(request, "content"): + # 'request.content' is either a StringIO or a TemporaryFile, and + # the file pointer is sitting at the beginning (seek(0,0)) + request.content.seek(0, 2) + length = request.content.tell() + request.content.seek(0, 0) + env["CONTENT_LENGTH"] = str(length) + + try: + qindex = request.uri.index(b"?") + except ValueError: + env["QUERY_STRING"] = "" + qargs = [] + else: + qs = env["QUERY_STRING"] = request.uri[qindex + 1 :] + if b"=" in qs: + qargs = [] + else: + qargs = [urllib.parse.unquote(x.decode()) for x in qs.split(b"+")] + + # Propagate HTTP headers + for title, header in request.getAllHeaders().items(): + envname = title.replace(b"-", b"_").upper() + if title not in (b"content-type", b"content-length", b"proxy"): + envname = b"HTTP_" + envname + env[envname] = header + # Propagate our environment + for key, value in os.environ.items(): + if key not in env: + env[key] = value + # And they're off! + self.runProcess(env, request, qargs) + return server.NOT_DONE_YET + + def runProcess(self, env, request, qargs=[]): + """ + Run the cgi script. + + @type env: A L{dict} of L{str}, or L{None} + @param env: The environment variables to pass to the process that will + get spawned. See + L{twisted.internet.interfaces.IReactorProcess.spawnProcess} for + more information about environments and process creation. + + @type request: L{twisted.web.http.Request} + @param request: An HTTP request. + + @type qargs: A L{list} of L{str} + @param qargs: The command line arguments to pass to the process that + will get spawned. + """ + p = CGIProcessProtocol(request) + self._reactor.spawnProcess( + p, + self.filename, + [self.filename] + qargs, + env, + os.path.dirname(self.filename), + ) + + +class FilteredScript(CGIScript): + """ + I am a special version of a CGI script, that uses a specific executable. + + This is useful for interfacing with other scripting languages that adhere + to the CGI standard. My C{filter} attribute specifies what executable to + run, and my C{filename} init parameter describes which script to pass to + the first argument of that script. + + To customize me for a particular location of a CGI interpreter, override + C{filter}. + + @type filter: L{str} + @ivar filter: The absolute path to the executable. + """ + + filter = "/usr/bin/cat" + + def runProcess(self, env, request, qargs=[]): + """ + Run a script through the C{filter} executable. + + @type env: A L{dict} of L{str}, or L{None} + @param env: The environment variables to pass to the process that will + get spawned. See + L{twisted.internet.interfaces.IReactorProcess.spawnProcess} + for more information about environments and process creation. + + @type request: L{twisted.web.http.Request} + @param request: An HTTP request. + + @type qargs: A L{list} of L{str} + @param qargs: The command line arguments to pass to the process that + will get spawned. + """ + p = CGIProcessProtocol(request) + self._reactor.spawnProcess( + p, + self.filter, + [self.filter, self.filename] + qargs, + env, + os.path.dirname(self.filename), + ) + + +class CGIProcessProtocol(protocol.ProcessProtocol, pb.Viewable): + handling_headers = 1 + headers_written = 0 + headertext = b"" + errortext = b"" + _log = Logger() + _requestFinished = False + + # Remotely relay producer interface. + + def view_resumeProducing(self, issuer): + self.resumeProducing() + + def view_pauseProducing(self, issuer): + self.pauseProducing() + + def view_stopProducing(self, issuer): + self.stopProducing() + + def resumeProducing(self): + self.transport.resumeProducing() + + def pauseProducing(self): + self.transport.pauseProducing() + + def stopProducing(self): + self.transport.loseConnection() + + def __init__(self, request): + self.request = request + self.request.notifyFinish().addBoth(self._finished) + + def connectionMade(self): + self.request.registerProducer(self, 1) + self.request.content.seek(0, 0) + content = self.request.content.read() + if content: + self.transport.write(content) + self.transport.closeStdin() + + def errReceived(self, error): + self.errortext = self.errortext + error + + def outReceived(self, output): + """ + Handle a chunk of input + """ + # First, make sure that the headers from the script are sorted + # out (we'll want to do some parsing on these later.) + if self.handling_headers: + text = self.headertext + output + headerEnds = [] + for delimiter in b"\n\n", b"\r\n\r\n", b"\r\r", b"\n\r\n": + headerend = text.find(delimiter) + if headerend != -1: + headerEnds.append((headerend, delimiter)) + if headerEnds: + # The script is entirely in control of response headers; + # disable the default Content-Type value normally provided by + # twisted.web.server.Request. + self.request.defaultContentType = None + + headerEnds.sort() + headerend, delimiter = headerEnds[0] + self.headertext = text[:headerend] + # This is a final version of the header text. + linebreak = delimiter[: len(delimiter) // 2] + headers = self.headertext.split(linebreak) + for header in headers: + br = header.find(b": ") + if br == -1: + self._log.error( + "ignoring malformed CGI header: {header!r}", header=header + ) + else: + headerName = header[:br].lower() + headerText = header[br + 2 :] + if headerName == b"location": + self.request.setResponseCode(http.FOUND) + if headerName == b"status": + try: + # "XXX <description>" sometimes happens. + statusNum = int(headerText[:3]) + except BaseException: + self._log.error("malformed status header") + else: + self.request.setResponseCode(statusNum) + else: + # Don't allow the application to control + # these required headers. + if headerName.lower() not in (b"server", b"date"): + self.request.responseHeaders.addRawHeader( + headerName, headerText + ) + output = text[headerend + len(delimiter) :] + self.handling_headers = 0 + if self.handling_headers: + self.headertext = text + if not self.handling_headers: + self.request.write(output) + + def processEnded(self, reason): + if reason.value.exitCode != 0: + self._log.error( + "CGI {uri} exited with exit code {exitCode}", + uri=self.request.uri, + exitCode=reason.value.exitCode, + ) + if self.errortext: + self._log.error( + "Errors from CGI {uri}: {errorText}", + uri=self.request.uri, + errorText=self.errortext, + ) + + if self.handling_headers: + self._log.error( + "Premature end of headers in {uri}: {headerText}", + uri=self.request.uri, + headerText=self.headertext, + ) + if not self._requestFinished: + self.request.write( + resource.ErrorPage( + http.INTERNAL_SERVER_ERROR, + "CGI Script Error", + "Premature end of script headers.", + ).render(self.request) + ) + + if not self._requestFinished: + self.request.unregisterProducer() + self.request.finish() + + def _finished(self, ignored): + """ + Record the end of the response generation for the request being + serviced. + """ + self._requestFinished = True |