Skip to content


Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

566 lines (427 sloc) 22.605 kb
# -*- coding: utf-8 -*-
# Cherokee-admin
# Authors:
# Alvaro Lopez Ortega <>
# 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
# 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 Page
import Cherokee
import validations
import Handler
import re
from Rule import Rule
from util import *
from consts import *
from configured import *
URL_BASE = r'^/vserver/([\d]+)/rule/content/([\d]+)/?$'
URL_CLONE = r'^/vserver/([\d]+)/rule/content/([\d]+)/clone$'
URL_APPLY = r'^/vserver/([\d]+)/rule/content/([\d]+)/apply'
URL_HEADER_OP_APPLY = r'^/vserver/([\d]+)/rule/content/([\d]+)/apply/header_op'
URL_FLCACHE_APPLY = r'^/vserver/([\d]+)/rule/content/([\d]+)/apply/flcache'
NOTE_TIMEOUT = N_('Apply a custom timeout to the connections matching this rule.')
NOTE_HANDLER = N_('How the connection will be handled.')
NOTE_HTTPS_ONLY = N_('Enable to allow access to the resource only by https.')
NOTE_ALLOW_FROM = N_('List of IPs and subnets allowed to access the resource.')
NOTE_VALIDATOR = N_('Which, if any, will be the authentication method.')
NOTE_EXPIRATION = N_('Points how long the files should be cached')
NOTE_RATE = N_("Set an outbound traffic limit. It must be specified in Bytes per second.")
NOTE_NO_LOG = N_("Do not log requests matching this rule.")
NOTE_FLCACHE = N_("Allow the server to cache the responses from this rule.")
NOTE_FLCACHE_POLICY = N_("How to behave when a response does not explicitly set a caching status.")
NOTE_FLCACHE_COOKIES = N_("Allow to cache responses setting cookies. (Defualt: No)")
NOTE_EXPIRATION_TIME = N_("""How long from the object can be cached.<br />
The <b>m</b>, <b>h</b>, <b>d</b> and <b>w</b> suffixes are allowed for minutes, hours, days, and weeks. Eg: 2d.
NOTE_CACHING_OPTIONS = N_("How an intermediate cache should treat the content.")
NOTE_CACHING_NO_STORE = N_("Prevents the retention of sensitive information. Caches must not store this content.")
NOTE_CACHING_NO_TRANSFORM = N_("Forbid intermediate caches from transforming the content.")
NOTE_CACHING_MUST_REVALIDATE = N_("The client must contact the server to revalidate the object.")
NOTE_CACHING_PROXY_REVALIDATE = N_("Proxy servers must contact the server to revalidate the object.")
NOTE_FLCACHE_EXPERIMENTAL = N_("This caching mechanism should still be considered an experimental technology. Use it with caution.")
("vserver![\d]+!rule![\d]+!document_root", validations.is_dev_null_or_local_dir_exists),
("vserver![\d]+!rule![\d]+!allow_from", validations.is_ip_or_netmask_list),
("vserver![\d]+!rule![\d]+!rate", validations.is_number_gt_0),
("vserver![\d]+!rule![\d]+!timeout", validations.is_number_gt_0),
("vserver![\d]+!rule![\d]+!expiration!time", validations.is_time)
('config_virtual_servers_rule', N_("Behavior rules")),
('modules_encoders_gzip', N_("GZip encoder")),
('modules_encoders_deflate', N_("Deflate encoder")),
('cookbook_authentication', N_('Authentication')),
('modules_validators', N_("Authentication modules"))
('', N_('Leave unset')),
('allow', N_('Allow')),
('forbid', N_('Forbid'))
('', N_('Leave unset')),
('allow', N_('Allow Caching')),
('forbid', N_('Forbid Caching'))
('explicitly_allowed', N_('Only explicitly cacheable responses')),
('all_but_forbidden', N_('All except explicitly forbidden responses'))
('add', N_('Add')),
('del', N_('Remove'))
def Clone():
vsrv, rule = re.findall(URL_CLONE, CTK.request.url)[0]
next = CTK.cfg.get_next_entry_prefix ('vserver!%s!rule'%(vsrv))
CTK.cfg.clone ('vserver!%s!rule!%s'%(vsrv,rule), next)
return CTK.cfg_reply_ajax_ok()
class RestrictionsWidget (CTK.Container):
def __init__ (self, vsrv, rule, apply):
CTK.Container.__init__ (self)
pre = 'vserver!%s!rule!%s' %(vsrv, rule)
# Timeout
table = CTK.PropsTable()
table.Add (_('Timeout'), CTK.TextCfg ('%s!timeout'%(pre), True), _(NOTE_TIMEOUT))
submit = CTK.Submitter (apply)
submit += table
self += CTK.RawHTML ("<h2>%s</h2>" % (_('Connections Timeout')))
self += CTK.Indenter (submit)
# Traffic Shaping
table = CTK.PropsTable()
table.Add (_('Limit traffic to'), CTK.TextCfg ('%s!rate'%(pre), False), _(NOTE_RATE))
submit = CTK.Submitter (apply)
submit += table
self += CTK.RawHTML ("<h2>%s</h2>" % (_('Traffic Shaping')))
self += CTK.Indenter (submit)
class SecurityWidget (CTK.Container):
def __init__ (self, vsrv, rule, apply):
CTK.Container.__init__ (self)
pre = 'vserver!%s!rule!%s' %(vsrv, rule)
# Logging
table = CTK.PropsTable()
table.Add (_('Skip Logging'), CTK.CheckCfgText ('%s!no_log'%(pre), False, _('Enabled')), _(NOTE_NO_LOG))
submit = CTK.Submitter (apply)
submit += table
self += CTK.RawHTML ("<h2>%s</h2>" % (_('Logging')))
self += CTK.Indenter (submit)
# Access Restrictions
table = CTK.PropsTable()
table.Add (_('Only https'), CTK.CheckCfgText ('%s!only_secure'%(pre), False, _('Enabled')), _(NOTE_HTTPS_ONLY))
table.Add (_('Allow From'), CTK.TextCfg ('%s!allow_from'%(pre), True), _(NOTE_ALLOW_FROM))
submit = CTK.Submitter (apply)
submit += table
self += CTK.RawHTML ("<h2>%s</h2>" % (_('Access Restrictions')))
self += CTK.Indenter (submit)
# Authentication
modul = CTK.PluginSelector('%s!auth'%(pre), trans_options( (VALIDATORS)))
table = CTK.PropsTable()
table.Add (_('Validation Mechanism'), modul.selector_widget, _(NOTE_VALIDATOR))
self += CTK.RawHTML ("<h2>%s</h2>" % (_('Authentication')))
self += CTK.Indenter (table)
self += modul
def HeaderOp_Apply():
tmp = re.findall (r'^/vserver/([\d]+)/rule/content/([\d]+)/', CTK.request.url)
vsrv = tmp[0][0]
rule = tmp[0][1]
tipe ='new_header_op_type')
name ='new_header_op_name')
value ='new_header_op_value')
# Validation
if not name:
return {'ret':'error', 'errors': {'new_header_op_name': _("Can not be empty")}}
if tipe == 'add' and not value:
return {'ret':'error', 'errors': {'new_header_op_value': _("Can not be empty")}}
next_pre = CTK.cfg.get_next_entry_prefix ('vserver!%s!rule!%s!header_op'%(vsrv, rule))
# Add the configuration entries
CTK.cfg['%s!type'%(next_pre)] = tipe
CTK.cfg['%s!header'%(next_pre)] = name
if tipe == 'add':
CTK.cfg['%s!value'%(next_pre)] = value
return CTK.cfg_reply_ajax_ok()
class HeaderOps (CTK.Container):
class OpsTable (CTK.Box):
def __init__ (self, pre, apply_url, refresh):
CTK.Box.__init__ (self)
def reorder (arg, pre=pre):
return CTK.SortableList__reorder_generic (arg, '%s!header_op'%(pre))
if CTK.cfg.keys('%s!header_op'%(pre)):
table = CTK.SortableList (reorder,
table += [CTK.RawHTML(x) for x in ('', '', _('Type'), _('Header'))]
table.set_header (1)
CTK.cfg.normalize ('%s!header_op'%(pre))
keys = CTK.cfg.keys('%s!header_op'%(pre))
keys.sort (lambda x,y: cmp(int(x), int(y)))
for n in keys:
pre2 = '%s!header_op!%s' %(pre,n)
type_ = CTK.cfg.get_val('%s!type'%(pre2))
type_name = CTK.RawHTML (_(dict(HEADER_OP_OPTIONS)[type_]))
header = CTK.TextCfg('%s!header'%(pre2))
value = CTK.TextCfg('%s!value' %(pre2))
delete = CTK.ImageStock('del')
delete.bind('click', CTK.JS.Ajax (apply_url, data = {pre2: ''},
complete = refresh.JS_to_refresh()))
if type_ == 'add':
table += [None, type_name, header, value, delete]
elif type_ == 'del':
table += [None, type_name, header, None, delete]
table[-1].props['id'] = n
table[-1][1].props['class'] = 'dragHandle'
self += table
def __init__ (self, vsrv, rule, apply_orig):
CTK.Container.__init__ (self)
pre = 'vserver!%s!rule!%s' %(vsrv, rule)
apply_url = '%s/header_op'%(apply_orig)
# Operation Table
refresh = CTK.Refreshable({'id': 'header_op'})
refresh.register (lambda: HeaderOps.OpsTable(pre, apply_orig, refresh).Render())
self += CTK.RawHTML ("<h2>%s</h2>" % (_('Header Operations')))
self += CTK.Indenter (refresh)
# New Entries
tipe = CTK.Combobox ({'name': 'new_header_op_type', 'class': 'noauto'}, HEADER_OP_OPTIONS)
header = CTK.TextField ({'name': 'new_header_op_name', 'class': 'noauto'})
value = CTK.TextField ({'name': 'new_header_op_value', 'class': 'noauto'})
button = CTK.SubmitterButton (_('Add'))
table = CTK.Table()
table.set_header (num=1)
table += [CTK.RawHTML(x) for x in (_('Action'), _('Header'), _('Value'))]
table += [tipe, header, value, button]
# Manage 3rd column
selector = ','.join (['#%s'%(row[3].id) for row in table])
tipe.bind('change', "if ($(this).val()=='add'){ $('%s').show(); }else{ $('%s').hide();}" %(selector, selector))
submit = CTK.Submitter (apply_url)
submit.bind ('submit_success', refresh.JS_to_refresh())
submit += table
self += CTK.RawHTML ('<h3>%s</h3>'%(_('Add New Header')))
self += CTK.Indenter (submit)
class FrontLine_Cache (CTK.Container):
class Apply:
def __call__ (self):
tmp = re.findall (r'^/vserver/([\d]+)/rule/content/([\d]+)/', CTK.request.url)
vsrv = tmp[0][0]
rule = tmp[0][1]
# Main operarion
op ='op')
if op == "docache_add":
# Validation
new_docache ='new_flcache_docache')
if not new_docache:
return {'ret':'error', 'errors': {'new_flcache_docache': _("Can not be empty")}}
# Add the configuration entries
next_pre = CTK.cfg.get_next_entry_prefix ('vserver!%s!rule!%s!flcache!cookies!do_cache'%(vsrv, rule))
CTK.cfg['%s!regex'%(next_pre)] = new_docache
return CTK.cfg_reply_ajax_ok()
elif op == "docache_del":
pre = 'vserver!%s!rule!%s!flcache!cookies!do_cache'%(vsrv, rule)
to_del ='to_del')
if to_del:
del (CTK.cfg['%s!%s'%(pre, to_del)])
return CTK.cfg_reply_ajax_ok()
return CTK.cfg_apply_post()
class docache_Table (CTK.Box):
def __init__ (self, pre, apply_url, refresh):
CTK.Box.__init__ (self)
do_cache_keys = CTK.cfg.keys('%s!cookies!do_cache'%(pre))
if do_cache_keys:
table = CTK.Table()
table += [CTK.RawHTML(_('Regular Expressions'))]
submit = CTK.Submitter (apply_url)
submit += CTK.Indenter (table, 2)
self += submit
for k in do_cache_keys:
pre2 = '%s!cookies!do_cache!%s!regex'%(pre, k)
value = CTK.TextCfg (pre2, False)
remove = CTK.ImageStock('del')
remove.bind('click', CTK.JS.Ajax (apply_url, data = {'op':'docache_del', 'to_del':k},
complete = refresh.JS_to_refresh()))
table += [value, remove]
def __init__ (self, vsrv, rule, apply_url):
CTK.Container.__init__ (self)
pre = 'vserver!%s!rule!%s!flcache' %(vsrv, rule)
apply_url = '%s/flcache'%(apply_url)
# Enable
rest = CTK.Box()
combo = CTK.ComboCfg (pre, trans_options(FLCACHE_OPTIONS))
combo_js = "if ($('#%s').val() == 'allow'){ $('#%s').show(); }else{ $('#%s').hide(); }"%(,,
combo.bind ('change', combo_js)
table = CTK.PropsTable()
table.Add (_('Content Caching'), combo, _(NOTE_FLCACHE))
submit = CTK.Submitter (apply_url)
submit += table
self += CTK.Indenter(submit)
self += rest
self += CTK.RawHTML (js=combo_js)
# Front-Line Cache Policies
table = CTK.PropsTable()
table.Add (_('Responses to cache'), CTK.ComboCfg ('%s!policy'%(pre), trans_options(FLCACHE_POLICIES)), _(NOTE_FLCACHE_POLICY))
submit = CTK.Submitter (apply_url)
submit += table
rest += CTK.RawHTML ('<h3>%s</h3>' %(_("Caching Policy")))
rest += CTK.Indenter(submit)
# Disregard Cookies
refresh = CTK.Refreshable ({'id': 'docache'})
refresh.register (lambda: FrontLine_Cache.docache_Table(pre, apply_url, refresh).Render())
new_regex = CTK.TextField ({'name': 'new_flcache_docache', 'class': 'noauto'})
new_add = CTK.SubmitterButton (_('Add'))
table = CTK.Table()
table += [CTK.RawHTML(_('New Regular Expression'))]
table += [new_regex, new_add]
submit = CTK.Submitter (apply_url)
submit.bind ('submit_success', refresh.JS_to_refresh() + new_regex.JS_to_clean())
submit += CTK.Indenter (table, 2)
submit += CTK.Hidden ('op', 'docache_add')
rest += CTK.RawHTML ('<h3>%s</h3>' %(_('Disregarded Cookies')))
rest += refresh
rest += submit
class CachingWidget (CTK.Container):
class Expiration (CTK.Container):
def __init__ (self, pre, apply, refresh):
CTK.Container.__init__ (self)
# Expiration
table = CTK.PropsTable()
table.Add (_('Expiration'), CTK.ComboCfg ('%s!expiration'%(pre), trans_options(EXPIRATION_TYPE)), _(NOTE_EXPIRATION))
if CTK.cfg.get_val ('%s!expiration'%(pre)) == 'time':
table.Add (_('Time to expire'), CTK.TextCfg ('%s!expiration!time'%(pre), False), _(NOTE_EXPIRATION_TIME))
# Caching options
if CTK.cfg.get_val ('%s!expiration'%(pre)):
table.Add (_('Management by caches'), CTK.ComboCfg('%s!expiration!caching'%(pre), trans_options(CACHING_OPTIONS)), _(NOTE_CACHING_OPTIONS))
if CTK.cfg.get_val ('%s!expiration!caching'%(pre)):
table.Add (_('No Store'), CTK.CheckCfgText('%s!expiration!caching!no-store'%(pre), False), _(NOTE_CACHING_NO_STORE))
table.Add (_('No Transform'), CTK.CheckCfgText('%s!expiration!caching!no-transform'%(pre), False), _(NOTE_CACHING_NO_TRANSFORM))
table.Add (_('Must Revalidate'), CTK.CheckCfgText('%s!expiration!caching!must-revalidate'%(pre), False), _(NOTE_CACHING_MUST_REVALIDATE))
table.Add (_('Proxies Revalidate'), CTK.CheckCfgText('%s!expiration!caching!proxy-revalidate'%(pre), False), _(NOTE_CACHING_PROXY_REVALIDATE))
submit = CTK.Submitter (apply)
submit.bind ('submit_success', refresh.JS_to_refresh())
submit += table
self += submit
def __init__ (self, vsrv, rule, apply):
CTK.Container.__init__ (self)
pre = 'vserver!%s!rule!%s' %(vsrv, rule)
# Expiration
refresh = CTK.Refreshable({'id': 'time_expiration'})
refresh.register (lambda: CachingWidget.Expiration(pre, apply, refresh).Render())
self += CTK.RawHTML ("<h2>%s</h2>" % (_('Content Expiration')))
self += CTK.Indenter (refresh)
# Front-line cache
self += CTK.RawHTML ("<h2>%s</h2>" % (_('Cache')))
self += CTK.Indenter (CTK.Notice (content = CTK.RawHTML(NOTE_FLCACHE_EXPERIMENTAL)))
self += FrontLine_Cache (vsrv, rule, apply)
class TransformsWidget (CTK.Container):
def __init__ (self, vsrv, rule, apply):
CTK.Container.__init__ (self)
pre = 'vserver!%s!rule!%s!encoder' %(vsrv, rule)
encoders = trans_options( (ENCODERS))
# Encoders
self += CTK.RawHTML ("<h2>%s</h2>" % (_('Information Encoders')))
for e,e_name in encoders:
key = '%s!%s'%(pre,e)
note = _("Use the %s encoder whenever the client requests it.") %(_(e_name))
combo = CTK.ComboCfg(key, trans_options(ENCODE_OPTIONS))
combo.bind ('change', combo.JS_to_trigger('update_rule_list'))
table = CTK.PropsTable()
table.Add ('%s %s'% (_(e_name), _("support")), combo, note)
submit = CTK.Submitter (apply)
submit += table
if CTK.cfg.get_val(key) in ['1', 'allow']:
module = CTK.instance_plugin (e, key)
self += CTK.RawHTML ("<h3>%s</h3>" % (e_name))
self += CTK.Indenter (submit, 2)
if CTK.cfg.get_val(key) in ['1', 'allow']:
self += CTK.Indenter (module, 2)
# Header Operations
self += HeaderOps (vsrv, rule, apply)
class RuleWidget (CTK.Refreshable):
class Content (CTK.Container):
def __init__ (self, refresh, refresh_header, vsrv, rule, apply):
CTK.Container.__init__ (self)
pre = 'vserver!%s!rule!%s!match' %(vsrv, rule)
rule = Rule (pre)
rule.bind ('changed', refresh.JS_to_refresh() + refresh_header.JS_to_refresh())
rule.bind ('submit_success', refresh.JS_to_refresh() + refresh_header.JS_to_refresh())
self += CTK.RawHTML ("<h2>%s</h2>" % (_('Matching Rule')))
self += CTK.Indenter (rule)
# Trigger the 'update_rule_list' so the Rule list sibling
# widget is updated.
rule.bind ('submit_success', rule.JS_to_trigger('update_rule_list'))
def __init__ (self, vsrv, rule, apply, refresh_header):
CTK.Refreshable.__init__ (self, {'id': 'rule'})
self.register (lambda: self.Content(self, refresh_header, vsrv, rule, apply).Render())
class Header (CTK.Container):
def __init__ (self, refresh, vsrv_num, rule_num, vsrv_nam):
rule = Rule ('vserver!%s!rule!%s!match'%(vsrv_num, rule_num))
rule_nam = rule.GetName()
self += CTK.RawHTML ('<h2><a href="/vserver/%s">%s</a> &rarr; %s</h2>' %(
vsrv_num, CTK.escape_html(vsrv_nam), rule_nam))
class HandlerWidget (CTK.Container):
def __init__ (self, vsrv, rule, apply):
CTK.Container.__init__ (self)
pre = 'vserver!%s!rule!%s!handler' %(vsrv, rule)
modul = CTK.PluginSelector(pre, trans_options( (HANDLERS)))
table = CTK.PropsTable()
table.Add (_('Handler'), modul.selector_widget, _(NOTE_HANDLER))
# Exceptionally, a custom document root option must be
# available when the handler is not yet set
submit = None
if CTK.cfg.get_val (pre) == None:
key = 'vserver!%s!rule!%s!document_root'%(vsrv, rule)
table2 = CTK.PropsTable()
if not CTK.cfg.get_val (key, '').startswith(CHEROKEE_OWS_ROOT):
table2.Add (_('Document Root'), CTK.TextCfg(key, True), _(Handler.NOTE_DOCUMENT_ROOT))
table2.Add (_('Document Root'), CTK.TextCfg(key, True, {'disabled':True}), _(Handler.NOTE_DOCUMENT_ROOT))
submit = CTK.Submitter (apply)
submit += table2
self += CTK.RawHTML ('<h2>%s</h2>' %(_('Handler')))
self += CTK.Indenter (table)
self += CTK.Indenter (submit)
self += modul
class Render:
def __call__ (self):
# Parse request
vsrv_num, rule_num = re.findall (URL_BASE, CTK.request.url)[0]
vsrv_nam = CTK.cfg.get_val ("vserver!%s!nick" %(vsrv_num), _("Unknown"))
url_apply = '/vserver/%s/rule/content/%s/apply' %(vsrv_num, rule_num)
# Ensure the rule exists
if not CTK.cfg.keys('vserver!%s!rule!%s'%(vsrv_num, rule_num)):
return CTK.HTTP_Redir ('/vserver/%s' %(vsrv_num))
# Header refresh
refresh = CTK.Refreshable ({'id': 'entry_header'})
refresh.register (lambda: Header(refresh, vsrv_num, rule_num, vsrv_nam).Render())
# Tabs
tabs = CTK.Tab()
tabs.Add (_('Rule'), RuleWidget (vsrv_num, rule_num, url_apply, refresh))
tabs.Add (_('Handler'), HandlerWidget (vsrv_num, rule_num, url_apply))
tabs.Add (_('Transforms'), TransformsWidget (vsrv_num, rule_num, url_apply))
tabs.Add (_('Caching'), CachingWidget (vsrv_num, rule_num, url_apply))
tabs.Add (_('Security'), SecurityWidget (vsrv_num, rule_num, url_apply))
tabs.Add (_('Restrictions'), RestrictionsWidget (vsrv_num, rule_num, url_apply))
cont = CTK.Container()
cont += refresh
cont += tabs
return cont.Render().toJSON()
CTK.publish (URL_BASE, Render)
CTK.publish (URL_CLONE, Clone, method="POST")
CTK.publish (URL_APPLY, CTK.cfg_apply_post, validation=VALIDATIONS, method="POST")
CTK.publish (URL_HEADER_OP_APPLY, HeaderOp_Apply, validation=VALIDATIONS, method="POST")
CTK.publish (URL_FLCACHE_APPLY, FrontLine_Cache.Apply, validation=VALIDATIONS, method="POST")
Jump to Line
Something went wrong with that request. Please try again.