def main(self,environ,start_response): '''dispatch request descibed by WSGI environ - satisfies request by corresponding names function in self.app - fallback is static file from self.dir (WSGI application function)''' try: if len(environ['PATH_INFO'])>1: assert environ['PATH_INFO'].startswith('/') #wsgi always does path=environ['PATH_INFO'][1:] else: path='index.html' pass return \ self.dispatchToFunction(path,environ,start_response) or \ self.dispatchToFile(path,start_response) or \ self.notFound(path,start_response) except Forbidden as e: self.log('INFO: '+str( inContext(l1(Dispatcher.main.__doc__).format(**vars())))) return self.forbidden(start_response) except ClientError as e: return self.clientError(start_response,e) except Exception as e: self.logError('ERROR: '+str( inContext(l1(Dispatcher.main.__doc__).format(**vars())))) return self.serverError(start_response) pass
def validateSchemaElement(x): 'verify that %(x)r is a valid json schema element' try: if x is None: return if x in [int,str,float,bool]: return if type(x) in [int,str]: return #literal if type(x) is dict: if len(x) == 1 and list(x.keys())[0] in (int,str): validateSchemaElement(list(x.values())[0]) return for name,y in x.items(): try: assert type(name) is str, type(name) validateSchemaElement(y) except: raise inContext('validate dict schema item %(name)r'%vars()) pass return if type(x) is list: if len(x) == 0: raise Exception( 'list schema must contain at least one element'%vars()) for xx in x: validateSchemaElement(xx) return if type(x) is tuple: for i,y in enumerate(x): try: validateSchemaElement(y) except: raise inContext('validate tuple schema item %(i)r'%vars()) pass return if isinstance(x,OneOf): for c in x.choices: validateSchemaElement(c) pass return if type(x) is bool: #fixed literal value True or False return if isinstance(x,Schema): return t=type(x) if t is object: t=x.__class__ raise Exception('jsonschema element may not be a %(t)s, it must be a list, a dictionary or int, str, float, bool, tuple or None'%vars()) except: raise inContext(l1(validateSchemaElement.__doc__)%vars()) pass
def __init__(self, server, staff_password): "login in to %(server)s/staff using password %(staff_password)s" try: self.c = httplib.HTTPConnection(server) self.c.set_debuglevel(1) self.c.request("GET", "/staff_login.html") r = self.c.getresponse() page = pq.parse(r.read()) assert len(page.find(tagName("input")).filter(attrEquals("name", "password"))), ( r, r.getheaders(), unicode(page), ) params = urllib.urlencode({"password": staff_password}) self.c.request("POST", "/staff_login.html", params) r = self.c.getresponse() assert r.status == 302, (r.status, r.read()) content = r.read() self.session = getSession(r.getheader("Set-Cookie")) assert not self.session is None, self.getheaders() self.headers = {"Cookie": "kc-session=%s" % self.session} assert r.getheader("location") == "http://" + server + "/events.html", repr((r, r.getheaders(), content)) except: raise inContext(l1(Staff.__init__.__doc__) % vars()) pass
def validateCookieName(name): '''validate RFC6265 cookie name {name!r}''' try: return rfc2616.validateToken(name) except: raise inContext(l1(validateCookieName.__doc__).format(**vars())) pass
def makeParams(remote_addr,method,headers,params,url,cookies,f): 'make dictionary of params for calling function %(f)s, getting them from remote_addr, method, headers, query params, post body, url, cookies''' try: json_params=fromJson(params.get('json_params','{}')) param_names=f.__code__.co_varnames[0:f.__code__.co_argcount] func_defaults=f.__defaults__ or [] no_default_params=len(param_names)-len(func_defaults) param_defaults=dict([ (param_name,default_value) for param_name,default_value in zip(param_names[no_default_params:],func_defaults)]) #request.__dict__ does not give us anything useful request_attrs={ 'params':params, 'cookies':cookies, 'headers':headers, 'method':method, 'url':url, 'remote_addr':remote_addr, } result=dict([ (_,getParam(_, json_params, params, request_attrs, param_defaults)) for _ in param_names ]) return result except: raise inContext(l1(makeParams.__doc__)%vars()) pass
def validateCookiePath(v): '''validate RFC6265 cookie Path value {v!r}''' try: for i,c in enumerate(v): try: if c in rfc2616.CTLs: raise Exception('{c!r} is a RFC2616 control character'.format(**vars())) if c==';': raise Exception('{c!r} is a semicolon'.format(**vars())) except: rest=v[i:] raise inContext('validate at ...{rest!r}'.format(**vars())) pass return v except: raise inContext(l1(validateCookiePath.__doc__).format(**vars())) pass
def validateSchemaElement(x): 'verify that %(x)r is a valid json schema element' try: if x is None: return if x in [IntType,StringType,FloatType,BooleanType]: return if type(x) in [IntType,StringType,UnicodeType]: return #literal if type(x) is DictType: if len(x) == 1 and x.keys()[0] in (IntType,StringType): validateSchemaElement(x.values()[0]) return for name,y in x.items(): try: assert type(name) is StringType, type(name) validateSchemaElement(y) except: raise inContext('validate dict schema item %(name)r'%vars()) pass return if type(x) is ListType: if not len(x) == 1: i=len(x) raise Xn('list schema must contain exactly one element, not %(i)s'%vars()) validateSchemaElement(x[0]) return if type(x) is TupleType: for i,y in enumerate(x): try: validateSchemaElement(y) except: raise inContext('validate tuple schema item %(i)r'%vars()) pass return if isinstance(x,OneOf): for c in x.choices: validateSchemaElement(c) pass return if isinstance(x,Schema): return t=type(x) if t is ObjectType: t=x.__class__ raise Xn('jsonschema element may not be a %(t)s, it must be a list, a dictionary or types.IntType, types.StringType, types.UnicodeType, types.FloatType, types.BooleanType, types.TupleType or None'%vars()) except: raise inContext(l1(validateSchemaElement.__doc__)%vars()) pass
def getHTTPHeadersFromWSGIenviron(environ): '''get HTTP_ headers from WSGI environ {environ!r} as dictionary''' '''like { headerName : str }, e.g.:''' try: return dict([(name,value) for name,value in environ.items() if name.startswith('HTTP_')]) except: raise inContext(l1(getHTTPHeadersFromWSGIenviron.__doc__).format(**vars())) pass
def parseDisposition(dispositionValue): '''Parse Content-Disposition value %(dispositionValue)s''' try: assert dispositionValue.strip().startswith('form-data;'), dispositionValue v=dispositionValue.split(';')[1:] v=[_.strip().split('=',1) for _ in v] v=[(_[0],parseQuoted(_[1])) for _ in v] return dict(v) except: raise inContext(parseDisposition.__doc__ % vars()) pass
def parseHeaders(mimePart): try: h,rest=mimePart.split('\r\n\r\n', 1) headers=h.split('\r\n',1) headers=[_.split(':',1) for _ in headers] headers=[(_[0], _[1].strip()) for _ in headers] return dict(headers), rest except: mps=mimePart[0:256] raise inContext('parse headers from mime part %(mps)s...'%vars()) pass
def verifyUploadedFile(self, uploadedFileId, mime_type, content): "verify that %(self)s uploaded_file %(uploadedFileId)s has type %(mime_type)s and content %(content)r" scope = Scope(l1(Staff.verifyUploadedFile.__doc__) % vars()) try: r = staff.get("/uploaded_file", {"id": "%(uploadedFileId)s" % vars()}) assert_equal(r.status, 200) assert_equal(r.getheader("Content-Type"), "text/plain") assert_equal(r.read(), content) except: raise inContext(scope.description) pass
def postJSON(self, url, params, headers={}): 'post %(params)r as json encoded "params" to %(url)s' "return json-decode response" scope = Scope(l1(Staff.postJSON.__doc__) % vars()) try: r = self.post(url, {"params": toJson(params)}, headers) assert_equal(r.status, 200) return scope.result(fromJson(r.read())) except: raise inContext(scope.description) pass
def post(self, url, params={}, headers={}): "send HTTP POST to %(self)r page %(url)r with params %(params)r and headers %(headers)r" scope = Scope(l1(Staff.post.__doc__) % vars()) try: assert url.startswith("/"), url headers["Cookie"] = "kc-session=%s" % self.session self.c.request("POST", url, urllib.urlencode(params), headers) return self.c.getresponse() except: raise inContext(scope.description) pass
def unicodeOfElements(l): result=[] for i,c in enumerate(l): try: result.append(unicode(c)) except: t=type(c).__name__ if t=='instance': t=c.__class__.__name__ raise inContext('get unicode representation of child %(i)r, %(c)r, which is of type %(t)s'%vars()) pass return result
def validateToken(name): '''validate RFC2616 token {name!r}''' try: if not len(name): raise Exception('{name!r} is empty'.format(**vars())) for i,c in enumerate(name): o=ord(c) try: if ord(c)<0 or ord(c)>127: raise Exception('{o} not 0..127'.format(**vars())) if c in CTLs: raise Exception('{c!r} is a control character'.format(**vars())) if c in separators: raise Exception('{c!r} is a separator'.format(**vars())) except: rest=name[i:] raise inContext('validate first char' if i==0 else 'validate char at ...{rest!r}'.format(**vars())) pass return name except: raise inContext(l1(validateToken.__doc__).format(**vars())) pass
def logout(self, headers={}): "logout %(self)s" scope = Scope(l1(Staff.logout.__doc__) % vars()) try: headers["Cookie"] = "kc-session=%s" % self.session self.c.request("GET", "/logout", "", headers) r = self.c.getresponse() content = fromJson(r.read()) assert content["result"] == "OK", content except: raise inContext(scope.description) pass
def __unicode__(self): try: encodedAttrs=[u'%s="%s"' % (_[0],encodeEntities(_[1])) for _ in self.attrs.items()] encodedAttrs.sort() start=u' '.join([self.tagName]+encodedAttrs) content=u''.join(unicodeOfElements(self.children)) end=self.end return u'''<%(start)s>%(content)s%(end)s'''%vars() except: raise inContext('get unicode representation of %(self)r'%vars()) pass
def verifyFileRefcount(self, uploadedFileId, expectedCount): "verify that %(self)s has %(expectedCount)s as uploaded_file %(uploadedFileId)s refcount" scope = Scope(l1(Staff.verifyFileRefcount.__doc__) % vars()) try: r = self.get("/uploaded_file_refcount", {"id": uploadedFileId}) assert r.status == 200, r.status content = fromJson(r.read()) assert "refcount" in content, content assert content["refcount"] == expectedCount, (content, expectedCount) except: raise inContext(scope.description) pass
def getCookiesFromWSGIenviron(environ): '''get cookies from WSGI environ {environ} as dictionary''' '''like { cookieName : str or [ str ] }, e.g.:''' ''' { "emailAddress" : "*****@*****.**", ''' ''' "preferredSizes" : [ "L", "XL" ] ''' try: result={} e=environ.get('HTTP_COOKIE',None) if e: result=dict([_.strip().split('=') for _ in e.split(';')]) return result except: raise inContext(l1(getCookiesFromWSGIenviron.__doc__).format(**vars())) pass
def dispatchToFunction(self,name,environ,start_response): 'dispatch {name} to function in {self.app} a name->function dictionary' 'e.g. app can be a module' try: fname=name.replace('-','_').replace(' ','_').replace('.','_') f=self.app.__dict__.get(fname,None) if not f: return None if not callable(f): raise Forbidden('%(fname)s is not callable ie not a function like "def %(fname)s():"'%vars()) if not f in public_functions and not f in restricted_functions: raise Forbidden('%(fname)s is not annotated with @wal.public (or @public) or @wal.restricted (or @restricted)'%vars()) result=None url=(environ.get('wsgi.url_scheme')+'://'+ environ.get('HTTP_HOST', environ.get('SERVER_NAME')+':'+ environ.get('SERVER_PORT'))+ environ.get('SCRIPT_NAME','')+ environ.get('PATH_INFO','')) cookies=getCookiesFromWSGIenviron(environ) if f in restricted_functions: result=restricted_functions[f](url,cookies) pass if not result: params=getVariablesFromWSGIenviron(environ) headers=getHTTPHeadersFromWSGIenviron(environ) method=environ['REQUEST_METHOD'], #e.g. 'GET', 'POST' remote_addr=environ['REMOTE_ADDR'] result=f(**makeParams(remote_addr, method, headers, params, url, cookies, f)) pass self.log('INFO: {name} used app.{fname}()'.format(**vars())) result=result if isinstance(result,Response) \ else promoteContent(result) headers=result.cookieHeaders() if result.location: headers.append( ('Location',result.location) ) start_response('301 Moved Permanently',headers) return [''.encode('utf-8')] headers.extend(result.headers) start_response('200 OK',headers) return [result.content] except: raise inContext(l1(Dispatcher.dispatchToFunction.__doc__).format( **vars())) pass
def cookieHeaders(self): '''return headers for {self.cookies}''' try: result=[] # ('Set-Cookie', xxxx } for name,va in self.cookies.items(): value=va[0]+''.join(['; {an}={av}'.format(**vars()) for an,av in va[1].items()]) result.append( ('Set-Cookie', '''{name}={value[0]}'''.format(**vars())) ) pass return result except: raise inContext( l1(Response.cookieHeaders.__doc__).format(**vars())) pass
def uploadFile(self, fileName, mime_type, content, headers={}): "post %(fileName)s of type %(mime_type)s and given content to /uploaded_file, returning resulting id" scope = Scope(l1(Staff.__doc__) % vars()) try: r = staff.postFile("/uploaded_file", "filename", fileName, mime_type, content, headers) result = fromJson(r.read()) assert "result" in result, toJson(result).encode("utf8") assert "id" in result["result"] xxx = result["result"]["id"] assert type(xxx) is types.IntType, xxx assert xxx != 0, xxx return xxx except: raise inContext(scope.description) pass
def postMulipartFormData(self, url, parts, headers={}): "post multipart/form-data to %(self)s url %(url)s" scope = Scope(l1(Staff.postMulipartFormData.__doc__) % vars()) try: boundary = "------------%08x%08x" % (random.randint(0, 0xFFFFFFFF), random.randint(0, 0xFFFFFFFF)) while len([_ for _ in parts if boundary in _]): boundary = "------------%08x%08x" % (random.randint(0, 0xFFFFFFFF), random.randint(0, 0xFFFFFFFF)) pass body = "\r\n".join(["--" + boundary + "\r\n" + part for part in parts] + ["--" + boundary + "--\r\n"]) h = dict(headers.items()) h["Content-Type"] = "multipart/form-data; boundary=%(boundary)s" % vars() h["Cookie"] = "kc-session=%s" % self.session self.c.request("POST", url, body, h) return self.c.getresponse() except: raise inContext(scope.description) pass
def deletePreFairHelper(stall_name,helper_name,email): 'delete pre-fair helper %(helper_name)s of email %(email)s from stall %(stall_name)s' scope=Scope(l1(deletePreFairHelper.__doc__)%vars()) try: entry_query = StallPreFairHelper.query( ancestor=stall_key(stall_name)) entries = entry_query.fetch(1000) entries=[_ for _ in entries \ if _.name==helper_name and _.email==email] if len(entries)==0: return scope.result('no such helper') entry=entries[0] entry.key.delete() return scope.result('entry deleted'%vars()) except: raise inContext(scope.description) pass
def deleteHelper(stall_name,hour,helper_number): 'delete helper with index %(helper_number)s from hour %(hour)s from stall %(stall_name)s' scope=Scope(l1(deleteHelper.__doc__)%vars()) try: entry_query = OneHourOfHelp.query(OneHourOfHelp.hour == hour, ancestor=stall_key(stall_name)) entries = entry_query.fetch(1) if len(entries)==0: return scope.result('no such hour of help') entry=entries[0] del entry.names[helper_number] del entry.emails[helper_number] del entry.phones[helper_number] entry.put() return scope.result('entry now %(entry)s'%vars()) except: raise inContext(scope.description) pass
def __radd__(self,lhs): 'add Response {self} to {lhs}' try: assert isinstance(lhs,Response) rhs=self assert lhs.content=='' or rhs.content=='', 'content specified more than once' result=Response() result.content=rhs.content or lhs.content result.contentType=rhs.contentType or lhs.contentType result.contentEncoding=rhs.contentEncoding or lhs.contentEncoding result.cookies=lhs.cookies.copy() result.cookies.update(rhs.cookies) result.headers=lhs.headers+rhs.headers result.location=rhs.location or lhs.location return result except: raise inContext(l1(Response.__radd__.__doc__).format(**vars())) pass
def postFile(self, url, fieldName, fileName, mime_type, data, headers={}): "post file %(fileName)s of type %(mime_type)s as field %(fieldName)s to %(self)s url %(url)s" scope = Scope(l1(Staff.postFile.__doc__) % vars()) try: lines = [ 'Content-Disposition: form-data; name="%(fieldName)s"; filename="%(fileName)s"' % { "fieldName": fieldName.replace(r'"', r"\"").encode("ascii"), "fileName": fileName.replace(r'"', r"\"").encode("ascii"), }, "Content-Type: %(mime_type)s" % vars(), "", data, ] part = "\r\n".join(_ for _ in lines) return self.postMulipartFormData(url, [part], headers) except: raise inContext(scope.description) pass
def promoteContent(content): '''promote content object of type {contentType} to a valid Response''' contentType=type(content) try: if isinstance(content,pq.Selection): return Response(content.utf8(), 'text/html; charset=UTF-8') if isinstance(content,dict) and ( 'result' in content or 'error' in content): return Response(toJson(content).encode('utf-8'), 'text/json; charset=UTF-8') if content is None or \ isinstance(content,dict) or isinstance(content,list) or \ isinstance(content,int) or isinstance(content,float): return Response(toJson({'result':content}).encode('utf-8'), 'text/json; charset=UTF-8') raise Exception('do not know what HTTP HTTP CONTENT-TYPE to use for a {contentType} object - return an explicit wal.Response to set CONTENT-TYPE'.format(**vars())) except: raise inContext(l1(promoteContent.__doc__).format(**vars())) pass
def validateCookieValue(v): '''validate RFC6265 cookie-value {v!r}''' try: if v.startswith('"') and not v.endswith('"'): raise Exception('{v!r} startswith double quote but does not end with double quote'.format(**vars())) x=v[1:-1] if v.startswith('"') else v for i,c in enumerate(x): o=ord(c) if o<0x21 or \ (o>0x21 and o<0x23) or \ (o>0x2b and o<0x2d) or \ (o>0x3a and o<0x3c) or \ (o>0x4b and o<0x5d) or \ (o>0x7e): rest=result[i:] raise Exception('invalid character at {rest!r}'.format(**vars())) pass return v except: raise inContext(l1(validateCookieValue.__doc__).format(**vars())) pass
def getParam(param_name, json_params, request_params, request_attrs, param_defaults): try: try: if param_name in json_params: return json_params.get(param_name) if param_name in request_params: return request_params.get(param_name) if param_name in request_attrs: return request_attrs.get(param_name) return param_defaults[param_name] except KeyError as e: raise Exception('unknown parameter') pass except: json_param_names=json_params.keys() request_param_names=request_params.keys() request_attr_names=request_attrs.keys() param_default_names=param_defaults.keys() raise inContext('get value of %(param_name)s from params supplied as json-encoded "json_params" HTTP param (%(json_param_names)s), HTTP params (%(request_param_names)s), webapp2 request attributes (%(request_attr_names)s) or function paramter defaults (%(param_default_names)s)'%vars()) pass