# -*- coding: utf-8 -*-
#
# Cherokee-admin
#
# Authors:
#      Alvaro Lopez Ortega <alvaro@alobbs.com>
#
# Copyright (C) 2001-2011 Alvaro Lopez Ortega
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of version 2 of the GNU General Public
# License as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
#

import CTK
import copy
import Page
import Cherokee
import validations

import os
import re

from util import *
from consts import *
from configured import *

from Rule import Rule
from CTK.util import find_copy_name

URL_BASE  = '/vserver/content'
URL_APPLY = '/vserver/content/apply'

NOTE_NICKNAME         = N_('Nickname for the virtual server.')
NOTE_CERT             = N_('This directive points to the PEM-encoded Certificate file for the server (Full path to the file)')
NOTE_CERT_KEY         = N_('PEM-encoded Private Key file for the server (Full path to the file)')
NOTE_CA_LIST          = N_('File containing the trusted CA certificates, utilized for checking the client certificates (Full path to the file)')
NOTE_CIPHERS          = N_('Ciphers that TLS/SSL is allowed to use. <a target="_blank" href="http://www.openssl.org/docs/apps/ciphers.html">Reference</a>. (Default: all ciphers supported by the OpenSSL version used).')
NOTE_CLIENT_CERTS     = N_('Skip, Accept or Require client certificates.')
NOTE_VERIFY_DEPTH     = N_('Limit up to which depth certificates in a chain are used during the verification procedure (Default: 1)')
NOTE_ERROR_HANDLER    = N_('Allows the selection of how to generate the error responses.')
NOTE_PERSONAL_WEB     = N_('Directory inside the user home directory to use as root web directory. Disabled if empty.')
NOTE_DISABLE_PW       = N_('The personal web support is currently turned on.')
NOTE_ADD_DOMAIN       = N_('Adds a new domain name. Wildcards are allowed in the domain name.')
NOTE_DOCUMENT_ROOT    = N_('Virtual Server root directory.')
NOTE_DIRECTORY_INDEX  = N_('List of name files that will be used as directory index. Eg: <em>index.html,index.php</em>.')
NOTE_MAX_UPLOAD_SIZE  = N_('The maximum size, in bytes, for POST uploads. (Default: unlimited)')
NOTE_KEEPALIVE        = N_('Whether this virtual server is allowed to use Keep-alive (Default: yes)')
NOTE_DISABLE_LOG      = N_('The Logging is currently enabled.')
NOTE_LOGGERS          = N_('Logging format. Apache compatible is highly recommended here.')
NOTE_ACCESSES         = N_('Back-end used to store the log accesses.')
NOTE_ERRORS           = N_('Back-end used to store the log errors.')
NOTE_ACCESSES_ERRORS  = N_('Back-end used to store the log accesses and errors.')
NOTE_WRT_FILE         = N_('Full path to the file where the information will be saved.')
NOTE_WRT_EXEC         = N_('Path to the executable that will be invoked on each log entry.')
NOTE_X_REAL_IP        = N_('Whether the logger should read and use the X-Real-IP and X-Forwarded-For headers (send by reverse proxies).')
NOTE_X_REAL_IP_ALL    = N_('Accept all the X-Real-IP and X-Forwarded-For headers. WARNING: Turn it on only if you are centain of what you are doing.')
NOTE_X_REAL_IP_ACCESS = N_('List of IP addresses and subnets that are allowed to send X-Real-IP and X-Forwarded-For headers.')
NOTE_EVHOST           = N_('How to support the "Advanced Virtual Hosting" mechanism. (Default: off)')
NOTE_LOGGER_TEMPLATE  = N_('The following variables are accepted: ')
NOTE_MATCHING_METHOD  = N_('Allows the selection of domain matching method.')
NOTE_COLLECTOR        = N_('Whether or not it should collected statistics about the traffic of this virtual server.')
NOTE_UTC_TIME         = N_('Time standard to use in the log file entries.')
NOTE_INDEX_USAGE      = N_('Remember that only "File Exists" rules and "List & Send" handlers use the Directory Indexes setting.')
NOTE_MATCH_NICK       = N_('Use this nickname as an additional host name for this virtual server (Default: yes)')

DEFAULT_HOST_NOTE     = N_("<p>The 'default' virtual server matches all the domain names.</p>")

CUSTOM_MACROS_LIST = [
    ('ip_remote',          N_("Remote IP address")),
    ('ip_local',           N_("Local IP address")),
    ('protocol',           N_("Request Protocol")),
    ('transport',          N_("Transport type: http or https")),
    ('port_server',        N_("Port of the server serving the request")),
    ('query_string',       N_("The query string, if exists")),
    ('request_first_line', N_("First line of HTTP request")),
    ('status',             N_("Response status code")),
    ('now',                N_("Time: in common log format time format")),
    ('time_secs',          N_("Time: seconds since Epoch")),
    ('time_msecs',         N_("Time: milliseconds since Epoch")),
    ('user_remote',        N_("Remote user (authentication)")),
    ('request',            N_("URL path requested")),
    ('request_original',   N_("URL path requested before any rewrite")),
    ('vserver_name',       N_("Virtual Server nick name")),
    ('vserver_name_req',   N_("Requested host (Host: header)")),
    ('response_size',      N_("Size of the response in bytes")),
    ('http_host',          N_("'Host:' header of the request")),
    ('http_referrer',      N_("'Referrer:' header of the request")),
    ('http_user_agent',    N_("'User-Agent:' header of the request")),
    ('http_cookie',        N_("'Cookie:' header of the request")),
]


JS_TR_ODD = """
$('#rules-table tr:odd').addClass('trodd');
"""

JS_TR_INACTIVE = """
$("#rules-table tr td .inactive-rule").parent().parent().addClass('inactive-rule');
"""

def validation_ca_list (text):
    "Empty or local_file_exists"
    if not text:
        return text
    text = validations.is_local_file_exists (text)
    return text

VALIDATIONS = [
    ("vserver![\d]+!user_dir",                   validations.is_safe_id),
    ("vserver![\d]+!document_root",              validations.is_dev_null_or_local_dir_exists),
    ("vserver![\d]+!post_max_len",               validations.is_positive_int),
    ("vserver![\d]+!ssl_certificate_file",       validations.is_local_file_exists),
    ("vserver![\d]+!ssl_certificate_key_file",   validations.is_local_file_exists),
    ("vserver![\d]+!ssl_ca_list_file",           validation_ca_list),
    ("vserver![\d]+!ssl_verify_depth",           validations.is_positive_int),
    ("vserver![\d]+!error_writer!filename",      validations.can_create_file),
    ("vserver![\d]+!error_writer!command",       validations.is_local_file_exists),
    ("vserver![\d]+!logger!.+?!filename",        validations.can_create_file),
    ("vserver![\d]+!logger!.+?!command",         validations.is_local_file_exists),
    ("vserver![\d]+!logger!x_real_ip_access$",   validations.is_ip_or_netmask_list),
]

HELPS = [
    ('config_virtual_servers', N_("Virtual Servers")),
    ('modules_loggers',        N_("Loggers")),
    ('cookbook_ssl',           N_("SSL cookbook"))
]

BEHAVIOR_TAGS = [
    (N_('Rule Match'),           N_('Match')),
    (N_('Assigned Handler'),     N_('Handler')),
    (N_('Authentication'),       N_('Auth')),
    (N_('Custom Document Root'), N_('Root')),
    (N_('Only HTTPS'),           N_('Secure')),
    (N_('Encoders'),             N_('Enc')),
    (N_('Cacheable'),            N_('Cache')),
    (N_('Expiration'),           N_('Exp')),
    (N_('Connection Timeout'),   N_('Timeout')),
    (N_('Traffic Shaping'),      N_('Shaping')),
    (N_('Logging Enabled'),      N_('Log')),
    (N_('Rule is Final'),        N_('Final')),
]


def commit_clone():
    num = re.findall(r'^%s/([\d]+)/clone$'%(URL_BASE), CTK.request.url)[0]
    next = CTK.cfg.get_next_entry_prefix ('vserver')

    orig  = CTK.cfg.get_val ('vserver!%s!nick'%(num))
    names = [CTK.cfg.get_val('vserver!%s!nick'%(x)) for x in CTK.cfg.keys('vserver')]
    new_nick = find_copy_name (orig, names)

    CTK.cfg.clone ('vserver!%s'%(num), next)
    CTK.cfg['%s!nick' %(next)] = new_nick
    return CTK.cfg_reply_ajax_ok()


class HostMatchWidget_GeneralProps (CTK.Container):
    def __init__ (self, vsrv_num, refresh):
        CTK.Container.__init__ (self)

        pre       = "vserver!%s" %(vsrv_num)
        url_apply = "%s/%s" %(URL_APPLY, vsrv_num)

        if CTK.cfg.get_val ('%s!match'%(pre)):
            table = CTK.PropsAuto (url_apply)
            table.Add (_('Match nickname'), CTK.CheckCfgText('%s!match!nick'%(pre), True, _('Match')), _(NOTE_MATCH_NICK))
            submit = CTK.Submitter (url_apply)
            submit += CTK.Indenter (table)

            self += CTK.RawHTML ('<h2>%s</h2>' %(_('Host Match')))
            self += submit


class HostMatchWidget (CTK.Container):
    def __init__ (self, vsrv_num):
        CTK.Container.__init__ (self)

        pre        = "vserver!%s" %(vsrv_num)
        url_apply  = "%s/%s" %(URL_APPLY, vsrv_num)
        is_default = CTK.cfg.get_lowest_entry("vserver") == int(vsrv_num)
        is_complex = CTK.cfg.get_val ('%s!match'%(pre)) == 'v_or'

        # Default Virtual Server
        if is_default:
            notice  = CTK.Notice()
            notice += CTK.RawHTML(_(DEFAULT_HOST_NOTE))
            self += notice
            return

        # Complex matches
        if is_complex:
            # General Properties
            refresh = CTK.Refreshable ({'id': 'vserver_match_props'})
            refresh.register (lambda: HostMatchWidget_GeneralProps(vsrv_num, refresh).Render())
            self += refresh

            # Left side rule
            table = CTK.PropsAuto (url_apply)
            modul = CTK.PluginSelector ('%s!match!left'%(pre), trans_options(Cherokee.support.filter_available(VRULES)), vsrv_num=vsrv_num)
            table.Add ('%s 1'%(_('Method')), modul.selector_widget, _(NOTE_MATCHING_METHOD), False)
            self += CTK.Indenter (table)
            self += modul

            submit = CTK.Submitter ('%s/%s/vmatch'%(URL_APPLY, vsrv_num))
            submit += CTK.Hidden ('op', 'del_1')
            submit.bind ('submit_success', refresh.JS_to_refresh())

            remove_1 = CTK.Link (None, CTK.RawHTML(_('Remove criteria')))
            remove_1.bind ('click', submit.JS_to_submit())
            self += submit
            self += remove_1

            # Right side rule
            table = CTK.PropsAuto (url_apply)
            modul = CTK.PluginSelector ('%s!match!right'%(pre), trans_options(Cherokee.support.filter_available(VRULES)), vsrv_num=vsrv_num)
            table.Add ('%s 2'%(_('Method')), modul.selector_widget, _(NOTE_MATCHING_METHOD), False)
            self += CTK.Indenter (table)
            self += modul

            submit = CTK.Submitter ('%s/%s/vmatch'%(URL_APPLY, vsrv_num))
            submit += CTK.Hidden ('op', 'del_2')
            submit.bind ('submit_success', refresh.JS_to_refresh())

            remove_2 = CTK.Link (None, CTK.RawHTML(_('Remove criteria')))
            remove_2.bind ('click', submit.JS_to_submit())
            self += submit
            self += remove_2

        # Simple match
        else:
            # General Properties
            refresh = CTK.Refreshable ({'id': 'vserver_match_props'})
            refresh.register (lambda: HostMatchWidget_GeneralProps(vsrv_num, refresh).Render())
            self += refresh

            # Match module
            table = CTK.PropsAuto (url_apply)
            modul = CTK.PluginSelector ('%s!match'%(pre), trans_options(Cherokee.support.filter_available(VRULES)), vsrv_num=vsrv_num)
            modul.selector_widget.bind ('changed', refresh.JS_to_refresh())
            table.Add (_('Method'), modul.selector_widget, _(NOTE_MATCHING_METHOD), False)

            self += CTK.RawHTML ('<h2>%s</h2>' %(_('How to match this Virtual Server')))
            self += CTK.Indenter (table)
            self += modul

            submit = CTK.Submitter ('%s/%s/vmatch'%(URL_APPLY, vsrv_num))
            submit += CTK.Hidden ('op', 'or')

            link_add = CTK.Link (None, CTK.RawHTML (_('Add an additional matching criteria')))
            link_add.bind ('click', submit.JS_to_submit())

            self += submit
            self += link_add


def HostMatchWidget_Apply():
    def move (old, new):
        val = CTK.cfg.get_val(old)
        tmp = copy.deepcopy (CTK.cfg[old])
        del (CTK.cfg[old])

        CTK.cfg[new] = val
        CTK.cfg.set_sub_node (new, tmp)

    vsrv_num = re.findall (r'^%s/(\d+)/vmatch'%(URL_APPLY), CTK.request.url)[0]
    pre      = 'vserver!%s!match' %(vsrv_num)

    op = CTK.post.get_val ('op')
    if op == 'or':
        # Ensure the first match is set
        # before adding a second entry.
        if not CTK.cfg.get_val (pre):
            return CTK.cfg_reply_ajax_ok()

        move (pre, "%s!left"%(pre))
        CTK.cfg[pre] = 'v_or'
        return CTK.cfg_reply_ajax_ok()

    elif op == 'del_1':
        # Is the second rule empty?
        if not CTK.cfg.get_val ('%s!right'%(pre)):
            del (CTK.cfg[pre])
            return CTK.cfg_reply_ajax_ok()

        move ('%s!right'%(pre), pre)
        return CTK.cfg_reply_ajax_ok()

    elif op == 'del_2':
        # Is the first rule empty?
        if not CTK.cfg.get_val ('%s!left'%(pre)):
            del (CTK.cfg[pre])
            return CTK.cfg_reply_ajax_ok()

        move ('%s!left'%(pre), pre)
        return CTK.cfg_reply_ajax_ok()

    return {'ret': 'fail'}


class RuleLink (CTK.Link):
    def __init__ (self, vsrv_num, rule_num, content):
        CTK.Link.__init__ (self, '/vserver/%s/rule/%s'%(vsrv_num, rule_num), content)
        self.vsrv_num = vsrv_num
        self.rule_num = rule_num

    def Render (self):
        from SelectionPanel import COOKIE_NAME_DEFAULT

        props = {'id':          self.id,
                 'vsrv_num':    self.vsrv_num,
                 'rule_num':    self.rule_num,
                 'cookie_name': COOKIE_NAME_DEFAULT}

        render = CTK.Link.Render (self)
        render.js += """$('#%(id)s').click(function(){
            $.cookie ('%(cookie_name)s', '%(rule_num)s_%(vsrv_num)s', { path: '/vserver/%(vsrv_num)s/rule'});
            window.location.replace ('/vserver/%(vsrv_num)s/rule');
            return false;
        });"""%(props)

        return render


class BehaviorWidget (CTK.Container):
    def __init__ (self, vsrv_num):
        CTK.Container.__init__ (self)

        # List
        table = CTK.Table({'id': 'rules-table'})
        table.set_header(1)
        table += [CTK.RawHTML('<span title="%s">%s</span>'%(_(x[0]),_(x[1]))) for x in BEHAVIOR_TAGS]

        rules = CTK.cfg.keys('vserver!%s!rule'%(vsrv_num))
        rules.sort (lambda x,y: cmp(int(x), int(y)))
        rules.reverse()

        for r in rules:
            table += self._get_row (vsrv_num, r)

        self += CTK.RawHTML ('<h2>%s</h2>' %(_('Behavior Rules')))
        self += CTK.RawHTML (js=JS_TR_ODD+JS_TR_INACTIVE)
        self += CTK.Indenter (table)

        if rules:
            first_rule = rules[0]
        else:
            first_rule = '0'

        button = CTK.Button(_('Rule Management'), {'id':'rule-management'})
        button.bind ('click', "window.location = '/vserver/%s/rule/%s'"%(vsrv_num, first_rule))
        self += CTK.Indenter (button)


    def _get_row (self, vsrv_num, r):
        rule = Rule ('vserver!%s!rule!%s!match'%(vsrv_num, r))
        rule_name = rule.GetName()
        link = RuleLink (vsrv_num, r, CTK.RawHTML ('<span title="%s">%s</span>' %(rule_name, rule_name)))

        handler, auth, root, secure, enc, exp, flcache, timeout, shaping, log, final, active = [None for x in range(12)]

        tmp = CTK.cfg.get_val ('vserver!%s!rule!%s!handler'%(vsrv_num, r), '')
        if tmp:
            handler = CTK.RawHTML (_(filter (lambda x: x[0] == tmp, HANDLERS)[0][1]))

        tmp = CTK.cfg.get_val ('vserver!%s!rule!%s!auth'%(vsrv_num, r), '')
        if tmp:
            auth = CTK.RawHTML (_(filter (lambda x: x[0] == tmp, VALIDATORS)[0][1]))

        tmp = (CTK.cfg.get_val ('vserver!%s!rule!%s!document_root'%(vsrv_num, r)))
        if tmp:
            root = CTK.ImageStock('tick', {'alt': tmp, 'title':tmp})

        tmp = bool (int (CTK.cfg.get_val ('vserver!%s!rule!%s!only_secure'%(vsrv_num, r), "0")))
        if tmp:
            tmp = _('Only accept Secure Connections')
            secure = CTK.ImageStock('tick', {'alt': tmp, 'title': tmp})

        tmp =  CTK.cfg.keys ('vserver!%s!rule!%s!encoder'%(vsrv_num, r))
        if tmp:
            diz = [_('Encoding enabled')]
            for e in tmp:
                val = CTK.cfg.get_val ('vserver!%s!rule!%s!encoder!%s'%(vsrv_num, r, e))
                if val == 'allow':
                    diz.append (e)
                elif val == 'forbid':
                    diz.append ("%s %s"%(_('no'), e))
            txt = ', '.join(diz)
            enc = CTK.ImageStock('tick', {'alt': txt, 'title':txt})

        tmp = CTK.cfg.get_val ('vserver!%s!rule!%s!expiration' %(vsrv_num, r))
        if tmp:
            tmp = tmp.capitalize()
            exp = CTK.ImageStock('tick', {'alt': tmp, 'title': tmp})

        tmp = CTK.cfg.get_val ('vserver!%s!rule!%s!timeout' %(vsrv_num, r))
        if tmp:
            tmp += _(' seconds')
            timeout = CTK.ImageStock('tick', {'alt': tmp, 'title': tmp})

        tmp = CTK.cfg.get_val ('vserver!%s!rule!%s!rate' %(vsrv_num, r))
        if tmp:
            tmp += _(' bytes per second')
            shaping = CTK.ImageStock('tick', {'alt': tmp, 'title': tmp})

        tmp = not bool (int (CTK.cfg.get_val ('vserver!%s!rule!%s!no_log' %(vsrv_num, r), "0")))
        if tmp:
            tmp = _('Logging enabled for this rule')
            log = CTK.ImageStock('tick', {'alt': tmp, 'title': tmp})

        tmp = bool (int (CTK.cfg.get_val('vserver!%s!rule!%s!match!final'%(vsrv_num,r), "1")))
        if tmp:
            tmp   = _('Prevents further rule evaluation')
            final = CTK.ImageStock('tick', {'alt': tmp, 'title': tmp})

        tmp = CTK.cfg.get_val('vserver!%s!rule!%s!flcache'%(vsrv_num,r))
        if tmp in ('allow', '1'):
            tmp     = _('Content can be cached')
            flcache = CTK.ImageStock('tick', {'alt': tmp, 'title': tmp})
        elif tmp in ('forbid', '0'):
            tmp     = _('Forbids content caching')
            flcache = CTK.ImageStock ('forbid', {'alt': tmp, 'title': tmp})

        return [link, handler, auth, root, secure, enc, flcache, exp, timeout, shaping, log, final]


class BasicsWidget (CTK.Container):
    def __init__ (self, vsrv_num):
        CTK.Container.__init__ (self)

        pre        = "vserver!%s" %(vsrv_num)
        url_apply  = "%s/%s" %(URL_APPLY, vsrv_num)
        is_default = CTK.cfg.get_lowest_entry("vserver") == int(vsrv_num)

        # Server ID
        if not is_default:
            table = CTK.PropsAuto (url_apply)
            table.Add (_('Virtual Server nickname'), CTK.TextCfg('%s!nick'%(pre)), _(NOTE_NICKNAME))

            self += CTK.RawHTML ('<h2>%s</h2>' %(_('Server ID')))
            self += CTK.Indenter (table)

        # Paths
        table = CTK.PropsAuto (url_apply)
        if not CTK.cfg.get_val ('%s!document_root'%(pre),'').startswith(CHEROKEE_OWS_ROOT):
            table.Add (_('Document Root'), CTK.TextCfg('%s!document_root'%(pre)),   _(NOTE_DOCUMENT_ROOT))
        else:
            table.Add (_('Document Root'), CTK.TextCfg('%s!document_root'%(pre), False, {'disabled':True}), _(NOTE_DOCUMENT_ROOT))

        table.Add (_('Directory Indexes'), CTK.TextCfg('%s!directory_index'%(pre)), _(NOTE_DIRECTORY_INDEX))

        self += CTK.RawHTML ('<h2>%s</h2>' %(_('Paths')))
        self += CTK.Indenter (table)

        if self.is_index_used (vsrv_num):
            tip = _(NOTE_INDEX_USAGE)
        else:
            tip = '%s %s' %(_(NOTE_INDEX_USAGE), _('Neither are in use at the moment.'))
        self += CTK.Indenter (CTK.Notice ('information', CTK.RawHTML(tip)))

        # Network
        table = CTK.PropsAuto (url_apply)
        table.Add (_('Keep-alive'),      CTK.CheckCfgText('%s!keepalive'%(pre), True, _('Allow')), _(NOTE_KEEPALIVE))
        table.Add (_('Max Upload Size'), CTK.TextCfg('%s!post_max_len'%(pre), True), _(NOTE_MAX_UPLOAD_SIZE))

        self += CTK.RawHTML ('<h2>%s</h2>' %(_('Network')))
        self += CTK.Indenter (table)

        # Advanced Virtual Hosting
        table = CTK.PropsAuto (url_apply)
        modul = CTK.PluginSelector('%s!evhost'%(pre), trans_options(Cherokee.support.filter_available(EVHOSTS)))
        table.Add (_('Method'), modul.selector_widget, _(NOTE_EVHOST), False)

        self += CTK.RawHTML ('<h2>%s</h2>' %(_('Advanced Virtual Hosting')))
        self += CTK.Indenter (table)
        self += CTK.Indenter (modul)


    def is_index_used (self, vsrv_num):
        key     = 'vserver!%s!rule' %(vsrv_num)
        matches = ['common', 'exists']

        def recursive_find (key):
            if CTK.cfg.get_val(key) in matches:
                return True

            for k in CTK.cfg.keys(key):
                if recursive_find ('%s!%s' %(key,k)):
                    return True
            return False

        if recursive_find (key):
            return True
        return False


class ErrorHandlerWidget (CTK.Container):
    def __init__ (self, vsrv_num):
        CTK.Container.__init__ (self)

        pre        = "vserver!%s" %(vsrv_num)
        url_apply  = "%s/%s" %(URL_APPLY, vsrv_num)

        table = CTK.PropsAuto (url_apply)
        modul = CTK.PluginSelector ('%s!error_handler'%(pre), trans_options(Cherokee.support.filter_available(ERROR_HANDLERS)), vsrv_num=vsrv_num)
        table.Add (_('Method'), modul.selector_widget, _(NOTE_ERROR_HANDLER), False)

        self += CTK.RawHTML ('<h2>%s</h2>' %(_('Error Handling hook')))
        self += CTK.Indenter (table)
        self += modul


class LogginWidgetContent (CTK.Container):
    def __init__ (self, vsrv_num, refreshable):
        CTK.Container.__init__ (self)

        pre        = "vserver!%s" %(vsrv_num)
        url_apply  = "%s/%s/log" %(URL_APPLY, vsrv_num)
        writers    = [('', _('Disabled'))] + trans_options(LOGGER_WRITERS)

        # Error writer
        table = CTK.PropsTable()
        table.Add (_('Write errors to'), CTK.ComboCfg('%s!error_writer!type'%(pre), writers), _(NOTE_ERRORS))

        writer = CTK.cfg.get_val ('%s!error_writer!type'%(pre))
        if writer == 'file':
            table.Add (_('Filename'), CTK.TextCfg('%s!error_writer!filename'%(pre)), _(NOTE_WRT_FILE))
        elif writer == 'exec':
            table.Add (_('Command'), CTK.TextCfg('%s!error_writer!command'%(pre)), _(NOTE_WRT_EXEC))

        submit = CTK.Submitter(url_apply)
        submit.bind ('submit_success', refreshable.JS_to_refresh())
        submit += table

        self += CTK.RawHTML ('<h2>%s</h2>' % (_('Error Logging')))
        self += CTK.Indenter (submit)

        # Access logger
        pre = 'vserver!%s!logger' %(vsrv_num)

        submit = CTK.Submitter(url_apply)
        submit.bind ('submit_success', refreshable.JS_to_refresh())
        submit += CTK.ComboCfg(pre, trans_options(Cherokee.support.filter_available(LOGGERS)))

        table = CTK.PropsTable()
        table.Add (_('Format'), submit, _(NOTE_LOGGERS))

        format = CTK.cfg.get_val(pre)
        if format:
            submit = CTK.Submitter(url_apply)
            submit.bind ('submit_success', refreshable.JS_to_refresh())
            submit += CTK.ComboCfg('%s!access!type'%(pre), trans_options(LOGGER_WRITERS))
            table.Add (_('Write accesses to'), submit, _(NOTE_ACCESSES))

            submit = CTK.Submitter(url_apply)
            writer = CTK.cfg.get_val ('%s!access!type'%(pre))
            if not writer:
                CTK.cfg['%s!access!type'%(pre)] = 'file'

            if writer == 'file' or not writer:
                # Set a default value
                if not CTK.cfg.get_val ('%s!access!filename'%(pre)):
                    CTK.cfg['%s!access!filename'%(pre)] = os.path.join (CHEROKEE_VAR_LOG, 'cherokee.log')

                submit += CTK.TextCfg('%s!access!filename'%(pre), False)
                table.Add (_('Filename'), submit, _(NOTE_WRT_FILE))
            elif writer == 'exec':
                submit += CTK.TextCfg('%s!access!command'%(pre))
                table.Add (_('Command'), submit, _(NOTE_WRT_EXEC))

            if format == 'custom':
                table_macros = CTK.Table ({'class': 'custom-logger-macros'})
                for macro, desc in CUSTOM_MACROS_LIST:
                    table_macros += [CTK.RawHTML('${%s}'%(macro)), CTK.RawHTML (_(desc))]

                collaps  = CTK.CollapsibleEasy ((_('Show macros'), _('Hide')))
                collaps += table_macros

                macros = CTK.Container()
                macros += CTK.RawHTML (_(NOTE_LOGGER_TEMPLATE))
                macros += collaps

                submit = CTK.Submitter(url_apply)
                submit += CTK.TextCfg('%s!access_template'%(pre))
                table.Add (_('Template: '), submit, macros)

        self += CTK.RawHTML ('<h2>%s</h2>' % (_('Access Logging')))
        self += CTK.Indenter (table)

        # Properties
        if CTK.cfg.get_val (pre):
            table = CTK.PropsTable()
            table.Add (_('Time standard'), CTK.ComboCfg ('%s!utc_time'%(pre), trans_options(UTC_TIME)), _(NOTE_UTC_TIME))
            table.Add (_('Accept Forwarded IPs'), CTK.CheckCfgText ('%s!x_real_ip_enabled'%(pre), False, _('Accept')), _(NOTE_X_REAL_IP))

            if int (CTK.cfg.get_val('%s!x_real_ip_enabled'%(pre), "0")):
                table.Add (_('Don\'t check origin'), CTK.CheckCfgText ('%s!x_real_ip_access_all'%(pre), False, _('Do not check')), _(NOTE_X_REAL_IP_ALL))

                if not int (CTK.cfg.get_val ('%s!x_real_ip_access_all'%(pre), "0")):
                    table.Add (_('Accept from Hosts'), CTK.TextCfg ('%s!x_real_ip_access'%(pre)), _(NOTE_X_REAL_IP_ACCESS))

            submit = CTK.Submitter(url_apply)
            submit.bind ('submit_success', refreshable.JS_to_refresh())
            submit += table

            self += CTK.RawHTML ('<h3>%s</h3>' % (_('Logging Options')))
            self += CTK.Indenter (submit)

def LogginWidgetContent_Apply():
    # Apply
    ret = CTK.cfg_apply_post()

    # Check Error Writer stale-entries
    vsrv_num = re.findall (r'^%s/(\d+)/log'%(URL_APPLY), CTK.request.url)[0]
    pre      = 'vserver!%s!error_writer' %(vsrv_num)

    if not CTK.cfg.get_val ('%s!type'%(pre)):
        del (CTK.cfg[pre])

    return ret


class LogginWidget (CTK.Container):
    def __init__ (self, vsrv_num):
        CTK.Container.__init__ (self)

        # Content
        refresh = CTK.Refreshable({'id': 'vserver_login'})
        refresh.register (lambda: LogginWidgetContent(vsrv_num, refresh).Render())
        self += refresh


class SecutiryWidgetContent (CTK.Container):
    def __init__ (self, vsrv_num, refreshable):
        CTK.Container.__init__ (self)

        pre        = "vserver!%s" %(vsrv_num)
        url_apply  = "%s/%s" %(URL_APPLY, vsrv_num)

        # Required SSL/TLS values
        table = CTK.PropsTable()
        table.Add (_('Certificate'),     CTK.TextCfg ('%s!ssl_certificate_file'%(pre), True), _(NOTE_CERT))
        table.Add (_('Certificate key'), CTK.TextCfg ('%s!ssl_certificate_key_file'%(pre), True), _(NOTE_CERT_KEY))

        submit = CTK.Submitter (url_apply)
        submit += table

        self += CTK.RawHTML ('<h2>%s</h2>' % (_('Required SSL/TLS Values')))
        self += CTK.Indenter (submit)

        # Advanced options
        table = CTK.PropsTable()
        table.Add (_('Ciphers'),               CTK.TextCfg ('%s!ssl_ciphers' %(pre), True), _(NOTE_CIPHERS))
        table.Add (_('Client Certs. Request'), CTK.ComboCfg('%s!ssl_client_certs' %(pre), trans_options(CLIENT_CERTS)), _(NOTE_CLIENT_CERTS))

        if CTK.cfg.get_val('%s!ssl_client_certs' %(pre)):
            table.Add (_('CA List'), CTK.TextCfg ('%s!ssl_ca_list_file' %(pre), False), _(NOTE_CA_LIST))

            if CTK.cfg.get_val('%s!ssl_ca_list_file' %(pre)):
                table.Add (_('Verify Depth'), CTK.TextCfg ('%s!ssl_verify_depth' %(pre), False, {'size': 4}), _(NOTE_VERIFY_DEPTH))

        submit = CTK.Submitter (url_apply)
        submit.bind ('submit_success', refreshable.JS_to_refresh())
        submit += table

        self += CTK.RawHTML ('<h2>%s</h2>' % (_('Advanced Options')))
        self += CTK.Indenter (submit)


class SecurityWidget (CTK.Container):
    def __init__ (self, vsrv_num):
        CTK.Container.__init__ (self)

        # Content
        refresh = CTK.Refreshable ({'id': 'vserver_security'})
        refresh.register (lambda: SecutiryWidgetContent(vsrv_num, refresh).Render())
        self += refresh


class RenderContent (CTK.Container):
    def __init__ (self, vsrv_num, vsrv_nam=''):
        CTK.Container.__init__ (self)

        tabs = CTK.Tab()
        tabs.Add (_('Basics'),        BasicsWidget (vsrv_num))
        tabs.Add (_('Host Match'),    HostMatchWidget (vsrv_num))
        tabs.Add (_('Behavior'),      BehaviorWidget (vsrv_num))
        tabs.Add (_('Error Handler'), ErrorHandlerWidget (vsrv_num))
        tabs.Add (_('Logging'),       LogginWidget (vsrv_num))
        tabs.Add (_('Security'),      SecurityWidget (vsrv_num))

        self += CTK.RawHTML ('<h2>%s: %s</h2>' %(_('Virtual Server'), CTK.escape_html (vsrv_nam)))
        self += tabs


class Render:
    def __call__ (self):
        # Parse request
        vsrv_num = re.findall (r'^%s/([\d]+)'%(URL_BASE), CTK.request.url)[0]
        vsrv_nam = CTK.cfg.get_val ("vserver!%s!nick" %(vsrv_num), _("Unknown"))

        # Ensure the vserver exists
        if not CTK.cfg.keys ("vserver!%s"%(vsrv_num)):
            return CTK.HTTP_Redir("/vserver")

        # Content
        cont = RenderContent (vsrv_num, vsrv_nam)
        return cont.Render().toJSON()


CTK.publish (r'^%s/[\d]+$'        %(URL_BASE),  Render)
CTK.publish (r'^%s/[\d]+$'        %(URL_APPLY), CTK.cfg_apply_post,        validation=VALIDATIONS, method="POST")
CTK.publish (r'^%s/[\d]+/log$'    %(URL_APPLY), LogginWidgetContent_Apply, validation=VALIDATIONS, method="POST")
CTK.publish (r'^%s/[\d]+/vmatch$' %(URL_APPLY), HostMatchWidget_Apply, method="POST")
CTK.publish (r'^%s/[\d]+/clone$'  %(URL_BASE),  commit_clone)

