diff --git a/README b/README new file mode 100644 index 0000000..e20498e --- /dev/null +++ b/README @@ -0,0 +1 @@ +I don't know yet if this is even possible. but lets say yes. diff --git a/Test.class b/Test.class new file mode 100644 index 0000000..c8b4bfa Binary files /dev/null and b/Test.class differ diff --git a/app.py b/app.py new file mode 100644 index 0000000..719267b --- /dev/null +++ b/app.py @@ -0,0 +1,39 @@ +#! /usr/bin/python + +# -*- Mode: Python -*- +# -*- coding: UTF-8 -*- +# Copyright (C) 2009 by Artur Ventura +# +# File: app.py +# Time-stamp: Sun Aug 9 16:30:18 2009 +# +# Author: Artur Ventura +# + + +import web +import commands + +urls = ( + '/(.*)', 'index', + +) +app = web.application(urls, globals()) + +class index: + def GET(self,filename): + if filename.endswith("favicon.ico"): + web.webapi.notfound() + return "" + if filename == "": + return file("index.html").read() + try: + return file(filename).read() + except: + web.webapi.notfound() + return "" + + + +if __name__ == "__main__": + app.run() diff --git a/constantPool.js b/constantPool.js new file mode 100644 index 0000000..1097067 --- /dev/null +++ b/constantPool.js @@ -0,0 +1,90 @@ +var CONSTANT_Class = 7; +var CONSTANT_Fieldref = 9; +var CONSTANT_Methodref = 10; +var CONSTANT_InterfaceMethodref = 11; +var CONSTANT_String = 8; +var CONSTANT_Integer = 3; +var CONSTANT_Float = 4; +var CONSTANT_Long = 5; +var CONSTANT_Double = 6; +var CONSTANT_NameAndType = 12; +var CONSTANT_Utf8 = 1; + +constUtf8 = function(){ + this.str = null; + this.read = ( dStream ) { + StringBuffer strBuf; + int len, charCnt; + byte one_byte; + char one_char; + + one_char = '\u0000'; + len = readU2( dStream ); + strBuf = new StringBuffer(); + charCnt = 0; + while (charCnt < len) { + one_byte = (byte)readU1( dStream ); + charCnt++; + if ((one_byte >> 7) == 1) { + short tmp; + + // its a multi-byte character + tmp = (short)(one_byte & 0x3f); // Bits 5..0 (six bits) + // read the next byte + one_byte = (byte)readU1( dStream ); + charCnt++; + tmp = (short)(tmp | ((one_byte & 0x3f) << 6)); + if ((one_byte >> 6) == 0x2) { + // We have 12 bits so far, get bits 15..12 + one_byte = (byte)readU1( dStream ); + charCnt++; + one_byte = (byte)(one_byte & 0xf); + tmp = (short)(tmp | (one_byte << 12)); + } + one_char = (char)tmp; + } + else { + one_char = (char)one_byte; + } + strBuf.append(one_char); + } // while + this.str = strBuf.toString(); + } // read +}; + +allocConstEntry = function(tag){ + var obj = null; + + switch ( tag ) { + case CONSTANT_Utf8: + obj = new constUtf8(); + break; + case CONSTANT_Integer: + obj = new constInt(); + break; + case CONSTANT_Float: + obj = new constFloat(); + break; + case CONSTANT_Long: + obj = new constLong(); + break; + case CONSTANT_Double: + obj = new constDouble(); + break; + case CONSTANT_Class: + case CONSTANT_String: + obj = new constClass_or_String(); + break; + case CONSTANT_Fieldref: + case CONSTANT_Methodref: + case CONSTANT_InterfaceMethodref: + obj = new constRef(); + break; + case CONSTANT_NameAndType: + obj = new constName_and_Type_info(); + break; + default: + System.out.println("allocConstEntry: bad tag value = " + tag); + break; + } // switch +} \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..26bb739 --- /dev/null +++ b/index.html @@ -0,0 +1,33 @@ + + + JS JVM + + + + + + + +

JS JVM

+ Open JavaScript Debug Window to read the debug; + + diff --git a/linearDatastream.js b/linearDatastream.js new file mode 100644 index 0000000..a58563a --- /dev/null +++ b/linearDatastream.js @@ -0,0 +1,58 @@ +DataStream = function(data){ + this.i = 0; + this.getU = function(size){ + switch(size){ + case 1: + this.constantPoolCount = new DataView(data,this.i,1).getUint8(0); + this.i += 1; + break; + case 2: + this.constantPoolCount = new DataView(data,this.i,2).getUint16(0); + this.i += 2; + break; + case 4: + this.constantPoolCount = new DataView(data,this.i,4).getUint32(0); + this.i += 4; + break; + case 8: + this.constantPoolCount = new DataView(data,this.i,8).getUint64(0); + this.i += 8; + break; + } + }; + + this.get = function(size){ + switch(size){ + case 1: + this.constantPoolCount = new DataView(data,this.i,1).getInt8(0); + this.i += 1; + break; + case 2: + this.constantPoolCount = new DataView(data,this.i,2).getInt16(0); + this.i += 2; + break; + case 4: + this.constantPoolCount = new DataView(data,this.i,4).getInt32(0); + this.i += 4; + break; + case 8: + this.constantPoolCount = new DataView(data,this.i,8).getInt64(0); + this.i += 8; + break; + } + } + + this.getUint8 = function () { return this.getU(1) } + this.getU1 = this.getUint8; + this.getUint16 = function () { return this.getU(2) } + this.getU2 = this.getUint16; + this.getUint32 = function () { return this.getU(4) } + this.getU4 = this.getUint32; + this.getUint64 = function () { return this.getU(8) } + this.getU8 = this.getUint64; + + this.getInt8 = function () { return this.get(1) } + this.getInt16 = function () { return this.get(2) } + this.getInt32 = function () { return this.get(4) } + this.getInt64 = function () { return this.get(8) } +} \ No newline at end of file diff --git a/main.js b/main.js new file mode 100644 index 0000000..3cee0b7 --- /dev/null +++ b/main.js @@ -0,0 +1,106 @@ +include("linearDataStream.js"); +include("constantPool.js"); + +function slurpFile (filename, fa) { + var xmlHttpRequest, response, result ; + // ie support if (typeof ActiveXObject == "function") return this.load_binary_ie9(filename, fa); + xmlHttpRequest = new XMLHttpRequest(); + xmlHttpRequest.open('GET', filename, false); + if ('mozResponseType' in xmlHttpRequest) { + xmlHttpRequest.mozResponseType = 'arraybuffer'; + } else if ('responseType' in xmlHttpRequest) { + xmlHttpRequest.responseType = 'arraybuffer'; + } else { + xmlHttpRequest.overrideMimeType('text/plain; charset=x-user-defined'); + } + xmlHttpRequest.send(null); + if (xmlHttpRequest.status != 200 && xmlHttpRequest.status != 0) { + throw "Error while loading " + filename; + } + bf = true; + if ('mozResponse' in xmlHttpRequest) { + response = xmlHttpRequest.mozResponse; + } else if (xmlHttpRequest.mozResponseArrayBuffer) { + response = xmlHttpRequest.mozResponseArrayBuffer; + } else if ('responseType' in xmlHttpRequest) { + response = xmlHttpRequest.response; + } else { + response = xmlHttpRequest.responseText; + bf = false; + } + if (bf) { + result = [response.byteLength, response]; + } else { + throw "No typed arrays"; + } + return result; +}; +log = function (msg){ + if (console){ + console.log(msg); + } +} + +ConstantPoolStruct = function (tag,info){ + log("0x" + tag.toString(16)) + switch(tag){ + case 7: + log("Class: "+ info.toString(16)); + break; + case 9: + log("FieldRef: "+ info.toString(16)); + break; + case 10: + log("MethodRef: "+ info.toString(16)); + break; + case 11: + log("InterfaceMethodRef: "+ info.toString(16)); + break; + case 8: + log("String: " + info.toString(16)); + break; + case 3: + log("Integer: " + info.toString(16)); + break; + case 4: + log("Float: " + info.toString(16)); + break; + case 5: + log("Long: " + info.toString(16)); + break; + case 6: + log("Long: " + info.toString(16)); + break; + case 12: + log("NameAndType: " + info.toString(16)); + break; + case 1: + log("Utf8: " + info.toString(16)); + break; + default: + throw "Unkown tag number 0x" + tag.toString(16); + } + this.tag = tag; + this.info = info; +}; + +ClassDefinition = function (file){ + var dataStream = new DataStream(slurpFile(file)[1]); + this.magic = dataStream.getU4(); + this.magic == 0xCAFEBABE || alert("Invalid Class Magic"); + this.minorVersion = dataStream.getU2(); + this.majorVersion = dataStream.getU2(); + this.constantPoolCount = dataStream.getU2(); + this.constantPool = []; + for(var i = 1; i <= this.constantPoolCount; i++){ + var tag = dataStream.getU1(); + var alloc = allocConstEntry(tag); + alloc.read(dataStream); + var info = new DataView(x, 10 + (i-1) * (1 + 1) + 1,1).getUint8(0); + this.constantPool[(i-1)] = new ConstantPoolStruct(tag,info); + } +} + +function main (args){ + var classDef = new ClassDefinition("Test.class"); +} \ No newline at end of file diff --git a/preload.js b/preload.js new file mode 100644 index 0000000..c7f1281 --- /dev/null +++ b/preload.js @@ -0,0 +1,6 @@ +function include(filename) +{ + document.write(' + + + +$def dicttable (d, kls='req', id=None): + $ items = d and d.items() or [] + $items.sort() + $:dicttable_items(items, kls, id) + +$def dicttable_items(items, kls='req', id=None): + $if items: + + + $for k, v in items: + + +
VariableValue
$k
$prettify(v)
+ $else: +

No data.

+ +
+

$exception_type at $ctx.path

+

$exception_value

+ + + + + + +
Python$frames[0].filename in $frames[0].function, line $frames[0].lineno
Web$ctx.method $ctx.home$ctx.path
+
+
+

Traceback (innermost first)

+ +
+ +
+$if ctx.output or ctx.headers: +

Response so far

+

HEADERS

+ $:dicttable_items(ctx.headers) + +

BODY

+

+ $ctx.output +

+ +

Request information

+ +

INPUT

+$:dicttable(web.input()) + + +$:dicttable(web.cookies()) + +

META

+$ newctx = [(k, v) for (k, v) in ctx.iteritems() if not k.startswith('_') and not isinstance(v, dict)] +$:dicttable(dict(newctx)) + +

ENVIRONMENT

+$:dicttable(ctx.env) +
+ +
+

+ You're seeing this error because you have web.config.debug + set to True. Set that to False if you don't to see this. +

+
+ + + +""" + +djangoerror_r = None + +def djangoerror(): + def _get_lines_from_file(filename, lineno, context_lines): + """ + Returns context_lines before and after lineno from file. + Returns (pre_context_lineno, pre_context, context_line, post_context). + """ + try: + source = open(filename).readlines() + lower_bound = max(0, lineno - context_lines) + upper_bound = lineno + context_lines + + pre_context = \ + [line.strip('\n') for line in source[lower_bound:lineno]] + context_line = source[lineno].strip('\n') + post_context = \ + [line.strip('\n') for line in source[lineno + 1:upper_bound]] + + return lower_bound, pre_context, context_line, post_context + except (OSError, IOError): + return None, [], None, [] + + exception_type, exception_value, tback = sys.exc_info() + frames = [] + while tback is not None: + filename = tback.tb_frame.f_code.co_filename + function = tback.tb_frame.f_code.co_name + lineno = tback.tb_lineno - 1 + pre_context_lineno, pre_context, context_line, post_context = \ + _get_lines_from_file(filename, lineno, 7) + frames.append(web.storage({ + 'tback': tback, + 'filename': filename, + 'function': function, + 'lineno': lineno, + 'vars': tback.tb_frame.f_locals, + 'id': id(tback), + 'pre_context': pre_context, + 'context_line': context_line, + 'post_context': post_context, + 'pre_context_lineno': pre_context_lineno, + })) + tback = tback.tb_next + frames.reverse() + urljoin = urlparse.urljoin + def prettify(x): + try: + out = pprint.pformat(x) + except Exception, e: + out = '[could not display: <' + e.__class__.__name__ + \ + ': '+str(e)+'>]' + return out + + global djangoerror_r + if djangoerror_r is None: + djangoerror_r = Template(djangoerror_t, filename=__file__, filter=websafe) + + t = djangoerror_r + globals = {'ctx': web.ctx, 'web':web, 'dict':dict, 'str':str, 'prettify': prettify} + t.t.func_globals.update(globals) + return t(exception_type, exception_value, frames) + +def debugerror(): + """ + A replacement for `internalerror` that presents a nice page with lots + of debug information for the programmer. + + (Based on the beautiful 500 page from [Django](http://djangoproject.com/), + designed by [Wilson Miner](http://wilsonminer.com/).) + """ + return web._InternalError(djangoerror()) + +def emailerrors(email_address, olderror): + """ + Wraps the old `internalerror` handler (pass as `olderror`) to + additionally email all errors to `email_address`, to aid in + debugging production websites. + + Emails contain a normal text traceback as well as an + attachment containing the nice `debugerror` page. + """ + def emailerrors_internal(): + error = olderror() + tb = sys.exc_info() + error_name = tb[0] + error_value = tb[1] + tb_txt = ''.join(traceback.format_exception(*tb)) + path = web.ctx.path + request = web.ctx.method+' '+web.ctx.home+web.ctx.fullpath + eaddr = email_address + text = ("""\ +------here---- +Content-Type: text/plain +Content-Disposition: inline + +%(request)s + +%(tb_txt)s + +------here---- +Content-Type: text/html; name="bug.html" +Content-Disposition: attachment; filename="bug.html" + +""" % locals()) + str(djangoerror()) + sendmail( + "your buggy site <%s>" % eaddr, + "the bugfixer <%s>" % eaddr, + "bug: %(error_name)s: %(error_value)s (%(path)s)" % locals(), + text, + headers={'Content-Type': 'multipart/mixed; boundary="----here----"'}) + return error + + return emailerrors_internal + +if __name__ == "__main__": + urls = ( + '/', 'index' + ) + from application import application + app = application(urls, globals()) + app.internalerror = debugerror + + class index: + def GET(self): + thisdoesnotexist + + app.run() diff --git a/web/form.py b/web/form.py new file mode 100644 index 0000000..76f5b0b --- /dev/null +++ b/web/form.py @@ -0,0 +1,264 @@ +""" +HTML forms +(part of web.py) +""" + +import copy, re +import webapi as web +import utils, net + +def attrget(obj, attr, value=None): + if hasattr(obj, 'has_key') and obj.has_key(attr): return obj[attr] + if hasattr(obj, attr): return getattr(obj, attr) + return value + +class Form: + r""" + HTML form. + + >>> f = Form(Textbox("x")) + >>> f.render() + '\n \n
' + """ + def __init__(self, *inputs, **kw): + self.inputs = inputs + self.valid = True + self.note = None + self.validators = kw.pop('validators', []) + + def __call__(self, x=None): + o = copy.deepcopy(self) + if x: o.validates(x) + return o + + def render(self): + out = '' + out += self.rendernote(self.note) + out += '\n' + for i in self.inputs: + out += ' ' % (i.id, net.websafe(i.description)) + out += "\n" + out += "
"+i.pre+i.render()+i.post+"
" + return out + + def render_css(self): + out = [] + out.append(self.rendernote(self.note)) + for i in self.inputs: + out.append('' % (i.id, net.websafe(i.description))) + out.append(i.pre) + out.append(i.render()) + out.append(i.post) + out.append('\n') + return ''.join(out) + + def rendernote(self, note): + if note: return '%s' % net.websafe(note) + else: return "" + + def validates(self, source=None, _validate=True, **kw): + source = source or kw or web.input() + out = True + for i in self.inputs: + v = attrget(source, i.name) + if _validate: + out = i.validate(v) and out + else: + i.value = v + if _validate: + out = out and self._validate(source) + self.valid = out + return out + + def _validate(self, value): + self.value = value + for v in self.validators: + if not v.valid(value): + self.note = v.msg + return False + return True + + def fill(self, source=None, **kw): + return self.validates(source, _validate=False, **kw) + + def __getitem__(self, i): + for x in self.inputs: + if x.name == i: return x + raise KeyError, i + + def __getattr__(self, name): + # don't interfere with deepcopy + inputs = self.__dict__.get('inputs') or [] + for x in inputs: + if x.name == name: return x + raise AttributeError, name + + def get(self, i, default=None): + try: + return self[i] + except KeyError: + return default + + def _get_d(self): #@@ should really be form.attr, no? + return utils.storage([(i.name, i.value) for i in self.inputs]) + d = property(_get_d) + +class Input(object): + def __init__(self, name, *validators, **attrs): + self.description = attrs.pop('description', name) + self.value = attrs.pop('value', None) + self.pre = attrs.pop('pre', "") + self.post = attrs.pop('post', "") + self.id = attrs.setdefault('id', name) + if 'class_' in attrs: + attrs['class'] = attrs['class_'] + del attrs['class_'] + self.name, self.validators, self.attrs, self.note = name, validators, attrs, None + + def validate(self, value): + self.value = value + for v in self.validators: + if not v.valid(value): + self.note = v.msg + return False + return True + + def render(self): raise NotImplementedError + + def rendernote(self, note): + if note: return '%s' % net.websafe(note) + else: return "" + + def addatts(self): + str = "" + for (n, v) in self.attrs.items(): + str += ' %s="%s"' % (n, net.websafe(v)) + return str + +#@@ quoting + +class Textbox(Input): + def render(self, shownote=True): + x = '>> urlencode({'text':'foo bar'}) + 'text=foo+bar' + """ + query = dict([(k, utils.utf8(v)) for k, v in query.items()]) + return urllib.urlencode(query) + +def changequery(query=None, **kw): + """ + Imagine you're at `/foo?a=1&b=2`. Then `changequery(a=3)` will return + `/foo?a=3&b=2` -- the same URL but with the arguments you requested + changed. + """ + if query is None: + query = web.input(_method='get') + for k, v in kw.iteritems(): + if v is None: + query.pop(k, None) + else: + query[k] = v + out = web.ctx.path + if query: + out += '?' + urlencode(query) + return out + +def url(path=None, **kw): + """ + Makes url by concatinating web.ctx.homepath and path and the + query string created using the arguments. + """ + if path is None: + path = web.ctx.path + if path.startswith("/"): + out = web.ctx.homepath + path + else: + out = path + + if kw: + out += '?' + urlencode(kw) + + return out + +def profiler(app): + """Outputs basic profiling information at the bottom of each response.""" + from utils import profile + def profile_internal(e, o): + out, result = profile(app)(e, o) + return out + ['
' + net.websafe(result) + '
'] + return profile_internal + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/web/httpserver.py b/web/httpserver.py new file mode 100644 index 0000000..317601e --- /dev/null +++ b/web/httpserver.py @@ -0,0 +1,225 @@ +__all__ = ["runsimple"] + +import sys, os +import webapi as web +import net +import utils + +def runbasic(func, server_address=("0.0.0.0", 8080)): + """ + Runs a simple HTTP server hosting WSGI app `func`. The directory `static/` + is hosted statically. + + Based on [WsgiServer][ws] from [Colin Stewart][cs]. + + [ws]: http://www.owlfish.com/software/wsgiutils/documentation/wsgi-server-api.html + [cs]: http://www.owlfish.com/ + """ + # Copyright (c) 2004 Colin Stewart (http://www.owlfish.com/) + # Modified somewhat for simplicity + # Used under the modified BSD license: + # http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5 + + import SimpleHTTPServer, SocketServer, BaseHTTPServer, urlparse + import socket, errno + import traceback + + class WSGIHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): + def run_wsgi_app(self): + protocol, host, path, parameters, query, fragment = \ + urlparse.urlparse('http://dummyhost%s' % self.path) + + # we only use path, query + env = {'wsgi.version': (1, 0) + ,'wsgi.url_scheme': 'http' + ,'wsgi.input': self.rfile + ,'wsgi.errors': sys.stderr + ,'wsgi.multithread': 1 + ,'wsgi.multiprocess': 0 + ,'wsgi.run_once': 0 + ,'REQUEST_METHOD': self.command + ,'REQUEST_URI': self.path + ,'PATH_INFO': path + ,'QUERY_STRING': query + ,'CONTENT_TYPE': self.headers.get('Content-Type', '') + ,'CONTENT_LENGTH': self.headers.get('Content-Length', '') + ,'REMOTE_ADDR': self.client_address[0] + ,'SERVER_NAME': self.server.server_address[0] + ,'SERVER_PORT': str(self.server.server_address[1]) + ,'SERVER_PROTOCOL': self.request_version + } + + for http_header, http_value in self.headers.items(): + env ['HTTP_%s' % http_header.replace('-', '_').upper()] = \ + http_value + + # Setup the state + self.wsgi_sent_headers = 0 + self.wsgi_headers = [] + + try: + # We have there environment, now invoke the application + result = self.server.app(env, self.wsgi_start_response) + try: + try: + for data in result: + if data: + self.wsgi_write_data(data) + finally: + if hasattr(result, 'close'): + result.close() + except socket.error, socket_err: + # Catch common network errors and suppress them + if (socket_err.args[0] in \ + (errno.ECONNABORTED, errno.EPIPE)): + return + except socket.timeout, socket_timeout: + return + except: + print >> web.debug, traceback.format_exc(), + + if (not self.wsgi_sent_headers): + # We must write out something! + self.wsgi_write_data(" ") + return + + do_POST = run_wsgi_app + do_PUT = run_wsgi_app + do_DELETE = run_wsgi_app + + def do_GET(self): + if self.path.startswith('/static/'): + SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self) + else: + self.run_wsgi_app() + + def wsgi_start_response(self, response_status, response_headers, + exc_info=None): + if (self.wsgi_sent_headers): + raise Exception \ + ("Headers already sent and start_response called again!") + # Should really take a copy to avoid changes in the application.... + self.wsgi_headers = (response_status, response_headers) + return self.wsgi_write_data + + def wsgi_write_data(self, data): + if (not self.wsgi_sent_headers): + status, headers = self.wsgi_headers + # Need to send header prior to data + status_code = status[:status.find(' ')] + status_msg = status[status.find(' ') + 1:] + self.send_response(int(status_code), status_msg) + for header, value in headers: + self.send_header(header, value) + self.end_headers() + self.wsgi_sent_headers = 1 + # Send the data + self.wfile.write(data) + + class WSGIServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): + def __init__(self, func, server_address): + BaseHTTPServer.HTTPServer.__init__(self, + server_address, + WSGIHandler) + self.app = func + self.serverShuttingDown = 0 + + print "http://%s:%d/" % server_address + WSGIServer(func, server_address).serve_forever() + +def runsimple(func, server_address=("0.0.0.0", 8080)): + """ + Runs [CherryPy][cp] WSGI server hosting WSGI app `func`. + The directory `static/` is hosted statically. + + [cp]: http://www.cherrypy.org + """ + from wsgiserver import CherryPyWSGIServer + from SimpleHTTPServer import SimpleHTTPRequestHandler + from BaseHTTPServer import BaseHTTPRequestHandler + + class StaticApp(SimpleHTTPRequestHandler): + """WSGI application for serving static files.""" + def __init__(self, environ, start_response): + self.headers = [] + self.environ = environ + self.start_response = start_response + + def send_response(self, status, msg=""): + self.status = str(status) + " " + msg + + def send_header(self, name, value): + self.headers.append((name, value)) + + def end_headers(self): + pass + + def log_message(*a): pass + + def __iter__(self): + environ = self.environ + + self.path = environ.get('PATH_INFO', '') + self.client_address = environ.get('REMOTE_ADDR','-'), \ + environ.get('REMOTE_PORT','-') + self.command = environ.get('REQUEST_METHOD', '-') + + from cStringIO import StringIO + self.wfile = StringIO() # for capturing error + + f = self.send_head() + self.start_response(self.status, self.headers) + + if f: + block_size = 16 * 1024 + while True: + buf = f.read(block_size) + if not buf: + break + yield buf + f.close() + else: + value = self.wfile.getvalue() + yield value + + class WSGIWrapper(BaseHTTPRequestHandler): + """WSGI wrapper for logging the status and serving static files.""" + def __init__(self, app): + self.app = app + self.format = '%s - - [%s] "%s %s %s" - %s' + + def __call__(self, environ, start_response): + def xstart_response(status, response_headers, *args): + write = start_response(status, response_headers, *args) + self.log(status, environ) + return write + + path = environ.get('PATH_INFO', '') + if path.startswith('/static/'): + return StaticApp(environ, xstart_response) + else: + return self.app(environ, xstart_response) + + def log(self, status, environ): + outfile = environ.get('wsgi.errors', web.debug) + req = environ.get('PATH_INFO', '_') + protocol = environ.get('ACTUAL_SERVER_PROTOCOL', '-') + method = environ.get('REQUEST_METHOD', '-') + host = "%s:%s" % (environ.get('REMOTE_ADDR','-'), + environ.get('REMOTE_PORT','-')) + + #@@ It is really bad to extend from + #@@ BaseHTTPRequestHandler just for this method + time = self.log_date_time_string() + + msg = self.format % (host, time, protocol, method, req, status) + print >> outfile, utils.safestr(msg) + + func = WSGIWrapper(func) + server = CherryPyWSGIServer(server_address, func, server_name="localhost") + + print "http://%s:%d/" % server_address + try: + server.start() + except KeyboardInterrupt: + server.stop() diff --git a/web/net.py b/web/net.py new file mode 100644 index 0000000..6c3ee85 --- /dev/null +++ b/web/net.py @@ -0,0 +1,190 @@ +""" +Network Utilities +(from web.py) +""" + +__all__ = [ + "validipaddr", "validipport", "validip", "validaddr", + "urlquote", + "httpdate", "parsehttpdate", + "htmlquote", "htmlunquote", "websafe", +] + +import urllib, time +try: import datetime +except ImportError: pass + +def validipaddr(address): + """ + Returns True if `address` is a valid IPv4 address. + + >>> validipaddr('192.168.1.1') + True + >>> validipaddr('192.168.1.800') + False + >>> validipaddr('192.168.1') + False + """ + try: + octets = address.split('.') + if len(octets) != 4: + return False + for x in octets: + if not (0 <= int(x) <= 255): + return False + except ValueError: + return False + return True + +def validipport(port): + """ + Returns True if `port` is a valid IPv4 port. + + >>> validipport('9000') + True + >>> validipport('foo') + False + >>> validipport('1000000') + False + """ + try: + if not (0 <= int(port) <= 65535): + return False + except ValueError: + return False + return True + +def validip(ip, defaultaddr="0.0.0.0", defaultport=8080): + """Returns `(ip_address, port)` from string `ip_addr_port`""" + addr = defaultaddr + port = defaultport + + ip = ip.split(":", 1) + if len(ip) == 1: + if not ip[0]: + pass + elif validipaddr(ip[0]): + addr = ip[0] + elif validipport(ip[0]): + port = int(ip[0]) + else: + raise ValueError, ':'.join(ip) + ' is not a valid IP address/port' + elif len(ip) == 2: + addr, port = ip + if not validipaddr(addr) and validipport(port): + raise ValueError, ':'.join(ip) + ' is not a valid IP address/port' + port = int(port) + else: + raise ValueError, ':'.join(ip) + ' is not a valid IP address/port' + return (addr, port) + +def validaddr(string_): + """ + Returns either (ip_address, port) or "/path/to/socket" from string_ + + >>> validaddr('/path/to/socket') + '/path/to/socket' + >>> validaddr('8000') + ('0.0.0.0', 8000) + >>> validaddr('127.0.0.1') + ('127.0.0.1', 8080) + >>> validaddr('127.0.0.1:8000') + ('127.0.0.1', 8000) + >>> validaddr('fff') + Traceback (most recent call last): + ... + ValueError: fff is not a valid IP address/port + """ + if '/' in string_: + return string_ + else: + return validip(string_) + +def urlquote(val): + """ + Quotes a string for use in a URL. + + >>> urlquote('://?f=1&j=1') + '%3A//%3Ff%3D1%26j%3D1' + >>> urlquote(None) + '' + >>> urlquote(u'\u203d') + '%E2%80%BD' + """ + if val is None: return '' + if not isinstance(val, unicode): val = str(val) + else: val = val.encode('utf-8') + return urllib.quote(val) + +def httpdate(date_obj): + """ + Formats a datetime object for use in HTTP headers. + + >>> import datetime + >>> httpdate(datetime.datetime(1970, 1, 1, 1, 1, 1)) + 'Thu, 01 Jan 1970 01:01:01 GMT' + """ + return date_obj.strftime("%a, %d %b %Y %H:%M:%S GMT") + +def parsehttpdate(string_): + """ + Parses an HTTP date into a datetime object. + + >>> parsehttpdate('Thu, 01 Jan 1970 01:01:01 GMT') + datetime.datetime(1970, 1, 1, 1, 1, 1) + """ + try: + t = time.strptime(string_, "%a, %d %b %Y %H:%M:%S %Z") + except ValueError: + return None + return datetime.datetime(*t[:6]) + +def htmlquote(text): + """ + Encodes `text` for raw use in HTML. + + >>> htmlquote("<'&\\">") + '<'&">' + """ + text = text.replace("&", "&") # Must be done first! + text = text.replace("<", "<") + text = text.replace(">", ">") + text = text.replace("'", "'") + text = text.replace('"', """) + return text + +def htmlunquote(text): + """ + Decodes `text` that's HTML quoted. + + >>> htmlunquote('<'&">') + '<\\'&">' + """ + text = text.replace(""", '"') + text = text.replace("'", "'") + text = text.replace(">", ">") + text = text.replace("<", "<") + text = text.replace("&", "&") # Must be done last! + return text + +def websafe(val): + """ + Converts `val` so that it's safe for use in UTF-8 HTML. + + >>> websafe("<'&\\">") + '<'&">' + >>> websafe(None) + '' + >>> websafe(u'\u203d') + '\\xe2\\x80\\xbd' + """ + if val is None: + return '' + if isinstance(val, unicode): + val = val.encode('utf-8') + val = str(val) + return htmlquote(val) + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/web/session.py b/web/session.py new file mode 100644 index 0000000..d506247 --- /dev/null +++ b/web/session.py @@ -0,0 +1,317 @@ +""" +Session Management +(from web.py) +""" + +import os, time, datetime, random, base64 +try: + import cPickle as pickle +except ImportError: + import pickle +try: + import hashlib + sha1 = hashlib.sha1 +except ImportError: + import sha + sha1 = sha.new + +import utils +import webapi as web + +__all__ = [ + 'Session', 'SessionExpired', + 'Store', 'DiskStore', 'DBStore', +] + +web.config.session_parameters = utils.storage({ + 'cookie_name': 'webpy_session_id', + 'cookie_domain': None, + 'timeout': 86400, #24 * 60 * 60, # 24 hours in seconds + 'ignore_expiry': True, + 'ignore_change_ip': True, + 'secret_key': 'fLjUfxqXtfNoIldA0A0J', + 'expired_message': 'Session expired', +}) + +class SessionExpired(web.HTTPError): + def __init__(self, message): + web.HTTPError.__init__(self, '200 OK', {}, data=message) + +class Session(utils.ThreadedDict): + """Session management for web.py + """ + + def __init__(self, app, store, initializer=None): + self.__dict__['store'] = store + self.__dict__['_initializer'] = initializer + self.__dict__['_last_cleanup_time'] = 0 + self.__dict__['_config'] = utils.storage(web.config.session_parameters) + + if app: + app.add_processor(self._processor) + + def _processor(self, handler): + """Application processor to setup session for every request""" + self._cleanup() + self._load() + + try: + return handler() + finally: + self._save() + + def _load(self): + """Load the session from the store, by the id from cookie""" + cookie_name = self._config.cookie_name + cookie_domain = self._config.cookie_domain + self.session_id = web.cookies().get(cookie_name) + + # protection against session_id tampering + if self.session_id and not self._valid_session_id(self.session_id): + self.session_id = None + + self._check_expiry() + if self.session_id: + d = self.store[self.session_id] + self.update(d) + self._validate_ip() + + if not self.session_id: + self.session_id = self._generate_session_id() + + if self._initializer: + if isinstance(self._initializer, dict): + self.update(self._initializer) + elif hasattr(self._initializer, '__call__'): + self._initializer() + + self.ip = web.ctx.ip + + def _check_expiry(self): + # check for expiry + if self.session_id and self.session_id not in self.store: + if self._config.ignore_expiry: + self.session_id = None + else: + return self.expired() + + def _validate_ip(self): + # check for change of IP + if self.session_id and self.get('ip', None) != web.ctx.ip: + if not self._config.ignore_change_ip: + return self.expired() + + def _save(self): + cookie_name = self._config.cookie_name + cookie_domain = self._config.cookie_domain + if not self.get('_killed'): + web.setcookie(cookie_name, self.session_id, domain=cookie_domain) + self.store[self.session_id] = dict(self) + else: + web.setcookie(cookie_name, self.session_id, expires=-1, domain=cookie_domain) + + def _generate_session_id(self): + """Generate a random id for session""" + + while True: + rand = os.urandom(16) + now = time.time() + secret_key = self._config.secret_key + session_id = sha1("%s%s%s%s" %(rand, now, utils.safestr(web.ctx.ip), secret_key)) + session_id = session_id.hexdigest() + if session_id not in self.store: + break + return session_id + + def _valid_session_id(self, session_id): + rx = utils.re_compile('^[0-9a-fA-F]+$') + return rx.match(session_id) + + def _cleanup(self): + """Cleanup the stored sessions""" + current_time = time.time() + timeout = self._config.timeout + if current_time - self._last_cleanup_time > timeout: + self.store.cleanup(timeout) + self.__dict__['_last_cleanup_time'] = current_time + + def expired(self): + """Called when an expired session is atime""" + raise SessionExpired(self._config.expired_message) + + def kill(self): + """Kill the session, make it no longer available""" + del self.store[self.session_id] + self._killed = True + +class Store: + """Base class for session stores""" + + def __contains__(self, key): + raise NotImplemented + + def __getitem__(self, key): + raise NotImplemented + + def __setitem__(self, key, value): + raise NotImplemented + + def cleanup(self, timeout): + """removes all the expired sessions""" + raise NotImplemented + + def encode(self, session_dict): + """encodes session dict as a string""" + pickled = pickle.dumps(session_dict) + return base64.encodestring(pickled) + + def decode(self, session_data): + """decodes the data to get back the session dict """ + pickled = base64.decodestring(session_data) + return pickle.loads(pickled) + +class DiskStore(Store): + """ + Store for saving a session on disk. + + >>> import tempfile + >>> root = tempfile.mkdtemp() + >>> s = DiskStore(root) + >>> s['a'] = 'foo' + >>> s['a'] + 'foo' + >>> time.sleep(0.01) + >>> s.cleanup(0.01) + >>> s['a'] + Traceback (most recent call last): + ... + KeyError: 'a' + """ + def __init__(self, root): + # if the storage root doesn't exists, create it. + if not os.path.exists(root): + os.mkdir(root) + self.root = root + + def _get_path(self, key): + if os.path.sep in key: + raise ValueError, "Bad key: %s" % repr(key) + return os.path.join(self.root, key) + + def __contains__(self, key): + path = self._get_path(key) + return os.path.exists(path) + + def __getitem__(self, key): + path = self._get_path(key) + if os.path.exists(path): + pickled = open(path).read() + return self.decode(pickled) + else: + raise KeyError, key + + def __setitem__(self, key, value): + path = self._get_path(key) + pickled = self.encode(value) + try: + f = open(path, 'w') + try: + f.write(pickled) + finally: + f.close() + except IOError: + pass + + def __delitem__(self, key): + path = self._get_path(key) + if os.path.exists(path): + os.remove(path) + + def cleanup(self, timeout): + now = time.time() + for f in os.listdir(self.root): + path = self._get_path(f) + atime = os.stat(path).st_atime + if now - atime > timeout : + os.remove(path) + +class DBStore(Store): + """Store for saving a session in database + Needs a table with the following columns: + + session_id CHAR(128) UNIQUE NOT NULL, + atime DATETIME NOT NULL default current_timestamp, + data TEXT + """ + def __init__(self, db, table_name): + self.db = db + self.table = table_name + + def __contains__(self, key): + data = self.db.select(self.table, where="session_id=$key", vars=locals()) + return bool(list(data)) + + def __getitem__(self, key): + now = datetime.datetime.now() + try: + s = self.db.select(self.table, where="session_id=$key", vars=locals())[0] + self.db.update(self.table, where="session_id=$key", atime=now, vars=locals()) + except IndexError: + raise KeyError + else: + return self.decode(s.data) + + def __setitem__(self, key, value): + pickled = self.encode(value) + now = datetime.datetime.now() + if key in self: + self.db.update(self.table, where="session_id=$key", data=pickled, vars=locals()) + else: + self.db.insert(self.table, False, session_id=key, data=pickled ) + + def __delitem__(self, key): + self.db.delete(self.table, where="session_id=$key", vars=locals()) + + def cleanup(self, timeout): + timeout = datetime.timedelta(timeout/(24.0*60*60)) #timedelta takes numdays as arg + last_allowed_time = datetime.datetime.now() - timeout + self.db.delete(self.table, where="$last_allowed_time > atime", vars=locals()) + +class ShelfStore: + """Store for saving session using `shelve` module. + + import shelve + store = ShelfStore(shelve.open('session.shelf')) + + XXX: is shelve thread-safe? + """ + def __init__(self, shelf): + self.shelf = shelf + + def __contains__(self, key): + return key in self.shelf + + def __getitem__(self, key): + atime, v = self.shelf[key] + self[k] = v # update atime + return v + + def __setitem__(self, key, value): + self.shelf[key] = time.time(), value + + def __delitem__(self, key): + try: + del self.shelf[key] + except KeyError: + pass + + def cleanup(self, timeout): + now = time.time() + for k in self.shelf.keys(): + atime, v = self.shelf[k] + if now - atime > timeout : + del self[k] + +if __name__ == '__main__' : + import doctest + doctest.testmod() diff --git a/web/template.py b/web/template.py new file mode 100644 index 0000000..db680f2 --- /dev/null +++ b/web/template.py @@ -0,0 +1,1376 @@ +""" +simple, elegant templating +(part of web.py) + +Template design: + +Template string is split into tokens and the tokens are combined into nodes. +Parse tree is a nodelist. TextNode and ExpressionNode are simple nodes and +for-loop, if-loop etc are block nodes, which contain multiple child nodes. + +Each node can emit some python string. python string emitted by the +root node is validated for safeeval and executed using python in the given environment. + +Enough care is taken to make sure the generated code and the template has line to line match, +so that the error messages can point to exact line number in template. (It doesn't work in some cases still.) + +Grammar: + + template -> defwith sections + defwith -> '$def with (' arguments ')' | '' + sections -> section* + section -> block | assignment | line + + assignment -> '$ ' + line -> (text|expr)* + text -> + expr -> '$' pyexpr | '$(' pyexpr ')' | '${' pyexpr '}' + pyexpr -> + +""" + +__all__ = [ + "Template", + "Render", "render", "frender", + "ParseError", "SecurityError", + "test" +] + +import tokenize +import os +import glob +import re + +from utils import storage, safeunicode, safestr, re_compile +from webapi import config +from net import websafe + +def splitline(text): + r""" + Splits the given text at newline. + + >>> splitline('foo\nbar') + ('foo\n', 'bar') + >>> splitline('foo') + ('foo', '') + >>> splitline('') + ('', '') + """ + index = text.find('\n') + 1 + if index: + return text[:index], text[index:] + else: + return text, '' + +class Parser: + """Parser Base. + """ + def __init__(self, text, name="