# common server code for koji # # Copyright (c) 2012-2014 Red Hat, Inc. # # Koji is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; # version 2.1 of the License. # # This software is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this software; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA # # Authors: # Mike McLean import koji import sys import traceback from koji.util import LazyDict try: from mod_python import apache except ImportError: apache = None class ServerError(Exception): """Base class for our server-side-only exceptions""" class ServerRedirect(ServerError): """Used to handle redirects""" class WSGIWrapper(object): """A very thin wsgi compat layer for mod_python This class is highly specific to koji and is not fit for general use. It does not support the full wsgi spec """ def __init__(self, req): self.req = req self._env = None host, port = req.connection.remote_addr environ = { 'REMOTE_ADDR' : req.connection.remote_ip, # or remote_addr[0]? # or req.get_remote_host(apache.REMOTE_NOLOOKUP)? 'REMOTE_PORT' : str(req.connection.remote_addr[1]), 'REMOTE_USER' : req.user, 'REQUEST_METHOD' : req.method, 'REQUEST_URI' : req.uri, 'PATH_INFO' : req.path_info, 'SCRIPT_FILENAME' : req.filename, 'QUERY_STRING' : req.args or '', 'SERVER_NAME' : req.hostname, 'SERVER_PORT' : str(req.connection.local_addr[1]), 'wsgi.version' : (1, 0), 'wsgi.input' : InputWrapper(req), 'wsgi.errors' : sys.stderr, #TODO - file_wrapper support } environ = LazyDict(environ) environ.lazyset('wsgi.url_scheme', self.get_scheme, []) environ.lazyset('modpy.env', self.env, []) environ.lazyset('modpy.opts', req.get_options, []) environ.lazyset('modpy.conf', req.get_config, []) environ.lazyset('SCRIPT_NAME', self.script_name, [], cache=True) env_keys = ['SSL_CLIENT_VERIFY', 'HTTPS', 'SSL_CLIENT_S_DN'] for key in env_keys: environ.lazyset(key, self.envget, [key]) # The component of the DN used for the username is usually the CN, # but it is configurable. # Allow retrieval of some common DN components from the environment. for comp in ['C', 'ST', 'L', 'O', 'OU', 'CN', 'Email']: key = 'SSL_CLIENT_S_DN_' + comp environ.lazyset(key, self.envget, [key]) #gather the headers we care about for key in req.headers_in: k2 = key.upper() k2 = k2.replace('-', '_') if k2 not in ['CONTENT_TYPE', 'CONTENT_LENGTH']: k2 = 'HTTP_' + k2 environ[k2] = req.headers_in[key] self.environ = environ self.set_headers = False def env(self): if self._env is None: self.req.add_common_vars() self._env = self.req.subprocess_env return self._env def envget(self, *args): return self.env().get(*args) def script_name(self): uri = self.req.uri path_info = self.req.path_info if uri.endswith(path_info): uri = uri[:-len(path_info)] uri = uri.rstrip('/') return uri def get_scheme(self): if self.envget('HTTPS') in ('yes', 'on', '1'): return 'https' else: return 'http' def no_write(self, string): """a fake write() callable returned by start_response we don't use the write() callable in koji, so it will raise an error if called """ raise RuntimeError, "wsgi write() callable not supported" def start_response(self, status, headers, exc_info=None): #XXX we don't deal with exc_info if self.set_headers: raise RuntimeError, "start_response() already called" self.req.status = int(status[:3]) for key, val in headers: if key.lower() == 'content-length': self.req.set_content_length(int(val)) elif key.lower() == 'content-type': self.req.content_type = val else: self.req.headers_out.add(key, val) self.set_headers = True return self.no_write def run(self, handler): try: result = handler(self.environ, self.start_response) self.write_result(result) return apache.OK except: sys.stderr.write(''.join(traceback.format_exception(*sys.exc_info()))) sys.stderr.flush() raise apache.SERVER_RETURN, apache.HTTP_INTERNAL_SERVER_ERROR def write_result(self, result): """called by run() to handle the application's result value""" req = self.req write = req.write if self.set_headers: for chunk in result: write(chunk) else: #slower version -- need to check for set_headers for chunk in result: if chunk and not self.set_headers: raise RuntimeError, "write() called before start_response()" write(data) if not req.bytes_sent: #application sent nothing back req.set_content_length(0) class InputWrapper(object): def __init__(self, req): self.req = req def close(self): pass def read(self, size=-1): return self.req.read(size) def readline(self): return self.req.readline() def readlines(self, hint=-1): return self.req.readlines(hint) def __iter__(self): line = self.readline() while line: yield line line = self.readline()