def __init__(self, application, global_conf=None, error_template_filename=None, xmlhttp_key=None, media_paths=None, templating_formatters=None, head_html='', footer_html='', reporters=None, libraries=None, **params): self.libraries = libraries or [] self.application = application self.debug_infos = {} self.templating_formatters = templating_formatters or [] self.head_html = HTMLTemplate(head_html) self.footer_html = HTMLTemplate(footer_html) if error_template_filename is None: error_template_filename = resource_filename( "weberror", "eval_template.html" ) if xmlhttp_key is None: if global_conf is None: xmlhttp_key = '_' else: xmlhttp_key = global_conf.get('xmlhttp_key', '_') self.xmlhttp_key = xmlhttp_key self.media_paths = media_paths or {} self.error_template = HTMLTemplate.from_filename(error_template_filename) if reporters is None: reporters = [] self.reporters = reporters
def login(self, req): """ The login form. """ if not self.check_ip(req): template = HTMLTemplate.from_filename(os.path.join(os.path.dirname(__file__), 'ip_denied.html')) return Response(template.substitute(req=req), status='403 Forbidden') if req.method == 'POST': username = req.str_POST['username'] password = req.str_POST['password'] if not self.check_login(username, password): msg = 'Invalid username or password' else: resp = exc.HTTPFound(location=req.params.get('back') or req.application_url) resp.set_cookie('__devauth', self.create_cookie(req, username)) return resp else: msg = req.params.get('msg') back = req.params.get('back') or req.application_url if msg == 'expired': msg = 'Your session has expired. You can log in again, or just <a href="%s">return to your previous page</a>' % ( html_escape(back)) template = HTMLTemplate.from_filename(os.path.join(os.path.dirname(__file__), 'login.html')) resp = Response(template.substitute(req=req, msg=msg, back=back, middleware=self)) try: if req.cookies.get('__devauth'): self.read_cookie(req, req.str_cookies['__devauth']) except exc.HTTPException: # This means the cookie is invalid resp.delete_cookie('__devauth') return resp
def __init__(self, pagename): lines = [] with open('web/' + pagename + '.tmplt', 'rb') as f: for l in f: l = l.decode('utf-8') if l: lines.append(l[:-1]) self.html = HTMLTemplate(''.join(lines))
def rebuild_static_files(): """ Rebuilds static files """ public_html_path = join(current_path(), '..', 'public_html') if not exists(public_html_path): os.mkdir(public_html_path) # Create index.html template = HTMLTemplate.from_filename(join(current_path(), '..', 'templates' , 'index.html')) products_data = load_products_data() all_products = sorted(products_data, key=lambda product: product['version'][0]['publish_time']) all_products.reverse() # Build index html files # First remove any existing files html_files_list = glob(join(public_html_path, 'index.html.*')) for html_filename in html_files_list: os.unlink(html_filename) # Create per page files for i in xrange(0, len(all_products), 5): products = all_products[i:i+5] page_nr = (i/5)+1 next_page_nr = page_nr + 1 if len(all_products) > i+5 else 0 prev_page_nr = page_nr - 1 if page_nr > 1 else 0 index_fname = join(public_html_path, 'index.html.%d' % page_nr) with open(index_fname, 'w') as index_html_file: index_html_file.write(template.substitute(locals())) # Build per product files template = HTMLTemplate.from_filename(join(current_path(), '..', 'templates' , 'product.html')) products_path = join(public_html_path, 'products') if not exists(products_path): os.mkdir(products_path) html_files_list = glob(join(products_path, '*.html')) # First remove any existing files for html_filename in html_files_list: os.unlink(html_filename) for product in all_products: index_name = basename(product['index_name']) product_fname = join(products_path, '%s.html' % index_name) with open(product_fname, 'w') as html_file: html_file.write(template.substitute(locals())) # Create/update screenshot thumbnails update_thumbs()
def __init__(self, template, language=None): if language: translation(language) self._page = HTMLTemplate.from_filename(template) # Render full page self._full = True
def find_template(self, template): """find a template of a given name""" # TODO: make this faster; the application should probably cache # a dict of the (loaded) templates unless (e.g.) debug is given for d in self.template_dirs: path = os.path.join(d, template) if os.path.exists(path): return HTMLTemplate.from_filename(path)
def render_template(filename, namespace, filters_module=None): # Install template filters if given if filters_module: filters_namespace = {} for name in filters_module.__all__: filter = getattr(filters_module, name) filters_namespace[filter.name] = filter # @@HACK Remove conflicting filter with HTMLTemplate del filters_namespace['html'] # Update namespace, possibly overriding names namespace.update(filters_namespace) return HTMLTemplate.from_filename(filename, namespace=namespace).substitute()
def find_template(self, name): """find a template of a given name""" # the application caches a dict of the templates if app.reload is False if name in self.template_cache: return self.template_cache[name] for d in self.template_dirs: path = os.path.join(d, name) if os.path.exists(path): template = HTMLTemplate.from_filename(path) if not self.app.reload: self.template_cache[name] = template return template
class PageTemplate: '''Class to load a page template from file and i18n it, implementation using tempita''' # Constructor def __init__(self, pagename): lines = [] with open('web/' + pagename + '.tmplt', 'rb') as f: for l in f: l = l.decode('utf-8') if l: lines.append(l[:-1]) self.html = HTMLTemplate(''.join(lines)) def getHtml(self, params=None): '''Use the given dictionary (if any) to populate the template''' if params is None: params = {} params["langs"] = I18nManager.texts return self.html.substitute(params)
def __init__( self, application, global_conf=None, error_template_filename=None, xmlhttp_key=None, media_paths=None, templating_formatters=None, head_html="", footer_html="", reporters=None, libraries=None, debug_url_prefix=None, **params ): self.libraries = libraries or [] self.application = application self.debug_infos = {} self.templating_formatters = templating_formatters or [] self.head_html = HTMLTemplate(head_html) self.footer_html = HTMLTemplate(footer_html) if error_template_filename is None: error_template_filename = resource_filename("weberror", "eval_template.html") if xmlhttp_key is None: if global_conf is None: xmlhttp_key = "_" else: xmlhttp_key = global_conf.get("xmlhttp_key", "_") self.xmlhttp_key = xmlhttp_key if debug_url_prefix is None: if global_conf is None: debug_url_prefix = "_debug" else: debug_url_prefix = global_conf.get("debug_url_prefix", "_debug") self.debug_url_prefix = debug_url_prefix.split("/") self.media_paths = media_paths or {} self.error_template = HTMLTemplate.from_filename(error_template_filename) if reporters is None: reporters = [] self.reporters = reporters
from formencode.api import Invalid from lxml import html try: # Python 2.5: from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText except ImportError: # Python 2.4: from email.MIMEMultipart import MIMEMultipart from email.MIMEText import MIMEText import smtplib here = os.path.dirname(__file__) ## FIXME: these don't auto-reload or restart the server on edit: form_html = HTMLTemplate.from_filename(os.path.join(here, 'form.html')) result_html = HTMLTemplate.from_filename(os.path.join(here, 'result.html')) class EmailIt(object): ## FIXME: should have some kind of host restriction, like ScriptTranscluder def __init__(self, smtp_server='localhost', smtp_username=None, smtp_password=None, smtp_use_tls=False): self.smtp_server = smtp_server self.smtp_username = smtp_username self.smtp_password = smtp_password self.smtp_use_tls = smtp_use_tls def __call__(self, environ, start_response): req = Request(environ) if req.method == 'GET': meth = self.form
message.tail = doc.body.text doc.body.text = '' doc.body.insert(0, message) text = tostring(doc) return Response(text) def _el_in_head(self, el): """True if the given element is in the HTML ``<head>``""" while el is not None: if el.tag == 'head': return True el = el.getparent() return False _not_found_template = HTMLTemplate( '''\ There were no elements that matched the selector <code>{{selector}}</code> ''', 'deliverance.middleware.DeliveranceMiddleware._not_found_template') _found_template = HTMLTemplate( '''\ {{if len(elements) == 1}} One element matched the selector <code>{{selector}}</code>; {{if elements[0][0]}} <a href="{{base_url}}#{{elements[0][0]}}">jump to element</a> {{else}} element is in head: {{highlight(elements[0][1])}} {{endif}} {{else}} {{len(elements)}} elements matched the selector <code>{{selector}}</code>: <ol> {{for anchor, el in elements}}
def render_template(filename, namespace): return HTMLTemplate.from_filename(filename, namespace=namespace).substitute()
def homepage(self, req): tmpl = HTMLTemplate.from_filename(os.path.join(here, 'homepage.html')) resp = tmpl.substitute(app=self, req=req, appIncludeJs=self.appinclude_js) return Response(body=resp)
class Editor(object): def __init__(self, base_dir=None, filename=None, title=None, force_syntax=None): assert base_dir or filename assert not base_dir or not filename if base_dir: self.base_dir = os.path.normcase(os.path.abspath(base_dir)) else: self.base_dir = None self.filename = filename self.title = title self.force_syntax = force_syntax def __call__(self, environ, start_response): req = Request(environ) if req.path_info_peek() == '.media': req.path_info_pop() app = StaticURLParser(os.path.join(os.path.dirname(__file__), 'media')) return app(environ, start_response) if self.base_dir: filename = os.path.join(self.base_dir, req.path_info.lstrip('/')) assert filename.startswith(self.base_dir) else: filename = self.filename if req.method not in ('GET', 'POST'): resp = exc.HTTPMethodNotAllowed('Bad method: %s' % req.method, allow='GET,POST') elif os.path.isdir(filename): if req.method == 'POST': resp = self.save_create(req, filename) else: if not req.path.endswith('/'): resp = exc.HTTPMovedPermanently(add_slash=True) else: resp = self.view_dir(req, filename) else: if req.method == 'POST': resp = self.save_file(req, filename) elif req.method == 'GET': resp = self.edit_file(req, filename) return resp(environ, start_response) def edit_url(self, req, filename): if self.filename: assert self.filename == filename return req.application_url else: assert filename.startswith(self.base_dir) filename = filename[len(self.base_dir):].lstrip('/').lstrip('\\') return req.application_url + '/' + filename def save_file(self, req, filename): content = req.POST['content'] f = open(filename, 'wb') f.write(content) f.close() return exc.HTTPFound( location=self.edit_url(req, filename)) syntax_map = { '.c': 'c', '.cf': 'coldfusion', '.cpp': 'cpp', '.c++': 'cpp', '.css': 'css', '.html': 'html', '.htm': 'html', '.xhtml': 'html', '.js': 'js', '.pas': '******', '.pl': 'perl', '.php': 'php', '.py': 'python', 'robots.txt': 'robotstxt', '.rb': 'ruby', '.sql': 'sql', '.tsql': 'tsql', '.vb': 'vb', '.xml': 'xml', } def syntax_for_filename(self, filename): if self.force_syntax: return self.force_syntax basename = os.path.basename(filename) if basename in self.syntax_map: return self.syntax_map[basename] else: ext = os.path.splitext(filename)[1].lower() if ext in self.syntax_map: return self.syntax_map[ext] mimetype, enc = mimetypes.guess_type(os.path.splitext(filename)[1]) if mimetype.startswith('application/') and mimetype.endswith('+xml'): return 'xml' return None def edit_file(self, req, filename): f = open(filename, 'rb') content = f.read() f.close() title = self.title or filename syntax = self.syntax_for_filename(filename) body = self.edit_template.substitute( content=content, filename=filename, title=title, req=req, edit_url=self.edit_url(req, filename), syntax=syntax) resp = Response(body=body) resp.cache_expires() return resp edit_template = HTMLTemplate.from_filename( os.path.join(os.path.dirname(__file__), 'editor_template.html')) def save_create(self, req, dir): file = req.POST.get('file') if file is None or file == '': content = req.POST['content'] filename = req.POST['filename'] else: content = file.value filename = req.POST.get('filename') or file.filename filename = filename.replace('\\', '/') filename = os.path.basename(os.path.normpath(filename)) filename = os.path.join(dir, filename) if os.path.exists(filename): return exc.HTTPForbidden( "The file %s already exists, you cannot upload over it" % filename) f = open(filename, 'wb') f.write(content) f.close() return exc.HTTPFound( location=self.edit_url(req, filename)) skip_files = ['.svn', 'CVS', '.hg'] def view_dir(self, req, dir): dir = os.path.normpath(dir) show_parent = dir != self.base_dir children = [os.path.join(dir, name) for name in os.listdir(dir) if name not in self.skip_files] def edit_url(filename): return self.edit_url(req, filename) title = self.title or dir body = self.view_dir_template.substitute( req=req, dir=dir, show_parent=show_parent, title=title, basename=os.path.basename, dirname=os.path.dirname, isdir=os.path.isdir, children=children, edit_url=edit_url, ) resp = Response(body=body) resp.cache_expires() return resp view_dir_template = HTMLTemplate.from_filename( os.path.join(os.path.dirname(__file__), 'view_dir_template.html'))
class Annotate(object): def __init__(self, dir): self.dir = dir self.static_app = Subber( 'package:seeitservices.annotate:./static-annotate/') @wsgify def __call__(self, req): req.charset = None peek = req.path_info_peek() if peek == 'save': return self.save(req) elif peek == 'page': return self.page(req) elif peek == 'annotation': return self.annotation(req) if req.path_info == '/describe': return self.describe(req) return self.static_app def split_path(self, req, prefix): path = req.path_info.strip('/') assert path.startswith(prefix + '/') path = path[len(prefix):].strip('/') email, rest = path.split('/', 1) return email, rest def make_filename(self, type, email, path): path = os.path.join(self.dir, type, urllib.quote(email, ''), urllib.quote(path, '')) assert os.path.abspath(path).startswith(os.path.abspath(self.dir)) return path @wsgify def describe(self, req): data = dict( name='Annotate a document', sendToPage=req.application_url + '/create.html', sendToPageFunction='savePage', types=['html'], ) return Response(json=data) @wsgify def save(self, req): if not req.email: return Response(status=403, content_type='text/plain', body='Not logged in') email, path = self.split_path(req, 'save') if email != req.email: return Response(status=403, content_type='text/plain', body='Email not correct (%r, not %r)' % (req.email, email)) if req.method != 'PUT': return exc.HTTPMethodNotAllowed(allow='PUT') data = req.json filename = self.make_filename('page', email, path) write_file(filename, json.dumps(data)) location = req.application_url + '/page/' + urllib.quote( email) + '/' + urllib.quote(path, '') return Response(json={'location': location}) page_template = HTMLTemplate('''\ <html> <head> <meta charset="UTF-8"> <base href="{{location}}"> {{head | html}} <link href="{{application_url}}/annotate.css" rel="stylesheet" type="text/css"> </head> <body{{body_attrs | html}}> {{body | html}} <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> <script src="{{application_url}}/annotate.js"></script> <script>annotationUrl = {{repr(annotation_url)}};</script> {{auth_html | html}} </body> </html> ''') @wsgify def page(self, req): email, path = self.split_path(req, 'page') filename = self.make_filename('page', email, path) if not os.path.exists(filename): return exc.HTTPNotFound() with open(filename, 'rb') as fp: data = json.loads(fp.read()) if data['data'].get('bodyAttrs'): body_attrs = [ ' %s="%s"' % (name, cgi.escape(value)) for name, value in data['data']['bodyAttrs'].items() ] else: body_attrs = '' page = self.page_template.substitute( location=data['location'], head=data['data']['head'], application_url=req.application_url, body_attrs=body_attrs, body=data['data']['body'], auth_html=req.get_sub('auth'), annotation_url=req.url.replace('/page/', '/annotation/'), ) return Response(page) @wsgify def annotation(self, req): email, path = self.split_path(req, 'annotation') filename = self.make_filename('annotation', email, path) if not os.path.exists(filename): data = {'annotations': []} else: with open(filename, 'rb') as fp: data = json.loads(fp.read()) if req.method == 'GET': return Response(json=data) elif req.method == 'POST': req_data = req.json if req_data.get('annotations'): data['annotations'].extend(req_data['annotations']) if req_data.get('deletes'): for delete in req_data['deletes']: for ann in list(data['annotations']): if ann['id'] == delete['id']: data['annotations'].remove(ann) if not os.path.exists(os.path.dirname(filename)): os.makedirs(os.path.dirname(filename)) with open(filename, 'wb') as fp: fp.write(json.dumps(data)) return Response(json=data) else: return exc.HTTPMethodNotAllowed(allow='GET,POST')
class Recorder(object): def __init__(self, app, file, intercept='/.webtestrecorder', require_devauth=False): self.app = app if isinstance(file, basestring): file = open(file, 'ab') self.file = file self.lock = threading.Lock() self.intercept = intercept self.require_devauth = require_devauth @classmethod def entry_point(cls, app, global_conf, filename, intercept='/.webtestrecorder', require_devauth=False): from paste.deploy.converters import asbool require_devauth = asbool(require_devauth) return cls(app, filename, intercept, require_devauth) @wsgify def __call__(self, req): if self.intercept and req.path_info.startswith(self.intercept): return self.internal(req) resp = req.get_response(self.app) self.write_record(req, resp) return resp def write_record(self, req, resp): data = [] data.append('--Request:\n') data.append(str(req)) if not req.content_length: data.append('\n') data.append('\n--Response:\n') data.append(str(resp)) if not resp.body: data.append('\n') data.append('\n') self.lock.acquire() try: self.file.write(''.join(data)) finally: self.lock.release() @wsgify def internal(self, req): if (self.require_devauth and not req.environ.get('x-wsgiorg.developer_user')): raise exc.HTTPForbidden('You must login') if req.method == 'POST': if req.params.get('clear'): name = self.file.name self.file.close() self.file = open(name, 'wb') else: false_req = Request.blank('/') false_resp = Response('', status='200 Internal Note') false_resp.write(req.params['note']) self.write_record(false_req, false_resp) raise exc.HTTPFound(req.url) if req.params.get('download'): if req.params['download'] == 'doctest': text = self.doctest(req) else: text = self.function_unittest(req) return Response(text, content_type='text/plain') return Response(self._intercept_template.substitute(req=req, s=self)) _intercept_template = HTMLTemplate('''\ <html> <head> <title>WebTest Recorder</title> <style type="text/css"> body { font-family: sans-serif; } pre { overflow: auto; } </style> </head> <body> <h1>WebTest Recorder</h1> <div> <a href="#doctest">doctest</a> (<a href="{{req.url}}?download=doctest">download</a>) | <a href="#function_unittest">function unittest</a> (<a href="{{req.url}}?download=function_unittest">download</a>) </div> <form action="{{req.url}}" method="POST"> <fieldset> You may add a note/comment to the record:<br> <textarea name="note" rows=4 style="width: 100%"></textarea><br> <button type="submit">Save note</button> <button type="submit" name="clear" value="1" style="background-color: #f99">Clear!</button> </fieldset> </form> <h1 id="doctest">Current tests as a doctest</h1> <pre>{{s.doctest(req)}}</pre> <h1 id="function_unittest">Current tests as a function unittest</h1> <pre>{{s.function_unittest(req)}}</pre> </body> </html> ''', name='_intercept_template') def doctest(self, req): records = self.get_records() out = StringIO() write_doctest(records, out, default_host=req.host) return out.getvalue() def function_unittest(self, req): records = self.get_records() out = StringIO() write_function_unittest(records, out, default_host=req.host) return out.getvalue() def get_records(self): self.file.flush() fn = self.file.name fp = open(fn, 'rb') content = StringIO(fp.read()) fp.close() return get_records(content)
"names" : [ "alice", "bob", "eve" ], "people" : [ { "name" : "alice", "role" : "communicator" }, { "name" : "bob", "role" : "communicator" }, { "name" : "eve", "role" : "eavesdropper", "blackhat" : True }, ], "a_bunch" : bunch(a=1,b=2,c=3), "plus_filter" : plus_filter, # Make available as filter } print t.substitute(context_params) print "\nDoing HTML subsitution..." html_template_string=""" {{# A little counter-intuitive, but use 'html()' to substitute without quotes }} <a href="{{ target_url | html }}">{{ target_name }}</a> """ t2 = HTMLTemplate(html_template_string) context_params2 = { "target_url" : "https://example.com/hello?world&value=2", "target_name" : "Here & There", } print t2.substitute(context_params2) sys.exit(0)
make_wrappable=formatter.make_wrappable, pprint_format=pprint_format) table_template = HTMLTemplate(''' {{py:i = 0}} <table> {{for name, value in items:}} {{py:i += 1}} {{py: value_html = html_quote(pprint_format(value, safe=True)) value_html = make_wrappable(value_html) if len(value_html) > 100: ## FIXME: This can break HTML; truncate before quoting? value_html, expand_html = value_html[:100], value_html[100:] else: expand_html = '' }} <tr class="{{if i%2}}even{{else}}odd{{endif}}" style="vertical-align: top"> <td><b>{{name}}</b></td> <td style="overflow: auto">{{preserve_whitespace(value_html, quote=False)|html}}{{if expand_html}} <a class="switch_source" style="background-color: #999" href="#" onclick="return expandLong(this)">...</a> <span style="display: none">{{expand_html|html}}</span> {{endif}} </td> </tr> {{endfor}} </table> ''', name='table_template') def pprint_format(value, safe=False): out = StringIO()
class SavingLogger(object): """ Logger that saves all its messages locally. """ def __init__(self, request, middleware): self.messages = [] self.middleware = middleware self.request = request # This is writable: self.theme_url = None # Also writable (list of (url, name)) self.edit_urls = [] def message(self, level, el, msg, *args, **kw): """Add one message at the given log level""" if args: msg = msg % args elif kw: msg = msg % kw self.messages.append((level, el, msg)) return msg def debug(self, el, msg, *args, **kw): """Log at the DEBUG level""" return self.message(logging.DEBUG, el, msg, *args, **kw) def info(self, el, msg, *args, **kw): """Log at the INFO level""" return self.message(logging.INFO, el, msg, *args, **kw) def notify(self, el, msg, *args, **kw): """Log at the NOTIFY level""" return self.message(NOTIFY, el, msg, *args, **kw) def warn(self, el, msg, *args, **kw): """Log at the WARN level""" return self.message(logging.WARN, el, msg, *args, **kw) warning = warn def error(self, el, msg, *args, **kw): """Log at the ERROR level""" return self.message(logging.ERROR, el, msg, *args, **kw) def fatal(self, el, msg, *args, **kw): """Log at the FATAL level""" return self.message(logging.FATAL, el, msg, *args, **kw) def finish_request(self, req, resp): """Called by the middleware at the end of the request. This gives the log an opportunity to add information to the page. """ if 'deliv_log' in req.GET and display_logging(req): resp.body += self.format_html_log() resp.cache_expires() return resp log_template = HTMLTemplate( '''\ <H1 style="border-top: 3px dotted #f00">Deliverance Information</h1> <div> {{if log.theme_url}} <a href="{{theme_url}}" target="_blank">theme</a> {{else}} theme: no theme set {{endif}} | <a href="{{unthemed_url}}" target="_blank">unthemed content</a> | <a href="{{content_source}}" target="_blank">content source</a> | <a href="{{content_browse}}" target="_blank">browse content</a> / <a href="{{theme_browse}}" target="_blank">theme</a> {{if log.edit_urls}} | <select onchange="if (this.value) {window.open(this.value, '_blank')}; this.selectedIndex=0;"> <option value="">edit location</option> {{for url, name in log.edit_urls}} <option value="{{url}}">{{name}}</option> {{endfor}} </select> {{endif}} {{if edit_rules}} | <a href="{{edit_rules}}" target="_blank">edit rules</a> {{endif}} </div> {{if log.messages}} {{div}} {{h2}}Log</h2> {{div_inner}} <table> <tr> <th>Level</th><th>Message</th><th>Context</th> </tr> {{for level, level_name, el, message in log.resolved_messages():}} {{py:color, bgcolor = log.color_for_level(level)}} <tr style="color: {{color}}; background-color: {{bgcolor}}; vertical-align: top"> {{td}}{{level_name}}</td> {{td}}{{message}}</td> {{td}}{{log.obj_as_html(el) | html}}</td> </tr> {{endfor}} </table> </div></div> {{else}} {{h2}}No Log Messages</h2> {{endif}} ''', name='deliverance.log.SavingLogger.log_template') tags = dict( h2=html( '<h2 style="color: #000; background-color: #f90; margin-top: 0; ' 'border-bottom: 1px solid #630">'), div=html('<div style="border: 2px solid #000; margin-bottom: 1em">'), div_inner=html('<div style="padding: 0.25em">'), td=html('<td style="margin-bottom: 0.25em; ' 'border-bottom: 1px solid #ddd; padding-right: 0.5em">'), ) def format_html_log(self): """Formats this log object as HTML""" content_source = self.link_to(self.request.url, source=True) content_browse = self.link_to(self.request.url, browse=True) theme_browse = self.link_to(self.theme_url, browse=True) if edit_local_files(self.request.environ): ## FIXME: also test for the local-ness of the file edit_rules = (self.request.environ['deliverance.base_url'] + '/.deliverance/edit_rules') else: edit_rules = None return self.log_template.substitute( log=self, middleware=self.middleware, unthemed_url=self._add_notheme(self.request.url), theme_url=self._add_notheme(self.theme_url), content_source=content_source, content_browse=content_browse, theme_browse=theme_browse, edit_rules=edit_rules, **self.tags) def _add_notheme(self, url): """Adds the necessary query string argument to the URL to suppress theming""" if url is None: return None if '?' in url: url += '&' else: url += '?' return url + 'deliv_notheme' def resolved_messages(self): """ Yields a list of ``(level, level_name, context_el, rendered_message)`` """ for level, el, msg in self.messages: level_name = logging.getLevelName(level) yield level, level_name, el, msg def obj_as_html(self, el): """ Returns the object formatted as HTML. This is used to show the context in log messages. """ ## FIXME: another magic method? if hasattr(el, 'log_description'): return el.log_description(self) elif isinstance(el, _Element): return html_quote(tostring(el)) else: return html_quote(str(el)) def color_for_level(self, level): """ The HTML foreground/background colors for a given level. """ return { logging.DEBUG: ('#666', '#fff'), logging.INFO: ('#333', '#fff'), NOTIFY: ('#000', '#fff'), logging.WARNING: ('#600', '#fff'), logging.ERROR: ('#fff', '#600'), logging.CRITICAL: ('#000', '#f33') }[level] def link_to(self, url, source=False, line=None, selector=None, browse=False): """ Gives a link to the given source view (just routes to `deliverance.middleware.DeliveranceMiddleware.link_to`). """ return self.middleware.link_to(self.request, url, source=source, line=line, selector=selector, browse=browse)
import os import re from webob import Request, Response from webob import exc from tempita import HTMLTemplate VIEW_TEMPLATE = HTMLTemplate("""\ <html> <head> <title>{{page.title}}</title> </head> <body> <h1>{{page.title}}</h1> {{if message}} <div style="background-color: #99f">{{message}}</div> {{endif}} <div>{{page.content|html}}</div> <hr> <a href="{{req.url}}?action=edit">Edit</a> </body> </html> """) EDIT_TEMPLATE = HTMLTemplate("""\ <html> <head> <title>Edit: {{page.title}}</title> </head> <body>
class DeliveranceMiddleware(object): """ The middleware that implements the Deliverance transformations """ ## FIXME: is log_factory etc very useful? def __init__(self, app, rule_getter, log_factory=SavingLogger, log_factory_kw={}, default_theme=None): self.app = app self.rule_getter = rule_getter self.log_factory = log_factory self.log_factory_kw = log_factory_kw self._default_theme = default_theme ## FIXME: clearly, this should not be a dictionary: self.known_html = set() self.known_titles = {} def default_theme(self, environ): """ The URI of the global default theme, if one is set, or None. This is a method that takes the WSGI environ so that subclasses can override the behavior (for example setting the default theme to a URI template that is interpolated on every request) """ return self._default_theme def log_description(self, log=None): """The description shown in the log for this context""" return 'Deliverance' def notheme_request(self, req): if 'deliv_notheme' in req.GET: return True def use_internal_subrequest(self, url, orig_req, log): """ Subclasses can override this method to control when Deliverance should make internal subrequests directly to the proxied application (in other words, calling the WSGI application that DeliveranceMiddleware is wrapping) rather than making an HTTP subrequest to a fully external URL. """ return url.startswith(orig_req.application_url + '/') def __call__(self, environ, start_response): req = Request(environ) if self.notheme_request(req): return self.app(environ, start_response) req.environ['deliverance.base_url'] = req.application_url ## FIXME: copy_get?: orig_req = Request(environ.copy()) if 'deliverance.log' in req.environ: log = req.environ['deliverance.log'] else: log = self.log_factory(req, self, **self.log_factory_kw) ## FIXME: should this be put in both the orig_req and this req? req.environ['deliverance.log'] = log def resource_fetcher(url, retry_inner_if_not_200=False): """ Return the Response object for the given URL """ return self.get_resource(url, orig_req, log, retry_inner_if_not_200) if req.path_info_peek() == '.deliverance': req.path_info_pop() resp = self.internal_app(req, resource_fetcher) return resp(environ, start_response) rule_set = self.rule_getter(resource_fetcher, self.app, orig_req) clientside = rule_set.check_clientside(req, log) if clientside and req.url in self.known_html: if req.cookies.get('jsEnabled'): log.debug(self, 'Responding to %s with a clientside theme' % req.url) return self.clientside_response(req, rule_set, resource_fetcher, log)(environ, start_response) else: log.debug( self, 'Not doing clientside theming because jsEnabled cookie not set' ) head_response = None if req.method == "HEAD": # We need to copy the request instead of reusing it, # in case the downstream app modifies req.environ in-place head_req = req.copy() head_response = head_req.get_response(self.app) req.method = "GET" resp = req.get_response(self.app) ## FIXME: also XHTML? if resp.content_type != 'text/html': ## FIXME: remove from known_html? return resp(environ, start_response) # XXX: Not clear why such responses would have a content type, but # they sometimes do (from Zope/Plone, at least) and that then breaks # when trying to apply a theme. if resp.status_int in (301, 302, 304): return resp(environ, start_response) if resp.content_length == 0: return resp(environ, start_response) if resp.body == '': return resp(environ, start_response) if clientside and req.url not in self.known_html: log.debug( self, '%s would have been a clientside check; in future will be since we know it is HTML' % req.url) self.known_titles[req.url] = self._get_title(resp.body) self.known_html.add(req.url) resp = rule_set.apply_rules(req, resp, resource_fetcher, log, default_theme=self.default_theme(environ)) if clientside: resp.decode_content() resp.body = self._substitute_jsenable(resp.body) resp = log.finish_request(req, resp) if head_response: head_response.headers = resp.headers resp = head_response return resp(environ, start_response) _title_re = re.compile(r'<title>(.*?)</title>', re.I | re.S) def _get_title(self, body): match = self._title_re.search(body) if match: return match.group(1) else: return None _end_head_re = re.compile(r'</head>', re.I) _jsenable_js = '''\ <script type="text/javascript"> document.cookie = 'jsEnabled=1; expires=__DATE__; path=/'; </script>''' _future_date = (datetime.datetime.now() + datetime.timedelta(days=10 * 365) ).strftime('%a, %d-%b-%Y %H:%M:%S GMT') def _substitute_jsenable(self, body): match = self._end_head_re.search(body) if not match: return body js = self._jsenable_js.replace('__DATE__', self._future_date) return body[:match.start()] + js + body[match.start():] def clientside_response(self, req, rule_set, resource_fetcher, log): theme_href = rule_set.default_theme.resolve_href(req, None, log) theme_doc = rule_set.get_theme(theme_href, resource_fetcher, log) js = CLIENTSIDE_JAVASCRIPT.replace('__DELIVERANCE_URL__', req.application_url) theme_doc.head.insert( 0, fromstring('''\ <script type="text/javascript"> %s </script>''' % js)) theme = tostring(theme_doc) ## FIXME: cache this, use the actual subresponse to get proper last-modified, etc title = self.known_titles.get(req.url) if title: theme = self._title_re.sub('<title>%s</title>' % title, theme) resp = Response(theme, conditional_response=True) if not resp.etag: resp.md5_etag() return resp def get_resource(self, url, orig_req, log, retry_inner_if_not_200=False, redirections=5): resp = self._get_resource(url, orig_req, log, retry_inner_if_not_200) if not resp.status.startswith("3") or not resp.location: return resp max_redirections = redirections while redirections > 0: redirections = redirections - 1 log.debug( self, "Request for %s returned %s; following redirect Location: %s" % (url, resp.status, resp.location)) url = resp.location resp = self._get_resource(url, orig_req, log, retry_inner_if_not_200) if not resp.status.startswith("3") or not resp.location: return resp log.debug( self, "Max redirects (%s) reached; returning response %s from %s" % (max_redirections, url, resp.status)) return resp def _get_resource(self, url, orig_req, log, retry_inner_if_not_200=False): """ Gets the resource at the given url, using the original request `orig_req` as the basis for constructing the subrequest. Returns a `webob.Response` object. If `url.startswith(orig_req.application_url + '/')`, then Deliverance will try to fetch the resource by making a subrequest to the app that is being wrapped by Deliverance, instead of an external subrequest. This can cause problems in some setups -- see #16. To work around this, if `retry_inner_if_not_200` is True, then, in the situation described above, non-200 responses from the inner app will be tossed out, and the request will be retried as an external http request. Currently this is used only by RuleSet.get_theme """ assert url is not None if url.lower().startswith('file:'): if not display_local_files(orig_req): ## FIXME: not sure if this applies generally; some ## calls to get_resource might be because of a more ## valid subrequest than displaying a file return exc.HTTPForbidden( "You cannot access file: URLs (like %r)" % url) filename = url_to_filename(url) if not os.path.exists(filename): return exc.HTTPNotFound("The file %r was not found" % filename) if os.path.isdir(filename): return exc.HTTPForbidden( "You cannot display a directory (%r)" % filename) subresp = Response() type, dummy = mimetypes.guess_type(filename) if not type: type = 'application/octet-stream' subresp.content_type = type ## FIXME: reading the whole thing obviously ain't great: f = open(filename, 'rb') subresp.body = f.read() f.close() return subresp elif self.use_internal_subrequest(url, orig_req, log): subreq = orig_req.copy_get() subreq.environ[ 'deliverance.subrequest_original_environ'] = orig_req.environ new_path_info = url[len(orig_req.application_url):] query_string = '' if '?' in new_path_info: new_path_info, query_string = new_path_info.split('?', 1) new_path_info = urllib.parse.unquote(new_path_info) assert new_path_info.startswith('/') subreq.path_info = new_path_info subreq.query_string = query_string subresp = subreq.get_response(self.app) ## FIXME: error if not HTML? ## FIXME: handle redirects? ## FIXME: handle non-200? log.debug(self, 'Internal request for %s: %s content-type: %s', url, subresp.status, subresp.content_type) if not retry_inner_if_not_200: return subresp if subresp.status_int == 200 or subresp.status.startswith("3"): return subresp elif 'x-deliverance-theme-subrequest' in orig_req.headers: log.debug( self, 'Internal request for %s was not 200 OK; ' 'returning it anyway.' % url) return subresp else: log.debug( self, 'Internal request for %s was not 200 OK; retrying as external request.' % url) ## FIXME: pluggable subrequest handler? subreq = self.build_external_subrequest(url, orig_req, log) subresp = subreq.get_response(proxy_exact_request) log.debug(self, 'External request for %s: %s content-type: %s', url, subresp.status, subresp.content_type) return subresp def build_external_subrequest(self, url, orig_req, log): """ Returns a webob.Request to be used when Deliverance is getting a resource via an external subrequest (as opposed to a file:// URL or an internal subrequest to the application being wrapped by Deliverance) The method returns a webob.Request object; the default implementation returns a blank Request with only the header ``x-deliverance-theme-subrequest`` set. Subclasses can override this behavior, e.g. to preserve certain headers from the original request into subrequests. ``url``: The URL of the resource to be fetched ``orig_req``: The original request received by Deliverance ``log``: The logging object """ subreq = Request.blank(url) subreq.headers['x-deliverance-theme-subrequest'] = "1" return subreq def link_to(self, req, url, source=False, line=None, selector=None, browse=False): """ Creates a link to the given url for debugging purposes. ``source=True``: link to the highlighted source for the file. ``line=#``: link to the specific line number ``selector="css/xpath"``: highlight the element that matches that css/xpath selector ``browse=True``: link to a display that lets you see ids and classes in the document """ base = req.environ['deliverance.base_url'] base += '/.deliverance/view' source = int(bool(source)) args = {'url': url} if source: args['source'] = '1' if line: args['line'] = str(line) if selector: args['selector'] = selector if browse: args['browse'] = '1' url = base + '?' + urllib.parse.urlencode(args) if selector: url += '#deliverance-selection' if line: url += '#code-%s' % line return url def internal_app(self, req, resource_fetcher): """ Handles all internal (``/.deliverance``) requests. """ segment = req.path_info_peek() method = 'action_%s' % segment method = getattr(self, method, None) if not display_logging(req) and not getattr(method, 'exposed', False): return exc.HTTPForbidden("Logging is not enabled for you") req.path_info_pop() if not method: return exc.HTTPNotFound('There is no %r action' % segment) try: return method(req, resource_fetcher) except exc.HTTPException as e: return e def action_media(self, req, resource_fetcher): """ Serves up media from the ``deliverance/media`` directory. """ ## FIXME: I'm not using this currently, because the Javascript ## didn't work. Dunno why. from paste.urlparser import StaticURLParser app = StaticURLParser(os.path.join(os.path.dirname(__file__), 'media')) ## FIXME: need to pop some segments from the req? req.path_info_pop() resp = req.get_response(app) if resp.content_type == 'application/x-javascript': resp.content_type = 'application/javascript' return resp def action_view(self, req, resource_fetcher): """ Views files; ``.link_to()`` creates links that go to this method. """ url = req.GET['url'] source = int(req.GET.get('source', '0')) browse = int(req.GET.get('browse', '0')) selector = req.GET.get('selector', '') subresp = resource_fetcher(url) if source: return self.view_source(req, subresp, url) elif browse: return self.view_browse(req, subresp, url) elif selector: return self.view_selection(req, subresp, url) else: return exc.HTTPBadRequest( "You must have a query variable source, browse, or selector") def action_edit_rules(self, req, resource_fetcher): if not edit_local_files(req.environ): return exc.HTTPForbidden('Editing is forbidden') rules = self.rule_getter(resource_fetcher, self.app, req) file_url = rules.source_location if not file_url.startswith('file:'): return exc.HTTPForbidden( 'The rule location (%s) is not a local file' % file_url) filename = url_to_filename(file_url) app = Editor(filename=filename, force_syntax='delivxml', title='rule file %s' % os.path.basename(filename)) return app def view_source(self, req, resp, url): """ View the highlighted source (from `action_view`). """ content_type = resp.content_type if content_type.startswith('application/xml'): lexer = XmlLexer() elif content_type == 'text/html': lexer = HtmlLexer() else: ## FIXME: what then? lexer = HtmlLexer() text = pygments_highlight( resp.body, lexer, HtmlFormatter(full=True, linenos=True, lineanchors='code')) return Response(text) def view_browse(self, req, resp, url): """ View the id/class browser (from `action_view`) """ import re body = resp.body f = open(os.path.join(os.path.dirname(__file__), 'media', 'browser.js')) content = f.read() f.close() extra_head = ''' <!-- Added by Deliverance for browsing: --> <script src="http://www.google.com/jsapi"></script> <script> %s </script> <style type="text/css"> .deliverance-highlight { border: 5px dotted #f00; } </style> <base href="%s"> <!-- Begin original page --> ''' % (content, posixpath.dirname(req.GET['url']) + '/') match = re.search(r'<head>', body, re.I) if match: body = body[:match.end()] + extra_head + body[match.end():] else: body = extra_head + body extra_body = ''' <div style="display: block; color: #000; background-color: #dfd; font-family: sans-serif; font-size: 100%; border-bottom: 2px dotted #f00" id="deliverance-browser"> <span style="float: right"><button onclick="window.close()">close</button></span> View by id/class: <select onchange="deliveranceChangeId()" name="deliverance-ids" id="deliverance-ids"></select> </div>''' match = re.search('<body.*?>', body, re.I) if match: body = body[:match.end()] + extra_body + body[match.end():] else: body = extra_body + body return Response(body) def view_selection(self, req, resp, url): """ View the highlighted selector (from `action_view`) """ from deliverance.selector import Selector doc = document_fromstring(resp.body) el = Element('base') el.set('href', posixpath.dirname(url) + '/') doc.head.insert(0, el) selector = Selector.parse(req.GET['selector']) dummy_type, elements, dummy_attributes = selector(doc) if not elements: template = self._not_found_template else: template = self._found_template all_elements = [] els_in_head = False for index, el in enumerate(elements): el_in_head = self._el_in_head(el) if el_in_head: els_in_head = True anchor = 'deliverance-selection' if index: anchor += '-%s' % index if el.get('id'): anchor = el.get('id') ## FIXME: is a <a name> better? if not el_in_head: el.set('id', anchor) else: anchor = None ## FIXME: add :target CSS rule ## FIXME: or better, some Javascript all_elements.append((anchor, el)) if not el_in_head: style = el.get('style', '') if style: style += '; ' style += '/* deliverance */ border: 2px dotted #f00' el.set('style', style) else: el.set('DELIVERANCE-MATCH', '1') def highlight(html_code): """Highlights the given code (for use in the template)""" if isinstance(html_code, _Element): html_code = tostring(html_code) return html( pygments_highlight(html_code, HtmlLexer(), HtmlFormatter(noclasses=True))) def format_tag(tag): """Highlights the lxml HTML tag""" return highlight(tostring(tag).split('>')[0] + '>') def wrap_html(html, width=100): if isinstance(html, _Element): html = tostring(html) lines = html.splitlines() new_lines = [] def wrap_html_line(line): if len(line) <= width: return [line] match_trail = re.search(r'^[^<]*</.*?>', line, re.S) if match_trail: result = [match_trail.group(0)] result.extend(wrap_html_line(line[match_trail.end():])) return result match1 = re.search(r'^[^<]*<[^>]*>', line, re.S) match2 = re.search(r'<[^>]*>[^<>]*$', line, re.S) if not match1 or not match2: return [line] result = [match1.group(0)] result.extend(wrap_html_line( line[match1.end():match2.start()])) result.append(match2.group(0)) return result for line in lines: new_lines.extend(wrap_html_line(line)) return '\n'.join(new_lines) def mark_deliv_match(highlighted_text): result = re.sub( r'(?:<[^/][^>]*>)*<.*?DELIVERANCE-MATCH=.*?>(?:</[^>]*>)*', lambda match: r'<b style="background-color: #ff8">%s</b>' % match.group(0), str(highlighted_text), re.S) return html(result) text = template.substitute(base_url=url, els_in_head=els_in_head, doc=doc, elements=all_elements, selector=selector, format_tag=format_tag, highlight=highlight, wrap_html=wrap_html, mark_deliv_match=mark_deliv_match) message = fromstring( self._message_template.substitute(message=text, url=url)) if doc.body.text: message.tail = doc.body.text doc.body.text = '' doc.body.insert(0, message) text = tostring(doc) return Response(text) def _el_in_head(self, el): """True if the given element is in the HTML ``<head>``""" while el is not None: if el.tag == 'head': return True el = el.getparent() return False _not_found_template = HTMLTemplate( '''\ There were no elements that matched the selector <code>{{selector}}</code> ''', 'deliverance.middleware.DeliveranceMiddleware._not_found_template') _found_template = HTMLTemplate( '''\ {{if len(elements) == 1}} One element matched the selector <code>{{selector}}</code>; {{if elements[0][0]}} <a href="{{base_url}}#{{elements[0][0]}}">jump to element</a> {{else}} element is in head: {{highlight(elements[0][1])}} {{endif}} {{else}} {{len(elements)}} elements matched the selector <code>{{selector}}</code>: <ol> {{for anchor, el in elements}} {{if anchor}} <li><a href="{{base_url}}#{{anchor}}"><code>{{format_tag(el)}}</code></a></li> {{else}} <li>{{format_tag(el)}}</li> {{endif}} {{endfor}} </ol> {{endif}} {{if els_in_head}} <div style="border-top: 2px solid #000"> <b>Elements matched in head. Showing head:</b><br> <div style="margin: 1em; padding: 0.25em; background-color: #fff"> {{mark_deliv_match(highlight(wrap_html(doc.head)))}} </div> </div> {{endif}} ''', 'deliverance.middleware.DeliveranceMiddleware._found_template') _message_template = HTMLTemplate( '''\ <div style="color: #000; background-color: #f90; border-bottom: 2px dotted #f00; padding: 1em"> <span style="float: right; font-size: 65%"><button onclick="window.close()">close</button></span> Viewing <code><a style="text-decoration: none" href="{{url}}">{{url}}</a></code><br> {{message|html}} </div>''', 'deliverance.middleware.DeliveranceMiddleware._message_template') def action_subreq(self, req, resource_fetcher): log = req.environ['deliverance.log'] from deliverance.log import PrintingLogger log = PrintingLogger(log.request, log.middleware) req.environ['deliverance.log'] = log url = req.GET['url'] subreq = req.copy_get() base = req.environ['deliverance.base_url'] assert url.startswith( base), 'Expected url %r to start with %r' % (url, base) rest = '/' + url[len(base):].lstrip('/') if '?' in rest: rest, qs = rest.split('?', 1) else: qs = '' subreq.script_name = urllib.parse.urlsplit(base)[2] subreq.path_info = rest subreq.query_string = qs resp = subreq.get_response(self.app) if resp.status_int == 304: return resp if resp.status_int != 200: assert 0, 'Failed response to request %s: %s' % (subreq.url, resp.status) assert resp.content_type == 'text/html', ( 'Unexpected content-type: %s (for url %s)' % (resp.content_type, subreq.url)) doc = fromstring(resp.body) if req.GET.get('action'): action = clientside_action(req.GET['action'], content_selector=req.GET['content'], theme_selector=req.GET['theme']) actions = action.clientside_actions(doc, log) else: rule_set = self.rule_getter(resource_fetcher, self.app, req) actions = rule_set.clientside_actions(subreq, resp, log) resp.body = simplejson.dumps(actions) resp.content_type = 'application/json' return resp action_subreq.exposed = True