Пример #1
0
 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
     request_modifications = []
     response_modifications = []
     strip_script_name = True
     keep_host = False
     editable = asbool(el.get('editable'))
     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)
             dest = ProxyDest.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':
             response_modifications.append(
                 ProxyResponseModification.parse_xml(child, source_location))
         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)
     match.proxy = inst
     return inst
Пример #2
0
 def parse_xml(cls, el, source_location):
     """
     Creates a Rule object from a parsed XML <rule> element.
     """
     assert el.tag == 'rule'
     classes = el.get('class', '').split()
     if not classes:
         classes = ['default']
     theme = None
     actions = []
     suppress_standard = asbool(el.get('suppress-standard'))
     for child in el.iterchildren():
         if child.tag == 'theme':
             ## FIXME: error if more than one theme
             theme = Theme.parse_xml(child, source_location)
             continue
         if child.tag is etree.Comment:
             continue
         action = parse_action(child, source_location)
         actions.append(action)
     match = None
     inst = cls(classes, actions, theme, match, suppress_standard, source_location)
     for attr in RuleMatch.match_attrs:
         if el.get(attr):
             inst.match = RuleMatch.parse_xml(inst, el, source_location)
             ## FIXME: would last="1" be a good alternative to suppress-standard?
             break
     return inst
Пример #3
0
 def __init__(self, pattern):
     pattern = pattern.strip()
     super(BooleanMatcher, self).__init__(pattern)
     if pattern.lower() == 'not':
         pattern = 'false'
     if not pattern:
         pattern = 'true'
     self.boolean = asbool(pattern)
Пример #4
0
 def __init__(self, pattern):
     pattern = pattern.strip()
     super(BooleanMatcher, self).__init__(pattern)
     if pattern.lower() == 'not':
         pattern = 'false'
     if not pattern:
         pattern = 'true'
     self.boolean = asbool(pattern)
Пример #5
0
 def __call__(self, s):
     try:
         value = asbool(s)
     except ValueError:
         value = False
     if self.boolean:
         return value
     else:
         return not value
Пример #6
0
    def from_xml(cls, tag, source_location):
        """
        Creates an instance of this object from the given parsed XML element
        """
        content = cls.compile_selector(tag, 'content', source_location)
        theme = cls.compile_selector(tag, 'theme', source_location)
        if_content = cls.compile_selector(tag, 'if-content', source_location, invertable=True)
        content_href = tag.get('href')
        move = asbool(tag.get('move', '1'))
        collapse_sources = asbool(tag.get('collapse-sources', '0'))

        return cls(source_location, content, theme, if_content=if_content,
                   content_href=content_href, move=move,
                   nocontent=tag.get('nocontent'),
                   notheme=tag.get('notheme'),
                   manytheme=tag.get('manytheme'),
                   manycontent=tag.get('manycontent'),
                   collapse_sources=collapse_sources)
Пример #7
0
 def __call__(self, s):
     try:
         value = asbool(s)
     except ValueError:
         value = False
     if self.boolean:
         return value
     else:
         return not value
Пример #8
0
 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)
Пример #9
0
 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)
Пример #10
0
 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)
Пример #11
0
 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)
Пример #12
0
 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)
Пример #13
0
 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)
Пример #14
0
    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)
Пример #15
0
    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
Пример #16
0
 def parse_xml(cls, el, source_location, environ=None, traverse=False):
     """Parse an instance from an etree XML element"""
     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)
         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,
     )
Пример #17
0
    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
Пример #18
0
    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)