Mercurial > traipse_dev
view plugins/cherrypy/_cphttptools.py @ 178:ff19dd30064b alpha
Traipse Alpha 'OpenRPG' {100114-02}
Traipse is a distribution of OpenRPG that is designed to be easy to
setup and go. Traipse also makes it easy for developers to work on code
without fear of sacrifice. 'Ornery-Orc' continues the trend of 'Grumpy'
and adds fixes to the code. 'Ornery-Orc's main goal is to offer more
advanced features and enhance the productivity of the user.
Update Summary (Keeping up with Beta)
New Features:
Added Bookmarks
Added 'boot' command to remote admin
Added confirmation window for sent nodes
Minor changes to allow for portability to an OpenSUSE linux OS
Miniatures Layer pop up box allows users to turn off Mini labels, from
FlexiRPG
Zoom Mouse plugin added
Images added to Plugin UI
Switching to Element Tree
Map efficiency, from FlexiRPG
Added Status Bar to Update Manager
New TrueDebug Class in orpg_log (See documentation for usage)
Portable Mercurial
Tip of the Day added, from Core and community
New Reference Syntax added for custom PC sheets
New Child Reference for gametree
New Parent Reference for gametree
New Gametree Recursion method, mapping, context sensitivity, and
effeciency..
New Features node with bonus nodes and Node Referencing help added
Dieroller structure from Core
New DieRoller portability for odd Dice
Added 7th Sea die roller; ie [7k3] = [7d10.takeHighest(3).open(10)]
New 'Mythos' System die roller added
Added new vs. die roller method for WoD; ie [3v3] = [3d10.vs(3)].
Included for Mythos roller also
New Warhammer FRPG Die Roller (Special thanks to Puu-san for the
support)
New EZ_Tree Reference system. Push a button, Traipse the tree, get a
reference (Beta!)
Fixes:
Fix to Text based Server
Fix to Remote Admin Commands
Fix to Pretty Print, from Core
Fix to Splitter Nodes not being created
Fix to massive amounts of images loading, from Core
Fix to Map from gametree not showing to all clients
Fix to gametree about menus
Fix to Password Manager check on startup
Fix to PC Sheets from tool nodes. They now use the tabber_panel
Fixed Whiteboard ID to prevent random line or text deleting.
Modified ID's to prevent non updated clients from ruining the fix.
default_manifest.xml renamed to default_upmana.xml
Fix to Update Manager; cleaner clode for saved repositories
Fixes made to Settings Panel and now reactive settings when Ok is
pressed
Fixes to Alternity roller's attack roll. Uses a simple Tuple instead of
a Splice
Fix to Use panel of Forms and Tabbers. Now longer enters design mode
Fix made Image Fetching. New fetching image and new failed image
author | sirebral |
---|---|
date | Thu, 14 Jan 2010 12:45:14 -0600 |
parents | 78407d627cba |
children |
line wrap: on
line source
""" Copyright (c) 2004, CherryPy Team (team@cherrypy.org) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the CherryPy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ import cpg, urllib, sys, time, traceback, types, StringIO, cgi, os import mimetypes, hashlib, random, string, _cputil, cperror, Cookie, urlparse from lib.filter import basefilter """ Common Service Code for CherryPy """ mimetypes.types_map['.dwg']='image/x-dwg' mimetypes.types_map['.ico']='image/x-icon' weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] monthname = [None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] class IndexRedirect(Exception): pass def parseFirstLine(data): cpg.request.path = data.split()[1] cpg.request.queryString = "" cpg.request.browserUrl = cpg.request.path cpg.request.paramMap = {} cpg.request.paramList = [] # Only used for Xml-Rpc cpg.request.filenameMap = {} cpg.request.fileTypeMap = {} i = cpg.request.path.find('?') if i != -1: # Parse parameters from URL if cpg.request.path[i+1:]: k = cpg.request.path[i+1:].find('?') if k != -1: j = cpg.request.path[:k].rfind('=') if j != -1: cpg.request.path = cpg.request.path[:j+1] + \ urllib.quote_plus(cpg.request.path[j+1:]) for paramStr in cpg.request.path[i+1:].split('&'): sp = paramStr.split('=') if len(sp) > 2: j = paramStr.find('=') sp = (paramStr[:j], paramStr[j+1:]) if len(sp) == 2: key, value = sp value = urllib.unquote_plus(value) if cpg.request.paramMap.has_key(key): # Already has a value: make a list out of it if type(cpg.request.paramMap[key]) == type([]): # Already is a list: append the new value to it cpg.request.paramMap[key].append(value) else: # Only had one value so far: start a list cpg.request.paramMap[key] = [cpg.request.paramMap[key], value] else: cpg.request.paramMap[key] = value cpg.request.queryString = cpg.request.path[i+1:] cpg.request.path = cpg.request.path[:i] def cookHeaders(clientAddress, remoteHost, headers, requestLine): """Process the headers into the request.headerMap""" cpg.request.headerMap = {} cpg.request.requestLine = requestLine cpg.request.simpleCookie = Cookie.SimpleCookie() # Build headerMap for item in headers.items(): # Warning: if there is more than one header entry for cookies (AFAIK, only Konqueror does that) # only the last one will remain in headerMap (but they will be correctly stored in request.simpleCookie) insertIntoHeaderMap(item[0],item[1]) # Handle cookies differently because on Konqueror, multiple cookies come on different lines with the same key cookieList = headers.getallmatchingheaders('cookie') for cookie in cookieList: cpg.request.simpleCookie.load(cookie) cpg.request.remoteAddr = clientAddress cpg.request.remoteHost = remoteHost # Set peer_certificate (in SSL mode) so the web app can examinate the client certificate try: cpg.request.peerCertificate = self.request.get_peer_certificate() except: pass _cputil.getSpecialFunction('_cpLogMessage')("%s - %s" % (cpg.request.remoteAddr, requestLine[:-2]), "HTTP") def parsePostData(rfile): # Read request body and put it in data len = int(cpg.request.headerMap.get("Content-Length","0")) if len: data = rfile.read(len) else: data="" # Put data in a StringIO so FieldStorage can read it newRfile = StringIO.StringIO(data) # Create a copy of headerMap with lowercase keys because # FieldStorage doesn't work otherwise lowerHeaderMap = {} for key, value in cpg.request.headerMap.items(): lowerHeaderMap[key.lower()] = value forms = cgi.FieldStorage(fp = newRfile, headers = lowerHeaderMap, environ = {'REQUEST_METHOD':'POST'}, keep_blank_values = 1) for key in forms.keys(): # Check if it's a list or not valueList = forms[key] if type(valueList) == type([]): # It's a list of values cpg.request.paramMap[key] = [] cpg.request.filenameMap[key] = [] cpg.request.fileTypeMap[key] = [] for item in valueList: cpg.request.paramMap[key].append(item.value) cpg.request.filenameMap[key].append(item.filename) cpg.request.fileTypeMap[key].append(item.type) else: # It's a single value # In case it's a file being uploaded, we save the filename in a map (user might need it) cpg.request.paramMap[key] = valueList.value cpg.request.filenameMap[key] = valueList.filename cpg.request.fileTypeMap[key] = valueList.type def applyFilterList(methodName): try: filterList = _cputil.getSpecialFunction('_cpFilterList') for filter in filterList: method = getattr(filter, methodName, None) if method: method() except basefilter.InternalRedirect: # If we get an InternalRedirect, we start the filter list # from scratch. Is cpg.request.path or cpg.request.objectPath # has been modified by the hook, then a new filter list # will be applied. # We use recursion so if there is an infinite loop, we'll # get the regular python "recursion limit exceeded" exception. applyFilterList(methodName) def insertIntoHeaderMap(key,value): normalizedKey = '-'.join([s.capitalize() for s in key.split('-')]) cpg.request.headerMap[normalizedKey] = value def initRequest(clientAddress, remoteHost, requestLine, headers, rfile, wfile): parseFirstLine(requestLine) cookHeaders(clientAddress, remoteHost, headers, requestLine) cpg.request.base = "http://" + cpg.request.headerMap['Host'] cpg.request.browserUrl = cpg.request.base + cpg.request.browserUrl cpg.request.isStatic = False cpg.request.parsePostData = True cpg.request.rfile = rfile # Change objectPath in filters to change the object that will get rendered cpg.request.objectPath = None applyFilterList('afterRequestHeader') if cpg.request.method == 'POST' and cpg.request.parsePostData: parsePostData(rfile) applyFilterList('afterRequestBody') def doRequest(clientAddress, remoteHost, requestLine, headers, rfile, wfile): # creates some attributes on cpg.response so filters can use them cpg.response.wfile = wfile cpg.response.sendResponse = 1 try: initRequest(clientAddress, remoteHost, requestLine, headers, rfile, wfile) except basefilter.RequestHandled: # request was already fully handled; it may be a cache hit return # Prepare response variables now = time.time() year, month, day, hh, mm, ss, wd, y, z = time.gmtime(now) date = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (weekdayname[wd], day, monthname[month], year, hh, mm, ss) cpg.response.headerMap = { "protocolVersion": cpg.configOption.protocolVersion, "Status": "200 OK", "Content-Type": "text/html", "Server": "CherryPy/" + cpg.__version__, "Date": date, "Set-Cookie": [], "Content-Length": 0 } cpg.response.simpleCookie = Cookie.SimpleCookie() try: handleRequest(cpg.response.wfile) except: # TODO: in some cases exceptions and filters are conflicting; # error reporting seems to be broken in some cases. This code is # a helper to check it err = "" exc_info_1 = sys.exc_info()[1] if hasattr(exc_info_1, 'args') and len(exc_info_1.args) >= 1: err = exc_info_1.args[0] try: _cputil.getSpecialFunction('_cpOnError')() # Still save session data if cpg.configOption.sessionStorageType and not cpg.request.isStatic: sessionId = cpg.response.simpleCookie[cpg.configOption.sessionCookieName].value expirationTime = time.time() + cpg.configOption.sessionTimeout * 60 _cputil.getSpecialFunction('_cpSaveSessionData')(sessionId, cpg.request.sessionMap, expirationTime) wfile.write('%s %s\r\n' % (cpg.response.headerMap['protocolVersion'], cpg.response.headerMap['Status'])) if (cpg.response.headerMap.has_key('Content-Length') and cpg.response.headerMap['Content-Length']==0): buf = StringIO.StringIO() [buf.write(x) for x in cpg.response.body] buf.seek(0) cpg.response.body = [buf.read()] cpg.response.headerMap['Content-Length'] = len(cpg.response.body[0]) for key, valueList in cpg.response.headerMap.items(): if key not in ('Status', 'protocolVersion'): if type(valueList) != type([]): valueList = [valueList] for value in valueList: wfile.write('%s: %s\r\n'%(key, value)) wfile.write('\r\n') for line in cpg.response.body: wfile.write(line) except: bodyFile = StringIO.StringIO() traceback.print_exc(file = bodyFile) body = bodyFile.getvalue() wfile.write('%s 200 OK\r\n' % cpg.configOption.protocolVersion) wfile.write('Content-Type: text/plain\r\n') wfile.write('Content-Length: %s\r\n' % len(body)) wfile.write('\r\n') wfile.write(body) def sendResponse(wfile): applyFilterList('beforeResponse') # Set the content-length if (cpg.response.headerMap.has_key('Content-Length') and cpg.response.headerMap['Content-Length']==0): buf = StringIO.StringIO() [buf.write(x) for x in cpg.response.body] buf.seek(0) cpg.response.body = [buf.read()] cpg.response.headerMap['Content-Length'] = len(cpg.response.body[0]) # Save session data if cpg.configOption.sessionStorageType and not cpg.request.isStatic: sessionId = cpg.response.simpleCookie[cpg.configOption.sessionCookieName].value expirationTime = time.time() + cpg.configOption.sessionTimeout * 60 _cputil.getSpecialFunction('_cpSaveSessionData')(sessionId, cpg.request.sessionMap, expirationTime) wfile.write('%s %s\r\n' % (cpg.response.headerMap['protocolVersion'], cpg.response.headerMap['Status'])) for key, valueList in cpg.response.headerMap.items(): if key not in ('Status', 'protocolVersion'): if type(valueList) != type([]): valueList = [valueList] for value in valueList: wfile.write('%s: %s\r\n' % (key, value)) # Send response cookies cookie = cpg.response.simpleCookie.output() if cookie: wfile.write(cookie+'\r\n') wfile.write('\r\n') for line in cpg.response.body: wfile.write(line) # finalization hook for filter cleanup & logging purposes applyFilterList('afterResponse') def handleRequest(wfile): # Clean up expired sessions if needed: now = time.time() if cpg.configOption.sessionStorageType and cpg.configOption.sessionCleanUpDelay and cpg._lastSessionCleanUpTime + cpg.configOption.sessionCleanUpDelay * 60 <= now: cpg._lastSessionCleanUpTime = now _cputil.getSpecialFunction('_cpCleanUpOldSessions')() # Save original values (in case they get modified by filters) cpg.request.originalPath = cpg.request.path cpg.request.originalParamMap = cpg.request.paramMap cpg.request.originalParamList = cpg.request.paramList path = cpg.request.path if path.startswith('/'): # Remove leading slash path = path[1:] if path.endswith('/'): # Remove trailing slash path = path[:-1] path = urllib.unquote(path) # Replace quoted chars (eg %20) from url # Handle static directories for urlDir, fsDir in cpg.configOption.staticContentList: if path == urlDir or path[:len(urlDir)+1]==urlDir+'/': cpg.request.isStatic = 1 fname = fsDir + path[len(urlDir):] start_url_var = cpg.request.browserUrl.find('?') if start_url_var != -1: fname = fname + cpg.request.browserUrl[start_url_var:] try: stat = os.stat(fname) except OSError: raise cperror.NotFound modifTime = stat.st_mtime strModifTime = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(modifTime)) # Check if browser sent "if-modified-since" in request header if cpg.request.headerMap.has_key('If-Modified-Since'): # Check if if-modified-since date is the same as strModifTime if cpg.request.headerMap['If-Modified-Since'] == strModifTime: cpg.response.headerMap = { 'Status': 304, 'protocolVersion': cpg.configOption.protocolVersion, 'Date': cpg.response.headerMap['Date']} cpg.response.body = [] sendResponse(wfile) return cpg.response.headerMap['Last-Modified'] = strModifTime # Set Content-Length and use an iterable (file object) # this way CP won't load the whole file in memory cpg.response.headerMap['Content-Length'] = stat[6] cpg.response.body = open(fname, 'rb') # Set content-type based on filename extension i = path.rfind('.') if i != -1: ext = path[i:] else: ext = "" contentType = mimetypes.types_map.get(ext, "text/plain") cpg.response.headerMap['Content-Type'] = contentType sendResponse(wfile) return # Get session data if cpg.configOption.sessionStorageType and not cpg.request.isStatic: now = time.time() # First, get sessionId from cookie try: sessionId = cpg.request.simpleCookie[cpg.configOption.sessionCookieName].value except: sessionId=None if sessionId: # Load session data from wherever it was stored sessionData = _cputil.getSpecialFunction('_cpLoadSessionData')(sessionId) if sessionData == None: sessionId = None else: cpg.request.sessionMap, expirationTime = sessionData # Check that is hasn't expired if now > expirationTime: # Session expired sessionId = None # Create a new sessionId if needed if not sessionId: cpg.request.sessionMap = {} sessionId = generateSessionId() cpg.request.sessionMap['_sessionId'] = sessionId cpg.response.simpleCookie[cpg.configOption.sessionCookieName] = sessionId cpg.response.simpleCookie[cpg.configOption.sessionCookieName]['path'] = '/' cpg.response.simpleCookie[cpg.configOption.sessionCookieName]['version'] = 1 try: func, objectPathList, virtualPathList = mapPathToObject() except IndexRedirect, inst: # For an IndexRedirect, we don't go through the regular # mechanism: we return the redirect immediately newUrl = urlparse.urljoin(cpg.request.base, inst.args[0]) wfile.write('%s 302\r\n' % (cpg.response.headerMap['protocolVersion'])) cpg.response.headerMap['Location'] = newUrl for key, valueList in cpg.response.headerMap.items(): if key not in ('Status', 'protocolVersion'): if type(valueList) != type([]): valueList = [valueList] for value in valueList: wfile.write('%s: %s\r\n'%(key, value)) wfile.write('\r\n') return # Remove "root" from objectPathList and join it to get objectPath cpg.request.objectPath = '/' + '/'.join(objectPathList[1:]) body = func(*(virtualPathList + cpg.request.paramList), **(cpg.request.paramMap)) # builds a uniform return type if not isinstance(body, types.GeneratorType): cpg.response.body = [body] else: cpg.response.body = body if cpg.response.sendResponse: sendResponse(wfile) def generateSessionId(): s = '' for i in range(50): s += random.choice(string.letters+string.digits) s += '%s'%time.time() return hashlib.shal(s).hexdigest() def getObjFromPath(objPathList, objCache): """ For a given objectPathList (like ['root', 'a', 'b', 'index']), return the object (or None if it doesn't exist). Also keep a cache for maximum efficiency """ # Let cpg be the first valid object. validObjects = ["cpg"] # Scan the objPathList in order from left to right for index, obj in enumerate(objPathList): # maps virtual filenames to Python identifiers (substitutes '.' for '_') obj = obj.replace('.', '_') # currentObjStr holds something like 'cpg.root.something.else' currentObjStr = ".".join(validObjects) #--------------- # Cache check #--------------- # Generate a cacheKey from the first 'index' elements of objPathList cacheKey = tuple(objPathList[:index+1]) # Is this cacheKey in the objCache? if cacheKey in objCache: # And is its value not None? if objCache[cacheKey]: # Yes, then add it to the list of validObjects validObjects.append(obj) # OK, go to the next iteration continue # Its value is None, so we stop # (This means it is not a valid object) break #----------------- # Attribute check #----------------- if getattr(eval(currentObjStr), obj, None): # obj is a valid attribute of the current object validObjects.append(obj) # Store it in the cache objCache[cacheKey] = eval(".".join(validObjects)) else: # obj is not a valid attribute # Store None in the cache objCache[cacheKey] = None # Stop, we won't process the remaining objPathList break # Return the last cached object (even if its None) return objCache[cacheKey] def mapPathToObject(path = None): # Traverse path: # for /a/b?arg=val, we'll try: # root.a.b.index -> redirect to /a/b/?arg=val # root.a.b.default(arg='val') -> redirect to /a/b/?arg=val # root.a.b(arg='val') # root.a.default('b', arg='val') # root.default('a', 'b', arg='val') # Also, we ignore trailing slashes # Also, a method has to have ".exposed = True" in order to be exposed if path is None: path = cpg.request.objectPath or cpg.request.path if path.startswith('/'): path = path[1:] # Remove leading slash if path.endswith('/'): path = path[:-1] # Remove trailing slash if not path: objectPathList = [] else: objectPathList = path.split('/') objectPathList = ['root'] + objectPathList + ['index'] # Try successive objects... (and also keep the remaining object list) objCache = {} isFirst = True isSecond = False isDefault = False foundIt = False virtualPathList = [] while objectPathList: if isFirst or isSecond: # Only try this for a.b.index() or a.b() candidate = getObjFromPath(objectPathList, objCache) if callable(candidate) and getattr(candidate, 'exposed', False): foundIt = True break # Couldn't find the object: pop one from the list and try "default" lastObj = objectPathList.pop() if (not isFirst) or (not path): virtualPathList.insert(0, lastObj) objectPathList.append('default') candidate = getObjFromPath(objectPathList, objCache) if callable(candidate) and getattr(candidate, 'exposed', False): foundIt = True isDefault = True break objectPathList.pop() # Remove "default" if isSecond: isSecond = False if isFirst: isFirst = False isSecond = True # Check results of traversal if not foundIt: raise cperror.NotFound # We didn't find anything if isFirst: # We found the extra ".index" # Check if the original path had a trailing slash (otherwise, do # a redirect) if cpg.request.path[-1] != '/': newUrl = cpg.request.path + '/' if cpg.request.queryString: newUrl += cpg.request.queryString raise IndexRedirect(newUrl) return candidate, objectPathList, virtualPathList