#!/usr/bin/python import MySQLdb import sys, os, urllib import sha import array import re uname_re_str = r'^[a-zA-Z0-9][a-zA-Z0-9\.\-_]{,19}$' class Ctx: def __init__(self, path_info, query_string = None): self.path_info = path_info path_l = path_info.split('/') if path_l[0] == '': path_l = path_l[1:] self.path_l = path_l self.query_string = query_string if query_string == None: self.query = None else: self.query = {} for q in query_string.split('&'): kv = q.split('=', 1) if len(kv) == 2: k, v = kv v = unicode(urllib.unquote_plus(v), 'utf-8') self.query[k] = v self.status = '200 Found' self.headers = [] self.content = None self.finalstr = [] self.db = None self.uname = None self.ustatus = None self.urlbase = '/' self.wikibase = 'wiki/' def flush_headers(self): if self.headers: print 'Status: ' + self.status for k, v in self.headers: print k + ': ' + v print self.headers = None def flush(self): self.write(self.finalstr) if self.content != None: for l in self.content: print l self.content = None def header(self, kw, val): self.headers.append((kw, val)) def write(self, str): if type(str) == type([]): for s in str: self.write_str(s) else: self.write_str(str) def write_str(self, str): if type(str) == type(u''): str = str.encode('utf-8') self.flush_headers() if self.content == None: print str else: self.content.append(str) def cursor(self): if self.db == None: self.db = MySQLdb.connect(getattr(self, 'dbhost', 'localhost'), getattr(self, 'dbuname', 'root'), getattr(self, 'dbpass', ''), getattr(self, 'dbdbname', 'junk')) self.dbversion = map(int, MySQLdb.get_client_info().split('.')) return self.db.cursor() def ctx_from_cgi(): path_info = os.getenv('PATH_INFO') cl = os.getenv('CONTENT_LENGTH') if cl: query_string = sys.stdin.read(int(cl)) else: query_string = os.getenv('QUERY_STRING') ctx = Ctx(path_info, query_string) cookie = os.getenv('HTTP_COOKIE') if cookie: ctx.cookie = cookie else: ctx.cookie = None return ctx def auth_user(ctx): if ctx.ustatus == None: ctx.ustatus = 'f' if ctx.cookie == None: return ctx.ustatus s = ctx.cookie.split('=') if len(s) != 2: return ctx.ustatus if s[0] == 'id': s = s[1].split(':') if len(s) != 2: return ctx.ustatus uname, qcookie = s c = ctx.cursor() c.execute('select cookie, status from users where uname = %s', uname) response = c.fetchone() if response and response[0] == qcookie: ctx.uname = uname ctx.ustatus = response[1] return ctx.ustatus # Convert a blob to a Unicode string def uniblob(s): return unicode(s.tostring(), 'utf-8') def htmlquote(s): s = s.replace('&', '&') s = s.replace('<', '<') s = s.replace('>', '>') return s def urlquote(s): s = s.replace('"', '%22') return s def urlquoteplus(s): s = urlquote(s) s = s.replace('+', '%2b') s = s.replace(' ', '+') return s def randhex(n): randstr = file('/dev/urandom', 'rb').read(n) return ''.join(['%x' % (ord(x) & 15) for x in randstr]) servtab = {} def stdpage(ctx, title): ctx.header('Content-Type', 'text/html; charset=utf-8') ctx.write(['
', '', 'What you are looking for may exist, but it is not here.
') def navbar(ctx, items = []): items.append(wikilink(ctx, '', True)) items.append(wikilink(ctx, 'RecentChanges', True)) if auth_user(ctx) == 'f': items.append('Log in') ctx.write(' ') def serve_home(ctx): stdpage(ctx, 'Home page') if auth_user(ctx) != 'f': navbar(ctx) else: ctx.write( ['Log in below, or go straight to the wiki.
', '', '', 'If you don\'t have an account and want one, please sign up.
']) def serve_signup(ctx, extra = None): stdpage(ctx, 'Sign up for a new account') ctx.write( ['Sign up for a new account here.
', '', '']) def serve_loginsub(ctx): uname = ctx.query['u'] c = ctx.cursor() c.execute("select salt, hash, cookie from users where uname = %s", uname) response = c.fetchone() if response: salt, hash, cookie = response else: salt, hash = '', '' qhash = sha.sha(salt + ctx.query['pass'].encode('utf-8')).hexdigest() if hash == qhash: ctx.header('Set-Cookie', 'id=' + uname + ':' + cookie) stdpage(ctx, 'Login successful') ctx.write("Username and password match, setting cookie.
") else: stdpage(ctx, 'Login failed') ctx.write( ['Oops, your login failed.
']) navbar(ctx) def serve_signupsub(ctx): uname = ctx.query['u'] if not re.match(uname_re_str, uname): serve_signup(ctx, 'Invalid username. The username must be no longer than 20 characters, begin with an alphanumeric, and consist of alphanumerics, period, underscore, or dash. In other words, it must match the regular expression syntax: ' + uname_re_str + '
') return c = ctx.cursor() c.execute("select realname from users where uname = %s", uname) response = c.fetchone() if response: realname = uniblob(response[0]) serve_signup(ctx, 'User already exists. The username ' + uname + ' already belongs to ' + htmlquote(realname) + '.
') return passwd = ctx.query['pass'] if len(passwd) < 4: serve_signup(ctx, 'Password too short. The password must be at least 4 characters.
') return realname = ctx.query['realname'].strip() if len(realname) < 1: serve_signup(ctx, 'Give your real name. You must put include your name.
') entropy = randhex(52) cookie = entropy[:40] salt = entropy[40:] hash = sha.sha(salt + passwd.encode('utf-8')).hexdigest() c.execute("insert into users (uname, salt, hash, status, cookie, realname, ctime) values (%s, %s, %s, 'y', %s, %s, now())", (uname, salt, hash, cookie, realname.encode('utf-8'))) ctx.header('Set-Cookie', 'id=' + uname + ':' + cookie) stdpage(ctx, 'Signup successful') ctx.write("Congratulations! You have created the account " + uname + ". Next, you'll probably want to edit the wiki page for your account: " + userlink(ctx, uname) + ".
") navbar(ctx) def check_auth(ctx, perms = 'r'): status = auth_user(ctx) if status == 'y' or not 'w' in perms: return True else: ctx.status = '403 Forbidden' stdpage(ctx, 'Not authorized') ctx.write("Sorry, you're not authorized to make changes to the wiki.
") navbar(ctx) return False def userlink(ctx, uname, body = None): c = ctx.cursor() c.execute('select realname from users where uname = %s', uname) response = c.fetchone() if response: realname = uniblob(response[0]) if body == None: body = realname return '' + htmlquote(body) + '' else: return '[deleted account ' + uname + ']' def wikiexists(ctx, page): # We should probably just always prepend the wikibase. if ctx.wikibase != 'wiki/' and page.find('/') < 0: page = ctx.wikibase + page page += '/_thm' # hack if page in ('RecentChanges',): return True c = ctx.cursor() if ctx.dbversion >= [4, 1, 0]: c.execute('select uname, mtime from log where seqno = (select max(seqno) from log where what = %s)', page) return c.fetchone() else: c.execute('select max(seqno) from log where what = %s', page) response = c.fetchone() if response: c.execute('select uname, mtime from log where seqno = %s', response) return c.fetchone() def getwiki(ctx, page): c = ctx.cursor() if ctx.dbversion >= [4, 1, 0]: c.execute('select data, uname, mtime from log where seqno = (select max(seqno) from log where what = %s)', page) response = c.fetchone() else: # Subqueries are the right way to do this. We'll want to take this # garbage out as soon as we know we're only running on 4.1.0 or higher. c.execute('select max(seqno) from log where what = %s', page) response = c.fetchone() if response: c.execute('select data, uname, mtime from log where seqno = %s', response) response = c.fetchone() if response: data = uniblob(response[0]) uname = response[1] mtime = response[2] return data, uname, mtime else: return None # Determine URL for given wiki resource def wikiurl(ctx, page): if page.find('/') >= 0: pref = '' else: pref = ctx.wikibase return ctx.urlbase + pref + urlquoteplus(page) def wikilink(ctx, str, exists = None, ispreblock = False): linkl = str.split('|', 1) if len(linkl) == 2: link, body = linkl else: link, = linkl body = None aclass = '' if re.match(r'[a-z\+]*://', link): url = link elif re.match('user/', link): return userlink(ctx, link[5:], body) else: if exists or (exists == None and wikiexists(ctx, str)): aclass = 'class="wiki" ' else: aclass = 'class="new" ' if str == '' and body == None: body = 'wiki home page' url = wikiurl(ctx, link) if body == None: body = link return '' + htmlquote(body) + '' def wikigh(ctx, str, ispreblock): return '' + htmlquote(str) + '' def wikipre(ctx, str, ispreblock): if ispreblock: return '' + htmlquote(str) + '' else: return '' + htmlquote(str) + '' class ListState: def __init__(self): self.level = 0 def begblock(self, result, level, tag = ''): if level > self.level: result.append('
') pos = 0 else: pos = 0 rline = '' while pos < len(line): m = special_re.search(line, pos) if len(stack): # Handle closing tags for inline markup pos2 = line.find(stack[-1][0], pos) if pos2 >= 0 and (not m or pos2 <= m.start()): rline += line[pos:pos2] + stack[-1][1] pos = pos2 + len(stack[-1][0]) del stack[-1] continue if m: rline += line[pos:m.start()] g = m.group() mend = m.end() if repls.has_key(g): rline += repls[g] pos = mend elif emphs.has_key(g): rline += '<' + emphs[g] + '>' stack.append((g, '' + emphs[g] + '>')) pos = mend elif g in embeds: wikiclose, embedfn = embeds[g] sline, spos = i - 1, mend ispreblock = g == '{{{' and mend == 3 and blank_re.match(line, 3) while sline < len(lines): end_m = wikiclose.search(lines[sline], spos) if end_m and ispreblock and (end_m.start() != 0 or not blank_re.match(lines[sline], 3)): end_m = None if end_m: break sline, spos = sline + 1, 0 if end_m: if sline == i - 1: str = line[mend:end_m.start()] else: str = line[mend:] + '\n' for j in range(i, sline): str += lines[j] + '\n' str += lines[sline][:end_m.start()] rline += embedfn(ctx, str, ispreblock = ispreblock) i = sline + 1 line = lines[sline] pos = end_m.end() continue elif g == '\\': if mend < len(line) and line[mend] in '\\{}_#[]*-/': rline += line[mend] pos = mend + 1 if pos < mend: # Nothing matched above, pass verbatim. rline += g pos = mend else: rline += line[pos:] break result.append(rline) liststate.begblock(result, 0) return result def serve_wiki_post(ctx, page): if not check_auth(ctx, 'w'): return data = ctx.query['data'].encode('utf-8') data = data.replace('\r', '') if ctx.query['submit'] == 'Preview': return serve_wiki_edit(ctx, page, data) c = ctx.cursor() c.execute("insert into log (uname, what, cmd, mtime, data) values (%s, %s, 'u', now(), %s)", (ctx.uname, page, data)) stdpage(ctx, 'Thanks') ctx.write('
Thank you for your update to ' + wikilink(ctx, page) + '.
') navbar(ctx) def serve_wiki_edit(ctx, page, data = None): if not check_auth(ctx, 'w'): return if data == None: response = getwiki(ctx, page) if response: data = response[0] stdpage(ctx, 'Editing ' + htmlquote(page)) else: stdpage(ctx, 'Creating ' + htmlquote(page)) data = '' else: stdpage(ctx, 'Preview of ' + htmlquote(page)) ctx.write(wikiformat(ctx, data)) ctx.write(['', '']) def serve_wiki_recent(ctx, uniq = True): stdpage(ctx, 'Recent Changes') c = ctx.cursor() c.execute('select what, uname, mtime from log order by seqno desc limit 100') pages = {} closelist = None date = None for page, name, mtime in c.fetchall(): page = unicode(page, 'utf-8') if not (uniq and pages.has_key(page)): datestr = mtime.isoformat(' ') if date != datestr[:10]: date = datestr[:10] if closelist: ctx.write(closelist) ctx.write('' + date + '
The wiki action "' + htmlquote(ctx.query['a']) + '" is not known.
') stdpage(ctx, 'Wiki: ' + htmlquote(page)) response = getwiki(ctx, page) if response: content, uname, mtime = response if extra != None: ctx.write(extra) #ctx.write(`content`) ctx.write(wikiformat(ctx, content)) ctx.write('
Last edited ' + mtime.isoformat(' ') + ' by ' + userlink(ctx, uname) + '
') else: if extra != None: ctx.write(extra) ctx.write('No entry exists yet for ' + htmlquote(page) + '.
') items = [] if auth_user(ctx) == 'y': items.append('Edit') navbar(ctx, items) def serve_wiki(ctx): if len(ctx.path_l) == 1: page = urllib.unquote_plus(ctx.path_l[0]) else: page = '' if page == 'RecentChanges': return serve_wiki_recent(ctx) serve_wiki_page(ctx, page) def serve_set(ctx): if len(ctx.path_l) == 1: label = urllib.unquote_plus(ctx.path_l[0]) else: return serve_404(ctx) stdpage(ctx, 'Theorem: ' + htmlquote(label)) response = getwiki(ctx, 'mm/set/%s/_norm' % label) if response: content, uname, mtime = response ctx.wikibase = 'mm/set/' ctx.write(wikiformat(ctx, content)) response = getwiki(ctx, 'mm/set/%s/_thm' % label) if response: content, uname, mtime = response ctx.write('') ctx.write([htmlquote(line) for line in content.split('\n')]) ctx.write('') ctx.wikibase = 'wiki/' navbar(ctx) def serve_user(ctx): if len(ctx.path_l) == 1 and re.match(uname_re_str, ctx.path_l[0]): uname = ctx.path_l[0] c = ctx.cursor() c.execute('select realname from users where uname = %s', uname) response = c.fetchone() if response: realname = uniblob(response[0]) extra = ['
Information for ' + htmlquote(realname) + ' (username ' + uname + ').
'] c.execute('select what, mtime from log where uname = %s order by seqno desc limit 1', uname) response = c.fetchone() if response: page, mtime = response page = unicode(page, 'utf-8') extra.append('Last page edited: ' + wikilink(ctx, page) + ' on ' + mtime.isoformat(' ') + '.
') extra.append('