class YuRequestHandler(BaseHTTPRequestHandler): """ Custom request handler to process HEAD, GET and POST requests """ server_version = '%s/%s' % (SERVER_NAME, SERVER_VERSION) #---------------------------------------------------------------------- def __init__(self, request, client_address, server): self._db = YuDatabase() self._logger = get_logger() self._header_only = False BaseHTTPRequestHandler.__init__(self, request, client_address, server) #---------------------------------------------------------------------- def __del__(self): self._db.close() #---------------------------------------------------------------------- def _get_config_value(self, section, key): """ Convenience function to retrieve config settings | **param** section (str) | **param** key (str) | **return** value (str) """ return config.get(section, key) #---------------------------------------------------------------------- def _get_config_template(self, key): """ Convenience function to retrieve a template filename from the config | **param** key (str) | **return** value (str) """ tmp_path = self._get_config_value('templates', 'path') + key return tmp_path #---------------------------------------------------------------------- def address_string(self): """ Return the client address formatted for logging. Only lookup the hostname if really requested. | **return** hostname (str) """ if self.server.log_ip_activated: host = self.client_address[0] else: host = '127.0.0.1' if self.server.resolve_clients: return socket.getfqdn(host) else: return host #---------------------------------------------------------------------- def log_request(self, code='-', size='-'): """ Overwrite the default log_request() method to make it a no-op. We call the original method ourselves to pass also the response size. """ pass #---------------------------------------------------------------------- def send_response(self, code, message=None, size='-'): """ Send the response header and log the response code. Also send two standard headers with the server software version and the current date. | **param** code (int) | **param** message (str) | **param** size (str) """ BaseHTTPRequestHandler.send_response(self, code, message) BaseHTTPRequestHandler.log_request(self, code, size) #---------------------------------------------------------------------- def log_message(self, msg_format, *args): """ Overwrite the default log_message() method which prints for some reason to stderr which we don't want. Instead, we use a logger. | **param** msg_format (str) | **return** args (seq of mixed) """ try: useragent = self.headers['User-Agent'] except KeyError: useragent = '-' try: referrer = self.headers['Referer'] except KeyError: referrer = '-' values = dict(client=self.address_string(), identity='-', user='******', timestr=time.strftime('%d/%a/%Y:%H:%M:%S %z'), request=msg_format % args, referrer='"%s"' % referrer, useragent='"%s"' % useragent) msg_format = '%(client)s %(identity)s %(user)s [%(timestr)s] %(request)s %(referrer)s %(useragent)s' access_logger = get_access_logger() access_logger.info(msg_format % values) #---------------------------------------------------------------------- def _send_head(self, text, code): """ Send common headers | **param** text (str) | **param** code (int) """ size = len(text) self.send_response(code, None, size) # Trying to figure out what we are going to send out. # Maybe this could be improved a bit further but should do # it for now. extension_start = self.path.rfind('.') extension = self.path[extension_start:] try: self.send_header('Content-Type', CONTENT_TYPES[extension]) except KeyError: self.send_header('Content-Type', 'text/html') self.send_header("Content-Length", size) self.end_headers() #------------------------------------------------------------------- def _send_homepage(self, message=''): """ Sends the default startpage. |**param** message (str) Optional message that should appear on startpage """ template_filename = self._get_config_template('homepage') text = read_template(template_filename, title=SERVER_NAME, header=SERVER_NAME, msg=message) self._send_response(text, 200) #------------------------------------------------------------------- def _send_blocked_page(self, reason): """ Send a block page which tells the user that the URL has been blocked for some reason. | **param** reason Reason, why page has been blocked (str) """ template_filename = self._get_config_template('blocked') text = read_template(template_filename, title=SERVER_NAME, header=SERVER_NAME, comment=reason) self._send_response(text, 200) #------------------------------------------------------------------- def _send_return_page(self, shorthash): """ Send the result page for a new created short URL. | **param** shorthash - new shorthash for URL """ template_filename = self._get_config_template('return') if shorthash == '1337': messagetext = '<p>Hey, you are 1337!</p>' else: messagetext = '' text = read_template(template_filename, message=messagetext, title='%s - Short URL Result' % SERVER_NAME, header='new URL', path=shorthash, hostname=self.server.hostname) self._send_response(text, 200) #------------------------------------------------------------------- def _send_response(self, content, code=200): """ This function is to be intended to consolidate the sending responses to on function. TODO: Sync with self.send_response() function which is already talking with HTTPServer-interface. | **param** content - text of page (str) | **param** code - response code e.g. 404 (int) """ if content: self._send_head(content, code) if not self._header_only: try: self.wfile.write(content) except socket.error: # clients like to stop reading after they got a 404 pass else: self._send_internal_server_error() #------------------------------------------------------------------- def _send_301(self, new_url): """ Send HTTP status code 301 | **param** new_url (str) """ try: self.send_response(301) self.send_header('Location', new_url) self.send_header('Content-type', 'text/html') self.end_headers() except UnicodeEncodeError: self._send_internal_server_error() #---------------------------------------------------------------------- def _send_404(self): """ Send HTTP status code 404 """ template_filename = self._get_config_template('404') text = read_template(template_filename, title='%s - 404' % SERVER_NAME, header='404 — Page not found', URL="Nothing") self._send_response(text, 404) #---------------------------------------------------------------------- def _send_internal_server_error(self): """ Send HTTP status code 500 """ template_filename = self._get_config_template('500') text = read_template(template_filename, title='%s - Internal Error' % SERVER_NAME, header='Internal error') if not text: # fallback to hard-coded template text = TEMPLATE_500 self._send_head(text, 500) if not self._header_only: self.wfile.write(text) #---------------------------------------------------------------------- def _send_database_problem(self): """ Send HTTP status code 500 due to a database connection error | **param** header_only (bool) """ template_filename = self._get_config_template('databaseerror') text = read_template(template_filename, title='%s - Datebase error' % SERVER_NAME, header='Database error') if not text: self._send_internal_server_error() return self._send_head(text, 500) if not self._header_only: self.wfile.write(text) #---------------------------------------------------------------------- def _send_mail(self, subject, content, email): """ Send a mail | **param** subject (str) | **param** content (str) | **param** email (str) """ msg = MIMEText(content, 'plain', 'utf-8') msg['Subject'] = '%s' % (subject) msg['From'] = email msg['To'] = self._get_config_value('email', 'toemail') try: smtp_conn = SMTP('localhost') smtp_conn.sendmail(msg['From'], [msg['To']], msg.as_string()) smtp_conn.quit() except (socket.error, SMTPException), e: self._logger.error('Mail could not be sent (%s)' % e) return False return True
class YuRequestHandler(BaseHTTPRequestHandler): """ Custom request handler to process HEAD, GET and POST requests """ server_version = "%s/%s" % (SERVER_NAME, SERVER_VERSION) # ---------------------------------------------------------------------- def __init__(self, request, client_address, server): self._db = YuDatabase() self._logger = get_logger() self._header_only = False BaseHTTPRequestHandler.__init__(self, request, client_address, server) # ---------------------------------------------------------------------- def __del__(self): self._db.close() # ---------------------------------------------------------------------- def _get_config_value(self, section, key): """ Convenience function to retrieve config settings | **param** section (str) | **param** key (str) | **return** value (str) """ return config.get(section, key) # ---------------------------------------------------------------------- def _get_config_template(self, key): """ Convenience function to retrieve a template filename from the config | **param** key (str) | **return** value (str) """ tmp_path = self._get_config_value("templates", "path") + key return tmp_path # ---------------------------------------------------------------------- def address_string(self): """ Return the client address formatted for logging. Only lookup the hostname if really requested. | **return** hostname (str) """ if self.server.log_ip_activated: host = self.client_address[0] else: host = "127.0.0.1" if self.server.resolve_clients: return socket.getfqdn(host) else: return host # ---------------------------------------------------------------------- def log_request(self, code="-", size="-"): """ Overwrite the default log_request() method to make it a no-op. We call the original method ourselves to pass also the response size. """ pass # ---------------------------------------------------------------------- def send_response(self, code, message=None, size="-"): """ Send the response header and log the response code. Also send two standard headers with the server software version and the current date. | **param** code (int) | **param** message (str) | **param** size (str) """ BaseHTTPRequestHandler.send_response(self, code, message) BaseHTTPRequestHandler.log_request(self, code, size) # ---------------------------------------------------------------------- def log_message(self, msg_format, *args): """ Overwrite the default log_message() method which prints for some reason to stderr which we don't want. Instead, we use a logger. | **param** msg_format (str) | **return** args (seq of mixed) """ try: useragent = self.headers["User-Agent"] except KeyError: useragent = "-" try: referrer = self.headers["Referer"] except KeyError: referrer = "-" values = dict( client=self.address_string(), identity="-", user="******", timestr=time.strftime("%d/%a/%Y:%H:%M:%S %z"), request=msg_format % args, referrer='"%s"' % referrer, useragent='"%s"' % useragent, ) msg_format = "%(client)s %(identity)s %(user)s [%(timestr)s] %(request)s %(referrer)s %(useragent)s" access_logger = get_access_logger() access_logger.info(msg_format % values) # ---------------------------------------------------------------------- def _send_head(self, text, code): """ Send common headers | **param** text (str) | **param** code (int) """ size = len(text) self.send_response(code, None, size) # Trying to figure out what we are going to send out. # Maybe this could be improved a bit further but should do # it for now. extension_start = self.path.rfind(".") extension = self.path[extension_start:] try: self.send_header("Content-Type", CONTENT_TYPES[extension]) except KeyError: self.send_header("Content-Type", "text/html") self.send_header("Content-Length", size) self.end_headers() # ------------------------------------------------------------------- def _send_homepage(self, message=""): """ Sends the default startpage. |**param** message (str) Optional message that should appear on startpage """ template_filename = self._get_config_template("homepage") text = read_template(template_filename, title=SERVER_NAME, header=SERVER_NAME, msg=message) self._send_response(text, 200) # ------------------------------------------------------------------- def _send_blocked_page(self, reason): """ Send a block page which tells the user that the URL has been blocked for some reason. | **param** reason Reason, why page has been blocked (str) """ template_filename = self._get_config_template("blocked") text = read_template(template_filename, title=SERVER_NAME, header=SERVER_NAME, comment=reason) self._send_response(text, 200) # ------------------------------------------------------------------- def _send_return_page(self, shorthash): """ Send the result page for a new created short URL. | **param** shorthash - new shorthash for URL """ template_filename = self._get_config_template("return") if shorthash == "1337": messagetext = "<p>Hey, you are 1337!</p>" else: messagetext = "" text = read_template( template_filename, message=messagetext, title="%s - Short URL Result" % SERVER_NAME, header="new URL", path=shorthash, hostname=self.server.hostname, ) self._send_response(text, 200) # ------------------------------------------------------------------- def _send_response(self, content, code=200): """ This function is to be intended to consolidate the sending responses to on function. TODO: Sync with self.send_response() function which is already talking with HTTPServer-interface. | **param** content - text of page (str) | **param** code - response code e.g. 404 (int) """ if content: self._send_head(content, code) if not self._header_only: try: self.wfile.write(content) except socket.error: # clients like to stop reading after they got a 404 pass else: self._send_internal_server_error() # ------------------------------------------------------------------- def _send_301(self, new_url): """ Send HTTP status code 301 | **param** new_url (str) """ try: self.send_response(301) self.send_header("Location", new_url) self.send_header("Content-type", "text/html") self.end_headers() except UnicodeEncodeError: self._send_internal_server_error() # ---------------------------------------------------------------------- def _send_404(self): """ Send HTTP status code 404 """ template_filename = self._get_config_template("404") text = read_template( template_filename, title="%s - 404" % SERVER_NAME, header="404 — Page not found", URL="Nothing" ) self._send_response(text, 404) # ---------------------------------------------------------------------- def _send_internal_server_error(self): """ Send HTTP status code 500 """ template_filename = self._get_config_template("500") text = read_template(template_filename, title="%s - Internal Error" % SERVER_NAME, header="Internal error") if not text: # fallback to hard-coded template text = TEMPLATE_500 self._send_head(text, 500) if not self._header_only: self.wfile.write(text) # ---------------------------------------------------------------------- def _send_database_problem(self): """ Send HTTP status code 500 due to a database connection error | **param** header_only (bool) """ template_filename = self._get_config_template("databaseerror") text = read_template(template_filename, title="%s - Datebase error" % SERVER_NAME, header="Database error") if not text: self._send_internal_server_error() return self._send_head(text, 500) if not self._header_only: self.wfile.write(text) # ---------------------------------------------------------------------- def _send_mail(self, subject, content, email): """ Send a mail | **param** subject (str) | **param** content (str) | **param** email (str) """ msg = MIMEText(content, "plain", "utf-8") msg["Subject"] = "%s" % (subject) msg["From"] = email msg["To"] = self._get_config_value("email", "toemail") try: smtp_conn = SMTP("localhost") smtp_conn.sendmail(msg["From"], [msg["To"]], msg.as_string()) smtp_conn.quit() except (socket.error, SMTPException), e: self._logger.error("Mail could not be sent (%s)" % e) return False return True