#!/usr/bin/env python2 # coding=utf-8 # Remove the first line to use the env command to locate python import os import time import datetime import random import cgi import _mysql from Cookie import SimpleCookie import tenjin import manage import oekaki import gettext from database import * from settings import Settings from framework import * from formatting import * from post import * from img import * __version__ = "0.8.8" # Set to True to disable weabot's exception routing and enable profiling _DEBUG = False # Set to True to save performance data to weabot.txt _LOG = False class weabot(object): def __init__(self, environ, start_response): global _DEBUG self.environ = environ if self.environ["PATH_INFO"].startswith("/weabot.py/"): self.environ["PATH_INFO"] = self.environ["PATH_INFO"][11:] self.start = start_response self.formdata = getFormData(self) self.output = "" self.handleRequest() # Localization Code lang = gettext.translation( 'weabot', './locale', languages=[Settings.LANG]) lang.install() logTime("**Start**") if _DEBUG: import cProfile prof = cProfile.Profile() prof.runcall(self.run) prof.dump_stats('stats.prof') else: try: self.run() except UserError, message: self.error(message) except Exception, inst: import sys import traceback exc_type, exc_value, exc_traceback = sys.exc_info() detail = ((os.path.basename(o[0]), o[1], o[2], o[3]) for o in traceback.extract_tb(exc_traceback)) self.exception(type(inst), inst, detail) # close database and finish CloseDb() logTime("**End**") if _LOG: logfile = open(Settings.ROOT_DIR + "weabot.txt", "w") logfile.write(logTimes()) logfile.close() def __iter__(self): self.handleResponse() self.start("200 OK", self.headers) yield self.output def error(self, message): board = Settings._.BOARD if board: if board['board_type'] == '1': info = {} info['host'] = self.environ["REMOTE_ADDR"] info['name'] = self.formdata.get('fielda', '') info['email'] = self.formdata.get('fieldb', '') info['message'] = self.formdata.get('message', '') self.output += renderTemplate("txt_error.html", {"info": info, "error": message}) else: mobile = self.formdata.get('mobile', '') if mobile: self.output += renderTemplate("mobile/error.html", {"error": message}) else: self.output += renderTemplate("error.html", { "error": message, "boards_url": Settings.BOARDS_URL, "board": board["dir"]}) else: self.output += renderTemplate("exception.html", {"exception": None, "error": message}) def exception(self, type, message, detail): self.output += renderTemplate("exception.html", {"exception": type, "error": message, "detail": detail}) def handleRequest(self): self.headers = [("Content-Type", "text/html")] self.handleCookies() def handleResponse(self): if self._newcookies: for newcookie in self._newcookies.values(): self.headers.append( ("Set-Cookie", newcookie.output(header=""))) def handleCookies(self): self._cookies = SimpleCookie() self._cookies.load(self.environ.get("HTTP_COOKIE", "")) self._newcookies = None def run(self): path_split = self.environ["PATH_INFO"].split("/") caught = False if Settings.FULL_MAINTENANCE: raise UserError, _( "%s is currently under maintenance. We'll be back.") % Settings.SITE_TITLE if len(path_split) > 1: if path_split[1] == "post": # Making a post caught = True if 'password' not in self.formdata: raise UserError, "El request está incompleto." # let's get all the POST data we need ip = self.environ["REMOTE_ADDR"] boarddir = self.formdata.get('board') parent = self.formdata.get('parent') trap1 = self.formdata.get('name', '') trap2 = self.formdata.get('email', '') name = self.formdata.get('fielda', '') email = self.formdata.get('fieldb', '') subject = self.formdata.get('subject', '') message = self.formdata.get('message', '') file = self.formdata.get('file') file_original = self.formdata.get('file_original') spoil = self.formdata.get('spoil') oek_file = self.formdata.get('oek_file') password = self.formdata.get('password', '') noimage = self.formdata.get('noimage') mobile = ("mobile" in self.formdata.keys()) # call post function (post_url, ttaken, unused) = self.make_post(ip, boarddir, parent, trap1, trap2, name, email, subject, message, file, file_original, spoil, oek_file, password, noimage, mobile) # make redirect self.output += make_redirect(post_url, ttaken) elif path_split[1] == "environ": caught = True self.output += repr(self.environ) elif path_split[1] == "delete": # Deleting a post caught = True boarddir = self.formdata.get('board') postid = self.formdata.get('delete') imageonly = self.formdata.get('imageonly') password = self.formdata.get('password') mobile = self.formdata.get('mobile') # call delete function self.delete_post(boarddir, postid, imageonly, password, mobile) elif path_split[1] == "anarkia": import anarkia caught = True OpenDb() anarkia.anarkia(self, path_split) elif path_split[1] == "manage": caught = True OpenDb() manage.manage(self, path_split) elif path_split[1] == "api": import api caught = True self.headers = [("Content-Type", "application/json"), ("Access-Control-Allow-Origin", "*"), ("Access-Control-Allow-Methods", "PUT, GET, POST, DELETE, OPTIONS"), ("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With")] OpenDb() api.api(self, path_split) elif path_split[1] == "threadlist": OpenDb() board = setBoard(path_split[2]) caught = True if board['board_type'] != '1': raise UserError, "No disponible para esta sección." self.output = threadList(0) elif path_split[1] == "mobile": OpenDb() board = setBoard(path_split[2]) caught = True self.output = threadList(1) elif path_split[1] == "mobilelist": OpenDb() board = setBoard(path_split[2]) caught = True self.output = threadList(2) elif path_split[1] == "mobilecat": OpenDb() board = setBoard(path_split[2]) caught = True self.output = threadList(3) elif path_split[1] == "mobilenew": OpenDb() board = setBoard(path_split[2]) caught = True self.output = renderTemplate('txt_newthread.html', {}, True) elif path_split[1] == "mobilehome": OpenDb() latest_age = getLastAge(Settings.HOME_LASTPOSTS) for threads in latest_age: content = threads['url'] content = content.replace('/read/', '/') content = content.replace('/res/', '/') content = content.replace('.html', '') threads['url'] = content caught = True self.output = renderTemplate( 'latest.html', {'latest_age': latest_age}, True) elif path_split[1] == "mobilenewest": OpenDb() newthreads = getNewThreads(Settings.HOME_LASTPOSTS) for threads in newthreads: content = threads['url'] content = content.replace('/read/', '/') content = content.replace('/res/', '/') content = content.replace('.html', '') threads['url'] = content caught = True self.output = renderTemplate( 'newest.html', {'newthreads': newthreads}, True) elif path_split[1] == "mobileread": OpenDb() board = setBoard(path_split[2]) caught = True if len(path_split) > 4 and path_split[4] and board['board_type'] == '1': # try: self.output = dynamicRead( int(path_split[3]), path_split[4], True) # except: # self.output = threadPage(path_split[3], True) elif board['board_type'] == '1': self.output = threadPage(0, True, path_split[3]) else: self.output = threadPage(path_split[3], True) elif path_split[1] == "catalog": OpenDb() board = setBoard(path_split[2]) caught = True sort = self.formdata.get('sort', '') self.output = catalog(sort) elif path_split[1] == "oekaki": caught = True OpenDb() oekaki.oekaki(self, path_split) elif path_split[1] == "play": # Module player caught = True boarddir = path_split[2] modfile = path_split[3] self.output = renderTemplate( 'mod.html', {'board': boarddir, 'modfile': modfile}) elif path_split[1] == "report": # Report post, check if they are enabled # Can't report if banned caught = True ip = self.environ["REMOTE_ADDR"] boarddir = path_split[2] postid = int(path_split[3]) reason = self.formdata.get('reason') try: txt = True postshow = int(path_split[4]) except: txt = False postshow = postid self.report(ip, boarddir, postid, reason, txt, postshow) elif path_split[1] == "stats": caught = True self.stats() elif path_split[1] == "random": caught = True OpenDb() board = FetchOne( "SELECT `id`, `dir`, `board_type` FROM `boards` WHERE `secret` = 0 AND `id` <> 1 AND `id` <> 13 AND `id` <> 34 ORDER BY RAND() LIMIT 1") thread = FetchOne( "SELECT `id`, `timestamp` FROM `posts` WHERE `parentid` = 0 AND `boardid` = %s ORDER BY RAND() LIMIT 1" % board['id']) if board['board_type'] == '1': url = Settings.HOME_URL + \ board['dir'] + '/read/' + thread['timestamp'] + '/' else: url = Settings.HOME_URL + \ board['dir'] + '/res/' + thread['id'] + '.html' self.output += '
...
' % url elif path_split[1] == "nostalgia": caught = True OpenDb() thread = FetchOne( "SELECT `timestamp` FROM `archive` WHERE `boardid` = 9 AND `timestamp` < 1462937230 ORDER BY RAND() LIMIT 1") url = Settings.HOME_URL + '/zonavip/read/' + \ thread['timestamp'] + '/' self.output += '...
' % url elif path_split[1] == "banned": OpenDb() packed_ip = inet_aton(self.environ["REMOTE_ADDR"]) bans = FetchAll("SELECT * FROM `bans` WHERE (`netmask` IS NULL AND `ip` = '"+str( packed_ip)+"') OR (`netmask` IS NOT NULL AND '"+str(packed_ip)+"' & `netmask` = `ip`)") if bans: for ban in bans: if ban["boards"] != "": boards = pickle.loads(ban["boards"]) if ban["boards"] == "" or path_split[2] in boards: caught = True if ban["boards"]: boards_str = '/' + '/, /'.join(boards) + '/' else: boards_str = _("all boards") if ban["until"] != "0": expire = formatTimestamp(ban["until"]) else: expire = "" template_values = { # 'return_board': path_split[2], 'boards_str': boards_str, 'reason': ban['reason'], 'added': formatTimestamp(ban["added"]), 'expire': expire, 'ip': self.environ["REMOTE_ADDR"], } self.output = renderTemplate( 'banned.html', template_values) else: if len(path_split) > 2: caught = True self.output += '%s
' % ( Settings.HOME_URL + path_split[2], _("Your ban has expired. Redirecting...")) elif path_split[1] == "read": # Textboard read: if len(path_split) > 4: caught = True # 2: board # 3: thread # 4: post(s) OpenDb() board = setBoard(path_split[2]) self.output = dynamicRead( int(path_split[3]), path_split[4]) elif path_split[1] == "preview": caught = True OpenDb() try: board = setBoard(self.formdata["board"]) message = format_post( self.formdata["message"], self.environ["REMOTE_ADDR"], self.formdata["parentid"]) self.output = message except Exception, messagez: self.output = "Error: " + \ str(messagez) + " : " + str(self.formdata) if not caught: # Redirect the user back to the front page self.output += '--> --> -->
' % Settings.HOME_URL def make_post(self, ip, boarddir, parent, trap1, trap2, name, email, subject, message, file, file_original, spoil, oek_file, password, noimage, mobile): _STARTTIME = time.clock() # Comment if not debug if addressIsUS(ip): raise UserError, "Host en lista negra." # open database OpenDb() # set the board board = setBoard(boarddir) if board["dir"] != ["anarkia"]: if addressIsProxy(ip): raise UserError, "Proxy prohibido en esta sección." # check length of fields if len(name) > 50: raise UserError, "El campo de nombre es muy largo." if len(email) > 50: raise UserError, "El campo de e-mail es muy largo." if len(subject) > 100: raise UserError, "El campo de asunto es muy largo." if len(message) > 8000: raise UserError, "El campo de mensaje es muy largo." if message.count('\n') > 50: raise UserError, "El mensaje tiene muchos saltos de línea." # anti-spam trap if trap1 or trap2: raise UserError, "Te quedan tres días de vida." # Create a single datetime now so everything syncs up t = time.time() # Delete expired bans deletedBans = UpdateDb( "DELETE FROM `bans` WHERE `until` != 0 AND `until` < " + str(timestamp())) if deletedBans > 0: regenerateAccess() # Redirect to ban page if user is banned if addressIsBanned(ip, board["dir"]): #raise UserError, 'Tu host está en la lista negra.' raise UserError, '' % board["dir"] # Disallow posting if the site OR board is in maintenance if Settings.MAINTENANCE: raise UserError, _( "%s is currently under maintenance. We'll be back.") % Settings.SITE_TITLE if board["locked"] == '1': raise UserError, _("This board is closed. You can't post in it.") # create post object post = Post(board["id"]) post["ip"] = inet_aton(ip) post["timestamp"] = post["bumped"] = int(t) post["timestamp_formatted"] = formatTimestamp(t) # load parent info if we are replying parent_post = None parent_timestamp = post["timestamp"] if parent: parent_post = get_parent_post(parent, board["id"]) parent_timestamp = parent_post['timestamp'] post["parentid"] = parent_post['id'] post["bumped"] = parent_post['bumped'] if parent_post['locked'] == '1': raise UserError, _( "The thread is closed. You can't post in it.") # check if the user is flooding flood_check(t, post, board["id"]) # use fields only if enabled if board["disable_name"] != '1': post["name"] = cleanString(name) post["email"] = cleanString(email, quote=True) if board["disable_subject"] != '1': post["subject"] = cleanString(subject) # process tripcodes post["name"], post["tripcode"] = tripcode(post["name"]) # Remove carriage return, they're useless message = message.replace("\r", "") # check ! functions before extend = extend_str = dice = ball = None if not post["parentid"] and board["dir"] not in ['bai', 'world']: # creating thread __extend = re.compile(r"^!extend(:\w+)(:\w+)?\n") res = __extend.match(message) if res: extend = res.groups() # truncate extend extend_str = res.group(0) message = message[res.end(0):] if board["dir"] in ['juegos', '0', 'polka']: __dice = re.compile(r"^!dado(:\w+)(:\w+)?\n") res = __dice.match(message) if res: dice = res.groups() message = message[res.end(0):] if board["dir"] in ['zonavip', '0', 'polka']: __ball = re.compile(r"^!bola8\n") res = __ball.match(message) if res: ball = True message = message[res.end(0):] # use and format message if message.strip(): post["message"] = format_post( message, ip, post["parentid"], parent_timestamp) # add function messages if extend_str: extend_str = extend_str.replace('!extend', 'EXTEND') post["message"] += '%s
' % ( ("/cgi/mobile/" if mobile else Settings.BOARDS_URL) + board["dir"], _("File deleted successfully.")) else: self.output += '%s
' % ( ("/cgi/mobile/" if mobile else Settings.BOARDS_URL) + board["dir"], _("Post deleted successfully.")) def report(self, ip, boarddir, postid, reason, txt, postshow): # don't allow if the report system is off if not Settings.REPORTS_ENABLE: raise UserError, _('Report system is deactivated.') # if there's not a reason, show the report page if reason is None: self.output += renderTemplate("report.html", {'finished': False, 'postshow': postshow, 'txt': txt}) return # check reason if not reason: raise UserError, _("Enter a reason.") if len(reason) > 100: raise UserError, _("Text too long.") # open database OpenDb() # set the board we're in board = setBoard(boarddir) # check if he's banned if addressIsBanned(ip, board["dir"]): raise UserError, _("You're banned.") # check if post exists post = FetchOne("SELECT `id`, `parentid`, `ip` FROM `posts` WHERE `id` = '%s' AND `boardid` = '%s'" % ( _mysql.escape_string(str(postid)), _mysql.escape_string(board['id']))) if not post: raise UserError, _("Post doesn't exist.") # generate link if board["board_type"] == '1': parent_post = get_parent_post(post["parentid"], board["id"]) link = "/%s/read/%s/%s" % (board["dir"], parent_post["timestamp"], postshow) else: link = "/%s/res/%d.html#%d" % (board["dir"], int( post["parentid"]) or int(post["id"]), int(post["id"])) # insert report t = time.time() message = cgi.escape(self.formdata["reason"]).strip()[0:8000] message = message.replace("\n", "