def compile_selector(self, expr, default_type): """ Compiles a single selector string to ``(selector_type, selector_object, expression_string, attributes)`` where the selector_type is a string (``"elements"``, ``"children"``, etc), selector_object is a callable that returns elements, expression_string is the original expression, passed in, and ``attributes`` is a list of attributes in the case of ``attributes(attr1, attr2):`` """ type, attributes, rest_expr = self.parse_prefix( expr, default_type=default_type) if not self.types_compatible(type, self.major_type): raise DeliveranceSyntaxError( "Expression %s in selector %r uses the type %r, but this is not " "compatible with the type %r already declared earlier in the selector" % (expr, self, type, self.major_type)) if rest_expr.startswith('/'): selector = XPath(rest_expr) else: try: selector = CSSSelector(rest_expr) except AssertionError as e: raise DeliveranceSyntaxError('Bad CSS selector: "%s" (%s)' % (expr, e)) return (type, selector, expr, attributes)
def parse_xml(cls, el, source_location, attr_name='pyref', default_function=None, default_objs={}): """ Parse an instance of this object from the attributes in the given element. """ s = el.get(attr_name) args = {} for name, value in list(el.attrib.items()): if name.startswith('pyarg-'): args[name[len('pyarg-'):]] = value if not s: if args: raise DeliveranceSyntaxError( "You provided pyargs-* attributes (%s) but no %s attribute" % (cls._format_args(args), attr_name), element=el, source_location=source_location) return None s = s.strip() module = filename = None if s.startswith('file:'): filename = s[len('file:'):] if ':' in filename: filename, func = filename.split(':', 1) else: func = default_function else: # A module name if ':' in s: module, func = s.split(':', 1) else: module = s if func is None: raise DeliveranceSyntaxError( "You must provide a function name", element=s, source_location=source_location) if filename: full_file = cls.expand_filename(filename, source_location) if not os.path.exists(full_file): if full_file != filename: raise DeliveranceSyntaxError( "The filename %r (expanded from %r) does not exist" % (full_file, filename), element=s, source_location=source_location) else: raise DeliveranceSyntaxError( "The filename %r does not exist" % full_file, element=s, source_location=source_location) return cls(module_name=module, filename=filename, function_name=func, args=args, attr_name=attr_name, default_objs=default_objs, source_location=source_location)
def parse_xml(cls, el, source_location): """ Parse an instance from an etree XML element """ app = el.get('app') if not app: raise DeliveranceSyntaxError( "A ``<wsgi>`` tag must have an ``app`` attribute", element=el, source_location=source_location) return cls(app, source_location=source_location)
def parse_xml(cls, el, source_location): """ Parses the <match> element into a match object """ matchargs = cls.parse_match_xml(el, source_location) assert el.tag == cls.element_name classes = el.get('class', '').split() abort = asbool(el.get('abort')) if not abort and not classes: ## FIXME: source location raise DeliveranceSyntaxError( "You must provide some classes in the class attribute") if abort and classes: ## FIXME: source location raise DeliveranceSyntaxError( 'You cannot provide both abort="1" and class="%s"' % (' '.join(classes))) last = asbool(el.get('last')) return cls(classes=classes, abort=abort, last=last, **matchargs)
def parse_xml(cls, el, source_location): """Parse an instance from an etree XML element""" assert el.tag == 'theme' href = el.get('href') pyref = PyReference.parse_xml(el, source_location, default_function='get_theme', default_objs=dict(AbortTheme=AbortTheme)) if not pyref and not href: ## FIXME: also warn when pyref and href? raise DeliveranceSyntaxError( 'You must provide at least one of href, pymodule, or the ' 'pyfile attribute', element=el) return cls(href=href, pyref=pyref, source_location=source_location)
def parse_xml(cls, el, source_location): """Parse an instance from an etree XML element""" href = el.get('href') pyref = PyReference.parse_xml( el, source_location, default_function='get_proxy_dest', default_objs=dict(AbortProxy=AbortProxy)) next = asbool(el.get('next')) if next and (href or pyref): raise DeliveranceSyntaxError( 'If you have a next="1" attribute you cannot also have an href ' 'or pyref attribute', element=el, source_location=source_location) return cls(href, pyref, next=next, source_location=source_location)
def parse_xml(cls, el, source_location): """Parse an instance from an etree XML element""" assert el.tag == 'request' pyref = PyReference.parse_xml(el, source_location, default_function='modify_proxy_request', default_objs=dict(AbortProxy=AbortProxy)) header = el.get('header') content = el.get('content') ## FIXME: the misspelling is annoying :( if (not header and content) or (not content and header): raise DeliveranceSyntaxError( "If you provide a header attribute you must provide a " "content attribute, and vice versa", element=el, source_location=source_location) return cls(pyref, header, content, source_location=source_location)
def parse_xml(cls, el, source_location): """Create an instance from a parsed element""" assert el.tag == 'response' pyref = PyReference.parse_xml( el, source_location, default_function='modify_proxy_response', default_objs=dict(AbortProxy=AbortProxy)) header = el.get('header') content = el.get('content') if (not header and content) or (not content and header): raise DeliveranceSyntaxError( "If you provide a header attribute you must provide a content " "attribute, and vice versa", element=el, source_location=source_location) rewrite_links = asbool(el.get('rewrite-links')) return cls(pyref=pyref, header=header, content=content, rewrite_links=rewrite_links, source_location=source_location)
def expand_filename(filename, source_location=None): """ Expand environmental variables in a filename """ if source_location and source_location.startswith('file:'): here = os.path.dirname(filetourl.url_to_filename(source_location)) else: ## FIXME: this is a lousy default: here = '' vars = NestedDict(dict(here=here), DefaultDict(os.environ)) tmpl = Template(filename) try: return tmpl.substitute(vars) except ValueError as e: raise DeliveranceSyntaxError( "The filename %r contains bad $ substitutions: %s" % (filename, e), filename, source_location=source_location)
def parse_xml(cls, doc, source_location): """ Parses the given XML/etree document into an instance of this class. """ assert doc.tag == 'ruleset' matchers = [] clientsides = [] rules = [] default_theme = None for el in doc.iterchildren(): if el.tag == 'match': matcher = Match.parse_xml(el, source_location) matchers.append(matcher) elif el.tag == 'clientside': matcher = ClientsideMatch.parse_xml(el, source_location) clientsides.append(matcher) elif el.tag == 'rule': rule = Rule.parse_xml(el, source_location) rules.append(rule) elif el.tag == 'theme': ## FIXME: Add parse error default_theme = Theme.parse_xml(el, source_location) elif el.tag in ('proxy', 'server-settings', Comment): # Handled elsewhere, so we just ignore this element continue else: ## FIXME: source location? raise DeliveranceSyntaxError( "Invalid tag %s (unknown tag name %r)" % (tostring(el).split('>', 1)[0] + '>', el.tag), element=el) rules_by_class = {} for rule in rules: for class_name in rule.classes: rules_by_class.setdefault(class_name, []).append(rule) return cls(matchers, clientsides, rules_by_class, default_theme=default_theme, source_location=source_location)
def parse_xml(cls, el, source_location, environ=None, traverse=False): """Parse an instance from an etree XML element""" middleware_factory = None middleware_factory_kwargs = None if traverse and el.tag != 'server-settings': try: el = el.xpath('//server-settings')[0] except IndexError: raise DeliveranceSyntaxError( "There is no <server-settings> element", element=el) if environ is None: environ = os.environ assert el.tag == 'server-settings' server_host = 'localhost:8080' ## FIXME: should these defaults be passed in: execute_pyref = True display_local_files = True edit_local_files = True dev_allow_ips = [] dev_deny_ips = [] dev_htpasswd = None dev_expiration = 0 dev_users = {} dev_secret_file = os.path.join(tempfile.gettempdir(), 'deliverance', 'devauth.txt') for child in el: if child.tag is Comment: continue ## FIXME: should some of these be attributes? elif child.tag == 'server': server_host = cls.substitute(child.text, environ) elif child.tag == 'execute-pyref': execute_pyref = asbool(cls.substitute(child.text, environ)) elif child.tag == 'dev-allow': dev_allow_ips.extend( cls.substitute(child.text, environ).split()) elif child.tag == 'dev-deny': dev_deny_ips.extend( cls.substitute(child.text, environ).split()) elif child.tag == 'dev-htpasswd': dev_htpasswd = os.path.join( os.path.dirname(url_to_filename(source_location)), cls.substitute(child.text, environ)) elif child.tag == 'dev-expiration': dev_expiration = cls.substitute(child.text, environ) if dev_expiration: dev_expiration = int(dev_expiration) elif child.tag == 'display-local-files': display_local_files = asbool( cls.substitute(child.text, environ)) elif child.tag == 'edit-local-files': edit_local_files = asbool(cls.substitute(child.text, environ)) elif child.tag == 'dev-user': username = cls.substitute(child.get('username', ''), environ) ## FIXME: allow hashed password? password = cls.substitute(child.get('password', ''), environ) if not username or not password: raise DeliveranceSyntaxError( "<dev-user> must have both a username and password attribute", element=child) if username in dev_users: raise DeliveranceSyntaxError( '<dev-user username="******"> appears more than once' % username, element=el) dev_users[username] = password elif child.tag == 'dev-secret-file': dev_secret_file = cls.substitute(child.text, environ) elif child.tag == 'middleware-factory': ref = PyReference.parse_xml(child, source_location) middleware_factory = ref.function middleware_factory_kwargs = ref.args or None else: raise DeliveranceSyntaxError( 'Unknown element in <server-settings>: <%s>' % child.tag, element=child) if dev_users and dev_htpasswd: raise DeliveranceSyntaxError( "You can use <dev-htpasswd> or <dev-user>, but not both", element=el) if not dev_users and not dev_htpasswd: ## FIXME: not sure this is the best way to warn print( 'Warning: no <dev-users> or <dev-htpasswd>; logging is inaccessible' ) ## FIXME: add a default allow_ips of 127.0.0.1? return cls(server_host, execute_pyref=execute_pyref, display_local_files=display_local_files, edit_local_files=edit_local_files, dev_allow_ips=dev_allow_ips, dev_deny_ips=dev_deny_ips, dev_users=dev_users, dev_htpasswd=dev_htpasswd, dev_expiration=dev_expiration, source_location=source_location, dev_secret_file=dev_secret_file, middleware_factory=middleware_factory, middleware_factory_kwargs=middleware_factory_kwargs)
def parse_xml(cls, el, source_location): """Parse this document from an XML/etree element""" assert el.tag == 'proxy' match = ProxyMatch.parse_xml(el, source_location) dest = None wsgi = None request_modifications = [] response_modifications = [] strip_script_name = True keep_host = False editable = asbool(el.get('editable')) rewriting_links = None ## FIXME: this inline validation is a bit brittle because it is ## order-dependent, but validation errors generally aren't for child in el: if child.tag == 'dest': if dest is not None: raise DeliveranceSyntaxError( "You cannot have more than one <dest> tag (second tag: %s)" % xml_tostring(child), element=child, source_location=source_location) if wsgi is not None: raise DeliveranceSyntaxError( "You cannot have both a <dest> tag and a <wsgi> tag (second tag: %s)" % xml_tostring(child), element=child, source_location=source_location) dest = ProxyDest.parse_xml(child, source_location) elif child.tag == 'wsgi': if wsgi is not None: raise DeliveranceSyntaxError( "You cannot have more than one <wsgi> tag (second tag: %s)" % xml_tostring(child), element=child, source_location=source_location) if dest is not None: raise DeliveranceSyntaxError( "You cannot have both a <dest> tag and a <wsgi> tag (second tag: %s)" % xml_tostring(child), element=child, source_location=source_location) if rewriting_links is not None: raise DeliveranceSyntaxError( "You cannot use ``<response rewrite-links='1'>`` in a proxy with a ``<wsgi>`` tag", element=child, source_location=source_location) wsgi = ProxyWsgi.parse_xml(child, source_location) elif child.tag == 'transform': if child.get('strip-script-name'): strip_script_name = asbool(child.get('strip-script-name')) if child.get('keep-host'): keep_host = asbool(child.get('keep-host')) ## FIXME: error on other attrs elif child.tag == 'request': request_modifications.append( ProxyRequestModification.parse_xml(child, source_location)) elif child.tag == 'response': mod = ProxyResponseModification.parse_xml( child, source_location) if mod.rewrite_links == True: rewriting_links = mod if wsgi is not None: raise DeliveranceSyntaxError( "You cannot use ``<response rewrite-links='1'>`` in a proxy with a ``<wsgi>`` tag", element=child, source_location=source_location) response_modifications.append(mod) elif child.tag is Comment: continue else: raise DeliveranceSyntaxError("Unknown tag in <proxy>: %s" % xml_tostring(child), element=child, source_location=source_location) if editable: if not dest: ## FIXME: should this always be a test? raise DeliveranceSyntaxError("You must have a <dest> tag", element=el, source_location=source_location) try: href = uri_template_substitute( dest.href, dict(here=posixpath.dirname(source_location))) except KeyError: raise DeliveranceSyntaxError( 'You can only use <proxy editable="1"> if you have a <dest href="..."> that only contains {here} (you have %s)' % (dest.href)) if not href.startswith('file:'): raise DeliveranceSyntaxError( 'You can only use <proxy editable="1"> if you have a <dest href="file:///..."> (you have %s)' % (dest)) classes = el.get('class', '').split() or None inst = cls(match, dest, request_modifications, response_modifications, strip_script_name=strip_script_name, keep_host=keep_host, source_location=source_location, classes=classes, editable=editable, wsgi=wsgi) match.proxy = inst return inst