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 __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))
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>
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()
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}}
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 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)
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)
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