+
+'''
+
+class Documentation
+ XPATH_NAMESPACES = { 'd' => 'urn:lighttpd.net:lighttpd2/doc1' }
+ TAB_WIDTH = 4
+
+ def initialize(basename)
+ @html_doc = Nokogiri::HTML::Document.parse(HTML_TEMPLATE)
+ @html_main = @html_doc.xpath('//div[@role="main"]')[0]
+ @html_toc = @html_doc.css('#sidebar')[0]
+ @title = nil
+
+ @depth = 0
+ @uniqueid = 0
+ @current_toc = @toc = []
+
+ @basename = basename
+
+ @actions = []
+ @setups = []
+ @options = []
+ end
+
+ def render_main
+ Nokogiri::XML::Builder.with(@html_main) do |html|
+ @html = html
+ yield
+ @html = nil
+ end
+ end
+
+ def title
+ @title
+ end
+
+ def title=(value)
+ @title = value
+ @html_doc.xpath('/html/head/title')[0].inner_html = value
+ end
+
+ def to_html_fragment
+ @html_main.inner_html
+ end
+
+ def to_html
+ @html_doc.to_html
+ end
+
+ def toc
+ @toc
+ end
+
+ def actions
+ @actions
+ end
+
+ def setups
+ @setups
+ end
+
+ def options
+ @options
+ end
+
+ def _store_toc(html, toc, rootToc = false)
+ return unless toc.length > 0
+ html.ul(:class => rootToc ? "nav bs-sidenav" : "nav" ) {
+ toc.each do |anchor, title, subtoc, cls|
+ html.li(:class => cls || '') {
+ html.a({:href => '#' + anchor}, title)
+ _store_toc(html, subtoc)
+ }
+ end
+ }
+ end
+
+ def store_toc
+ Nokogiri::HTML::Builder.with(@html_toc) do |html|
+ _store_toc(html, @toc, true)
+ end
+ end
+
+ def _unique_anchor(title)
+ id = @uniqueid
+ @uniqueid += 1
+ ("%02x-" % id) + title.downcase.gsub(/[^a-z0-9]+/, '_')
+ end
+
+ # although '.' and ':' are allowed in anchors (IDs), they
+ # don't work in CSS selectors like: #anchor:foo and #anchor.bar
+ def escape_anchor(anchor)
+ anchor.gsub(/[.:]/, '-')
+ end
+
+ def nest(title, anchor = nil, cls = nil)
+ attribs = {}
+ have_toc = nil != @current_toc
+ anchor = (@depth < 3 and have_toc) ? _unique_anchor(title) : nil if anchor == '#'
+ if anchor
+ anchor = (anchor == '') ? @basename : @basename + '__' + anchor
+ anchor = escape_anchor(anchor)
+ attribs[:id] = anchor
+ end
+
+ use_toc = have_toc && anchor
+ old_toc = @current_toc
+ @current_toc = use_toc ? [] : nil
+
+ @depth += 1
+ if cls
+ @html.div(:class => cls) {
+ @html.send("h#{@depth}", attribs, title)
+ yield
+ }
+ else
+ @html.send("h#{@depth}", attribs, title)
+ yield
+ end
+ @depth -= 1
+
+ old_toc << [ anchor, title, @current_toc, cls ] if use_toc
+ @current_toc = old_toc
+ return anchor
+ end
+
+ ## Code formatting
+ def _count_indent(line)
+ /^[ \t]*/.match(line)[0].each_char.reduce(0) do |i, c|
+ c == ' ' ? i + 1 : (i + TAB_WIDTH) - (i % TAB_WIDTH)
+ end
+ end
+ def _remove_indent(indent, line)
+ p = 0
+ l = line.length
+ i = 0
+ while i < indent && p < l
+ case line[p]
+ when ' '
+ i += 1
+ when "\t"
+ i = (i + TAB_WIDTH) - (i % TAB_WIDTH)
+ else
+ return line[p..-1]
+ end
+ p += 1
+ end
+ return line[p..-1]
+ end
+ def _format_code(code)
+ lines = code.rstrip.lines
+ real_lines = lines.grep(/\S/)
+ return '' if real_lines.length == 0
+
+ indent = real_lines.map { |l| _count_indent l }.min
+
+ code = lines.map { |line| _remove_indent(indent, line).rstrip }.join("\n") + "\n"
+ code.gsub(/\A\n+/, "").gsub(/\n\n+/, "\n\n").gsub(/\n+\Z/, "\n")
+ end
+
+
+ def _parse_code(xml)
+ @html.pre { @html.code { @html << _format_code(xml.inner_html) } }
+ end
+
+ def _parse_markdown(xml)
+ md = _format_code(xml.inner_html)
+ @html << BlueCloth.new(md).to_html
+ end
+
+ def _parse_textile(xml)
+ tx = _format_code(xml.inner_html)
+ @html << RedCloth.new(tx).to_html
+ end
+
+ def _parse_html(xml)
+ @html << xml.inner_html
+ end
+
+ def _parse_description(xmlParent)
+ return unless xmlParent
+ xml = xmlParent.xpath('d:description[1]', XPATH_NAMESPACES)[0]
+ return unless xml
+
+ xml.children.each do |child|
+ if child.text?
+ @html.p child.content.strip
+ elsif ['html','textile','markdown'].include? child.name
+ self.send('_parse_' + child.name, child)
+ else
+ raise 'invalid description element ' + child.name
+ end
+ end
+ end
+
+ def <=>(other)
+ ordername <=> other.ordername
+ end
+
+ def ordername=(value)
+ @ordername = value
+ end
+
+ def ordername
+ @ordername || @basename
+ end
+
+ def basename
+ @basename
+ end
+
+ def filename
+ basename + '.html'
+ end
+
+ def write_disk(output_directory)
+ puts "Writing #{output_directory}: #{filename}"
+ File.open(File.join(output_directory, self.filename), "w") { |f| f.write self.to_html }
+ end
+end
+
+
+class ModuleDocumentation < Documentation
+ def initialize(filename, xml)
+ super(File.basename(filename, '.xml'))
+
+ self.title = basename
+ render_main { _parse_module(xml.root) }
+
+ store_toc
+ end
+
+ def _parse_short(xmlParent, makeDiv = false)
+ return unless xmlParent
+ xml = xmlParent.xpath('d:short[1]', XPATH_NAMESPACES)[0]
+ return unless xml
+ text = xml.content.strip
+ return unless text
+
+ if makeDiv
+ @html.p.short text
+ else
+ @html.text text
+ end
+ text
+ end
+
+ def _parse_parameters(xml)
+ @html.dl {
+ xml.xpath('d:parameter', XPATH_NAMESPACES).each do |param|
+ @html.dt param['name']
+ child = param.element_children[0]
+ if child.name == 'short'
+ @html.dd { _parse_short param }
+ elsif child.name == 'table'
+ @html.dd {
+ @html.text "A key-value table with the following entries:"
+ @html.dl {
+ child.element_children.each do |entry|
+ @html.dt entry['name']
+ @html.dd { _parse_short entry }
+ end
+ }
+ }
+ end
+ end
+ }
+ end
+
+ def _parse_default(xml)
+ @html.div(:class => 'default') {
+ @html.text "Default value: "
+
+ child = xml.element_children[0]
+ @html.span({:class => child.name}, child.content.strip)
+ }
+ end
+
+ def _parse_aso(xml, type)
+ name = xml['name']
+ raise "#{type} requires a name" unless name
+ parameter_names = xml.xpath('d:parameter', XPATH_NAMESPACES).map { |p| p['name'] }
+ parameter_names = ['value'] if parameter_names.length == 0 and type == 'option'
+
+ title = "#{name} (#{type})"
+ anchor = "#{type}_#{name}"
+ cls = "aso #{type}"
+ short = nil
+
+ anchor = nest(title, anchor, cls) {
+ short = _parse_short(xml, true)
+
+ @html.pre(:class => "template") {
+ @html.span.key name
+ if parameter_names.length == 1
+ @html.text ' '
+ @html.span.param parameter_names[0]
+ @html.text ';'
+ elsif parameter_names.length > 0
+ @html.text ' ('
+ first = true
+ parameter_names.each do |pname|
+ @html.text ', ' unless first
+ first = false
+ @html.span.param pname
+ end
+ @html.text ');'
+ else
+ @html.text ';'
+ end
+ }
+
+ if type == 'option'
+ _parse_default(xml.xpath('d:default', XPATH_NAMESPACES)[0])
+ else
+ _parse_parameters(xml) if parameter_names.length > 0
+ end
+
+ _parse_description(xml)
+ xml.xpath('d:example', XPATH_NAMESPACES).each do |child|
+ _parse_example(child)
+ end
+ }
+
+ [name, filename + '#' + anchor, short, self]
+ end
+
+ def _parse_action(xml)
+ @actions << _parse_aso(xml, 'action')
+ end
+
+ def _parse_setup(xml)
+ @setups << _parse_aso(xml, 'setup')
+ end
+
+ def _parse_option(xml)
+ @options << _parse_aso(xml, 'option')
+ end
+
+ def _parse_example(xml)
+ nest(xml['title'] || 'Example', xml['anchor'], 'example') {
+ _parse_description(xml)
+
+ config = xml.xpath('d:config[1]', XPATH_NAMESPACES)
+ _parse_code(config[0])
+ }
+ end
+
+ def _parse_section(xml)
+ title = xml['title']
+ raise 'section requires a title' unless title
+
+ nest(title, xml['anchor'] || '#', 'section') {
+ xml.children.each do |child|
+ if child.text?
+ text = child.content.strip
+ @html.p text if text.length > 0
+ elsif ['action','setup','option','html','textile','markdown','example','section'].include? child.name
+ self.send('_parse_' + child.name, child)
+ else
+ raise 'invalid section element ' + child.name
+ end
+ end
+ }
+ end
+
+ def _parse_module(xml)
+ raise 'unexpected root node' if xml.name != 'module'
+
+ self.title = xml['title'] || self.title
+ self.ordername = xml['order']
+
+ nest(title, '', 'module') {
+ @html.p {
+ @html.text (basename + ' ')
+ @short = _parse_short(xml, false)
+ }
+ _parse_description(xml)
+
+ xml.element_children.each do |child|
+ if ['action','setup','option','example','section'].include? child.name
+ self.send('_parse_' + child.name, child)
+ elsif ['short', 'description'].include? child.name
+ nil # skip
+ else
+ raise 'invalid module element ' + child.name
+ end
+ end
+ }
+ end
+
+ def short
+ @short
+ end
+
+ def link(html_builder)
+ html_builder.a({:href => self.filename + '#' + escape_anchor(self.basename)}, self.basename)
+ end
+end
+
+class ChapterDocumentation < Documentation
+ def initialize(filename, xml)
+ super(File.basename(filename, '.xml'))
+
+ render_main { _parse_chapter(xml.root) }
+
+ store_toc
+ end
+
+ def _parse_example(xml)
+ nest(xml['title'] || 'Example', xml['anchor'], 'example') {
+ _parse_description(xml)
+
+ config = xml.xpath('d:config[1]', XPATH_NAMESPACES)
+ _parse_code(config[0])
+ }
+ end
+
+ def _parse_section(xml)
+ title = xml['title']
+ raise 'section requires a title' unless title
+
+ nest(title, xml['anchor'] || '#', 'section') {
+ xml.children.each do |child|
+ if child.text?
+ text = child.content.strip
+ @html.p text if text.length > 0
+ elsif ['html','textile','markdown','example','section'].include? child.name
+ self.send('_parse_' + child.name, child)
+ else
+ raise 'invalid section element ' + child.name
+ end
+ end
+ }
+ end
+
+ def _parse_chapter(xml)
+ raise 'unexpected root node' if xml.name != 'chapter'
+
+ self.title = xml['title']
+ raise 'chapter requires a title' unless self.title
+ self.ordername = xml['order']
+
+ nest(self.title, '', 'chapter') {
+ _parse_description(xml)
+
+ xml.element_children.each do |child|
+ if ['example','section'].include? child.name
+ self.send('_parse_' + child.name, child)
+ elsif ['description'].include? child.name
+ nil # skip
+ else
+ raise 'invalid chapter element ' + child.name
+ end
+ end
+ }
+ end
+end
+
+class ModuleIndex < Documentation
+ def modules_table(modules)
+ nest('Modules', 'modules') {
+ @html.table(:class => 'table table-striped') {
+ @html.tr {
+ @html.th "name"
+ @html.th "description"
+ }
+ modules.each do |mod|
+ next unless mod.is_a? ModuleDocumentation
+ @html.tr {
+ @html.td {
+ mod.link(@html)
+ }
+ @html.td mod.short
+ }
+ end
+ }
+
+ }
+ end
+
+ def aso_html_table(name, list)
+ nest(name.capitalize + 's', name + 's') {
+ @html.table(:class => 'table table-striped aso') {
+ @html.tr {
+ @html.th "name"
+ @html.th "module"
+ @html.th "description"
+ }
+ list.each do |id, href, short, mod|
+ @html.tr {
+ @html.td {
+ @html.a({:href => href}, id)
+ }
+ @html.td {
+ mod.link(@html)
+ }
+ @html.td short
+ }
+ end
+ }
+ @html.text "none" unless list.length > 0
+ }
+ end
+
+ def initialize(modules)
+ super('index_modules')
+
+ actions = []
+ setups = []
+ options = []
+ modules.each do |mod|
+ actions += mod.actions
+ setups += mod.setups
+ options += mod.options
+ end
+
+ render_main do
+ nest('Modules overview', '', 'index_modules') {
+ modules_table(modules)
+ aso_html_table('action', actions.sort)
+ aso_html_table('setup', setups.sort)
+ aso_html_table('option', options.sort)
+ }
+ end
+
+ self.title = "lighttpd2 - all in one"
+ store_toc
+ end
+end
+
+class AllPage < Documentation
+ def fix_link(a)
+ href = a['href']
+ return unless href
+ m = /^([^#]*)(#.*)$/.match(href)
+ a['href'] = m[2] if m && @href_map[m[1]]
+ end
+
+ def initialize(pages)
+ super('all')
+
+ @href_map = {}
+ pages.each do |page|
+ @href_map[page.filename] = true
+ end
+
+ render_main do
+ pages.each do |page|
+ @html << page.to_html_fragment
+ @toc += page.toc
+ end
+ end
+
+ @html_doc.xpath('//a').each do |a|
+ fix_link(a)
+ end
+
+ self.title = "lighttpd2 - all in one"
+ store_toc
+ end
+end
+
+
+def loadXML(filename)
+ xml = Nokogiri::XML(File.read filename) do |config|
+ config.strict.nonet
+ end
+
+ if xml.root.name == 'module'
+ ModuleDocumentation.new(filename, xml)
+ elsif xml.root.name == 'chapter'
+ ChapterDocumentation.new(filename, xml)
+ end
+end
+
+
+if __FILE__ == $0
+ output_directory = ARGV[0] || '.'
+
+ if not system("xmllint --noout --schema doc_schema.xsd *.xml 2>&1")
+ STDERR.puts "Couldn't validate XML files"
+ exit 1
+ end
+
+ pages = []
+
+ Dir["*.xml"].each do |file|
+ puts "Compiling #{file}"
+ pages << loadXML(file)
+ end
+
+ pages.sort!
+ pages << ModuleIndex.new(pages)
+
+
+ pages.sort!
+ pages << AllPage.new(pages)
+
+ pages.sort!
+ pages.each { |page| page.write_disk(output_directory) }
+end
diff --git a/doc/core_config.xml b/doc/core_config.xml
new file mode 100644
index 0000000..fb83146
--- /dev/null
+++ b/doc/core_config.xml
@@ -0,0 +1,336 @@
+
+
+
+
+
+ At the heart of each webserver is the configuration file. It controls the whole behaviour and therefore has to be mighty but at the same time easy enough to get the desired results without hassle.
+ In the lighttpd config you control how it reacts on requests. To achieve this you have to express some kind of logic - just like in a programming language.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1, "b" => 10 ]@
+
+ As with normal lists the final value can have a trailing @,@ too. You cannot mix simple values and key-value associations in one list (nesting them works).
+
+ The @key => value@ operator is also only syntactic sugar for a @[key, value]@ list, the above example is the same as:
+ * @[ [a, 1], [b, 10] ]@
+
+ This especially means that key-value lists are ordered too, although they can get converted to hash tables in certain function calls internally.
+ ]]>
+
+
+
+
+
+
+
+
+ {
+ log "hello world";
+ if req.path =$ ".hidden" { static; }
+ }
+
+
+ The complete config is an action block too (but without the surrounding curly braces); conditionals also use action blocks for their branches.
+
+ Each action block that is not a condition branch starts a new nested variable scope;
+ ]]>
+
+
+
+
+ my_types = [".txt" => "text/html"];
+ php = {
+ if phys.path =$ ".php" { fastcgi "unix:/var/run/lighttpd/php.sock"; }
+ };
+
+
+ By default variables assignment overwrites an existing variable (in its previous scope) or, if it doesn't exist, creates a new one in the local scope (i.e. it will only be available in the current scope and nested descendants).
+
+ You can explicitly create a new variable in the local scope (hiding variables in parent scopes with the same name) by prefixing the assignment with @local@:
+ @local wwwpath = "/var/www/example.com";
+
+ You can also create variables in the global scope by prefixing the assignment with @global@.
+ The main config already is in a nested scope (i.e. *not* the global scope). The global scope is not destroyed after config loading, and can be used in delayed config loading (say from SQL in the future).
+
+ If a variable name is used in a context it will always use the definition from the nearest scope.
+ ]]>
+
+
+
+ This example illustrates that variables are evaluated while parsing the config.
+
+
+ foo = "bar";
+ if req.path == "/somepath" {
+ foo = "baz";
+ }
+
+ # at this point foo will ALWAYS contain "baz" no matter if "/somepath" was requested or not
+
+
+
+
+
+ This example illustrates scoping.
+
+
+ foo = "bar";
+ php = {
+ local foo = "baz";
+ # in this block (and nested blocks within) foo is now "baz"
+ # ...
+ };
+ # foo is now "bar" again
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ if req.host == "mydomain.tld" {
+ if req.path == "/" or req.path == "/index.html" {
+ static;
+ } else if req.path == "/upload" {
+ if req.content_length > 100mbyte {
+ access.deny;
+ } else {
+ proxy "127.0.0.1:8080";
+ }
+ }
+ }
+
+
+
+
+ { ... }@
+ * @if { ... } else { ... }@
+ * @if { ... } else if { ... }@
+ * @if { ... } else if { ... } ...@ (continue with @else@ or @else if@)
+
+ A condition expression @@ is:
+ * @()@
+ * @ and @
+ * @ or @ (@ and or @ = @( and ) or @; @and@ has higher precedence)
+ * @@ for @@ being a condition variable (with a type different from boolean), @@ an condition operator and @@ a string or a number.
+ * @@ or @!@ for a boolean condition variable.
+ ]]>
+
+
+
+
+
+
+
+ == | compares two values on equality | != | negative == |
+ | <= | less than or equal | < | less than |
+ | >= | greater than or equal | > | greater than |
+ | =~ | regular expression match | !~ | negative =~ |
+ | =^ | prefix match | !^ | negative =^ |
+ | =$ | suffix match | !$ | negative =$ |
+ | =/ | cidr match | !/ | negative =/ |
+ ]]>
+
+
+
+
diff --git a/doc/core_fetch.xml b/doc/core_fetch.xml
new file mode 100644
index 0000000..862c167
--- /dev/null
+++ b/doc/core_fetch.xml
@@ -0,0 +1,10 @@
+
+
+
+
+ The Fetch API provides a common interface between lighttpd modules to lookup entries in a database. Both lookup key and data are simple (binary) strings.
+
+ So far only a "fetch.files_static":plugin_core.html#plugin_core__setup_fetch-files_static is providing a database, and only "gnutls":mod_gnutls.html#mod_gnutls__setup_gnutls is using it to lookup SNI certificates.
+
+
+
diff --git a/doc/core_pattern.xml b/doc/core_pattern.xml
new file mode 100644
index 0000000..cfad7b9
--- /dev/null
+++ b/doc/core_pattern.xml
@@ -0,0 +1,53 @@
+
+
+
+
+ The lighttpd config supports "patterns" in various places (docroot, redirect, rewrite, env.set, ...); and they share the following structure.
+
+ There are two kinds of "captures" available; one from the action itself (like captures from a regular expression in redirect/rewrite, or the labels from the hostname in docroot), and the captures from the previous matching regular expression conditional in the action stack. If there was no capture of the selected kind the values will be empty strings.
+
+
+
+
+
+ A pattern is a string consisting of the following parts:
+ * simple text. can contain special characters $ and % only when they are escaped with \ - remember, that the \ has to be escaped too for the config, so you'll probably have to use \\ to escape. You are allowed to escape ? too (used for special "split" in rewrite).
+ * "%" capture references (previous matching regular expression conditional); either followed by a single digit, or a range (see below for range syntax)
+ * "$" capture references (depends on action); either followed by a single digit, or a range (see below for range syntax)
+ * "%" references to "condition variables":core_config.html#core_connfig__condition_vars, for example: @%{req.path}@; the conditional can be prefixed with "enc:" (@%{enc:req.path}@), in which case the value will be urlencoded.
+
+
+
+
+ m@.
+
+ There are now two different ways ranges are used:
+ * ranges of regular expression captures: the captures are just inserted for all values in the range; if the range is reversed, it starts with the highest index in the range.
+ * ranges of labels in a hostname: similar to the first range, but the inserted labels are separated by a "."; the index 0 stands for "complete hostname", and ranges including 0 are reduced to the complete hostname; the labels are numbered from top-level, and the range is intepreted reversed (just have a look at the examples, and it will be clear).
+ ]]>
+
+
+
+
+ redirect "http://%{req.host}%{req.path}?redirected=1";
+
+
+
+
+
+
+ * a request to "http://example.com/project/trunk" would lead to docroot "/var/www/project/trunk/htdocs"
+ * a request to "http://sub.example.com/" would lead to docroot "/var/www/vhosts/com/sub.example"
+
+
+
+ if req.path =~ "^/project/([^/]*)" {
+ docroot "/var/www/projects/%1/htdocs";
+ } else {
+ docroot "/var/www/vhosts/$1/${2-}/htdocs";
+ }
+
+
+
diff --git a/doc/core_regex.xml b/doc/core_regex.xml
new file mode 100644
index 0000000..dd5d559
--- /dev/null
+++ b/doc/core_regex.xml
@@ -0,0 +1,21 @@
+
+
+
+
+ lighttpd2 uses the "Perl-compatible regular expressions" implementation from GLib, see their "Regular expression syntax":https://developer.gnome.org/glib/stable/glib-regex-syntax.html documentation.
+
+ The config format has different ways to provide strings (you can quote with either @'@ or @"@; the character used to quote has to be escaped with @\@ if used inside the string).
+ The simple (standard) way @"text"@ has the following escape rules:
+ * @"\n"@ is a newline, @"\r"@ a carriage return, @"\t"@ a tab stop
+ * @"\\"@ is one @\@, @"\""@ is one double quote @"@ and @"\'"@ a single quote @'@
+ * escaping single/double quote is optional if the symbol is not used to terminate the string, i.e. @'\"'@ = @'"'@ and @"\'"@ = @"'"@
+ * @"\xNN"@: NN must be hexadecimal characters, and the string is replaced with the decoded 8-bit value as a single byte
+ * All other @\@ occurences are *not* removed from the string.
+
+ This way is the preferred one for regular expressions; only to actually match a @\@ you have to do additional escaping (@"\\\\"@; @"\x5C"@ = @"\\"@ is not working), and @\\@ is usually not doing what you wanted (matching a digit: @"\\d"@ = @"\d"@). All other escape rules are compatible with what pcre is doing.
+
+ The second way is to place an @e@ before the string like this: @e"text"@. It has the same rules like the normal string, but does not allow unknown escape sequences (the last rule above).
+ To match a digit with pcre this way you'd have to write @e"\\d"@ instead of @"\d"@.
+
+
+
diff --git a/doc/doc_schema.xsd b/doc/doc_schema.xsd
new file mode 100644
index 0000000..d70d415
--- /dev/null
+++ b/doc/doc_schema.xsd
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/doc/jquery-1.10.1.min.js b/doc/jquery-1.10.1.min.js
new file mode 100644
index 0000000..e407e76
--- /dev/null
+++ b/doc/jquery-1.10.1.min.js
@@ -0,0 +1,6 @@
+/*! jQuery v1.10.1 | (c) 2005, 2013 jQuery Foundation, Inc. | jquery.org/license
+//@ sourceMappingURL=jquery-1.10.1.min.map
+*/
+(function(e,t){var n,r,i=typeof t,o=e.location,a=e.document,s=a.documentElement,l=e.jQuery,u=e.$,c={},p=[],f="1.10.1",d=p.concat,h=p.push,g=p.slice,m=p.indexOf,y=c.toString,v=c.hasOwnProperty,b=f.trim,x=function(e,t){return new x.fn.init(e,t,r)},w=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=/\S+/g,C=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,N=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,k=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,E=/^[\],:{}\s]*$/,S=/(?:^|:|,)(?:\s*\[)+/g,A=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,j=/"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,D=/^-ms-/,L=/-([\da-z])/gi,H=function(e,t){return t.toUpperCase()},q=function(e){(a.addEventListener||"load"===e.type||"complete"===a.readyState)&&(_(),x.ready())},_=function(){a.addEventListener?(a.removeEventListener("DOMContentLoaded",q,!1),e.removeEventListener("load",q,!1)):(a.detachEvent("onreadystatechange",q),e.detachEvent("onload",q))};x.fn=x.prototype={jquery:f,constructor:x,init:function(e,n,r){var i,o;if(!e)return this;if("string"==typeof e){if(i="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:N.exec(e),!i||!i[1]&&n)return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e);if(i[1]){if(n=n instanceof x?n[0]:n,x.merge(this,x.parseHTML(i[1],n&&n.nodeType?n.ownerDocument||n:a,!0)),k.test(i[1])&&x.isPlainObject(n))for(i in n)x.isFunction(this[i])?this[i](n[i]):this.attr(i,n[i]);return this}if(o=a.getElementById(i[2]),o&&o.parentNode){if(o.id!==i[2])return r.find(e);this.length=1,this[0]=o}return this.context=a,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):x.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),x.makeArray(e,this))},selector:"",length:0,toArray:function(){return g.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return x.each(this,e,t)},ready:function(e){return x.ready.promise().done(e),this},slice:function(){return this.pushStack(g.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(x.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:h,sort:[].sort,splice:[].splice},x.fn.init.prototype=x.fn,x.extend=x.fn.extend=function(){var e,n,r,i,o,a,s=arguments[0]||{},l=1,u=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[1]||{},l=2),"object"==typeof s||x.isFunction(s)||(s={}),u===l&&(s=this,--l);u>l;l++)if(null!=(o=arguments[l]))for(i in o)e=s[i],r=o[i],s!==r&&(c&&r&&(x.isPlainObject(r)||(n=x.isArray(r)))?(n?(n=!1,a=e&&x.isArray(e)?e:[]):a=e&&x.isPlainObject(e)?e:{},s[i]=x.extend(c,a,r)):r!==t&&(s[i]=r));return s},x.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),noConflict:function(t){return e.$===x&&(e.$=u),t&&e.jQuery===x&&(e.jQuery=l),x},isReady:!1,readyWait:1,holdReady:function(e){e?x.readyWait++:x.ready(!0)},ready:function(e){if(e===!0?!--x.readyWait:!x.isReady){if(!a.body)return setTimeout(x.ready);x.isReady=!0,e!==!0&&--x.readyWait>0||(n.resolveWith(a,[x]),x.fn.trigger&&x(a).trigger("ready").off("ready"))}},isFunction:function(e){return"function"===x.type(e)},isArray:Array.isArray||function(e){return"array"===x.type(e)},isWindow:function(e){return null!=e&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?c[y.call(e)]||"object":typeof e},isPlainObject:function(e){var n;if(!e||"object"!==x.type(e)||e.nodeType||x.isWindow(e))return!1;try{if(e.constructor&&!v.call(e,"constructor")&&!v.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(r){return!1}if(x.support.ownLast)for(n in e)return v.call(e,n);for(n in e);return n===t||v.call(e,n)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||a;var r=k.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=x.buildFragment([e],t,i),i&&x(i).remove(),x.merge([],r.childNodes))},parseJSON:function(n){return e.JSON&&e.JSON.parse?e.JSON.parse(n):null===n?n:"string"==typeof n&&(n=x.trim(n),n&&E.test(n.replace(A,"@").replace(j,"]").replace(S,"")))?Function("return "+n)():(x.error("Invalid JSON: "+n),t)},parseXML:function(n){var r,i;if(!n||"string"!=typeof n)return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(o){r=t}return r&&r.documentElement&&!r.getElementsByTagName("parsererror").length||x.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&x.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(D,"ms-").replace(L,H)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,a=M(e);if(n){if(a){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(a){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:b&&!b.call("\ufeff\u00a0")?function(e){return null==e?"":b.call(e)}:function(e){return null==e?"":(e+"").replace(C,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(M(Object(e))?x.merge(n,"string"==typeof e?[e]:e):h.call(n,e)),n},inArray:function(e,t,n){var r;if(t){if(m)return m.call(t,e,n);for(r=t.length,n=n?0>n?Math.max(0,r+n):n:0;r>n;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,o=0;if("number"==typeof r)for(;r>o;o++)e[i++]=n[o];else while(n[o]!==t)e[i++]=n[o++];return e.length=i,e},grep:function(e,t,n){var r,i=[],o=0,a=e.length;for(n=!!n;a>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,a=M(e),s=[];if(a)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(s[s.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(s[s.length]=r);return d.apply([],s)},guid:1,proxy:function(e,n){var r,i,o;return"string"==typeof n&&(o=e[n],n=e,e=o),x.isFunction(e)?(r=g.call(arguments,2),i=function(){return e.apply(n||this,r.concat(g.call(arguments)))},i.guid=e.guid=e.guid||x.guid++,i):t},access:function(e,n,r,i,o,a,s){var l=0,u=e.length,c=null==r;if("object"===x.type(r)){o=!0;for(l in r)x.access(e,n,l,r[l],!0,a,s)}else if(i!==t&&(o=!0,x.isFunction(i)||(s=!0),c&&(s?(n.call(e,i),n=null):(c=n,n=function(e,t,n){return c.call(x(e),n)})),n))for(;u>l;l++)n(e[l],r,s?i:i.call(e[l],l,n(e[l],r)));return o?e:c?n.call(e):u?n(e[0],r):a},now:function(){return(new Date).getTime()},swap:function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i}}),x.ready.promise=function(t){if(!n)if(n=x.Deferred(),"complete"===a.readyState)setTimeout(x.ready);else if(a.addEventListener)a.addEventListener("DOMContentLoaded",q,!1),e.addEventListener("load",q,!1);else{a.attachEvent("onreadystatechange",q),e.attachEvent("onload",q);var r=!1;try{r=null==e.frameElement&&a.documentElement}catch(i){}r&&r.doScroll&&function o(){if(!x.isReady){try{r.doScroll("left")}catch(e){return setTimeout(o,50)}_(),x.ready()}}()}return n.promise(t)},x.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){c["[object "+t+"]"]=t.toLowerCase()});function M(e){var t=e.length,n=x.type(e);return x.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}r=x(a),function(e,t){var n,r,i,o,a,s,l,u,c,p,f,d,h,g,m,y,v,b="sizzle"+-new Date,w=e.document,T=0,C=0,N=lt(),k=lt(),E=lt(),S=!1,A=function(){return 0},j=typeof t,D=1<<31,L={}.hasOwnProperty,H=[],q=H.pop,_=H.push,M=H.push,O=H.slice,F=H.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},B="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",P="[\\x20\\t\\r\\n\\f]",R="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",W=R.replace("w","w#"),$="\\["+P+"*("+R+")"+P+"*(?:([*^$|!~]?=)"+P+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+W+")|)|)"+P+"*\\]",I=":("+R+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+$.replace(3,8)+")*)|.*)\\)|)",z=RegExp("^"+P+"+|((?:^|[^\\\\])(?:\\\\.)*)"+P+"+$","g"),X=RegExp("^"+P+"*,"+P+"*"),U=RegExp("^"+P+"*([>+~]|"+P+")"+P+"*"),V=RegExp(P+"*[+~]"),Y=RegExp("="+P+"*([^\\]'\"]*)"+P+"*\\]","g"),J=RegExp(I),G=RegExp("^"+W+"$"),Q={ID:RegExp("^#("+R+")"),CLASS:RegExp("^\\.("+R+")"),TAG:RegExp("^("+R.replace("w","w*")+")"),ATTR:RegExp("^"+$),PSEUDO:RegExp("^"+I),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+P+"*(even|odd|(([+-]|)(\\d*)n|)"+P+"*(?:([+-]|)"+P+"*(\\d+)|))"+P+"*\\)|)","i"),bool:RegExp("^(?:"+B+")$","i"),needsContext:RegExp("^"+P+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+P+"*((?:-\\d)?\\d*)"+P+"*\\)|)(?=[^-]|$)","i")},K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,et=/^(?:input|select|textarea|button)$/i,tt=/^h\d$/i,nt=/'|\\/g,rt=RegExp("\\\\([\\da-f]{1,6}"+P+"?|("+P+")|.)","ig"),it=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:0>r?String.fromCharCode(r+65536):String.fromCharCode(55296|r>>10,56320|1023&r)};try{M.apply(H=O.call(w.childNodes),w.childNodes),H[w.childNodes.length].nodeType}catch(ot){M={apply:H.length?function(e,t){_.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function at(e,t,n,i){var o,a,s,l,u,c,d,m,y,x;if((t?t.ownerDocument||t:w)!==f&&p(t),t=t||f,n=n||[],!e||"string"!=typeof e)return n;if(1!==(l=t.nodeType)&&9!==l)return[];if(h&&!i){if(o=Z.exec(e))if(s=o[1]){if(9===l){if(a=t.getElementById(s),!a||!a.parentNode)return n;if(a.id===s)return n.push(a),n}else if(t.ownerDocument&&(a=t.ownerDocument.getElementById(s))&&v(t,a)&&a.id===s)return n.push(a),n}else{if(o[2])return M.apply(n,t.getElementsByTagName(e)),n;if((s=o[3])&&r.getElementsByClassName&&t.getElementsByClassName)return M.apply(n,t.getElementsByClassName(s)),n}if(r.qsa&&(!g||!g.test(e))){if(m=d=b,y=t,x=9===l&&e,1===l&&"object"!==t.nodeName.toLowerCase()){c=bt(e),(d=t.getAttribute("id"))?m=d.replace(nt,"\\$&"):t.setAttribute("id",m),m="[id='"+m+"'] ",u=c.length;while(u--)c[u]=m+xt(c[u]);y=V.test(e)&&t.parentNode||t,x=c.join(",")}if(x)try{return M.apply(n,y.querySelectorAll(x)),n}catch(T){}finally{d||t.removeAttribute("id")}}}return At(e.replace(z,"$1"),t,n,i)}function st(e){return K.test(e+"")}function lt(){var e=[];function t(n,r){return e.push(n+=" ")>o.cacheLength&&delete t[e.shift()],t[n]=r}return t}function ut(e){return e[b]=!0,e}function ct(e){var t=f.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function pt(e,t,n){e=e.split("|");var r,i=e.length,a=n?null:t;while(i--)(r=o.attrHandle[e[i]])&&r!==t||(o.attrHandle[e[i]]=a)}function ft(e,t){var n=e.getAttributeNode(t);return n&&n.specified?n.value:e[t]===!0?t.toLowerCase():null}function dt(e,t){return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}function ht(e){return"input"===e.nodeName.toLowerCase()?e.defaultValue:t}function gt(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||D)-(~e.sourceIndex||D);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function mt(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function yt(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function vt(e){return ut(function(t){return t=+t,ut(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}s=at.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},r=at.support={},p=at.setDocument=function(e){var n=e?e.ownerDocument||e:w,i=n.parentWindow;return n!==f&&9===n.nodeType&&n.documentElement?(f=n,d=n.documentElement,h=!s(n),i&&i.frameElement&&i.attachEvent("onbeforeunload",function(){p()}),r.attributes=ct(function(e){return e.innerHTML="",pt("type|href|height|width",dt,"#"===e.firstChild.getAttribute("href")),pt(B,ft,null==e.getAttribute("disabled")),e.className="i",!e.getAttribute("className")}),r.input=ct(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")}),pt("value",ht,r.attributes&&r.input),r.getElementsByTagName=ct(function(e){return e.appendChild(n.createComment("")),!e.getElementsByTagName("*").length}),r.getElementsByClassName=ct(function(e){return e.innerHTML="",e.firstChild.className="i",2===e.getElementsByClassName("i").length}),r.getById=ct(function(e){return d.appendChild(e).id=b,!n.getElementsByName||!n.getElementsByName(b).length}),r.getById?(o.find.ID=function(e,t){if(typeof t.getElementById!==j&&h){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){return e.getAttribute("id")===t}}):(delete o.find.ID,o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){var n=typeof e.getAttributeNode!==j&&e.getAttributeNode("id");return n&&n.value===t}}),o.find.TAG=r.getElementsByTagName?function(e,n){return typeof n.getElementsByTagName!==j?n.getElementsByTagName(e):t}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},o.find.CLASS=r.getElementsByClassName&&function(e,n){return typeof n.getElementsByClassName!==j&&h?n.getElementsByClassName(e):t},m=[],g=[],(r.qsa=st(n.querySelectorAll))&&(ct(function(e){e.innerHTML="",e.querySelectorAll("[selected]").length||g.push("\\["+P+"*(?:value|"+B+")"),e.querySelectorAll(":checked").length||g.push(":checked")}),ct(function(e){var t=n.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("t",""),e.querySelectorAll("[t^='']").length&&g.push("[*^$]="+P+"*(?:''|\"\")"),e.querySelectorAll(":enabled").length||g.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),g.push(",.*:")})),(r.matchesSelector=st(y=d.webkitMatchesSelector||d.mozMatchesSelector||d.oMatchesSelector||d.msMatchesSelector))&&ct(function(e){r.disconnectedMatch=y.call(e,"div"),y.call(e,"[s!='']:x"),m.push("!=",I)}),g=g.length&&RegExp(g.join("|")),m=m.length&&RegExp(m.join("|")),v=st(d.contains)||d.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},r.sortDetached=ct(function(e){return 1&e.compareDocumentPosition(n.createElement("div"))}),A=d.compareDocumentPosition?function(e,t){if(e===t)return S=!0,0;var i=t.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(t);return i?1&i||!r.sortDetached&&t.compareDocumentPosition(e)===i?e===n||v(w,e)?-1:t===n||v(w,t)?1:c?F.call(c,e)-F.call(c,t):0:4&i?-1:1:e.compareDocumentPosition?-1:1}:function(e,t){var r,i=0,o=e.parentNode,a=t.parentNode,s=[e],l=[t];if(e===t)return S=!0,0;if(!o||!a)return e===n?-1:t===n?1:o?-1:a?1:c?F.call(c,e)-F.call(c,t):0;if(o===a)return gt(e,t);r=e;while(r=r.parentNode)s.unshift(r);r=t;while(r=r.parentNode)l.unshift(r);while(s[i]===l[i])i++;return i?gt(s[i],l[i]):s[i]===w?-1:l[i]===w?1:0},n):f},at.matches=function(e,t){return at(e,null,null,t)},at.matchesSelector=function(e,t){if((e.ownerDocument||e)!==f&&p(e),t=t.replace(Y,"='$1']"),!(!r.matchesSelector||!h||m&&m.test(t)||g&&g.test(t)))try{var n=y.call(e,t);if(n||r.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(i){}return at(t,f,null,[e]).length>0},at.contains=function(e,t){return(e.ownerDocument||e)!==f&&p(e),v(e,t)},at.attr=function(e,n){(e.ownerDocument||e)!==f&&p(e);var i=o.attrHandle[n.toLowerCase()],a=i&&L.call(o.attrHandle,n.toLowerCase())?i(e,n,!h):t;return a===t?r.attributes||!h?e.getAttribute(n):(a=e.getAttributeNode(n))&&a.specified?a.value:null:a},at.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},at.uniqueSort=function(e){var t,n=[],i=0,o=0;if(S=!r.detectDuplicates,c=!r.sortStable&&e.slice(0),e.sort(A),S){while(t=e[o++])t===e[o]&&(i=n.push(o));while(i--)e.splice(n[i],1)}return e},a=at.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=a(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=a(t);return n},o=at.selectors={cacheLength:50,createPseudo:ut,match:Q,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(rt,it),e[3]=(e[4]||e[5]||"").replace(rt,it),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||at.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&at.error(e[0]),e},PSEUDO:function(e){var n,r=!e[5]&&e[2];return Q.CHILD.test(e[0])?null:(e[3]&&e[4]!==t?e[2]=e[4]:r&&J.test(r)&&(n=bt(r,!0))&&(n=r.indexOf(")",r.length-n)-r.length)&&(e[0]=e[0].slice(0,n),e[2]=r.slice(0,n)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(rt,it).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=N[e+" "];return t||(t=RegExp("(^|"+P+")"+e+"("+P+"|$)"))&&N(e,function(e){return t.test("string"==typeof e.className&&e.className||typeof e.getAttribute!==j&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=at.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,l){var u,c,p,f,d,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!l&&!s;if(m){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){c=m[b]||(m[b]={}),u=c[e]||[],d=u[0]===T&&u[1],f=u[0]===T&&u[2],p=d&&m.childNodes[d];while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[T,d,f];break}}else if(v&&(u=(t[b]||(t[b]={}))[e])&&u[0]===T)f=u[1];else while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(v&&((p[b]||(p[b]={}))[e]=[T,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=o.pseudos[e]||o.setFilters[e.toLowerCase()]||at.error("unsupported pseudo: "+e);return r[b]?r(t):r.length>1?(n=[e,e,"",t],o.setFilters.hasOwnProperty(e.toLowerCase())?ut(function(e,n){var i,o=r(e,t),a=o.length;while(a--)i=F.call(e,o[a]),e[i]=!(n[i]=o[a])}):function(e){return r(e,0,n)}):r}},pseudos:{not:ut(function(e){var t=[],n=[],r=l(e.replace(z,"$1"));return r[b]?ut(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:ut(function(e){return function(t){return at(e,t).length>0}}),contains:ut(function(e){return function(t){return(t.textContent||t.innerText||a(t)).indexOf(e)>-1}}),lang:ut(function(e){return G.test(e||"")||at.error("unsupported lang: "+e),e=e.replace(rt,it).toLowerCase(),function(t){var n;do if(n=h?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===d},focus:function(e){return e===f.activeElement&&(!f.hasFocus||f.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!o.pseudos.empty(e)},header:function(e){return tt.test(e.nodeName)},input:function(e){return et.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:vt(function(){return[0]}),last:vt(function(e,t){return[t-1]}),eq:vt(function(e,t,n){return[0>n?n+t:n]}),even:vt(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:vt(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:vt(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:vt(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}};for(n in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})o.pseudos[n]=mt(n);for(n in{submit:!0,reset:!0})o.pseudos[n]=yt(n);function bt(e,t){var n,r,i,a,s,l,u,c=k[e+" "];if(c)return t?0:c.slice(0);s=e,l=[],u=o.preFilter;while(s){(!n||(r=X.exec(s)))&&(r&&(s=s.slice(r[0].length)||s),l.push(i=[])),n=!1,(r=U.exec(s))&&(n=r.shift(),i.push({value:n,type:r[0].replace(z," ")}),s=s.slice(n.length));for(a in o.filter)!(r=Q[a].exec(s))||u[a]&&!(r=u[a](r))||(n=r.shift(),i.push({value:n,type:a,matches:r}),s=s.slice(n.length));if(!n)break}return t?s.length:s?at.error(e):k(e,l).slice(0)}function xt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function wt(e,t,n){var r=t.dir,o=n&&"parentNode"===r,a=C++;return t.first?function(t,n,i){while(t=t[r])if(1===t.nodeType||o)return e(t,n,i)}:function(t,n,s){var l,u,c,p=T+" "+a;if(s){while(t=t[r])if((1===t.nodeType||o)&&e(t,n,s))return!0}else while(t=t[r])if(1===t.nodeType||o)if(c=t[b]||(t[b]={}),(u=c[r])&&u[0]===p){if((l=u[1])===!0||l===i)return l===!0}else if(u=c[r]=[p],u[1]=e(t,n,s)||i,u[1]===!0)return!0}}function Tt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function Ct(e,t,n,r,i){var o,a=[],s=0,l=e.length,u=null!=t;for(;l>s;s++)(o=e[s])&&(!n||n(o,r,i))&&(a.push(o),u&&t.push(s));return a}function Nt(e,t,n,r,i,o){return r&&!r[b]&&(r=Nt(r)),i&&!i[b]&&(i=Nt(i,o)),ut(function(o,a,s,l){var u,c,p,f=[],d=[],h=a.length,g=o||St(t||"*",s.nodeType?[s]:s,[]),m=!e||!o&&t?g:Ct(g,f,e,s,l),y=n?i||(o?e:h||r)?[]:a:m;if(n&&n(m,y,s,l),r){u=Ct(y,d),r(u,[],s,l),c=u.length;while(c--)(p=u[c])&&(y[d[c]]=!(m[d[c]]=p))}if(o){if(i||e){if(i){u=[],c=y.length;while(c--)(p=y[c])&&u.push(m[c]=p);i(null,y=[],u,l)}c=y.length;while(c--)(p=y[c])&&(u=i?F.call(o,p):f[c])>-1&&(o[u]=!(a[u]=p))}}else y=Ct(y===a?y.splice(h,y.length):y),i?i(null,a,y,l):M.apply(a,y)})}function kt(e){var t,n,r,i=e.length,a=o.relative[e[0].type],s=a||o.relative[" "],l=a?1:0,c=wt(function(e){return e===t},s,!0),p=wt(function(e){return F.call(t,e)>-1},s,!0),f=[function(e,n,r){return!a&&(r||n!==u)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;i>l;l++)if(n=o.relative[e[l].type])f=[wt(Tt(f),n)];else{if(n=o.filter[e[l].type].apply(null,e[l].matches),n[b]){for(r=++l;i>r;r++)if(o.relative[e[r].type])break;return Nt(l>1&&Tt(f),l>1&&xt(e.slice(0,l-1).concat({value:" "===e[l-2].type?"*":""})).replace(z,"$1"),n,r>l&&kt(e.slice(l,r)),i>r&&kt(e=e.slice(r)),i>r&&xt(e))}f.push(n)}return Tt(f)}function Et(e,t){var n=0,r=t.length>0,a=e.length>0,s=function(s,l,c,p,d){var h,g,m,y=[],v=0,b="0",x=s&&[],w=null!=d,C=u,N=s||a&&o.find.TAG("*",d&&l.parentNode||l),k=T+=null==C?1:Math.random()||.1;for(w&&(u=l!==f&&l,i=n);null!=(h=N[b]);b++){if(a&&h){g=0;while(m=e[g++])if(m(h,l,c)){p.push(h);break}w&&(T=k,i=++n)}r&&((h=!m&&h)&&v--,s&&x.push(h))}if(v+=b,r&&b!==v){g=0;while(m=t[g++])m(x,y,l,c);if(s){if(v>0)while(b--)x[b]||y[b]||(y[b]=q.call(p));y=Ct(y)}M.apply(p,y),w&&!s&&y.length>0&&v+t.length>1&&at.uniqueSort(p)}return w&&(T=k,u=C),x};return r?ut(s):s}l=at.compile=function(e,t){var n,r=[],i=[],o=E[e+" "];if(!o){t||(t=bt(e)),n=t.length;while(n--)o=kt(t[n]),o[b]?r.push(o):i.push(o);o=E(e,Et(i,r))}return o};function St(e,t,n){var r=0,i=t.length;for(;i>r;r++)at(e,t[r],n);return n}function At(e,t,n,i){var a,s,u,c,p,f=bt(e);if(!i&&1===f.length){if(s=f[0]=f[0].slice(0),s.length>2&&"ID"===(u=s[0]).type&&r.getById&&9===t.nodeType&&h&&o.relative[s[1].type]){if(t=(o.find.ID(u.matches[0].replace(rt,it),t)||[])[0],!t)return n;e=e.slice(s.shift().value.length)}a=Q.needsContext.test(e)?0:s.length;while(a--){if(u=s[a],o.relative[c=u.type])break;if((p=o.find[c])&&(i=p(u.matches[0].replace(rt,it),V.test(s[0].type)&&t.parentNode||t))){if(s.splice(a,1),e=i.length&&xt(s),!e)return M.apply(n,i),n;break}}}return l(e,f)(i,t,!h,n,V.test(e)),n}o.pseudos.nth=o.pseudos.eq;function jt(){}jt.prototype=o.filters=o.pseudos,o.setFilters=new jt,r.sortStable=b.split("").sort(A).join("")===b,p(),[0,0].sort(A),r.detectDuplicates=S,x.find=at,x.expr=at.selectors,x.expr[":"]=x.expr.pseudos,x.unique=at.uniqueSort,x.text=at.getText,x.isXMLDoc=at.isXML,x.contains=at.contains}(e);var O={};function F(e){var t=O[e]={};return x.each(e.match(T)||[],function(e,n){t[n]=!0}),t}x.Callbacks=function(e){e="string"==typeof e?O[e]||F(e):x.extend({},e);var n,r,i,o,a,s,l=[],u=!e.once&&[],c=function(t){for(r=e.memory&&t,i=!0,a=s||0,s=0,o=l.length,n=!0;l&&o>a;a++)if(l[a].apply(t[0],t[1])===!1&&e.stopOnFalse){r=!1;break}n=!1,l&&(u?u.length&&c(u.shift()):r?l=[]:p.disable())},p={add:function(){if(l){var t=l.length;(function i(t){x.each(t,function(t,n){var r=x.type(n);"function"===r?e.unique&&p.has(n)||l.push(n):n&&n.length&&"string"!==r&&i(n)})})(arguments),n?o=l.length:r&&(s=t,c(r))}return this},remove:function(){return l&&x.each(arguments,function(e,t){var r;while((r=x.inArray(t,l,r))>-1)l.splice(r,1),n&&(o>=r&&o--,a>=r&&a--)}),this},has:function(e){return e?x.inArray(e,l)>-1:!(!l||!l.length)},empty:function(){return l=[],o=0,this},disable:function(){return l=u=r=t,this},disabled:function(){return!l},lock:function(){return u=t,r||p.disable(),this},locked:function(){return!u},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],!l||i&&!u||(n?u.push(t):c(t)),this},fire:function(){return p.fireWith(this,arguments),this},fired:function(){return!!i}};return p},x.extend({Deferred:function(e){var t=[["resolve","done",x.Callbacks("once memory"),"resolved"],["reject","fail",x.Callbacks("once memory"),"rejected"],["notify","progress",x.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return x.Deferred(function(n){x.each(t,function(t,o){var a=o[0],s=x.isFunction(e[t])&&e[t];i[o[1]](function(){var e=s&&s.apply(this,arguments);e&&x.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[a+"With"](this===r?n.promise():this,s?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?x.extend(e,r):r}},i={};return r.pipe=r.then,x.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=g.call(arguments),r=n.length,i=1!==r||e&&x.isFunction(e.promise)?r:0,o=1===i?e:x.Deferred(),a=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?g.call(arguments):r,n===s?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},s,l,u;if(r>1)for(s=Array(r),l=Array(r),u=Array(r);r>t;t++)n[t]&&x.isFunction(n[t].promise)?n[t].promise().done(a(t,u,n)).fail(o.reject).progress(a(t,l,s)):--i;return i||o.resolveWith(u,n),o.promise()}}),x.support=function(t){var n,r,o,s,l,u,c,p,f,d=a.createElement("div");if(d.setAttribute("className","t"),d.innerHTML="
a",n=d.getElementsByTagName("*")||[],r=d.getElementsByTagName("a")[0],!r||!r.style||!n.length)return t;s=a.createElement("select"),u=s.appendChild(a.createElement("option")),o=d.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t.getSetAttribute="t"!==d.className,t.leadingWhitespace=3===d.firstChild.nodeType,t.tbody=!d.getElementsByTagName("tbody").length,t.htmlSerialize=!!d.getElementsByTagName("link").length,t.style=/top/.test(r.getAttribute("style")),t.hrefNormalized="/a"===r.getAttribute("href"),t.opacity=/^0.5/.test(r.style.opacity),t.cssFloat=!!r.style.cssFloat,t.checkOn=!!o.value,t.optSelected=u.selected,t.enctype=!!a.createElement("form").enctype,t.html5Clone="<:nav>"!==a.createElement("nav").cloneNode(!0).outerHTML,t.inlineBlockNeedsLayout=!1,t.shrinkWrapBlocks=!1,t.pixelPosition=!1,t.deleteExpando=!0,t.noCloneEvent=!0,t.reliableMarginRight=!0,t.boxSizingReliable=!0,o.checked=!0,t.noCloneChecked=o.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!u.disabled;try{delete d.test}catch(h){t.deleteExpando=!1}o=a.createElement("input"),o.setAttribute("value",""),t.input=""===o.getAttribute("value"),o.value="t",o.setAttribute("type","radio"),t.radioValue="t"===o.value,o.setAttribute("checked","t"),o.setAttribute("name","t"),l=a.createDocumentFragment(),l.appendChild(o),t.appendChecked=o.checked,t.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,d.attachEvent&&(d.attachEvent("onclick",function(){t.noCloneEvent=!1}),d.cloneNode(!0).click());for(f in{submit:!0,change:!0,focusin:!0})d.setAttribute(c="on"+f,"t"),t[f+"Bubbles"]=c in e||d.attributes[c].expando===!1;d.style.backgroundClip="content-box",d.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===d.style.backgroundClip;for(f in x(t))break;return t.ownLast="0"!==f,x(function(){var n,r,o,s="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",l=a.getElementsByTagName("body")[0];l&&(n=a.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",l.appendChild(n).appendChild(d),d.innerHTML="
").append(x.parseHTML(e)).find(i):e)}).complete(r&&function(e,t){s.each(r,o||[e.responseText,t,e])}),this},x.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){x.fn[t]=function(e){return this.on(t,e)}}),x.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:yn,type:"GET",isLocal:Cn.test(mn[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Dn,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":x.parseJSON,"text xml":x.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?_n(_n(e,x.ajaxSettings),t):_n(x.ajaxSettings,e)},ajaxPrefilter:Hn(An),ajaxTransport:Hn(jn),ajax:function(e,n){"object"==typeof e&&(n=e,e=t),n=n||{};var r,i,o,a,s,l,u,c,p=x.ajaxSetup({},n),f=p.context||p,d=p.context&&(f.nodeType||f.jquery)?x(f):x.event,h=x.Deferred(),g=x.Callbacks("once memory"),m=p.statusCode||{},y={},v={},b=0,w="canceled",C={readyState:0,getResponseHeader:function(e){var t;if(2===b){if(!c){c={};while(t=Tn.exec(a))c[t[1].toLowerCase()]=t[2]}t=c[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return 2===b?a:null},setRequestHeader:function(e,t){var n=e.toLowerCase();return b||(e=v[n]=v[n]||e,y[e]=t),this},overrideMimeType:function(e){return b||(p.mimeType=e),this},statusCode:function(e){var t;if(e)if(2>b)for(t in e)m[t]=[m[t],e[t]];else C.always(e[C.status]);return this},abort:function(e){var t=e||w;return u&&u.abort(t),k(0,t),this}};if(h.promise(C).complete=g.add,C.success=C.done,C.error=C.fail,p.url=((e||p.url||yn)+"").replace(xn,"").replace(kn,mn[1]+"//"),p.type=n.method||n.type||p.method||p.type,p.dataTypes=x.trim(p.dataType||"*").toLowerCase().match(T)||[""],null==p.crossDomain&&(r=En.exec(p.url.toLowerCase()),p.crossDomain=!(!r||r[1]===mn[1]&&r[2]===mn[2]&&(r[3]||("http:"===r[1]?"80":"443"))===(mn[3]||("http:"===mn[1]?"80":"443")))),p.data&&p.processData&&"string"!=typeof p.data&&(p.data=x.param(p.data,p.traditional)),qn(An,p,n,C),2===b)return C;l=p.global,l&&0===x.active++&&x.event.trigger("ajaxStart"),p.type=p.type.toUpperCase(),p.hasContent=!Nn.test(p.type),o=p.url,p.hasContent||(p.data&&(o=p.url+=(bn.test(o)?"&":"?")+p.data,delete p.data),p.cache===!1&&(p.url=wn.test(o)?o.replace(wn,"$1_="+vn++):o+(bn.test(o)?"&":"?")+"_="+vn++)),p.ifModified&&(x.lastModified[o]&&C.setRequestHeader("If-Modified-Since",x.lastModified[o]),x.etag[o]&&C.setRequestHeader("If-None-Match",x.etag[o])),(p.data&&p.hasContent&&p.contentType!==!1||n.contentType)&&C.setRequestHeader("Content-Type",p.contentType),C.setRequestHeader("Accept",p.dataTypes[0]&&p.accepts[p.dataTypes[0]]?p.accepts[p.dataTypes[0]]+("*"!==p.dataTypes[0]?", "+Dn+"; q=0.01":""):p.accepts["*"]);for(i in p.headers)C.setRequestHeader(i,p.headers[i]);if(p.beforeSend&&(p.beforeSend.call(f,C,p)===!1||2===b))return C.abort();w="abort";for(i in{success:1,error:1,complete:1})C[i](p[i]);if(u=qn(jn,p,n,C)){C.readyState=1,l&&d.trigger("ajaxSend",[C,p]),p.async&&p.timeout>0&&(s=setTimeout(function(){C.abort("timeout")},p.timeout));try{b=1,u.send(y,k)}catch(N){if(!(2>b))throw N;k(-1,N)}}else k(-1,"No Transport");function k(e,n,r,i){var c,y,v,w,T,N=n;2!==b&&(b=2,s&&clearTimeout(s),u=t,a=i||"",C.readyState=e>0?4:0,c=e>=200&&300>e||304===e,r&&(w=Mn(p,C,r)),w=On(p,w,C,c),c?(p.ifModified&&(T=C.getResponseHeader("Last-Modified"),T&&(x.lastModified[o]=T),T=C.getResponseHeader("etag"),T&&(x.etag[o]=T)),204===e||"HEAD"===p.type?N="nocontent":304===e?N="notmodified":(N=w.state,y=w.data,v=w.error,c=!v)):(v=N,(e||!N)&&(N="error",0>e&&(e=0))),C.status=e,C.statusText=(n||N)+"",c?h.resolveWith(f,[y,N,C]):h.rejectWith(f,[C,N,v]),C.statusCode(m),m=t,l&&d.trigger(c?"ajaxSuccess":"ajaxError",[C,p,c?y:v]),g.fireWith(f,[C,N]),l&&(d.trigger("ajaxComplete",[C,p]),--x.active||x.event.trigger("ajaxStop")))}return C},getJSON:function(e,t,n){return x.get(e,t,n,"json")},getScript:function(e,n){return x.get(e,t,n,"script")}}),x.each(["get","post"],function(e,n){x[n]=function(e,r,i,o){return x.isFunction(r)&&(o=o||i,i=r,r=t),x.ajax({url:e,type:n,dataType:o,data:r,success:i})}});function Mn(e,n,r){var i,o,a,s,l=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),o===t&&(o=e.mimeType||n.getResponseHeader("Content-Type"));if(o)for(s in l)if(l[s]&&l[s].test(o)){u.unshift(s);break}if(u[0]in r)a=u[0];else{for(s in r){if(!u[0]||e.converters[s+" "+u[0]]){a=s;break}i||(i=s)}a=a||i}return a?(a!==u[0]&&u.unshift(a),r[a]):t}function On(e,t,n,r){var i,o,a,s,l,u={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)u[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!l&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),l=o,o=c.shift())if("*"===o)o=l;else if("*"!==l&&l!==o){if(a=u[l+" "+o]||u["* "+o],!a)for(i in u)if(s=i.split(" "),s[1]===o&&(a=u[l+" "+s[0]]||u["* "+s[0]])){a===!0?a=u[i]:u[i]!==!0&&(o=s[0],c.unshift(s[1]));break}if(a!==!0)if(a&&e["throws"])t=a(t);else try{t=a(t)}catch(p){return{state:"parsererror",error:a?p:"No conversion from "+l+" to "+o}}}return{state:"success",data:t}}x.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(e){return x.globalEval(e),e}}}),x.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),x.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=a.head||x("head")[0]||a.documentElement;return{send:function(t,i){n=a.createElement("script"),n.async=!0,e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,t){(t||!n.readyState||/loaded|complete/.test(n.readyState))&&(n.onload=n.onreadystatechange=null,n.parentNode&&n.parentNode.removeChild(n),n=null,t||i(200,"success"))},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(t,!0)}}}});var Fn=[],Bn=/(=)\?(?=&|$)|\?\?/;x.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Fn.pop()||x.expando+"_"+vn++;return this[e]=!0,e}}),x.ajaxPrefilter("json jsonp",function(n,r,i){var o,a,s,l=n.jsonp!==!1&&(Bn.test(n.url)?"url":"string"==typeof n.data&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Bn.test(n.data)&&"data");return l||"jsonp"===n.dataTypes[0]?(o=n.jsonpCallback=x.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,l?n[l]=n[l].replace(Bn,"$1"+o):n.jsonp!==!1&&(n.url+=(bn.test(n.url)?"&":"?")+n.jsonp+"="+o),n.converters["script json"]=function(){return s||x.error(o+" was not called"),s[0]},n.dataTypes[0]="json",a=e[o],e[o]=function(){s=arguments},i.always(function(){e[o]=a,n[o]&&(n.jsonpCallback=r.jsonpCallback,Fn.push(o)),s&&x.isFunction(a)&&a(s[0]),s=a=t}),"script"):t});var Pn,Rn,Wn=0,$n=e.ActiveXObject&&function(){var e;for(e in Pn)Pn[e](t,!0)};function In(){try{return new e.XMLHttpRequest}catch(t){}}function zn(){try{return new e.ActiveXObject("Microsoft.XMLHTTP")}catch(t){}}x.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&In()||zn()}:In,Rn=x.ajaxSettings.xhr(),x.support.cors=!!Rn&&"withCredentials"in Rn,Rn=x.support.ajax=!!Rn,Rn&&x.ajaxTransport(function(n){if(!n.crossDomain||x.support.cors){var r;return{send:function(i,o){var a,s,l=n.xhr();if(n.username?l.open(n.type,n.url,n.async,n.username,n.password):l.open(n.type,n.url,n.async),n.xhrFields)for(s in n.xhrFields)l[s]=n.xhrFields[s];n.mimeType&&l.overrideMimeType&&l.overrideMimeType(n.mimeType),n.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");try{for(s in i)l.setRequestHeader(s,i[s])}catch(u){}l.send(n.hasContent&&n.data||null),r=function(e,i){var s,u,c,p;try{if(r&&(i||4===l.readyState))if(r=t,a&&(l.onreadystatechange=x.noop,$n&&delete Pn[a]),i)4!==l.readyState&&l.abort();else{p={},s=l.status,u=l.getAllResponseHeaders(),"string"==typeof l.responseText&&(p.text=l.responseText);try{c=l.statusText}catch(f){c=""}s||!n.isLocal||n.crossDomain?1223===s&&(s=204):s=p.text?200:404}}catch(d){i||o(-1,d)}p&&o(s,c,p,u)},n.async?4===l.readyState?setTimeout(r):(a=++Wn,$n&&(Pn||(Pn={},x(e).unload($n)),Pn[a]=r),l.onreadystatechange=r):r()},abort:function(){r&&r(t,!0)}}}});var Xn,Un,Vn=/^(?:toggle|show|hide)$/,Yn=RegExp("^(?:([+-])=|)("+w+")([a-z%]*)$","i"),Jn=/queueHooks$/,Gn=[nr],Qn={"*":[function(e,t){var n=this.createTween(e,t),r=n.cur(),i=Yn.exec(t),o=i&&i[3]||(x.cssNumber[e]?"":"px"),a=(x.cssNumber[e]||"px"!==o&&+r)&&Yn.exec(x.css(n.elem,e)),s=1,l=20;if(a&&a[3]!==o){o=o||a[3],i=i||[],a=+r||1;do s=s||".5",a/=s,x.style(n.elem,e,a+o);while(s!==(s=n.cur()/r)&&1!==s&&--l)}return i&&(a=n.start=+a||+r||0,n.unit=o,n.end=i[1]?a+(i[1]+1)*i[2]:+i[2]),n}]};function Kn(){return setTimeout(function(){Xn=t}),Xn=x.now()}function Zn(e,t,n){var r,i=(Qn[t]||[]).concat(Qn["*"]),o=0,a=i.length;for(;a>o;o++)if(r=i[o].call(n,t,e))return r}function er(e,t,n){var r,i,o=0,a=Gn.length,s=x.Deferred().always(function(){delete l.elem}),l=function(){if(i)return!1;var t=Xn||Kn(),n=Math.max(0,u.startTime+u.duration-t),r=n/u.duration||0,o=1-r,a=0,l=u.tweens.length;for(;l>a;a++)u.tweens[a].run(o);return s.notifyWith(e,[u,o,n]),1>o&&l?n:(s.resolveWith(e,[u]),!1)},u=s.promise({elem:e,props:x.extend({},t),opts:x.extend(!0,{specialEasing:{}},n),originalProperties:t,originalOptions:n,startTime:Xn||Kn(),duration:n.duration,tweens:[],createTween:function(t,n){var r=x.Tween(e,u.opts,t,n,u.opts.specialEasing[t]||u.opts.easing);return u.tweens.push(r),r},stop:function(t){var n=0,r=t?u.tweens.length:0;if(i)return this;for(i=!0;r>n;n++)u.tweens[n].run(1);return t?s.resolveWith(e,[u,t]):s.rejectWith(e,[u,t]),this}}),c=u.props;for(tr(c,u.opts.specialEasing);a>o;o++)if(r=Gn[o].call(u,e,c,u.opts))return r;return x.map(c,Zn,u),x.isFunction(u.opts.start)&&u.opts.start.call(e,u),x.fx.timer(x.extend(l,{elem:e,anim:u,queue:u.opts.queue})),u.progress(u.opts.progress).done(u.opts.done,u.opts.complete).fail(u.opts.fail).always(u.opts.always)}function tr(e,t){var n,r,i,o,a;for(n in e)if(r=x.camelCase(n),i=t[r],o=e[n],x.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),a=x.cssHooks[r],a&&"expand"in a){o=a.expand(o),delete e[r];for(n in o)n in e||(e[n]=o[n],t[n]=i)}else t[r]=i}x.Animation=x.extend(er,{tweener:function(e,t){x.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;i>r;r++)n=e[r],Qn[n]=Qn[n]||[],Qn[n].unshift(t)},prefilter:function(e,t){t?Gn.unshift(e):Gn.push(e)}});function nr(e,t,n){var r,i,o,a,s,l,u=this,c={},p=e.style,f=e.nodeType&&nn(e),d=x._data(e,"fxshow");n.queue||(s=x._queueHooks(e,"fx"),null==s.unqueued&&(s.unqueued=0,l=s.empty.fire,s.empty.fire=function(){s.unqueued||l()}),s.unqueued++,u.always(function(){u.always(function(){s.unqueued--,x.queue(e,"fx").length||s.empty.fire()})})),1===e.nodeType&&("height"in t||"width"in t)&&(n.overflow=[p.overflow,p.overflowX,p.overflowY],"inline"===x.css(e,"display")&&"none"===x.css(e,"float")&&(x.support.inlineBlockNeedsLayout&&"inline"!==ln(e.nodeName)?p.zoom=1:p.display="inline-block")),n.overflow&&(p.overflow="hidden",x.support.shrinkWrapBlocks||u.always(function(){p.overflow=n.overflow[0],p.overflowX=n.overflow[1],p.overflowY=n.overflow[2]}));for(r in t)if(i=t[r],Vn.exec(i)){if(delete t[r],o=o||"toggle"===i,i===(f?"hide":"show"))continue;c[r]=d&&d[r]||x.style(e,r)}if(!x.isEmptyObject(c)){d?"hidden"in d&&(f=d.hidden):d=x._data(e,"fxshow",{}),o&&(d.hidden=!f),f?x(e).show():u.done(function(){x(e).hide()}),u.done(function(){var t;x._removeData(e,"fxshow");for(t in c)x.style(e,t,c[t])});for(r in c)a=Zn(f?d[r]:0,r,u),r in d||(d[r]=a.start,f&&(a.end=a.start,a.start="width"===r||"height"===r?1:0))}}function rr(e,t,n,r,i){return new rr.prototype.init(e,t,n,r,i)}x.Tween=rr,rr.prototype={constructor:rr,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||"swing",this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(x.cssNumber[n]?"":"px")},cur:function(){var e=rr.propHooks[this.prop];return e&&e.get?e.get(this):rr.propHooks._default.get(this)},run:function(e){var t,n=rr.propHooks[this.prop];return this.pos=t=this.options.duration?x.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):rr.propHooks._default.set(this),this}},rr.prototype.init.prototype=rr.prototype,rr.propHooks={_default:{get:function(e){var t;return null==e.elem[e.prop]||e.elem.style&&null!=e.elem.style[e.prop]?(t=x.css(e.elem,e.prop,""),t&&"auto"!==t?t:0):e.elem[e.prop]},set:function(e){x.fx.step[e.prop]?x.fx.step[e.prop](e):e.elem.style&&(null!=e.elem.style[x.cssProps[e.prop]]||x.cssHooks[e.prop])?x.style(e.elem,e.prop,e.now+e.unit):e.elem[e.prop]=e.now}}},rr.propHooks.scrollTop=rr.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},x.each(["toggle","show","hide"],function(e,t){var n=x.fn[t];x.fn[t]=function(e,r,i){return null==e||"boolean"==typeof e?n.apply(this,arguments):this.animate(ir(t,!0),e,r,i)}}),x.fn.extend({fadeTo:function(e,t,n,r){return this.filter(nn).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(e,t,n,r){var i=x.isEmptyObject(e),o=x.speed(t,n,r),a=function(){var t=er(this,x.extend({},e),o);(i||x._data(this,"finish"))&&t.stop(!0)};return a.finish=a,i||o.queue===!1?this.each(a):this.queue(o.queue,a)},stop:function(e,n,r){var i=function(e){var t=e.stop;delete e.stop,t(r)};return"string"!=typeof e&&(r=n,n=e,e=t),n&&e!==!1&&this.queue(e||"fx",[]),this.each(function(){var t=!0,n=null!=e&&e+"queueHooks",o=x.timers,a=x._data(this);if(n)a[n]&&a[n].stop&&i(a[n]);else for(n in a)a[n]&&a[n].stop&&Jn.test(n)&&i(a[n]);for(n=o.length;n--;)o[n].elem!==this||null!=e&&o[n].queue!==e||(o[n].anim.stop(r),t=!1,o.splice(n,1));(t||!r)&&x.dequeue(this,e)})},finish:function(e){return e!==!1&&(e=e||"fx"),this.each(function(){var t,n=x._data(this),r=n[e+"queue"],i=n[e+"queueHooks"],o=x.timers,a=r?r.length:0;for(n.finish=!0,x.queue(this,e,[]),i&&i.stop&&i.stop.call(this,!0),t=o.length;t--;)o[t].elem===this&&o[t].queue===e&&(o[t].anim.stop(!0),o.splice(t,1));for(t=0;a>t;t++)r[t]&&r[t].finish&&r[t].finish.call(this);delete n.finish})}});function ir(e,t){var n,r={height:e},i=0;for(t=t?1:0;4>i;i+=2-t)n=Zt[i],r["margin"+n]=r["padding"+n]=e;return t&&(r.opacity=r.width=e),r}x.each({slideDown:ir("show"),slideUp:ir("hide"),slideToggle:ir("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,t){x.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),x.speed=function(e,t,n){var r=e&&"object"==typeof e?x.extend({},e):{complete:n||!n&&t||x.isFunction(e)&&e,duration:e,easing:n&&t||t&&!x.isFunction(t)&&t};return r.duration=x.fx.off?0:"number"==typeof r.duration?r.duration:r.duration in x.fx.speeds?x.fx.speeds[r.duration]:x.fx.speeds._default,(null==r.queue||r.queue===!0)&&(r.queue="fx"),r.old=r.complete,r.complete=function(){x.isFunction(r.old)&&r.old.call(this),r.queue&&x.dequeue(this,r.queue)},r},x.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2}},x.timers=[],x.fx=rr.prototype.init,x.fx.tick=function(){var e,n=x.timers,r=0;for(Xn=x.now();n.length>r;r++)e=n[r],e()||n[r]!==e||n.splice(r--,1);n.length||x.fx.stop(),Xn=t},x.fx.timer=function(e){e()&&x.timers.push(e)&&x.fx.start()},x.fx.interval=13,x.fx.start=function(){Un||(Un=setInterval(x.fx.tick,x.fx.interval))},x.fx.stop=function(){clearInterval(Un),Un=null},x.fx.speeds={slow:600,fast:200,_default:400},x.fx.step={},x.expr&&x.expr.filters&&(x.expr.filters.animated=function(e){return x.grep(x.timers,function(t){return e===t.elem}).length}),x.fn.offset=function(e){if(arguments.length)return e===t?this:this.each(function(t){x.offset.setOffset(this,e,t)});var n,r,o={top:0,left:0},a=this[0],s=a&&a.ownerDocument;if(s)return n=s.documentElement,x.contains(n,a)?(typeof a.getBoundingClientRect!==i&&(o=a.getBoundingClientRect()),r=or(s),{top:o.top+(r.pageYOffset||n.scrollTop)-(n.clientTop||0),left:o.left+(r.pageXOffset||n.scrollLeft)-(n.clientLeft||0)}):o},x.offset={setOffset:function(e,t,n){var r=x.css(e,"position");"static"===r&&(e.style.position="relative");var i=x(e),o=i.offset(),a=x.css(e,"top"),s=x.css(e,"left"),l=("absolute"===r||"fixed"===r)&&x.inArray("auto",[a,s])>-1,u={},c={},p,f;l?(c=i.position(),p=c.top,f=c.left):(p=parseFloat(a)||0,f=parseFloat(s)||0),x.isFunction(t)&&(t=t.call(e,n,o)),null!=t.top&&(u.top=t.top-o.top+p),null!=t.left&&(u.left=t.left-o.left+f),"using"in t?t.using.call(e,u):i.css(u)}},x.fn.extend({position:function(){if(this[0]){var e,t,n={top:0,left:0},r=this[0];return"fixed"===x.css(r,"position")?t=r.getBoundingClientRect():(e=this.offsetParent(),t=this.offset(),x.nodeName(e[0],"html")||(n=e.offset()),n.top+=x.css(e[0],"borderTopWidth",!0),n.left+=x.css(e[0],"borderLeftWidth",!0)),{top:t.top-n.top-x.css(r,"marginTop",!0),left:t.left-n.left-x.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||s;while(e&&!x.nodeName(e,"html")&&"static"===x.css(e,"position"))e=e.offsetParent;return e||s})}}),x.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);x.fn[e]=function(i){return x.access(this,function(e,i,o){var a=or(e);return o===t?a?n in a?a[n]:a.document.documentElement[i]:e[i]:(a?a.scrollTo(r?x(a).scrollLeft():o,r?o:x(a).scrollTop()):e[i]=o,t)},e,i,arguments.length,null)}});function or(e){return x.isWindow(e)?e:9===e.nodeType?e.defaultView||e.parentWindow:!1}x.each({Height:"height",Width:"width"},function(e,n){x.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){x.fn[i]=function(i,o){var a=arguments.length&&(r||"boolean"!=typeof i),s=r||(i===!0||o===!0?"margin":"border");return x.access(this,function(n,r,i){var o;return x.isWindow(n)?n.document.documentElement["client"+e]:9===n.nodeType?(o=n.documentElement,Math.max(n.body["scroll"+e],o["scroll"+e],n.body["offset"+e],o["offset"+e],o["client"+e])):i===t?x.css(n,r,s):x.style(n,r,i,s)},n,a?i:t,a,null)}})}),x.fn.size=function(){return this.length},x.fn.andSelf=x.fn.addBack,"object"==typeof module&&module&&"object"==typeof module.exports?module.exports=x:(e.jQuery=e.$=x,"function"==typeof define&&define.amd&&define("jquery",[],function(){return x}))})(window);
diff --git a/doc/mod_access.xml b/doc/mod_access.xml
new file mode 100644
index 0000000..f21332f
--- /dev/null
+++ b/doc/mod_access.xml
@@ -0,0 +1,68 @@
+
+
+
+ lets you filter clients by IP address.
+
+
+
+ denies access by returning a 403 status code
+
+
+
+ allows or denies access based on client IP address
+
+ A key value list mapping "access" and/or "deny" keys to a list of CIDR addresses or "all".
+
+
+ Checks the client IP address agains the rules. Default is to deny all addresses. The most precise matching rule defines the result ("192.168.100.0/24" takes precedence over "192.168.0.0/16"; similar to routing tables); if the same CIDR is in both lists the second action is taken. "all" is a synonym for "0.0.0.0/0" and "::/0", matching all IPv4 and IPv6 addresses.
+
+
+
+ Limit access to clients from the local network. The deny rule isn't strictly required, as the default is to deny anyway. The smaller CIDR strings for the local networks override the global deny rule.
+
+
+ setup {
+ module_load "mod_access";
+ }
+
+ access.check (
+ "allow" => ("127.0.0.0/8", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"),
+ "deny" => ("all")
+ );
+
+
+
+
+ Limit access to clients from "192.168.10.0/24", but deny access to "192.168.10.1". As "192.168.10.1" (equivalent to "192.168.10.1/32") is a more precise match it overwrites the allow rule for the subnet "192.168.10.0/24" containing it.
+
+
+ setup {
+ module_load "mod_access";
+ }
+
+ access.check (
+ "allow" => ("192.168.10.0/24"),
+ "deny" => ("192.168.10.1")
+ );
+
+
+
+
+
+
+
+
+
diff --git a/doc/mod_accesslog.xml b/doc/mod_accesslog.xml
new file mode 100644
index 0000000..77c6ed0
--- /dev/null
+++ b/doc/mod_accesslog.xml
@@ -0,0 +1,69 @@
+
+
+ logs requests handled by lighttpd to files, pipes or syslog. The format of the logs can be customized using printf-style placeholders.
+
+
+
+
+
diff --git a/doc/mod_auth.xml b/doc/mod_auth.xml
new file mode 100644
index 0000000..0c223b7
--- /dev/null
+++ b/doc/mod_auth.xml
@@ -0,0 +1,144 @@
+
+
+ requires authentication from clients using a username and password. It supports the basic (digest not yet) authentication method as well as plaintext, htpasswd and htdigest backends.
+
+
+
+ *IMPORTANT NOTE*: You need to put the auth actions before generating content! If a content handler is already active (like php or static or dirlist), auth will be ignored!
+
+ * Basic:
+ The "basic" method transfers the username and the password in cleartext over the network (base64 encoded) and might result in security problems if not used in conjunction with an encrypted communication channel between client and server.
+ It is recommend to use https in conjunction with basic authentication.
+
+ * Digest (not supported yet):
+ The "digest" method only transfers a hashed value over the network which performs a lot of work to harden the authentication process in insecure networks (like the internet).
+ The "digest" method doesn't work with the htpasswd backend, only with plaintext and htdigest.
+
+ *NOTE*: The digest method is broken in Internet Explorer < 7. Use basic instead if this is a problem for you. (not supported for now anyway)
+
+
+
+
+
+ requires authentication using a plaintext file
+
+
+
+ "basic" or "digest" - for now only "basic" is supported, but you still have to specify it.
+
+
+ the realm name to send in the "Need authentication" response to the browser; used in the hash for htdigest too.
+
+
+ the filename of the backend data
+
+
+ (optional) after how many seconds lighty reloads the password file if it got changed and is needed again (defaults to 10 seconds)
+
+
+
+
+ requires authentication using a plaintext file containing user:password pairs seperated by newlines (\n).
+
+
+
+
+ requires authentication using a htpasswd file
+
+
+
+ only "basic" is supported
+
+
+ the realm name to send in the "Need authentication" response to the browser; used in the hash for htdigest too.
+
+
+ the filename of the backend data
+
+
+ (optional) after how many seconds lighty reloads the password file if it got changed and is needed again (defaults to 10 seconds)
+
+
+
+
+
+ * requires authentication using a htpasswd file containing user:encrypted_password pairs seperated by newlines (\n)
+ * passwords are encrypted using crypt(3), use the htpasswd binary from apache to manage the file
+ ** hashes starting with "$apr1$" ARE supported (htpasswd -m)
+ ** hashes starting with "{SHA}" ARE supported (followed by sha1_base64(password), htpasswd -s)
+
+
+
+
+
+ requires authentication using a htdigest file
+
+
+
+ "basic" or "digest" - for now only "basic" is supported, but you still have to specify it.
+
+
+ the realm name to send in the "Need authentication" response to the browser; used in the hash for htdigest too.
+
+
+ the filename of the backend data
+
+
+ (optional) after how many seconds lighty reloads the password file if it got changed and is needed again (defaults to 10 seconds)
+
+
+
+
+
+ * requires authentication using a htdigest file containing user:realm:hashed_password tuples seperated by newlines (\n)
+ * the hashes are bound to the realm, so you can't change the realm without resetting the passwords
+ * passwords are saved as (modified) md5 hashes:
+ @md5hex(username + ":" + realm + ":" + password)@
+
+
+
+
+
+ handles request with "401 Unauthorized"
+
+
+
+
+
+
+ setup {
+ module_load "mod_auth";
+ }
+
+ #/members/ is for known users only
+ if request.path =^ "/members/" {
+ auth.plain [ "method" => "basic", "realm" => "members only", "file" => "/etc/lighttpd/users.txt"];
+ if req.env["REMOTE_USER"] !~ "^(admin1|user2|user3)$" { auth.deny; }
+ }
+
+
+
+
+
+
+ You can use @auth.require_user@ from the mod_lua plugin "contrib/core.lua":http://git.lighttpd.net/lighttpd/lighttpd2/tree/contrib/core.lua for the REMOTE_USER check too:
+
+
+
+
+ setup {
+ module_load ("mod_auth", "mod_lua");
+ lua.plugin "contrib/core.lua";
+ }
+
+ #/members/ is for known users only
+ if request.path =^ "/members/" {
+ auth.plain [ "method" => "basic", "realm" => "members only", "file" => "/etc/lighttpd/users.txt"];
+ auth.require_user ("admin1", "user2", "user3");
+ }
+
+
+
diff --git a/doc/mod_balance.xml b/doc/mod_balance.xml
new file mode 100644
index 0000000..7209869
--- /dev/null
+++ b/doc/mod_balance.xml
@@ -0,0 +1,51 @@
+
+
+ balances between different backends.
+
+
+ Using an action from mod_balance also activates a backlog: lighttpd2 will then put requests in a backlog if no backend is available and try again later.
+
+ Be careful: the referenced actions may get executed more than once (until one is successful!), so don't loop rewrites in them or something similar.
+
+
+
+ balance between actions (list or single action) with Round-Robin
+
+ the actions to balance between
+
+
+ Round-Robin (rr) the requests are distributed equally over all backends.
+
+
+
+ balance.rr { fastcgi "127.0.0.1:9090"; };
+
+
+
+
+ balance.rr ({ fastcgi "127.0.0.1:9090"; }, { fastcgi "127.0.0.1:9091"; });
+
+
+
+
+
+ balance between actions (list or single action) with SQF
+
+ the actions to balance between
+
+
+ Shortest-Queue-First (sqf) is similar to Round-Robin and prefers the backend with the shortest wait-queue.
+
+
+
+ balance.sqf { fastcgi "127.0.0.1:9090"; };
+
+
+
+
+
+
+
diff --git a/doc/mod_cache_disk_etag.xml b/doc/mod_cache_disk_etag.xml
new file mode 100644
index 0000000..29749f9
--- /dev/null
+++ b/doc/mod_cache_disk_etag.xml
@@ -0,0 +1,40 @@
+
+
+ caches generated content on disk if an etag response header is set; if the backend sends an already cached etag, the backend is closed and the file is sent directly.
+
+
+
+ Please note: This will not skip the backend, as it will need at least the reponse headers.
+
+ *Hint:*
+ Use a cron-job like the following to remove old cached data, e.g. in crontab daily:
+
+
+ *Hint:*
+ Have a look at mod_deflate to see this module in action.
+
+
+
+
+ cache responses based on the ETag response header
+
+ directory to store the cached results in
+
+
+ This blocks action progress until the response headers are done (i.e. there has to be a content generator before it (like fastcgi/dirlist/static file).
+ You could insert it multiple times of course (e.g. before and after deflate).
+
+
+
+
+ setup {
+ module_load "mod_cache_disk_etag";
+ }
+
+ cache.disk.etag "/var/lib/lighttpd/cache_etag"
+
+
+
+
diff --git a/doc/mod_core.lua.xml b/doc/mod_core.lua.xml
new file mode 100644
index 0000000..7ceb197
--- /dev/null
+++ b/doc/mod_core.lua.xml
@@ -0,0 +1,122 @@
+
+
+ provides some useful helpers written in lua
+
+
+
+ By default distributions (and @make install@) should provide the necessary files; but you can always find them in the "contrib":http://git.lighttpd.net/lighttpd/lighttpd2.git/tree/contrib folder:
+ * @core.lua@
+ * @core__cached_html.lua@
+ * @core__xsendfile.lua@
+
+ That way you can modify them for your own needs if you have to (although it is recommended to change the names of the files and the actions, so you don't get conflicts).
+
+ lighttpd should search for @core.lua@ in the correct (install) locations, so you don't need the absolute path here.
+
+
+
+
+ Splits the url into SCRIPT_NAME (the subdirectory a web application is mounted at) and PATH_INFO (the path the web application should route)
+
+ URL prefix ("subdirectory") the web application is mounted at
+
+
+ action block connecting to the wsgi backend
+
+
+
+ See "Howto WSGI":https://redmine.lighttpd.net/projects/lighttpd2/wiki/Howto_WSGI for an example.
+
+ WSGI applications expect the url to be split into @SCRIPT_NAME@ and @PATH_INFO@ (CGI environment variables); @SCRIPT_NAME@ is their "application root", and @PATH_INFO@ the requsted resource in the application.
+ By default, lighttpd uses an empty @PATH_INFO@ (unless you used the "pathinfo;" action, but this doesn't help as we're not dealing with static files here).
+
+ *Important*: WSGI is an "extension" of CGI; it doesn't specify a transport protocol, you can use it with plain CGI, FastCGI or SCGI (or anything else that supports the basic CGI protocol)
+
+
+
+
+
+
+ Example: Trac in @/trac@, listening via FastCGI on @unix:/var/run/lighttpd/trac.socket@.
+
+
+
+ setup {
+ module_load ("mod_lua", "mod_fastcgi");
+ lua.plugin "core.lua";
+ }
+ core.wsgi ( "/trac", { fastcgi "unix:/var/run/lighttpd/trac.socket"; } );
+
+
+
+
+
+ try to find a file for the current url with ".html" suffix, if we couldn't find a static file for the url yet and the url doesn't already have the ".html" suffix.
+
+
+ setup {
+ module_load "mod_lua";
+ lua.plugin "core.lua";
+ }
+ docroot "/some/dynamic/app/public";
+ core.cached_html;
+ if physical.is_file {
+ header.add ("X-cleanurl", "hit");
+ } else {
+ header.add ("X-cleanurl", "miss");
+ fastcgi "/var/run/lighttpd/dynamic-app.sock";
+ }
+
+
+
+
+
+ provides a simple X-Sendfile feature; send a "X-Sendfile: /path/to/file" response header from your backend
+
+ (optional) doc-root the files has be in
+
+
+
+ setup {
+ module_load ("mod_lua", "mod_fastcgi");
+ lua.plugin "core.lua";
+ }
+ fastcgi "/var/run/lighttpd/dynamic-app.sock";
+ core.xsendfile "/some/dynamic/app/";
+
+
+
+
+
+ require a specific authenticated user
+
+ list of usernames to allow
+
+
+
+ This helper constructs a regular expression to match against request.environment["REMOTE_USER"], so the example below is the same as:
+
+
+
+ This action uses lua only to create the action, no lua is executed to handle requests in this case.
+
+ Be careful: the empty username matches unauthenticated users.
+
+
+
+
+ setup {
+ module_load "mod_lua";
+ lua.plugin "core.lua";
+ }
+ auth.plain [ "method" => "basic", "realm" => "test", "file" => "/etc/lighttpd2/test.plain" ];
+ auth.require_user ("foo1", "foo2");
+
+
+
+
+
+
diff --git a/doc/mod_debug.xml b/doc/mod_debug.xml
new file mode 100644
index 0000000..ddadad6
--- /dev/null
+++ b/doc/mod_debug.xml
@@ -0,0 +1,28 @@
+
+
+
+ offers various utilities to aid you debug a problem.
+
+
+
+ shows a page similar to the one from mod_status, listing all active connections
+
+ By specifying one or more "connection ids" via querystring (parameter "con"), one can request additional debug output for specific connections.
+
+
+
+ if req.path == "/debug/connections" { debug.show_connections; }
+
+
+
+
+
+ dumps all allocated memory to the profiler output file if profiling enabled
+
+
+
+ lighttpd2 needs to be compiled with profiler support, and profiling has to be enabled by setting the environment variable @LIGHTY_PROFILE_MEM@ to the path of a target log file.
+
+
+
+
diff --git a/doc/mod_deflate.xml b/doc/mod_deflate.xml
new file mode 100644
index 0000000..3bf4887
--- /dev/null
+++ b/doc/mod_deflate.xml
@@ -0,0 +1,115 @@
+
+
+ mod_deflate compresses content on the fly
+
+
+ waits for response headers; if the response can be compressed deflate adds a filter to compress it
+
+
+
+ supported method, depends on whats compiled in (default: "deflate,gzip,bzip2")
+
+
+ blocksize is the number of kilobytes to compress at one time, it allows the webserver to do other work (network I/O) in between compression (default: 4096)
+
+
+ output-buffer is a per connection buffer for compressed output, it can help decrease the response size (fewer chunks to encode). If it is set to zero a shared buffer will be used. (default: 4096)
+
+
+ 0-9: lower numbers means faster compression but results in larger files/output, high numbers might take longer on compression but results in smaller files/output (depending on files ability to be compressed), this option is used for all selected encoding variants (default: 1)
+
+
+
+
+
+ deflate [ "encodings" => "deflate,gzip,bzip2", "blocksize" => 4096, "output-buffer" => 4096, "compression-level" => 1 ];
+
+
+
+
+ deflate [ "compression-level" => 6 ];
+
+
+
+
+
+
+
+
+ *Important*: As deflate; waits for the response headers, you must handle the request before it (see below how to check whether the request is handled).
+ If the request is not handled, you will get a "500 - Internal error" and a message in the error.log.
+
+ Does not compress:
+ * response status: 100, 101, 204, 205, 206, 304
+ * already compressed content
+ * if more than one etag response header is sent
+ * if no common encoding is found
+
+ Supported encodings
+ * gzip, deflate (needs zlib)
+ * bzip2 (needs bzip2)
+
+ * Modifies etag response header (if present)
+ * Adds "Vary: Accept-Encoding" response header
+ * Resets Content-Length header
+
+
+
+
+
+ setup {
+ module_load "mod_deflate";
+ }
+
+ # define our own "action"
+ do_deflate = {
+ # make sure static files get handled before we try deflate
+ static;
+ # we can only wait for response headers if we already have a request handler! (static only handles GET/HEAD requests)
+ if request.is_handled {
+ # limit content-types we want to compress -> see mimetypes
+ if response.header["Content-Type"] =~ "^(.*/javascript|text/.*)(;|$)" {
+ deflate;
+ }
+ }
+ };
+
+ # now add do_deflate; in places where you want deflate. for example at the end of your config:
+
+ do_deflate;
+
+
+
+
+
+ setup {
+ module_load ("mod_deflate", "mod_cache_disk_etag");
+ }
+
+ # define our own "action"
+ do_deflate = {
+ # make sure static files get handled before we try deflate
+ static;
+ # we can only wait for response headers if we already have a request handler! (static only handles GET/HEAD requests)
+ if request.is_handled {
+ # limit content-types we want to compress -> see mimetypes
+ if response.header["Content-Type"] =~ "^(.*/javascript|text/.*)(;|$)" {
+ deflate;
+ # the following block needs mod_cache_disk_etag (and is optional)
+ # only cache compressed result of static files
+ if physical.is_file {
+ cache.disk.etag "/var/cache/lighttpd/cache_etag";
+ }
+ }
+ }
+ };
+
+ # now add do_deflate; in places where you want deflate. for example at the end of your config:
+
+ do_deflate;
+
+
+
diff --git a/doc/mod_dirlist.xml b/doc/mod_dirlist.xml
new file mode 100644
index 0000000..f83fe9d
--- /dev/null
+++ b/doc/mod_dirlist.xml
@@ -0,0 +1,71 @@
+
+
+ lists files inside a directory. The output can be customized in various ways from style via css to excluding certain entries.
+
+
+ lists file in a directory
+
+
+
+ string: url to external stylesheet (default: inline internal css)
+
+
+ boolean: hide entries beginning with a dot (default: true)
+
+
+ boolean: hide entries ending with a tilde (~), often used for backups (default: true)
+
+
+ boolean: hide directories from the directory listing (default: false)
+
+
+ boolean: include HEADER.txt above the directory listing (default: false)
+
+
+ boolean: hide HEADER.txt from the directory listing (default: false)
+
+
+ boolean: html-encode HEADER.txt (if included), set to false if it contains real HTML (default: true)
+
+
+ boolean: include README.txt below the directory listing (default: true)
+
+
+ boolean: hide README.txt from the directory listing (default: false)
+
+
+ boolean: html-encode README.txt (if included), set to false if it contains real HTML (default: true)
+
+
+ list of strings: hide entries that end with one of the strings supplied (default: empty list)
+
+
+ list of strings: hide entries that begin with one of the strings supplied (default: empty list)
+
+
+ boolean: output debug information to log (default: false)
+
+
+ string: content-type to return in HTTP response headers (default: "text/html; charset=utf-8")
+
+
+
+
+
+
+ shows a directory listing including the content of HEADER.txt above the list and hiding itself from it; also hides all files ending in ".bak"
+
+
+ setup {
+ module_load ("mod_dirlist");
+ }
+
+ if req.path =^ "/files/" {
+ dirlist ("include-header" => true, "hide-header" => true, "hide->suffix" => (".bak"));
+ }
+
+
+
+
+
+
diff --git a/doc/mod_expire.xml b/doc/mod_expire.xml
new file mode 100644
index 0000000..52f024b
--- /dev/null
+++ b/doc/mod_expire.xml
@@ -0,0 +1,58 @@
+
+
+ add "Expires" and "Cache-Control" headers to the response
+
+
+ @ if your content changes in specific intervals like every 15 minutes.
+ ]]>
+
+
+
+ adds an "Expires" header to the response
+
+ the rule to calculate the "Expires" header value with
+
+
+
+ [plus] ()+
+
+
+ * @@ is one of "access", "now" or "modification"; "now" being equivalent to "access".
+ * "@plus@" is optional and does nothing.
+ * @@ is any positive integer.
+ * @@ is one of "seconds, "minutes", "hours", "days", "weeks", "months" or "years".
+
+ The trailing "s" in @@ is optional.
+
+ If you use "modification" as @@ and the file does not exist or cannot be accessed, mod_expire will do nothing and request processing will go on.
+
+ The expire action will overwrite any existing "Expires" header.
+ It will append the max-age value to any existing "Cache-Control" header.
+ ]]>
+
+
+
+
+ Cache image, css, txt and js files for 1 week.
+
+
+ setup {
+ module_load "mod_expire";
+ }
+
+ if req.path =~ "\.(jpe?g|png|gif|txt|css|js)$" {
+ expire "access plus 1 week";
+ }
+
+
+
+
+
diff --git a/doc/mod_fastcgi.xml b/doc/mod_fastcgi.xml
new file mode 100644
index 0000000..9290425
--- /dev/null
+++ b/doc/mod_fastcgi.xml
@@ -0,0 +1,37 @@
+
+
+ connect to FastCGI backends for generating response content
+
+
+ connect to FastCGI backend
+
+ socket to connect to, either "ip:port" or "unix:/path"
+
+
+
+ Don't confuse FastCGI with CGI! Not all CGI backends can be used as FastCGI backends (but you can use "fcgi-cgi":https://redmine.lighttpd.net/projects/fcgi-cgi/wiki to run CGI backends with lighttpd2).
+
+
+
+
+ fastcgi "127.0.0.1:9090"
+
+
+
+
+
+ Start php for example with spawn-fcgi: @spawn-fcgi -n -s /var/run/lighttpd2/php.sock -- /usr/bin/php5-cgi@
+
+
+
+ setup {
+ module_load "mod_fastcgi";
+ }
+
+ if phys.path =$ ".php" {
+ fastcgi "unix:/var/run/lighttpd2/php.sock";
+ }
+
+
+
+
diff --git a/doc/mod_flv.xml b/doc/mod_flv.xml
new file mode 100644
index 0000000..b4db5a9
--- /dev/null
+++ b/doc/mod_flv.xml
@@ -0,0 +1,38 @@
+
+
+ provides flash pseudo streaming
+
+
+ pseudo stream the current file as flash
+
+
+ Lets flash players seek with the "start" query string parameter to an (byte) offset in the file, and prepends a simple flash header before streaming the file from that offset.
+
+ Uses "video/x-flv" as hard-coded content type.
+
+
+
+
+ if phys.path =$ ".flv" {
+ flv;
+ }
+
+
+
+
+
+ Use caching and bandwidth throttling to save traffic. Use a small burst threshold to prevent the player from buffering at the beginning.
+
+ This config will make browsers cache videos for 1 month and limit bandwidth to 150 kilobyte/s after 500 kilobytes.
+
+
+
+ if phys.path =$ ".flv" {
+ expire "access 1 month";
+ io.throttle 500kbyte => 150kbyte;
+ flv;
+ }
+
+
+
+
diff --git a/doc/mod_fortune.xml b/doc/mod_fortune.xml
new file mode 100644
index 0000000..af3762a
--- /dev/null
+++ b/doc/mod_fortune.xml
@@ -0,0 +1,34 @@
+
+
+ loads quotes (aka fortune coookies) from a file and provides actions to add a random quote as response header (X-fortune) or display it as a page.
+
+
+ loads cookies from a file, can be called multiple times to load data from multiple files
+
+ the file to load the cookies from
+
+
+
+
+ adds a random quote as response header "X-fortune".
+
+
+
+ shows a random cookie as response text
+
+
+
+
+ setup {
+ module_load "mod_fortune";
+ fortune.load "/var/www/fortunes.txt";
+ }
+
+ if req.path == "/fortune" {
+ fortune.page;
+ } else {
+ fortune.header;
+ }
+
+
+
diff --git a/doc/mod_gnutls.xml b/doc/mod_gnutls.xml
new file mode 100644
index 0000000..e0e2dc9
--- /dev/null
+++ b/doc/mod_gnutls.xml
@@ -0,0 +1,155 @@
+
+
+ listens on separate sockets for TLS connections (https) using GnuTLS
+
+
+ setup a TLS socket
+
+
+
+ (mandatory) the socket address to listen on (same as "listen":plugin_core.html#plugin_core__setup_listen), can be specified more than once to setup multiple sockets with the same options
+
+
+ (mandatory) file containing the private key, certificate and intermediate certificates (the root certificate is usually not included)
+
+
+ GnuTLS priority string, specifying ciphers and other GnuTLS options (default: "NORMAL")
+
+
+ filename with generated dh-params (default: fixed 4096-bit parameters)
+
+
+ whether to force RC4 on TLS1.0 and SSL3.0 connections by appending ":-CIPHER-ALL:+ARCFOUR-128" to the priority string (default: false)
+
+
+ size of session database, 0 to disable session support (TLS ticket support is always enabled if GnuTLS supports it)
+
+
+ "fetch" backend name to search certificates in with the SNI servername as key (only available if SNI in lighttpd2 was enabled)
+
+
+ certificate to use if request contained SNI servername, but the sni-backend didn't find anything; if request didn't contain SNI the standard "pemfile"(s) are used
+
+
+
+
+
+
+ setup {
+ module_load "mod_gnutls";
+ gnutls (
+ "priority" => "PFS:-3DES-CBC:-ARCFOUR-128:-VERS-SSL3.0:-SHA1:+SHA1:+RSA:%SERVER_PRECEDENCE",
+ "listen" => "0.0.0.0:443",
+ "listen" => "[::]:443",
+ "pemfile" => "/etc/certs/lighttpd.pem"
+ );
+ }
+
+
+
+
+
+ setup {
+ module_load "mod_gnutls";
+ gnutls (
+ "priority" => "PFS:-3DES-CBC:-ARCFOUR-128:-VERS-SSL3.0:-SHA1:+SHA1:+RSA:%SERVER_PRECEDENCE",
+ "listen" => "0.0.0.0:443",
+ "listen" => "[::]:443",
+ "pemfile" => "/etc/certs/www.example.com.pem"
+ "pemfile" => "/etc/certs/mail.example.com.pem"
+ );
+ }
+
+
+
+
+
+
+ For a SNI hostname @example.com@ lighttpd2 will try to find the private key and certificate(s) in @/etc/certs/lighttpd_sni_example.com.pem@.
+
+
+
+ setup {
+ fetch.files_static "sni" => "/etc/certs/lighttpd_sni_*.pem";
+
+ module_load "mod_gnutls";
+ gnutls (
+ "priority" => "PFS:-3DES-CBC:-ARCFOUR-128:-VERS-SSL3.0:-SHA1:+SHA1:+RSA:%SERVER_PRECEDENCE",
+ "sni-backend" => "sni",
+ "listen" => "0.0.0.0:443",
+ "listen" => "[::]:443",
+ "pemfile" => "/etc/certs/lighttpd.pem"
+ );
+ }
+
+
+
+
+
+
+ "TLS SNI":http://en.wikipedia.org/wiki/Server_Name_Indication means that a client can send the hostname of the server it tries to connect to *unencrypted* in the TLS handshake.
+ If you want to host multiple hostnames on the same IP address (quite common) there are some options how to do it (they can be combined):
+ * Use a wildcard as "CommonName" (CN) in the certificate like @*.example.com@ (although this usually doesn't match @example.com@)
+ * Use "Subject Alternative Names" in the certificate
+ * Provide different certificates based on the SNI hostname in the TLS handshake.
+
+ Clients supporting SNI usually support the other options too, but not all clients support SNI.
+
+ GnuTLS has some basic SNI support built in; if you specify multiple @pemfile@ s in the options, it will pick the first with a certificate that matches the SNI hostname.
+
+ lighttpd2 has an optional extended SNI support (which has to be enabled at compile time, and is required for the @sni-*@ options to be available). It is designed to load certificates asynchronously from backends (files, SQL, ...) on demand, using the "fetch API":core_fetch.html#core_fetch.
+ In this case lighttpd2 will fetch the certificate based on the SNI hostname from the given @sni-backend@ before GnuTLS is started for a connection.
+ If a SNI hostname was given by the client, but no certificate was found in the backend, it will use @sni-fallback-pemfile@ (if set) instead of @pemfile@.
+
+ Combining @sni-backend@, @sni-fallback-pemfile@ and multiple @pemfile@ s won't work - it will only use the first configured @pemfile@ (if no SNI hostname was given by the client, otherwise @sni-*@ certificates are used).
+
+ Also note that lighttpd2 does NOT verify whether the SNI hostname matches the hostname in the HTTP request. SNI is only used by the client to tell the server for which hostname it should send the certificate (i.e. what the client is using to verify it).
+
+
+
+
+
+ The GnuTLS priority string configures which ciphers and protocol versions are available, and also a small set of options (workarounds to activate and so on).
+
+ Example: @"SECURE:-VERS-SSL3.0:-SHA1:+SHA1:-RSA:+RSA:%SERVER_PRECEDENCE"@
+ * @SECURE@: starts with @SECURE@ level: only secure ciphers and secure curves
+ * @-VERS-SSL3.0@: disables SSL3.0 support (all clients should support at least TLS1.0 now)
+ * @-SHA1:SHA1@ puts SHA1 back at the list for MAC algorithms (preferring the other SHA variants)
+ * @-RSA:+RSA@: prefer (RSA) DHE/ECDHE key exchanges over simple RSA
+ * @%SERVER_PRECEDENCE@ a flag telling GnuTLS to use its own order of preference instead of the order provided by the client.
+
+ See also:
+ * "Priority strings":http://gnutls.org/manual/html_node/Priority-Strings.html#Priority-Strings (GnuTLS manual)
+ * "GnuTLS Priority Strings":http://blog.lighttpd.net/gnutls-priority-strings.html
+ * @gnutls-cli -l --priority "SECURE:-VERS-SSL3.0:-SHA1:+SHA1:-RSA:+RSA:%SERVER_PRECEDENCE"@
+
+
+
+
+
+ "BEAST":http://en.wikipedia.org/wiki/Transport_Layer_Security#BEAST_attack is considered mitigated on clients by many now, so this workaround is no longer recommended and disabled by default.
+
+ It enabled it will enforce the usage of RC4 on TLS1.0 and before (TLS1.1 and TLS1.2 are not vulnerable); but RC4 has issues on its own.
+
+
+
+
+
+ The @DHE_RSA@ key exchange requires parameters (similar to the curves used for @ECDHE_RSA@); the parameters specify a prime @p@ and a group generator @g@ of the multiplicative group of integers modulo @p@ (i.e. for all @x@ in @1..p-1@ exists an @e@ with @g^e = x@); sometimes @g@ might only create a sufficiently large subgroup (for example of size @(p-1)/2@ for Sophie Germain primes).
+
+ The security of the DH key exchange depends (among other things) on the bit length of @p@; therefore lighttpd includes default 4096-bit parameters (provided by the GnuTLS certtool with @certtool --get-dh-params --bits=4096@ in version 3.0.22), and these should be safe to use.
+
+ You can use either GnuTLS or openssl to generate your own parameters:
+ * @certtool --generate-dh-params --bits=2048@
+ * @openssl dhparam -dsaparam 2048@
+ * @openssl dhparam -2 2048@
+ * @openssl dhparam -5 2048@
+
+ The GnuTLS @certtool@ only generates "dsaparam" style parameters (any prime with a large generator), while @openssl@ can generate parameters with a generator of 2 or 5 combined with a Sophie Germain prime @p@ (i.e. (p-1)/2 is a prime too); such strong parameters take a lot more time to generate.
+ ("dsaparam" style parameters should not be reused, i.e. one should generate a new private key for each connection.)
+
+ See also:
+ * "Diffie-Hellman(-Merkle) key exchange":http://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange
+
+
+
diff --git a/doc/mod_limit.xml b/doc/mod_limit.xml
new file mode 100644
index 0000000..ea3de51
--- /dev/null
+++ b/doc/mod_limit.xml
@@ -0,0 +1,117 @@
+
+
+ limits concurrent connections or requests per second.
+
+
+ Both limits can be "in total" or per IP.
+
+
+
+
+ limits the total amount of concurrent connections to the specified limit.
+
+ the maximum number of concurrent connections
+
+
+ (optional) an action to be executed when the limit is reached
+
+
+ If no action is defined a 503 error page will be returned. If it is specified there is no other special handling apart from running the specified action when the limit is reached.
+
+
+
+ limit.con 10;
+
+
+
+
+
+ limits the total amount of concurrent connections per IP to the specified limit.
+
+ the maximum number of concurrent connections per IP
+
+
+ (optional) an action to be executed when the limit is reached
+
+
+ If no action is defined a 503 error page will be returned. If it is specified there is no other special handling apart from running the specified action when the limit is reached.
+
+
+
+ limit.con_ip 2;
+
+
+
+
+
+ limits the amount of requests per second to the specified limit.
+
+ the maximum number of requests per second
+
+
+ (optional) an action to be executed when the limit is reached
+
+
+ If no action is defined a 503 error page will be returned. If it is specified there is no other special handling apart from running the specified action when the limit is reached.
+
+
+
+ limit.req 100;
+
+
+
+
+
+ limits the amount of requests per second per IP to the specified limit.
+
+ the maximum number of requests per second per IP
+
+
+ (optional) an action to be executed when the limit is reached
+
+
+ If no action is defined a 503 error page will be returned. If it is specified there is no other special handling apart from running the specified action when the limit is reached.
+
+
+
+ limit.req_ip 100;
+
+
+
+
+
+
+ This config snippet will allow only 10 active downloads overall and 1 per IP. If the limit is exceeded, either because more than 10 people try to access this resource or one person tries a second time while having one download running already, they will be redirected to /connection_limit_reached.html.
+
+
+ setup {
+ module_load ("mod_limit","mod_redirect");
+ }
+
+ limit_reached = {
+ redirect "/connection_limit_reached.html";
+ };
+
+ if req.path =^ "/downloads/" {
+ limit.con 10 => limit_reached;
+ limit.con_ip 1 => limit_reached;
+ }
+
+
+
+
+
+ This config snippet will write a message to the log containing the clien IP address if the /login page is hit more than once in a second.
+ It will however also not do anything else. The client will be able to use the /login page as often as he wants.
+
+
+ setup {
+ module_load "mod_limit";
+ }
+
+ if req.path == "/login" {
+ limit.req_ip 1 => { log.write "Possible bruteforce from %{req.remoteip}"; };
+ }
+
+
+
diff --git a/doc/mod_lua.xml b/doc/mod_lua.xml
new file mode 100644
index 0000000..9f23a8a
--- /dev/null
+++ b/doc/mod_lua.xml
@@ -0,0 +1,96 @@
+
+
+ load lua plugins and actions
+
+
+ load file as lua plugin
+
+ the file containing the lua code
+
+
+ A key-value table with options; no options available yet
+
+
+ arguments forwarded to the lua plugin
+
+
+
+ A lua plugin can register setup and action callbacks (like any C module) by creating a setups / actions table in the global lua namespace.
+
+ The filename and the third argument @lua-args@ are available as parameters in @...@ in the lua script.
+
+
+
+
+ setup {
+ module_load "mod_lua";
+ lua.plugin "secdownload.lua";
+ }
+
+
+
+
+
+
+ (see "contrib/core.lua":http://git.lighttpd.net/lighttpd/lighttpd2/tree/contrib/core.lua for a real example)
+
+
+ local filename, args = ...
+
+ -- args are from the lua.plugin line
+
+ local function simple(actionarg)
+ -- actionarg is the parameter from the 'single "/xxx";' action line
+
+ -- create an action:
+ return action.respond()
+ end
+
+ actions = {
+ ["simple"] = simple,
+ }
+
+
+
+
+
+ load file as lua config
+
+ the file containing the lua code
+
+
+
+
+ time in seconds after which the file is checked for modifications and reloaded. 0 disables reloading (default 0)
+
+
+
+
+ arguments forwarded to the lua plugin
+
+
+
+ lua.handler is basically the same as "include_lua":plugin_core.html#plugin_core__action_include_lua with the following differences:
+ * each worker loads the lua file itself
+ * it isn't loaded before it is used, so you won't see errors in the script at load time
+ * it cannot call setup functions
+ * it supports arguments to the script (@local filename, args = ...@)
+ * doesn't lock the global lua lock, so it performs better when you use multiple workers
+
+ See "contrib/core.lua":http://git.lighttpd.net/lighttpd/lighttpd2/tree/contrib/core.lua for how we load some external actions like "contrib/core__xsendfile.lua":http://git.lighttpd.net/lighttpd/lighttpd2/tree/contrib/core__xsendfile.lua
+
+
+
+
+ setup {
+ module_load "mod_lua";
+ lua.plugin "secdownload.lua";
+ }
+
+ if req.path =^ "/app" {
+ lua.handler "/etc/lighttpd/pathrewrite.lua", [ "ttl" => 300 ], "/app";
+ }
+
+
+
+
diff --git a/doc/mod_memcached.xml b/doc/mod_memcached.xml
new file mode 100644
index 0000000..764b6af
--- /dev/null
+++ b/doc/mod_memcached.xml
@@ -0,0 +1,108 @@
+
+
+ caches content on memcached servers
+
+
+
+ @lookup@ tries to find data associated with the key, and returns it as http body with status 200 if it finds something.
+ @store@ stores a http body (generated by another backend) in memcached.
+
+ Caching always requires you to think about what you want; you cannot cache content that changes with every request!
+
+ So most of the time you probably want to set a TTL for the stored content in memcached; your users probably don't need new content to be available the next second, perhaps 60 seconds is still good (obviously not true for a chat...).
+
+ The other way is to purge the keys in your dynamic backend; you can set the memcached content from your backend too, which probably is faster than @memcached.store@.
+
+ If the key is longer than 255 bytes or contains characters outside the range 0x21 - 0x7e we will use a hash of it instead (for now sha1, but that may change).
+
+
+
+
+ searches the content in a memcached database
+
+
+
+ socket address as string (default: 127.0.0.1:11211)
+
+
+ (boolean, not supported yet) whether to lookup headers too. if false content-type determined by request.uri.path (default: false)
+
+
+ pattern for lookup key (default: "%{req.path}")
+
+
+
+
+ action to run on cache hit (lookup was successful)
+
+
+ action to run on cache miss (lookup was not successful)
+
+
+
+
+ searches the content in a memcached database
+
+
+
+ socket address as string (default: 127.0.0.1:11211)
+
+
+ (integer) flags for storing data (default 0)
+
+
+ ttl for storing (default 30; use 0 if you want to cache "forever")
+
+
+ maximum size in bytes we want to store (default: 64*1024)
+
+
+ (boolean, not supported yet) whether to store headers too (default: false)
+
+
+ pattern for store key (default: "%{req.path}")
+
+
+
+
+
+
+
+ setup {
+ module_load "mod_memcached";
+ }
+
+ memcached.lookup (["key" => "%{req.scheme}://%{req.host}%{req.path}"], {
+ header.add "X-Memcached" => "Hit";
+ }, {
+ header.add "X-Memcached" => "Miss";
+ docroot "/var/www";
+ # important: You need a content handler before memcached.store
+ static;
+ memcached.store ["key" => "%{req.scheme}://%{req.host}%{req.path}"];
+ });
+
+
+
+
+
+ Exports a lua api to per-worker luaStates too (for use in lua.handler):
+
+ @memcached.new(address)@ creates a new connection; a connection provides:
+ * @req = con:get(key, cb | vr)@
+ * @req = con:set(key, value, cb | vr, [ttl])@
+ * @con:setq(key, value, [ttl])@
+
+ If a callback was given, the callback gets called with a response object; otherwise the response will be in req.response when ready.
+
+ The response object has:
+ * @code@: 1 - Ok, 2 - Not stored, 3 - Exists, 4 - Not found, 5 - Error
+ * @error@: error message
+ * @key@: the lookup key
+ * @flags@
+ * @ttl@
+ * @cas@
+ * @data@
+
+
+
diff --git a/doc/mod_openssl.xml b/doc/mod_openssl.xml
new file mode 100644
index 0000000..47cd5c7
--- /dev/null
+++ b/doc/mod_openssl.xml
@@ -0,0 +1,119 @@
+
+
+ listens on separate sockets for TLS connections (https) using OpenSSL
+
+
+ setup a TLS socket
+
+
+
+ (mandatory) the socket address to listen on (same as "listen":plugin_core.html#plugin_core__setup_listen), can be specified more than once to setup multiple sockets with the same options
+
+
+ (mandatory) file containing the private key, certificate and (optionally) intermediate certificates (the root certificate is usually not included)
+
+
+ file containing the intermediate certificates
+
+
+ OpenSSL ciphers string
+
+
+ filename with generated dh-params (default: fixed 4096-bit parameters)
+
+
+ OpenSSL ecdh-curve name
+
+
+ list of OpenSSL options (default: NO_SSLv2, CIPHER_SERVER_PREFERENCE, NO_COMPRESSION)
+
+
+ enable client certificate verification (default: false)
+
+
+ allow all CAs and self-signed certificates, for manual checking (default: false)
+
+
+ sets client verification depth (default: 1)
+
+
+ abort clients failing verification (default: false)
+
+
+ file containing client CA certificates (to verify client certificates)
+
+
+
+
+
+
+ For @ciphers@ see OpenSSL "ciphers":http://www.openssl.org/docs/apps/ciphers.html string
+
+ For @options@ see "options":https://www.openssl.org/docs/ssl/SSL_CTX_set_options.html. Explicitly specify the reverse flag by toggling the "NO_" prefix to override defaults.
+
+
+
+
+
+ setup {
+ module_load "mod_openssl";
+ openssl [
+ "listen" => "0.0.0.0:443",
+ "listen" => "[::]:443",
+ "pemfile" => "/etc/certs/lighttpd.pem",
+ "options" => ["NO_SSLv3"],
+ ];
+ }
+
+
+
+
+
+ setup {
+ module_load "mod_openssl";
+ openssl (
+ "listen" => "0.0.0.0:443",
+ "listen" => "[::]:443",
+ "pemfile" => "/etc/certs/lighttpd.pem",
+ "client-ca-file" => "/etc/certs/myCA.pem",
+ "verify" => true,
+ "verify-require" => true
+ );
+ }
+
+
+
+
+
+ setup {
+ module_load "mod_openssl";
+ openssl (
+ "listen" => "0.0.0.0:443",
+ "listen" => "[::]:443",
+ "pemfile" => "/etc/certs/lighttpd.pem",
+ "verify" => true,
+ "verify-any" => true,
+ "verify-depth" => 9
+ );
+ }
+ openssl.setenv "client-cert";
+
+
+
+
+
+ set SSL environment strings
+
+ list of subsets to export
+
+
+
+ Supported subsets:
+ * "client" - set @SSL_CLIENT_S_DN_@ short-named entries
+ * "client-cert" - set @SSL_CLIENT_CERT@ to client certificate PEM
+ * "server" - set @SSL_SERVER_S_DN_@ short-named entries
+ * "server-cert" - set @SSL_SERVER_CERT@ to server certificate PEM
+
+
+
+
diff --git a/doc/mod_progress.xml b/doc/mod_progress.xml
new file mode 100644
index 0000000..45b988d
--- /dev/null
+++ b/doc/mod_progress.xml
@@ -0,0 +1,88 @@
+
+
+ track connection progress (state) via a unique identifier
+
+
+
+ mod_progress lets you track connection progress (or rather state) using a lookup table in which connections are registered via a random unique identifier specified with the request.
+ It is most commonly used to implement progress bars for file uploads.
+
+ A request to the webserver is registered using the @progress.track@ action and being tracked by a random unique identifier supplied with the @X-Progress-Id@ querystring parameter.
+ From that moment on, other requests can fetch the state of the first request through the @progress.show@ action specifying the @X-Progress-Id@ used earlier.
+ Even after a tracked request finished and the connection to the client is gone, requests can for a limited amount of time get the status of it to see it as "done".
+
+ A live demonstration of a progress bar implementation can be seen at http://demo.lighttpd.net/progress/
+ Check the sourcecode there for further insight.
+
+
+
+
+
+ Time to live in seconds for entries after a disconnect in the internal lookup table
+
+ time to live in seconds (default: 30)
+
+
+
+
+
+
+ tracks the current request if the X-Progress-ID querystring key is supplied
+
+
+
+ returns state information about the request tracked with the ID specified by the X-Progress-ID querystring parameter
+
+ (optional) output format, one of "legacy", "json" or "jsonp". Defaults to "json".
+
+
+
+ Output formats:
+ * legacy: @new Object({"state": "running"", "received": 123456, "sent": 0, "request_size": 200000, "response_size": 0})@
+ * json: @{"state": "running", "received": 123456, "sent": 0, "request_size": 200000, "response_size": 0}@
+ * jsonp: @progress({"state": "running", "received": 123456, "sent": 0, "request_size": 200000, "response_size": 0})@
+ The function name (default "progress") can be altered by supplying a X-Progress-Callback querystring parameter.
+
+ The JSON object can contain the following members:
+ * *state*: One of @"unknown"@, @"running"@, @"done"@ or @"error"@.
+ * *received*: Bytes received by lighty or uploaded by the client.
+ * *request_size*: Total size of request or uploaded file as specified via the Content-Length request header.
+ * *sent*: Bytes sent by lighty or downloaded by the client.
+ * *response_size*: Total size of response. Attention: this might grow over time in case of streaming from a backend.
+ * *status*: HTTP status code of response.
+
+ @received@, @request_size@, @sent@ and @response_size@ are only available if @state@ is @"running"@ or @"done"@.
+ @status@ is only available if @state@ is @"error"@.
+
+
+
+
+
+ setup {
+ module_load "mod_progress";
+ }
+
+ if req.path == "/upload.php" { progress.track; }
+ if req.path == "/progress" { progress.show; }
+
+
+
+
+
+
+
diff --git a/doc/mod_proxy.xml b/doc/mod_proxy.xml
new file mode 100644
index 0000000..21d4d19
--- /dev/null
+++ b/doc/mod_proxy.xml
@@ -0,0 +1,20 @@
+
+
+ connect to HTTP backends for generating response content
+
+
+ connect to HTTP backend
+
+ socket to connect to, either "ip:port" or "unix:/path"
+
+
+
+ setup {
+ module_load "mod_proxy";
+ }
+
+ proxy "127.0.0.1:8080";
+
+
+
+
diff --git a/doc/mod_redirect.xml b/doc/mod_redirect.xml
new file mode 100644
index 0000000..a360ec2
--- /dev/null
+++ b/doc/mod_redirect.xml
@@ -0,0 +1,77 @@
+
+
+ redirect clients by sending a http status code 301 plus Location header
+
+
+
+ It supports matching "regular expressions":core_regex.html#core_regex and "substitution":core_pattern.html#core_pattern with captured substrings as well as other placeholders.
+
+
+
+
+ redirect clients
+
+ a simple target string or one rule, mapping a regular expression to a target string, or a list of rules.
+
+
+
+ * The target string is a "pattern":core_pattern.html#core_pattern.
+ * The "regular expressions":core_regex.html#core_regex are used to match the request path (always starts with "/"!)
+ * If a list of rules is given, redirect stops on the first match.
+ * By default the target string is interpreted as absolute uri; if it starts with '?' it will replace the query string, if it starts with '/' it will replace the current path, and if it starts with './' it is interpreted relative to the current "directory" in the path.
+
+
+
+
+ setup {
+ module_load "mod_redirect";
+ }
+ if request.scheme == "http" {
+ if request.query == "" {
+ redirect "https://%{request.host}%{enc:request.path}";
+ } else {
+ redirect "https://%{request.host}%{enc:request.path}?%{request.query}";
+ }
+ }
+
+
+
+
+ setup {
+ module_load "mod_redirect";
+ }
+ if request.host !~ "^www\.(.*)$" {
+ if request.query == "" {
+ redirect "http://www.%{request.host}%{enc:request.path}";
+ } else {
+ redirect "http://www.%{request.host}%{enc:request.path}?%{request.query}";
+ }
+ }
+
+
+
+
+ setup {
+ module_load "mod_redirect";
+ }
+ redirect "^/old_url$" => "http://new.example.tld/url"
+
+
+
+
+
+ redirect all non www. requests. for example: foo.tld/bar?x=y to www.foo.tld/bar?x=y
+
+
+ if request.host !~ "^www\.(.*)$" {
+ redirect "." => "http://www.%1/$0?%{request.query}";
+ }
+
+
+
+
+
+
diff --git a/doc/mod_rewrite.xml b/doc/mod_rewrite.xml
new file mode 100644
index 0000000..0c0ac9d
--- /dev/null
+++ b/doc/mod_rewrite.xml
@@ -0,0 +1,83 @@
+
+
+ modifies request path and querystring
+
+
+
+ It supports matching "regular expressions":core_regex.html#core_regex and "substitution":core_pattern.html#core_pattern with captured substrings as well as other placeholders.
+
+ If your rewrite target does not contain any questionmark (@?@), then the querystring will not be altered.
+ If it does contain @?@ the querystring will be overwritten with the part after the @?@. To append the original querystring, use @%{request.query}@.
+
+ *IMPORTANT*: rewrite only changes the url, not the physical filename that it got mapped to by docroot or alias actions. So you need your docroot and alias actions after the rewrite.
+ If you have conditional rewrites like @if !phys.is_file { rewrite ... }@ you need docroot/alias both before (so it can check for the physical file) *and* after it (to build the new physical path).
+
+
+
+
+ modify request path and querystring
+
+ a simple target string or one rule, mapping a regular expression to a target string, or a list of rules.
+
+
+
+ * The target string is a "pattern":core_pattern.html#core_pattern.
+ * The "regular expressions":core_regex.html#core_regex are used to match the request path (always starts with "/" and does *not* include the query string!)
+ * If a list of rules is given, rewrite stops on the first match.
+ * Replaces the query string iff the target string contains an @?@
+
+
+
+
+ setup {
+ module_load "mod_rewrite";
+ }
+ rewrite "/new/path";
+
+
+
+
+ setup {
+ module_load "mod_rewrite";
+ }
+ rewrite "regex" => "/new/path";
+
+
+
+
+ setup {
+ module_load "mod_rewrite";
+ }
+ rewrite ("regex1" => "/new/path1", ..., "regexN" => "/new/pathN");
+
+
+
+
+
+ Note: you really should move the logic for such rewrites into your application, and just rewrite all pretty urls to your index.php without putting the actions into the querystring.
+
+
+ "/article.php?id=$1",
+ "^/download/(\d+)/(.*)$" => "/download.php?fileid=$1&filename=$2"
+ );
+ rewrite "^/user/(.+)$" => "/user.php?name=$1";
+
+ # good: handle it in the backend (easier to port the config)
+ rewrite "^/(article|download|user)(/|$)" => "/index.php";
+ ]]>
+
+
+
+
+
+
diff --git a/doc/mod_scgi.xml b/doc/mod_scgi.xml
new file mode 100644
index 0000000..98885f4
--- /dev/null
+++ b/doc/mod_scgi.xml
@@ -0,0 +1,23 @@
+
+
+ connect to SCGI backends for generating response content
+
+
+ connect to SCGI backend
+
+ socket to connect to, either "ip:port" or "unix:/path"
+
+
+
+ setup {
+ module_load "mod_scgi";
+ }
+
+ if req.path =^ "/RPC2" {
+ scgi "127.0.0.1:5000";
+ }
+
+
+
+
+
diff --git a/doc/mod_secdownload.lua.xml b/doc/mod_secdownload.lua.xml
new file mode 100644
index 0000000..792dcd6
--- /dev/null
+++ b/doc/mod_secdownload.lua.xml
@@ -0,0 +1,80 @@
+
+
+ protects files with a time limited code
+
+
+
+ By default distributions (and @make install@) should provide the necessary files; but you can always find them in the "contrib":http://git.lighttpd.net/lighttpd/lighttpd2.git/tree/contrib folder:
+ * @secdownload.lua@
+ * @secdownload__secdownload.lua@
+
+ That way you can modify them for your own needs if you have to (although it is recommended to change the names of the files and the actions, so you don't get conflicts).
+
+
+
+
+ protect files with a time limited code
+
+
+
+ URL path prefix to protect; default "/"
+
+
+ where the secret files are stored on disk
+
+
+ shared secret used to create and verify urls.
+
+
+ how long a generated url is valid in seconds (maximum allowed time difference); default is 60
+
+
+
+
+
+ The @prefix@ is not used to build the filename; include it manually in the @document-root@ (works like @"alias":plugin_core.html#plugin_core__action_alias "/prefix" => "/docroot").
+ secdownload doesn't actually handle the (valid) request, it just provides the mapping to a filename (and rejects invalid requests).
+
+
+
+
+ setup {
+ module_load "mod_lua";
+ lua.plugin "secdownload.lua";
+ }
+ secdownload [ "prefix" => "/sec/", "document-root" => "/secret/path", "secret" => "abc", "timeout" => 600 ];
+
+
+
+
+
+
+ $secret = "abc";
+ $uri_prefix = "/sec/";
+
+ # filename; please note file name starts with "/"
+ $f = "/secret-file.txt";
+
+ # current timestamp
+ $t = time();
+
+ $t_hex = sprintf("%08x", $t);
+ $m = md5($secret.$f.$t_hex);
+
+ # generate link
+ printf('%s', $uri_prefix, $m, $t_hex, $f, $f);
+
+
+ The config example above would map this url to the file @/secret/path/secret-file.txt@.
+
+ For more examples see "mod_secdownload (lighttpd 1.4.x)":https://redmine.lighttpd.net/projects/lighttpd/wiki/Docs_ModSecDownload#Examples
+ ]]>
+
+
+
diff --git a/doc/mod_status.xml b/doc/mod_status.xml
new file mode 100644
index 0000000..05f71d6
--- /dev/null
+++ b/doc/mod_status.xml
@@ -0,0 +1,45 @@
+
+
+ displays a page with internal statistics like amount of requests (total or per second), active connections etc.
+
+
+
+
+ returns the status page to the client
+
+ (optional) "short"
+
+
+
+ The "short" mode removes connection and runtime details (recommended for "public" status).
+
+ The status page accepts the following query-string parameters:
+ * @?mode=runtime@: shows the runtime details
+ * "@format=plain@: shows the "short" stats in plain text format
+
+
+
+
+ If /server-status is requested, a page with lighttpd statistics is displayed.
+
+
+ setup {
+ module_load "mod_status";
+ }
+
+ if req.path == "/server-status" {
+ status.info;
+ }
+
+
+
+
diff --git a/doc/mod_throttle.xml b/doc/mod_throttle.xml
new file mode 100644
index 0000000..2d108c9
--- /dev/null
+++ b/doc/mod_throttle.xml
@@ -0,0 +1,78 @@
+
+
+ limits outgoing bandwidth usage
+
+
+ All rates are in bytes/sec. The magazines are filled up in fixed intervals (compile time constant; defaults to 200ms).
+
+
+
+ set the outgoing throttle limits for current connection
+
+ bytes/sec limit
+
+
+ optional, defaults to 2*rate
+
+
+
+ @burst@ is the initial and maximum value for the @magazine@; doing IO drains the @magazine@, which fills up again over time with the specified @rate@.
+
+
+
+
+
+ adds the current connection to a throttle pool for outgoing limits
+
+ bytes/sec limit
+
+
+
+ all connections in the same pool are limited as whole. Each @io.throttle_pool@ action creates its own pool.
+
+
+
+
+ Using the same pool in more than one place:
+
+
+ setup {
+ module_load "mod_throttle";
+ }
+ downloadLimit = {
+ io.throttle_pool 1mbyte;
+ }
+ # now use it wherever you need it...
+ downloadLimit;
+
+
+
+
+
+ adds the current connection to an IP-based throttle pool for outgoing limits
+
+ bytes/sec limit
+
+
+
+ all connections from the same IP address in the same pool are limited as whole. Each @io.throttle_ip@ action creates its own pool.
+
+
+
+
+ Using the same pool in more than one place:
+
+
+ setup {
+ module_load "mod_throttle";
+ }
+ downloadLimit = {
+ io.throttle_ip 200kbyte;
+ }
+ # now use it wherever you need it...
+ downloadLimit;
+
+
+
+
+
diff --git a/doc/mod_userdir.xml b/doc/mod_userdir.xml
new file mode 100644
index 0000000..37747fb
--- /dev/null
+++ b/doc/mod_userdir.xml
@@ -0,0 +1,50 @@
+
+
+ allows you to have user-specific document roots being accessed through http://domain/~user/
+
+
+
+ The document root can be built by using the home directory of a user which is specified by /~username/ at the beginning of the request path.
+ Alternatively, mod_userdir can build the docroot from a pattern similar to @vhost.pattern@ but using the username instead of the hostname.
+
+
+
+
+ builds the document root by replacing certain placeholders in path with (parts of) the username.
+
+ the path to build the document root with
+
+
+
+
+
+
+ setup {
+ module_load "mod_userdir";
+ }
+
+ userdir "public_html";
+
+
+
+
diff --git a/doc/mod_vhost.xml b/doc/mod_vhost.xml
new file mode 100644
index 0000000..0e9b098
--- /dev/null
+++ b/doc/mod_vhost.xml
@@ -0,0 +1,93 @@
+
+
+ offers various ways to implement virtual webhosts
+
+
+
+ It can map hostnames to actions and offers multiple ways to do so.
+ These ways differ in the flexibility of mapping (what to map and what to map to) as well as performance.
+
+
+
+
+ maps given hostnames to action blocks
+
+ key-value list with hostnames as keys and actions as values
+
+
+
+ @vhost.map@ offers a fast (lookup through hash-table) and flexible mapping, but maps only exact hostnames (no pattern/regex matching). The server port is never considered part of the hostname. Use the key @default@ (keyword, not as string) to specify a default action.
+
+
+
+
+ vhost.map ["host1" => actionblock1, "host2" => actionblock2, ..., "hostN" => actionblockN, default => actionblock0];
+
+
+
+
+
+ maps matching hostname patterns to action blocks
+
+ key-value list with regular expressions for hostnames as keys and actions as values
+
+
+
+ @vhost.map_regex@ walks through the list in the given order and stops at the first match; if no regular expression matched it will use the @default@ action (if specified).
+
+
+
+
+ vhost.map_regex ["host1regex" => actionblock1, "host2regex" => actionblock2, ..., "hostNregex" => actionblockN, default => actionblock0];
+
+
+
+
+
+
+
+
+
+ Combining both actions is also possible.
+
+ What it does (for example):
+ * if @example.com@ or @www.example.com@ is requested, the block named "examplesite" will be executed
+ * if @mydomain.tld@ is requested, the block named "mydomain" will be executed
+ * if @mydomain.com@ or @www.mydomain.net@ is requested, the block named "mydomain" will be executed (through the regex matching)
+ * if @www.mydomain.tld@ or something entirely different is requested, the block named "defaultdom" will be executed
+
+
+
+ setup {
+ module_load "mod_vhost";
+ }
+
+ examplesite = {
+ #... more config here ...
+ };
+
+ mydomain = {
+ #... more config here ...
+ };
+
+ defaultdom = {
+ #... more config here ...
+ };
+
+ vhost.map [
+ "example.com" => examplesite,
+ "www.example.com" => examplesite,
+ "mydomain.tld" => mydomain,
+ default => {
+ vhost.map_regex [
+ "^(www\.)?mydomain\.(com|net|org)$" => mydomain,
+ default => defaultdom
+ ];
+ }
+ ];
+
+
+
diff --git a/doc/plugin_core.xml b/doc/plugin_core.xml
new file mode 100644
index 0000000..082743a
--- /dev/null
+++ b/doc/plugin_core.xml
@@ -0,0 +1,801 @@
+
+
+ contains the core features for generic request handling, static files, log files and buffer limits.
+
+
+ The following address formats can be used:
+
+
+
+ Either with port @192.168.0.1:80@ or without @192.168.0.1@; you can either use real IPs or @0.0.0.0@ to listen on all network interfaces.
+
+
+
+
+
+ Similar to IPv4; just put the IPv6 between "[" and "]" like this: @[::1]:80@ (IPv6 localhost with port 80).
+
+ Please note that lighttpd always listens to IPv6 only (some platforms listen to IPv4 too on [::] by default).
+
+
+
+
+
+ A unix domain socket needs a filename where the socket is placed; use @unix:/path/to/socket@ as socket address.
+
+ Please don't put unix domain sockets in @/tmp@. Use @/var/run/lighttpd/@ or something like that, where only root or selected "trusted" users can create files.
+
+ This may be not supported in all places where a socket address can be specified.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ These action are not needed (or usable) in non-lua configs.
+
+
+ (lua) combines a list of actions into one action, only needed in lua
+
+ list of actions to combine
+
+
+
+ (lua) build a conditional block (only usable in lua)
+
+ A condition; can only be constructed in lua
+
+
+ action to run if condition was true or lua "nil"
+
+
+ (optional) action to run if condition was false
+
+
+
+
+
+
+ sets doc-root, and builds physical path for requested file
+
+ One or more patterns to build docroot from
+
+
+
+ Uses "patterns":core_pattern.html#core_pattern to build document roots (base location of files to server).
+ @docroot@ uses the first pattern that results in an existing directory; otherwise it uses the *last* entry.
+ You'll want the @docroot@ action *before* @alias@ actions!
+
+
+
+
+ docroot ("/var/www/vhosts/$0/htdocs", "/var/www/default/htdocs");
+
+
+
+
+
+ sets doc-root depending on a matching prefix
+
+ maps prefix to base location on disk
+
+
+
+ The prefix is removed from the url path before it is appended to the base location.
+ You'll want the @docroot@ action *before* @alias@ actions!
+
+
+
+
+ docroot ("/var/www/vhosts/$0/htdocs", "/var/www/default/htdocs");
+ alias [
+ "/phpmyadmin/" => "/usr/share/phpmyadmin",
+ "/pma/" => "/usr/share/phpmyadmin",
+ ];
+ alias "/favicon.ico" => "/var/www/favicon.ico";
+
+
+
+
+ default filenames to show in a directory
+
+ filenames to look for
+
+
+
+ If the physical path is a directory search for the specified filenames; prefix a filename with '/' to search in the doc-root.
+
+
+
+
+ setup {
+ module_load "mod_dirlist";
+ }
+
+ # if a directory was requested, first search for some default files
+ index ["index.php", "index.html", "/index.php"];
+ # if none of them did exists show a simple directory listing
+ dirlist;
+ # ... + handle PHP and static files
+
+
+
+
+ splits physical path into existing file/directory and the remaining PATH_INFO
+
+
+ Searches for the longest prefix of the physical path name that exists, splitting only at the directory separator @/@; also never leaves the document root (technically speaking the filename can't get shorter than the document root).
+
+
+
+
+ The following example maps @http://example.com/index.php/some/site@ to the file @/var/www/index.php@ with @PATH_INFO=/some/site@ (given @/var/www/index.php@ is a normal file).
+
+
+ docroot "/var/www";
+ pathinfo;
+ if phys.path =$ ".php" { fastcgi "unix:/var/run/lighttpd/php.sock"; }
+
+
+
+
+ The following example maps @http://example.com/some/site@ to the file @/var/www/index.php@ with @PATH_INFO=/some/site@ (given @/var/www/index.php@ is a normal file, and @/var/www/some@ does not exist).
+
+
+ docroot "/var/www";
+ pathinfo;
+ index ("index.php");
+ if phys.path =$ ".php" { fastcgi "unix:/var/run/lighttpd/php.sock"; }
+
+
+
+
+
+
+
+ handle GET and HEAD requests with a static file from disk
+
+
+ This action is automatically appended to the global config (unless a lua config is specified at the command line).
+
+ Does nothing if:
+ * the request is already handled
+ * no physical path was set (missing @docroot@, @alias@, ...)
+ * the physical path points to a directory
+
+ All other problems lead to an error page, for example:
+ * wrong request method (405)
+ * file not found (404)
+ * couldn't open file (403)
+ * filename matches @static.exclude_extensions@ (403)
+ * ...
+
+
+
+
+ handle GET and HEAD requests with a static file from disk
+
+
+ same as @static@, but doesn't return any error pages; instead request handling continues.
+
+
+
+
+ returns a quick response with optional body
+
+ HTTP response status code
+
+
+ (optional) pattern for response body
+
+
+
+ Generates a simple response (our favorite benchmark handler).
+ The body is parsed as "pattern":core_pattern.html#core_pattern.
+
+
+
+
+ respond 403 => "Forbidden";
+
+
+
+
+ respond 200 => "benchmark content!";
+
+
+
+
+
+
+
+
+ For standard logging ("error.log") lighttpd knows the following levels:
+ * @debug@
+ * @info@
+ * @warning@
+ * @error@
+ * @abort@ (right before terminating the process)
+ * @backend@ (for log data from backends, like FastCGI stderr stream)
+
+
+
+
+
+ The following log targets are known:
+ * not logging: empty string
+ * files: @file:/var/log/error.log@ or just @/var/log/error.log@
+ * stderr: @stderr:@ or @stderr@
+ * syslog: @syslog:@ (not supported yet)
+ * pipes: @pipe:command@ or @| command@ (not supported yet)
+
+ Unknown strings are mapped to @stderr@.
+
+
+
+
+ overwrite log targets for all log levels
+
+ mapping log levels (or default) to log targets
+
+
+
+ log [
+ "error" => "/var/log/lighttpd/error.log",
+ "abort" => "/var/log/lighttpd/error.log",
+ "backend" => "/var/log/lighttpd/backend.log",
+ default => "/var/log/lighttpd/debug.log",
+ ];
+
+
+
+
+ writes a log message to the "info" log level
+
+ message pattern string
+
+
+
+ Writes the specified message to the log using level @info@; the message is parsed as "pattern":core_pattern.html#core_pattern.
+
+
+
+
+ log.write "hello world";
+
+
+
+
+ sets default log targets for all log levels
+
+ mapping log levels (or default) to log targets
+
+
+
+ setup {
+ log [
+ "error" => "/var/log/lighttpd/error.log",
+ "abort" => "/var/log/lighttpd/error.log",
+ "backend" => "/var/log/lighttpd/backend.log",
+ default => "/var/log/lighttpd/debug.log",
+ ];
+ }
+
+
+
+
+ sets the format string to use for timestamps in the log
+
+ a strftime format string
+
+
+
+ See "strftime":http://pubs.opengroup.org/onlinepubs/007904875/functions/strftime.html for the format string syntax.
+
+ The default format string is @"%d/%b/%Y %T %Z"@.
+
+
+
+
+
+
+
+ The connection environment is a set of variable with names and values (both simple strings). CGI backends will forward the environment in addition to the standard CGI environment variables.
+ The connection environment overwrites the standard CGI values.
+
+
+
+ sets a connection environment variable
+
+ the variable name to set
+
+
+ the pattern value to set
+
+
+
+ The value is parsed as "pattern":core_pattern.html#core_pattern.
+
+
+
+
+ env.set "INFO" => "%{req.path}";
+
+
+
+
+ sets a connection environment variable if not already set
+
+ the variable name to set
+
+
+ the pattern value to set
+
+
+
+ The value is parsed as "pattern":core_pattern.html#core_pattern. @env.add@ does not overwrite already existing values.
+
+
+
+
+ env.add "INFO" => "%{req.path}";
+
+
+
+
+ removes a connection environment variable
+
+ the variable name to remove
+
+
+
+ env.remove "INFO";
+
+
+
+
+ removes all connection environment variables
+
+
+ env.clear;
+
+
+
+
+
+
+ All header values that get set are parsed as "patterns":core_pattern.html#core_pattern.
+
+
+ adds a new response header line
+
+ header name
+
+
+ pattern header value
+
+
+
+ The HTTP spec requires that multiple headers with the same name could be merged by joining their values with ",".
+ In real life this doesn't work always, especially not for "Cookie" headers; so this action actually adds a separate header line.
+
+
+
+
+ header.add "Cache-Control" => "public";
+
+
+
+
+ appends value to response header line
+
+ header name
+
+
+ pattern header value
+
+
+
+ If header already exists appends new value separated by ", "; otherwise adds a new header line.
+
+
+
+
+ overwrite response header line or add new one
+
+ header name
+
+
+ pattern header value
+
+
+
+ If header already exists overwrites the value; otherwise a new line gets added.
+
+
+
+
+ remove existing response header
+
+ header name
+
+
+
+ # ... some PHP handling
+ # wait for response headers to be ready
+ if resp.status >= 0 {
+ header.remove "X-Powered-By";
+ }
+
+
+
+
+
+ modify HTTP status code
+
+
+ Modifies the HTTP status code, but doesn't handle the request in any way.
+ Later actions could overwrite the status, or a backend (FastCGI, proxy, ...) might overwrite it if the response is parsed later.
+ Only works if some action actually handled the request.
+
+ Lighttpd will generate error pages (if it knows the code) if the action that handled the request didn't generate a response body and a body is allowed.
+
+
+
+
+ # hide all 404s at end of config by setting 403
+ static;
+ if resp.status == 404 { set_status 403; }
+
+
+
+
+
+
+ All header values that get set are parsed as "patterns":core_pattern.html#core_pattern.
+
+
+ adds a new request header line
+
+ header name
+
+
+ pattern header value
+
+
+
+ Same as "header.add":plugin_core.html#plugin_core__action_header-add for request headers.
+
+
+
+
+ appends value to request header line
+
+ header name
+
+
+ pattern header value
+
+
+
+ Same as "header.append":plugin_core.html#plugin_core__action_header-append for request headers.
+
+
+
+
+ overwrite request header line or add new one
+
+ header name
+
+
+ pattern header value
+
+
+
+ Same as "header.overwrite":plugin_core.html#plugin_core__action_header-overwrite for request headers.
+
+
+
+
+ remove existing request header
+
+ header name
+
+
+
+ Same as "header.remove":plugin_core.html#plugin_core__action_header-remove for request headers.
+
+
+
+
+
+ Remove @Accept-Encoding@ request header to workaround the "BREACH":http://en.wikipedia.org/wiki/BREACH_(security_exploit) vulnerability in https.
+
+
+
+ if request.scheme == "https" {
+ # create a copy of the header value
+ req_header.add "HTTPS-Accept-Encoding" => '%{req.header[Accept-Encoding]}';
+ req_header.remove "Accept-Encoding";
+ }
+
+
+
+
+
+
+ set memory limit for outgoing chunkqueues (default is 256KiB)
+
+ limit in bytes (0 means unlimited)
+
+
+
+ io.buffer_out 512kbyte;
+
+
+
+
+ set memory limit for intcoming chunkqueues (default is 256KiB)
+
+ limit in bytes (0 means unlimited)
+
+
+
+ io.buffer_in 512kbyte;
+
+
+
+
+
+ maps the result of a pattern to a user defined action
+
+ the evaluation of this pattern is used as key in the mapping
+
+
+ maps strings (or default) to actions
+
+
+
+ The pattern is parsed as "pattern":core_pattern.html#core_pattern. Have a look at "mod_vhost":mod_vhost.html#mod_vhost for special mappings on hostnames.
+
+
+
+
+ map "%{req.path}" => [
+ "/" => {
+ respond 200 => "root";
+ },
+ "/news" => {
+ respond 200 => "news";
+ },
+ default => {
+ respond 404;
+ },
+ ];
+
+
+
+
+
+ listen to a socket address, see above for accepted formats (default TCP port is 80)
+
+ socket address to listen to
+
+
+
+ setup {
+ listen "0.0.0.0";
+ listen "[::]";
+ listen "127.0.0.1:8080";
+ }
+
+
+
+
+ sets worker count; each worker runs in its own thread and works on the connections it gets assigned from the master worker
+
+ number of workers (default is 1)
+
+
+
+ setup {
+ workers 2;
+ }
+
+
+
+
+ binds worker threads to a cpu, only available on Linux systems
+
+ list of integers or a list of lists of integers
+
+
+
+ workers.cpu_affinity [0, 1];
+
+
+
+
+ load the given module(s)
+
+ string or list of strings with the module name(s)
+
+
+ modules can be "loaded" more than once without error
+
+
+
+ setup {
+ module_load "mod_rewrite";
+ }
+
+
+
+
+ sets the global I/O timeout (wait for network read and write)
+
+ timeout value in seconds, default is 300s
+
+
+
+ set TTL for stat cache entries
+
+ time to live in seconds, default is 10s
+
+
+
+ sets number of background threads for blocking tasks
+
+ number of threads
+
+
+ 0@ each worker has its own thread pool with @threads@ threads.
+ ]]>
+
+
+
+ starts a Fetch API provider
+
+ name of the storage
+
+
+ A filename pattern including exactly on *
+
+
+ Loads all filenames matching the wildcard pattern (which must include exactly on @*@) into the fetch storage.
+
+
+
+ setup {
+ fetch.files_static "sni" => "/etc/certs/lighttpd_sni_*.pem";
+ }
+
+
+
+
diff --git a/doc/style.css b/doc/style.css
new file mode 100644
index 0000000..6d647c1
--- /dev/null
+++ b/doc/style.css
@@ -0,0 +1,128 @@
+div.aso .template .key { font-weight: bold; }
+div.aso .template .param { font-style: italic; }
+
+div.option .default .value { font-family: monospace; }
+
+div.aso dt { font-family: monospace; }
+div.aso dd { margin-left: 3em; }
+div.aso dl dl { margin-top: 0em; margin-bottom: 0em; }
+
+div#main > div { margin-left: 20pt; }
+div#main > div h1 { margin-left: -20pt; }
+
+div.index_modules table.aso > tbody > tr > td:first-child { font-family: monospace; }
+
+div#main > div { margin-top: 20px; padding-bottom: 40px; }
+
+/* hide modules if not active */
+.bs-sidebar.affix > .nav > .module:not(:first-child),
+.bs-sidebar.affix-top > .nav > .module:not(:first-child),
+.bs-sidebar.affix-bottom > .nav > .module:not(:first-child) {
+ display: none;
+}
+.bs-sidebar.affix > .nav > .module.active,
+.bs-sidebar.affix-top > .nav > .module.active,
+.bs-sidebar.affix-bottom > .nav > .module.active {
+ display: block;
+}
+
+/*
+ * Side navigation
+ *
+ * Scrollspy and affixed enhanced navigation to highlight sections and secondary
+ * sections of docs content.
+ */
+
+/* affixed scroll enhance */
+.bs-sidebar.affix,.bs-sidebar.affix-top,.bs-sidebar.affix-bottom {
+ position:static;
+}
+
+@media (min-width: 992px) {
+ .bs-sidebar {
+ width: 213px;
+ }
+ .bs-sidebar.affix-top {
+ position: static;
+ margin-top:30px;
+ }
+ .bs-sidebar.affix {
+ position: fixed;
+ top:30px;
+ }
+ .bs-sidebar.affix-bottom {
+ position: absolute; /* Undo the static from mobile first approach */
+ }
+}
+@media (min-width: 1200px) {
+ /* Widen the fixed sidebar again */
+ .bs-sidebar {
+ width: 263px;
+ }
+}
+
+.bs-sidebar.affix > .nav,
+.bs-sidebar.affix-top > .nav,
+.bs-sidebar.affix-bottom > .nav {
+ margin: 0;
+}
+
+/* First level of nav */
+.bs-sidebar > .nav {
+ margin: 30px 0;
+ padding-top: 10px;
+ padding-bottom: 10px;
+ text-shadow: 0 1px 0 #fff;
+ background-color: #f7f5fa;
+ border-radius: 5px;
+}
+
+/* All levels of nav */
+.bs-sidebar .nav > li > a {
+ display: block;
+ color: #716b7a;
+ padding: 5px 20px;
+}
+.bs-sidebar .nav > li > a:hover,
+.bs-sidebar .nav > li > a:focus {
+ text-decoration: none;
+ background-color: #e5e3e9;
+ border-right: 1px solid #dbd8e0;
+}
+.bs-sidebar .nav > .active > a,
+.bs-sidebar .nav > .active:hover > a,
+.bs-sidebar .nav > .active:focus > a {
+ font-weight: bold;
+ color: #563d7c;
+ background-color: transparent;
+ border-right: 1px solid #563d7c;
+}
+
+/* Nav: second level (shown on .active) */
+.bs-sidebar .nav .nav {
+ margin-bottom: 8px;
+}
+.bs-sidebar .nav .nav > li > a {
+ padding-top: 3px;
+ padding-bottom: 3px;
+ padding-left: 30px;
+ font-size: 90%;
+}
+.bs-sidebar .nav .nav .nav > li > a {
+ padding-left: 40px;
+}
+/* hide second level when javascript is active (-> affix) */
+.bs-sidebar.affix .nav .nav,
+.bs-sidebar.affix-top .nav .nav,
+.bs-sidebar.affix-bottom .nav .nav {
+ display: none; /* Hide by default */
+}
+
+/* Show and affix the side nav when space allows it */
+@media (min-width: 992px) {
+ .bs-sidebar.affix .nav > .active > .nav,
+ .bs-sidebar.affix-top .nav > .active > .nav,
+ .bs-sidebar.affix-bottom .nav > .active > .nav {
+ display: block;
+ }
+}
diff --git a/src/main/config_parser.rl b/src/main/config_parser.rl
index 8204d54..6c304b2 100644
--- a/src/main/config_parser.rl
+++ b/src/main/config_parser.rl
@@ -700,9 +700,9 @@ static gboolean is_value_start_token(liConfigToken token) {
static int _op_precedence(liConfigToken op) {
switch (op) {
- case TK_AND:
- return 1;
case TK_OR:
+ return 1;
+ case TK_AND:
return 2;
case TK_PLUS:
case TK_MINUS:
diff --git a/src/main/connection.c b/src/main/connection.c
index 81c2826..7f0a992 100644
--- a/src/main/connection.c
+++ b/src/main/connection.c
@@ -3,6 +3,8 @@
#include
#include
+#define LI_CONNECTION_DEFAULT_CHUNKQUEUE_LIMIT (256*1024)
+
void li_connection_simple_tcp(liConnection **pcon, liIOStream *stream, gpointer *context, liIOStreamEvent event) {
liConnection *con;
goffset transfer_in = 0, transfer_out = 0;
@@ -533,8 +535,8 @@ void li_connection_start(liConnection *con, liSocketAddress remote_addr, int s,
assert(NULL != con->con_sock.raw_in || NULL != con->con_sock.raw_out);
- li_chunkqueue_use_limit(con->con_sock.raw_in->out, 256*1024);
- li_chunkqueue_use_limit(con->con_sock.raw_out->out, 256*1024);
+ li_chunkqueue_use_limit(con->con_sock.raw_in->out, LI_CONNECTION_DEFAULT_CHUNKQUEUE_LIMIT);
+ li_chunkqueue_use_limit(con->con_sock.raw_out->out, LI_CONNECTION_DEFAULT_CHUNKQUEUE_LIMIT);
li_stream_connect(&con->out, con->con_sock.raw_out);
li_stream_connect(con->con_sock.raw_in, &con->in);
@@ -842,8 +844,8 @@ static void li_connection_reset_keep_alive(liConnection *con) {
memset(&con->in_chunked_decode_state, 0, sizeof(con->in_chunked_decode_state));
/* restore chunkqueue limits */
- li_chunkqueue_use_limit(con->con_sock.raw_in->out, 512*1024);
- li_chunkqueue_use_limit(con->con_sock.raw_out->out, 512*1024);
+ li_chunkqueue_use_limit(con->con_sock.raw_in->out, LI_CONNECTION_DEFAULT_CHUNKQUEUE_LIMIT);
+ li_chunkqueue_use_limit(con->con_sock.raw_out->out, LI_CONNECTION_DEFAULT_CHUNKQUEUE_LIMIT);
/* reset stats */
con->info.stats.bytes_in = G_GUINT64_CONSTANT(0);
diff --git a/src/modules/mod_access.c b/src/modules/mod_access.c
index feb3d4b..01500b4 100644
--- a/src/modules/mod_access.c
+++ b/src/modules/mod_access.c
@@ -1,39 +1,6 @@
/*
* mod_access - restrict access to the webserver for certain clients
*
- * Description:
- * mod_access lets you filter clients by IP address.
- *
- * Setups:
- * access.load "file";
- * - Loads access rules from a file. One rule per line in the format .
- * Example file (\n is newline): allow 127.0.0.1\ndeny 10.0.0.0/8\nallow 192.168.0.0/24
- * Options:
- * access.redirect_url = "url";
- * - if set, clients are redirected to this url if access is refused
- * Actions:
- * access.deny;
- * - Denies access by returning a 403 status code
- * access.check ("allow" => iplist, "deny" => iplist);
- * - "allow" and "deny" are optional. If left out, they default to "all"
- * - iplists are lists of strings representing IP addresses with optional CIDR suffix
- * - To represent all IPs, you can either use "x.x.x.x/0" or "all"
- *
- * Example config:
- * access.redirect_url = "http://www.example.tld/denied.html";
- * access.check (
- * "allow" => ("127.0.0.0/24", "192.168.0.0/16", "::1"),
- * "deny" => ( "all" )
- * );
- * if req.path =$ ".inc" { access.deny; }
- *
- * This config snippet will grant access only to clients from the local network (127.0.0.* or 192.168.*.*).
- * Additionally it will deny access to any file ending with ".inc", no matter what client.
- * Clients that are denied access, will be redirected to http://www.example.tld/denied.html
- *
- * Tip:
- * none
- *
* Todo:
* - access.redirect_url
*
diff --git a/src/modules/mod_accesslog.c b/src/modules/mod_accesslog.c
index 594265e..5d95b7f 100644
--- a/src/modules/mod_accesslog.c
+++ b/src/modules/mod_accesslog.c
@@ -1,26 +1,6 @@
/*
* mod_accesslog - log access to the server
*
- * Description:
- * mod_accesslog can log requests handled by lighttpd to files, pipes or syslog
- * the format of the logs can be customized by using printf-style placeholders
- *
- * Setups:
- * none
- * Options:
- * accesslog = ; - log target
- * type: string
- * default: none
- * accesslog.format = ; - log format
- * type: string
- * default: "%h %V %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\""
- * Actions:
- * none
- *
- * Example config:
- * accesslog = "/var/log/lighttpd/access.log";
- * accesslog.format = "%h %V %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"";
- *
* Todo:
* - implement format key for %t: %{format}t
* - implement missing format identifiers
diff --git a/src/modules/mod_auth.c b/src/modules/mod_auth.c
index a76f831..51649ef 100644
--- a/src/modules/mod_auth.c
+++ b/src/modules/mod_auth.c
@@ -1,56 +1,8 @@
/*
* mod_auth - require authentication from clients using username + password
*
- * Description:
- * mod_auth lets you require authentication from clients using a username and password.
- * It supports basic and digest authentication methods as well as plaintext, htpass and htdigest backends.
- *
- * Basic:
- * The "basic" method transfers the username and the password in cleartext over the network (base64 encoded)
- * and might result in security problems if not used in conjunction with a crypted channel between client and server.
- * It is recommend to use https in conjunction with basic authentication.
- *
- * Digest:
- * The "digest" method only transfers a hashed value over the network which performs a lot of work to harden
- * the authentication process in insecure networks (like the internet).
- *
* Relevant RFCs: 2617
*
- * Setups:
- * none
- * Options:
- * auth.debug = ;
- * - if set, debug information is written to the log
- * Actions:
- * auth.plain ["method": method, "realm": realm, "file": path, "ttl": 10];
- * - requires authentication using a plaintext file containing user:password pairs seperated by newlines (\n)
- * auth.htpasswd ["method": method, "realm": realm, "file": path, "ttl": 10];
- * - requires authentication using a htpasswd file containing user:encrypted_password pairs seperated by newlines (\n)
- * - passwords are encrypted using crypt(3), use the htpasswd binary from apache to manage the file
- * + hashes starting with "$apr1$" ARE supported (htpasswd -m)
- * + hashes starting with "{SHA}" ARE supported (followed by sha1_base64(password), htpasswd -s)
- * - only supports "basic" method
- * auth.htdigest ["method": method, "realm": realm, "file": path, "ttl": 10];
- * - requires authentication using a htdigest file containing user:realm:hashed_password tuples seperated by newlines (\n)
- * - passwords are saved as (modified) md5 hashes:
- * md5hex(username + ":" + realm + ":" + password)
- *
- * ttl specifies how often lighty checks the files for modifications (in seconds), 0 means it will never check after the first load.
- *
- * auth.deny;
- * - handles request with "401 Unauthorized"
- *
- * Example config:
- * # /members/ is for known users only
- * if request.path =^ "/members/" {
- * auth.plain ["method": "basic", "realm": "members only", "file": "/etc/lighttpd/users.txt"];
- * }
- * if req.env["REMOTE_USER"] !~ "^(admin1|user2|user3)$" { auth.deny; }
- *
- *
- * Tip:
- * The digest method is broken in Internet Explorer < 7. Use basic instead if this is a problem for you. (not supported for now anyway)
- *
* Todo:
* - method: digest
*
diff --git a/src/modules/mod_balance.c b/src/modules/mod_balance.c
index 260c1d2..e7faef3 100644
--- a/src/modules/mod_balance.c
+++ b/src/modules/mod_balance.c
@@ -1,23 +1,6 @@
/*
* mod_balance - balance between different backends
*
- * Description:
- * mod_balance balances between different backends;
- *
- * Setups:
- * none
- * Options:
- * none
- * Actions:
- * balance.rr - balance between actions (list or single action) with RoundRobin
- * balance.sqf - balance between actions (list or single action) with SQF
- *
- * Be careful: these actions may get executed more than once (until one is successful!),
- * so don't loop rewrites in them or something similar
- *
- * Example config:
- * balance.sqf ( ${ fastcgi "127.0.0.1:9090"; }, ${ fastcgi "127.0.0.1:9091"; } );
- *
* Author:
* Copyright (c) 2009-2010 Stefan Bühler
*/
diff --git a/src/modules/mod_cache_disk_etag.c b/src/modules/mod_cache_disk_etag.c
index 0142bf7..f16b401 100644
--- a/src/modules/mod_cache_disk_etag.c
+++ b/src/modules/mod_cache_disk_etag.c
@@ -1,23 +1,6 @@
/*
* mod_cache_disk_etag - cache generated content on disk if etag header is set
*
- * Description:
- * cache generated content on disk if etag header is set
- *
- * Setups:
- * none
- * Options:
- * none
- * Actions:
- * cache.disk.etag - cache in specified directory
- * path: string
- * This blocks action progress until the response headers are
- * done (i.e. there has to be a content generator before it (like fastcgi/static file)
- * You could insert it multiple times of course (e.g. before and after deflate).
- *
- * Example config:
- * cache.disk.etag "/var/lib/lighttpd/cache_etag"
- *
* Todo:
* - use stat cache
*
diff --git a/src/modules/mod_debug.c b/src/modules/mod_debug.c
index 6fb3912..e728bd4 100644
--- a/src/modules/mod_debug.c
+++ b/src/modules/mod_debug.c
@@ -1,27 +1,6 @@
/*
* mod_debug - utilities to debug lighttpd
*
- * Description:
- * mod_debug offers various utilities to aid you debug a problem.
- *
- * Setups:
- * none
- * Options:
- * none
- * Actions:
- * debug.show_connections;
- * - shows a page similar to the one from mod_status, listing all active connections
- * - by specifying one or more "connection ids" via querystring (parameter "con"),
- * one can request additional debug output for specific connections
- * debug.profiler_dump;
- * - dumps all allocated memory to the profiler output file if profiling enabled (LIGHTY_PROFILE_MEM=profiler.log)
- *
- * Example config:
- * if req.path == "/debug/connections" { debug.show_connections; }
- *
- * Tip:
- * none
- *
* Todo:
* - prettier output
* - more detailed output
diff --git a/src/modules/mod_deflate.c b/src/modules/mod_deflate.c
index 4ac3101..bb8be78 100644
--- a/src/modules/mod_deflate.c
+++ b/src/modules/mod_deflate.c
@@ -1,36 +1,6 @@
/*
* mod_deflate - compress content on the fly
*
- * Description:
- * compress content on the fly
- *
- * Does not compress:
- * - response status: 100, 101, 204, 205, 206, 304
- * - already compressed content
- * - if more than one etag response header is sent
- * - if no common encoding is found
- *
- * Supported encodings
- * - gzip, deflate (needs zlib)
- * - bzip2 (needs bzip2)
- *
- * + Modifies etag response header (if present)
- * + Adds "Vary: Accept-Encoding" response header
- * + Resets Content-Length header
- *
- * Setups:
- * none
- *
- * Options:
- * deflate.debug
- *
- * Actions:
- * deflate [ "encodings": "deflate,gzip,bzip2", "blocksize": 4096, "output-buffer": 4096, "compression-level": 1 ];
- * - options are all optional, default values shown in line above :)
- *
- * Example config:
- * deflate;
- *
* Author:
* Copyright (c) 2009 Stefan Bühler
* Copyright (c) 2010 Thomas Porzelt
diff --git a/src/modules/mod_dirlist.c b/src/modules/mod_dirlist.c
index 1dec8dc..f15dfa9 100644
--- a/src/modules/mod_dirlist.c
+++ b/src/modules/mod_dirlist.c
@@ -1,47 +1,6 @@
/*
* mod_dirlist - directory listing
*
- * Description:
- * mod_dirlist enables you to list files inside a directory.
- * The output can be customized in various ways from style via css to excluding certain entries.
- *
- * Setups:
- * none
- * Options:
- * none
- * Actions:
- * dirlist [options] - show directory listing
- * options: optional (not required), array, can contain any of the following string => value pairs:
- * "sort" => criterium - string, one of "name", "size" or "type"
- * "css" => url - string, external css to use for styling, default: use internal css
- *
- * "hide-dotfiles" => bool - hide entries beginning with a dot, default: true
- * "hide-tildefiles" => bool - hide entries ending with a tilde (~), often used for backups, default: true
- * "hide-directories" => bool - hide directories from the directory listing, default: false
- *
- * "include-header" => bool - include HEADER.txt above the directory listing, default: false
- * "hide-header" => bool - hide HEADER.txt from the directory listing, default: false
- * "encode-header" => bool - html-encode HEADER.txt (if included), set to false if it contains real HTML, default: true
- *
- * "include-readme" => bool - include README.txt below the directory listing, default: true
- * "hide-readme" => bool - hide README.txt from the directory listing, default: false
- * "encode-readme" => bool - html-encode README.txt (if included), set to false if it contains real HTML, default: true
- *
- * "exclude-suffix" => suffixlist - list of strings, filter entries that end with one of the strings supplied
- * "exclude-prefix" => prefixlist - list of strings, filter entries that begin with one of the strings supplied
- *
- * "debug" => bool - outout debug information to log, default: false
- *
- * Example config:
- * if req.path =^ "/files/" {
- * dirlist ("include-header" => true, "hide-header" => true, "hide->suffix" => (".bak"));
- * }
- * - shows a directory listing including the content of HEADER.txt above the list and hiding itself from it
- * also hides all files ending in ".bak"
- *
- * Tip:
- * xyz
- *
* Todo:
* - make output generating "async", give up control every N entries
* - filters for entries (pattern, regex)
@@ -779,8 +738,6 @@ static liAction* dirlist_create(liServer *srv, liWorker *wrk, liPlugin* p, liVal
}
static const liPluginOption options[] = {
- { "dirlist.debug", LI_VALUE_BOOLEAN, 0, NULL },
-
{ NULL, 0, 0, NULL }
};
diff --git a/src/modules/mod_expire.c b/src/modules/mod_expire.c
index e566319..05e2960 100644
--- a/src/modules/mod_expire.c
+++ b/src/modules/mod_expire.c
@@ -1,44 +1,6 @@
/*
* mod_expire - add "Expires" and "Cache-Control" headers to response
*
- * Description:
- * mod_expire lets you control client-side caching of responses based on a simple rule/formula.
- * If a response is cached using an "Expires" and "Cache-Control" header, then the client will not issue a new
- * request for it until the date specified by the header is reached.
- *
- * The rule/formula used here, complies with the one mod_expire for Apache uses:
- * [plus] ()+
- * Where is one of "access", "now" or "modification" - "now" being equivalent to "access".
- * is optional and does nothing.
- * is any positive integer.
- * is one of "seconds, "minutes", "hours", "days", "weeks", "months" or "years".
- * The trailing s in is optional.
- *
- * If you use "modification" as and the file does not exist or cannot be accessed,
- * mod_expire will do nothing and request processing will go on.
- *
- * The expire action will overwrite any existing "Expires" header.
- * It will append the max-age value to any existing "Cache-Control" header.
- *
- * Setups:
- * none
- * Options:
- * none
- * Actions:
- * expire "rule";
- * - adds an Expires header to the response based on the specified rule.
- *
- * Example config:
- * if request.path =~ "^/(css|js|images)/" {
- * expire "access plus 1 day";
- * }
- *
- *
- * Tip:
- * Adding expire headers to static content like css, javascript, images or similar,
- * can greatly reduce the amount of requests you get and therefor save resources.
- * Use "modification" as if your content changes in specific intervals like every 15 minutes.
- *
* Todo:
* none
*
diff --git a/src/modules/mod_fastcgi.c b/src/modules/mod_fastcgi.c
index 9acc8dd..e64854c 100644
--- a/src/modules/mod_fastcgi.c
+++ b/src/modules/mod_fastcgi.c
@@ -1,21 +1,5 @@
/*
- * mod_fastcgi - connect to fastcgi backends for generating content
- *
- * Description:
- * mod_fastcgi connects to a backend over tcp or unix sockets
- *
- * Setups:
- * none
- * Options:
- * fastcgi.log_plain_errors - whether to prepend timestamp and other info to
- * fastcgi stderr lines in the "backend" log.
- * type: boolean
- * Actions:
- * fastcgi - connect to backend at
- * socket: string, either "ip:port" or "unix:/path"
- *
- * Example config:
- * fastcgi "127.0.0.1:9090"
+ * mod_fastcgi - connect to fastcgi backends for generating response content
*
* Todo:
* - reuse fastcgi connections (keepalive)
diff --git a/src/modules/mod_flv.c b/src/modules/mod_flv.c
index ff5a07d..af78df2 100644
--- a/src/modules/mod_flv.c
+++ b/src/modules/mod_flv.c
@@ -1,36 +1,6 @@
/*
* mod_flv - flash pseudo streaming
*
- * Description:
- * mod_flv lets you stream .flv files in a way that flash players can seek into positions in the timeline.
- *
- * Setups:
- * none
- *
- * Options:
- * none
- *
- * Actions:
- * flv;
- * - enables .flv pseudo streaming
- *
- * Example config:
- * if phys.path =$ ".flv" {
- * flv;
- * }
- *
- * Tip:
- * Use caching and bandwidth throttling to save traffic.
- * To prevent the player from buffering at the beginning, use a small burst threshold.
- *
- * if phys.path =$ ".flv" {
- * expire "access 1 month";
- * io.throttle 500kbyte => 150kbyte;
- * flv;
- * }
- *
- * This config will make browsers cache videos for 1 month and limit bandwidth to 150 kilobyte/s after 500 kilobytes.
- *
* Todo:
* - flv audio container support?
*
diff --git a/src/modules/mod_fortune.c b/src/modules/mod_fortune.c
index 9e70f67..f771cc6 100644
--- a/src/modules/mod_fortune.c
+++ b/src/modules/mod_fortune.c
@@ -1,28 +1,6 @@
/*
* mod_fortune - fortune cookies for everyone
*
- * Description:
- * mod_fortune loads quotes (aka fortune coookies) from a file and provides actions
- * to add a random quote as response header (X-fortune) or display it as a page
- *
- * Setups:
- * fortune.load - loads cookies from a file, can be called multiple times to load data from multiple files
- * Options:
- * none
- * Actions:
- * fortune.header - adds a random quote as response header X-fortune
- * fortune.page - returns a random quote as response content
- *
- * Example config:
- * setup {
- * fortune.load "/var/www/fortunes.txt";
- * }
- *
- * req.path == "/fortune" {
- * fortune.page;
- * } else {
- * fortune.header;
- * }
*/
#include
diff --git a/src/modules/mod_gnutls.c b/src/modules/mod_gnutls.c
index 3b6e7b9..1edd67f 100644
--- a/src/modules/mod_gnutls.c
+++ b/src/modules/mod_gnutls.c
@@ -1,34 +1,5 @@
/*
- * mod_gnutls - ssl support
- *
- * Description:
- * mod_gnutls listens on separate sockets for ssl connections (https://...)
- *
- * Setups:
- * gnutls - setup a ssl socket; takes a hash/key-value list of following parameters:
- * listen - (mandatory) the socket address (same as standard listen)
- * pemfile - (mandatory) contains key and certificate and intermediate certificates ("chain") for the key (PEM format)
- * priority - contains priority string (specifying ciphers and gnutls options), default: "NORMAL"
- * protect-against-beast - whether to append ":-CIPHER-ALL:+ARCFOUR-128" for SSL3/TLS1.0 connections to priority
- * dh-params - file with genereated dh-params. default: pre generated 4096-bit params included in the source
- * session-db-size - size of session db (TLS session cookies). set to <= 0 to disable. default: 256
- * when SNI was enabled
- * sni-backend - "fetch" backend name to search certificates in with the SNI servername as key
- * sni-fallback-pemfile - certificate to use if request contained SNI servername, but the sni-backend didn't find anything
- * if request didn't contain SNI the standard "pemfile"(s) are used
- * NOTES:
- * * gnutls has some SNI support builtin - you can just load all certificates with multiple "pemfile" parameters,
- * and gnutls will try to pick the right one.
- * * listen and pemfile can be specified more than once
- * * certificates in a file have to be ordered from bottom to top (each certificate is followed by the one that signed it)
- *
- * Example config:
- * setup gnutls ( "listen" => "0.0.0.0:8443", "listen" => "[::]:8443", "pemfile" => "server.pem" ];
- *
- * setup {
- * fetch.files_static "sni" => "/etc/lighttpd2/certs/sni_*_server.pem";
- * gnutls ( "listen" => "0.0.0.0:8443", "listen" => "[::]:8443", "pemfile" => "server.pem", "sni-backend" => "sni" );
- * }
+ * mod_gnutls - TLS listen support
*
* TODO:
* * support client certificate authentication: http://www.gnutls.org/manual/gnutls.html#Client-certificate-authentication
@@ -206,7 +177,6 @@ static void mod_gnutls_context_release(mod_context *ctx) {
gnutls_priority_deinit(ctx->server_priority_beast);
gnutls_priority_deinit(ctx->server_priority);
gnutls_certificate_free_credentials(ctx->server_cert);
- gnutls_dh_params_deinit(ctx->dh_params);
#ifdef HAVE_SESSION_TICKET
/* wtf. why is there no function in gnutls for this... */
if (NULL != ctx->ticket_key.data) {
@@ -232,7 +202,7 @@ static void mod_gnutls_context_release(mod_context *ctx) {
ctx->sni_fallback_cert = NULL;
}
#endif
-
+ gnutls_dh_params_deinit(ctx->dh_params);
g_slice_free(mod_context, ctx);
}
@@ -837,17 +807,20 @@ static gboolean gnutls_setup(liServer *srv, liPlugin* p, liValue *val, gpointer
gnutls_strerror_name(r), gnutls_strerror(r));
goto error_free_ctx;
}
-
- gnutls_certificate_set_dh_params(ctx->server_cert, ctx->dh_params);
} else {
if (GNUTLS_E_SUCCESS != (r = load_dh_params_4096(&ctx->dh_params))) {
ERROR(srv, "couldn't load dh parameters(%s): %s",
gnutls_strerror_name(r), gnutls_strerror(r));
goto error_free_ctx;
}
+ }
- gnutls_certificate_set_dh_params(ctx->server_cert, ctx->dh_params);
+ gnutls_certificate_set_dh_params(ctx->server_cert, ctx->dh_params);
+#ifdef USE_SNI
+ if (NULL != ctx->sni_fallback_cert) {
+ gnutls_certificate_set_dh_params(ctx->sni_fallback_cert, ctx->dh_params);
}
+#endif
if (priority) {
const char *errpos = NULL;
diff --git a/src/modules/mod_limit.c b/src/modules/mod_limit.c
index f8669c4..8925cab 100644
--- a/src/modules/mod_limit.c
+++ b/src/modules/mod_limit.c
@@ -1,48 +1,6 @@
-
/*
* mod_limit - limit concurrent connections or requests per second
*
- * Description:
- * mod_limit lets you limit the number of concurrent connections or requests per second.
- * Both limits can be "in total" or per IP.
- *
- * Setups:
- * none
- *
- * Options:
- * none
-
- * Actions:
- * limit.con [=> action];
- * - is the number of concurrent connections
- * - [action] is an action to be executed if the limit is reached
- * limit.con_ip [=> action];
- * - is the number of concurrent connections per IP
- * - [action] is an action to be executed if the limit is reached
- * limit.req [=> action];
- * - is the number of requests per second
- * - [action] is an action to be executed if the limit is reached
- * limit.req_ip [=> action];
- * - is the number of requests per second per IP
- * - [action] is an action to be executed if the limit is reached
- *
- * Example config:
- * if req.path =^ "/downloads/" {
- * limit.con 10;
- * limit.con_ip 1;
- * }
- *
- * This config snippet will allow only 10 active downloads overall and 1 per IP.
- *
- * if req.path == "/login" {
- * limit.req_ip 1 => ${ log.write "Possible bruteforce from %{req.remoteip}"; };
- * }
- *
- * This config snippet will write a message to the log containing the clien IP address if the /login page is hit more than once in a second.
- *
- * Todo:
- * -
- *
* Author:
* Copyright (c) 2010 Thomas Porzelt
* License:
diff --git a/src/modules/mod_lua.c b/src/modules/mod_lua.c
index 0669f94..1086e6b 100644
--- a/src/modules/mod_lua.c
+++ b/src/modules/mod_lua.c
@@ -1,27 +1,5 @@
/*
- * mod_lua - include lua actions
- *
- * Description:
- * mod_lua
- *
- * Setups:
- * lua.plugin (filename, { options }, )
- * - No options available yet
- * - Can register setup.* and action.* callbacks (like any c module)
- * via creating a setups / actions table in the global lua namespace
- * Options:
- * none
- * Actions:
- * lua.handler (filename, { "ttl" => 300 }, )
- * - Basically the same as include_lua (no setup.* calls allowed), but loads the script
- * in a worker specific lua_State, so it doesn't use the server wide lua lock.
- * - You can give a ttl, after which the file is checked for modifications
- * and reloaded. The default value 0 disables reloading.
- * - The third parameter is available as second parameter in the lua file:
- * local filename, args = ...
- *
- * Example config:
- * lua.handler "/etc/lighttpd/pathrewrite.lua";
+ * mod_lua - load lua plugins and actions
*
* Todo:
* - Add more lua plugin features (plugin hooks)
diff --git a/src/modules/mod_memcached.c b/src/modules/mod_memcached.c
index 1aba144..fb42a65 100644
--- a/src/modules/mod_memcached.c
+++ b/src/modules/mod_memcached.c
@@ -1,34 +1,6 @@
/*
* mod_memcached - cache content on memcached servers
*
- * Description:
- * cache content on memcached servers
- *
- * Setups:
- * none
- * Options:
- * none
- * Actions:
- * (trailing parameters are optional)
- * memcached.lookup , ,
- * memcached.store
- * options: hash of
- * - server: socket address as string (default: 127.0.0.1:11211)
- * - flags: flags for storing (default 0)
- * - ttl: ttl for storing (default 0 - forever)
- * - maxsize: maximum size in bytes we want to store
- * - headers: whether to store/lookup headers too (not supported yet)
- * if disabled: get mime-type from request.uri.path for lookup
- * - key: pattern for lookup/store key
- * default: "%{req.path}"
- *
- * Example config:
- * memcached.lookup [], ${ header.add "X-Memcached" => "Hit" }, ${ header.add "X-Memcached" => "Miss" };
- *
- * memcached.lookup ["key": "%{req.scheme}://%{req.host}%{req.path}"];
- *
- * Exports a lua api to per-worker luaStates too.
- *
* Todo:
* - store/lookup headers too
*
diff --git a/src/modules/mod_openssl.c b/src/modules/mod_openssl.c
index 20572c2..dacf820 100644
--- a/src/modules/mod_openssl.c
+++ b/src/modules/mod_openssl.c
@@ -1,41 +1,6 @@
/*
* mod_openssl - ssl support
*
- * Description:
- * mod_openssl listens on separate sockets for ssl connections (https://...)
- *
- * Setups:
- * openssl - setup a ssl socket; takes a hash/key-value list of following parameters:
- * listen - (mandatory) the socket address (same as standard listen)
- * pemfile - (mandatory) contains key and certificate (+ optional chain) for the key (PEM format)
- * ca-file - contains certificate chain
- * ciphers - contains colon separated list of allowed ciphers
- * default: "ECDHE-RSA-AES256-SHA384:AES256-SHA256:RC4-SHA:RC4:HIGH:!MD5:!aNULL:!EDH:!AESGCM"
- * options - (list of strings) set OpenSSL-specific options (default: NO_SSLv2, CIPHER_SERVER_PREFERENCE, NO_COMPRESSION)
- * to overwrite defaults you need to explicitly specify the reverse flag (toggle "NO_" prefix)
- * example: use sslv2 and compression: [ options: ("SSLv2", "COMPRESSION") ]
- * verify - (boolean) enable client certificate verification (default: false)
- * verify-any - (boolean) allow all CAs and self-signed certificates (for manual checking, default: false)
- * verify-depth - (number) sets client verification depth (default: 1)
- * verify-require - (boolean) abort clients failing verification (default: false)
- * client-ca-file - (string) path to file containing client CA certificates
- *
- * Actions:
- * openssl.setenv [options] - set SSL environment strings
- * options: (list), may contain strings:
- * "client" - set SSL_CLIENT_S_DN_ short-named entries
- * "client-cert" - set SSL_CLIENT_CERT to client certificate PEM
- * "server" - set SSL_SERVER_S_DN_ short-named entries
- * "server-cert" - set SSL_SERVER_CERT to server certificate PEM
- *
- * Example config:
- * setup openssl (
- * "listen": "0.0.0.0:443",
- * "listen": "0.0.0.0:443",
- * "pemfile": "server.pem"
- * );
- * openssl.setenv "client";
- *
* Author:
* Copyright (c) 2009-2013 Stefan Bühler, Joe Presbrey
*/
diff --git a/src/modules/mod_progress.c b/src/modules/mod_progress.c
index 3b77bf8..1b1a027 100644
--- a/src/modules/mod_progress.c
+++ b/src/modules/mod_progress.c
@@ -1,56 +1,6 @@
/*
* mod_progress - track connection progress (state) via a unique identifier
*
- * Description:
- * mod_progress lets you track connection progress (or rather state) using a lookup table
- * in which connections are registered via a random unique identifier specified with the request
- *
- * Setups:
- * progress.ttl ;
- * - Sets the time to live in seconds for entries after a disconnect in the internal lookup table.
- * Defaults to 30 seconds.
- * Options:
- * progress.debug = ;
- * - if true, debug info is written to the log
- * progress.methods = ;
- * - list of methods that should be tracked, defaults to POST only. Example: progress.methods = ("GET", "POST");
- * Actions:
- * progress.track;
- * - tracks the current connection if the X-Progress-ID querystring key is supplied
- * progress.show [format];
- * - returns the current progress/state of
- * - [format] can be one of "legacy", "json" or "jsonp" . See example responses below.
- * Defaults to "json".
- *
- * Examples responses:
- * - legacy format
- * new Object({"state": "running"", "received": 123456, "sent": 0, "request_size": 200000, "response_size": 0})
- * - json format
- * {"state": "running", "received": 123456, "sent": 0, "request_size": 200000, "response_size": 0}
- * - jsonp format (function name specified via X-Progress-Callback querystring key, defaults to "progress")
- * progress({"state": "running", "received": 123456, "sent": 0, "request_size": 200000, "response_size": 0})
- *
- * Possible response objects:
- * - {"state": "unknown"}
- * - {"state": "running", "received": , "sent": , "request_size": , "response_size": }
- * - {"state": "done", "received": , "sent": , "request_size": , "response_size": }
- * - {"state": "error", "status": }
- *
- * Example config:
- * if req.path == "/upload.php" {
- * progress.track;
- * } else if req.path == "/progress" {
- * progress.show;
- * }
- *
- * The config snippet will track all POST requests (uploads) to /upload.php?X-Progress-ID=
- * where is a random unqiue ID.
- * The progress of a particular request can then be fetched via /progress?X-Progress-ID=
- * where is the ID specified with the POST request to /upload.php
- *
- * Tip:
- * none
- *
* Todo:
* - stop waitqueues
* - "dump" format to return an array of all tracked requests?
diff --git a/src/modules/mod_proxy.c b/src/modules/mod_proxy.c
index 3e82c6a..4cb262a 100644
--- a/src/modules/mod_proxy.c
+++ b/src/modules/mod_proxy.c
@@ -1,19 +1,5 @@
/*
- * mod_proxy - connect to proxy backends for generating content
- *
- * Description:
- * mod_proxy connects to a backend over tcp or unix sockets
- *
- * Setups:
- * none
- * Options:
- * none
- * Actions:
- * proxy - connect to backend at
- * socket: string, either "ip:port" or "unix:/path"
- *
- * Example config:
- * proxy "127.0.0.1:9090"
+ * mod_proxy - connect to HTTP backends for generating response content
*
* TODO:
* - keep-alive connections
diff --git a/src/modules/mod_redirect.c b/src/modules/mod_redirect.c
index 4d8dd50..6334786 100644
--- a/src/modules/mod_redirect.c
+++ b/src/modules/mod_redirect.c
@@ -1,49 +1,8 @@
/*
* mod_redirect - redirect clients by sending a http status code 301 plus Location header
*
- * Description:
- * mod_redirect acts similar to mod_rewrite but redirects clients instead of rewriting the request.
- * It supports matching regular expressions and substitution with captured substrings as well as other placeholders.
- * A so called redirect rule consist of a regular expression and a target string.
- *
- * Placeholders:
- * - $1..9 replaced by captured substring of current regex
- * - $0 replaced by whole string that matched the regex
- * - %0..9 same as $n but uses regex from previous conditional
- * - %{var} with var being one of the req.* or phys.* e.g. %{request.host}
- * supported vars: request.host, request.path, request.query, request.remoteip, request.localip, request.content_length
- * - %{enc:var} same as %{var} but urlencoded e.g. %{enc:request.path}
- *
- * ?, $ and % can be escaped using \?, \$ and \% respectively.
- *
- * Setups:
- * none
- * Options:
- * redirect.debug = ;
- * - if set, debug information is written to the log
- * Actions:
- * redirect "http://example.tld/";
- * - redirects the client, substituting all placeholders. $0..$9 get replaced by empty strings.
- * redirect "regex" => "/new/path";
- * - redirects client if "regex" matched the request.path.
- * - $0..$9 get replaced by the captured substrings of the regular expression "regex".
- * redirect ("regex1" => "/new/path1", ..., "regexN" => "/new/pathN");
- * - traverses the list of redirect rules.
- * - redirects client to the corresponding "/new/path" if the regex matches and stops traversing the list.
- *
- * Example config:
- * # redirect all non www. requests. for example: foo.tld/bar?x=y to www.foo.tld/bar?x=y
- * if request.host !~ "^www\.(.*)$" {
- * redirect "." => "http://www.%1/$0?%{request.query}";
- * }
- *
- *
- * Tip:
- * As both config parser and regex compiler use backslashes to escape special characters, you will have to escape them twice.
- * For example "^foo\\dbar$" will end up as "^foo\dbar$" as regex input, which would match things like "foo3bar".
- *
* Todo:
- * none
+ * support "//example.com/path" targets, keeping the current scheme
*
* Authors:
* Copyright (c) 2009 Thomas Porzelt
diff --git a/src/modules/mod_rewrite.c b/src/modules/mod_rewrite.c
index 8915548..18c034f 100644
--- a/src/modules/mod_rewrite.c
+++ b/src/modules/mod_rewrite.c
@@ -1,51 +1,6 @@
/*
* mod_rewrite - modify request path and querystring with support for regular expressions
*
- * Description:
- * mod_rewrite lets you modify (rewrite) the path and querystring of a request.
- * It supports matching regular expressions and substitution with captured substrings as well as other placeholders.
- * A so called rewrite rule consist of a regular expression and a target string.
- *
- * If your rewrite target does not contain any questionmark (?), then the querystring will not be altered.
- * If it does, then it will be overwritten. To append the original querystring, use %{request.query}.
- *
- * Placeholders:
- * - $1..9 replaced by captured substring of current regex
- * - $0 replaced by whole string that matched the regex
- * - %0..9 same as $n but uses regex from previous conditional
- * - %{var} with var being one of the req.* or phys.* e.g. %{request.host}
- * supported vars: request.host, request.path, request.query, request.remoteip, request.localip, request.content_length
- * - %{enc:var} same as %{var} but urlencoded e.g. %{enc:request.path}
- *
- * ?, $ and % can be escaped using \?, \$ and \% respectively.
- *
- * Setups:
- * none
- * Options:
- * rewrite.debug = ;
- * - if set, debug information is written to the log
- * Actions:
- * rewrite "/new/path";
- * - sets request.path to "/new/path", substituting all placeholders. $0..$9 get replaced by empty strings.
- * rewrite "regex" => "/new/path";
- * - sets request.path to "/new/path" if "regex" matched the original req.path.
- * - $0..$9 get replaced by the captured substrings of the regular expression "regex".
- * rewrite ("regex1" => "/new/path1", ..., "regexN" => "/new/pathN");
- * - traverses the list of rewrite rules.
- * - rewrites request.path to the corresponding "/new/path" if the regex matches and stops traversing the list.
- *
- * Example config:
- * rewrite (
- * "^/article/(\d+)/.*$" => "/article.php?id=$1",
- * "^/download/(\d+)/(.*)$" => "/download.php?fileid=$1&filename=$2"
- * );
- * rewrite "^/user/(.+)$" => "/user.php?name=$1";
- *
- *
- * Tip:
- * As both config parser and regex compiler use backslashes to escape special characters, you will have to escape them twice.
- * For example "^foo\\dbar$" will end up as "^foo\dbar$" as regex input, which would match things like "foo3bar".
- *
* Todo:
* - implement rewrite_optimized which reorders rules according to hitcount
* - implement rewrite_raw which uses the raw uri
diff --git a/src/modules/mod_scgi.c b/src/modules/mod_scgi.c
index 827eae9..b2c2444 100644
--- a/src/modules/mod_scgi.c
+++ b/src/modules/mod_scgi.c
@@ -1,19 +1,5 @@
/*
- * mod_scgi - connect to scgi backends for generating content
- *
- * Description:
- * mod_scgi connects to a backend over tcp or unix sockets
- *
- * Setups:
- * none
- * Options:
- * none
- * Actions:
- * scgi - connect to backend at
- * socket: string, either "ip:port" or "unix:/path"
- *
- * Example config:
- * scgi "127.0.0.1:9090"
+ * mod_scgi - connect to SCGI backends for generating response content
*
* Author:
* Copyright (c) 2013 Stefan Bühler
diff --git a/src/modules/mod_status.c b/src/modules/mod_status.c
index 033b7c2..16e3f56 100644
--- a/src/modules/mod_status.c
+++ b/src/modules/mod_status.c
@@ -1,33 +1,6 @@
/*
* mod_status - display server status
*
- * Description:
- * mod_status can display a page with statistics like requests, traffic and active connections.
- * It can be customized with different stylesheets (css)
- *
- * Setups:
- * none
- * Options:
- * status.css - set the stylesheet to use, optional
- * type: string; values: "default", "blue" or a url to an external css file
- * Actions:
- * status.info - returns the status info page to the client
- * status.info "short" - returns only "non-sensitive" data; no connection details, no runtime section
- *
- * The status page accepts parameters in the query-string:
- * - mode=runtimes : show runtime information
- * - format=plain : returns "short" information in plain text format, easy to parse
- * - auto : returns "legacy" plain text format, for 1.5 migration or apache_ munin plugins
- *
- * Example config:
- * req.path == "/srv-status" {
- * status.css = "http://mydomain/status.css";
- * status.info;
- * }
- *
- * Todo:
- * -
- *
* Author:
* Copyright (c) 2008-2010 Thomas Porzelt
* License:
diff --git a/src/modules/mod_userdir.c b/src/modules/mod_userdir.c
index f8c6486..784a388 100644
--- a/src/modules/mod_userdir.c
+++ b/src/modules/mod_userdir.c
@@ -1,25 +1,6 @@
/*
* mod_userdir - user-specific document roots
*
- * Description:
- * mod_userdir allows you to have user-specific document roots being accessed through http://domain/~user/
- *
- * Setups:
- * none
- *
- * Options:
- * none
- *
- * Actions:
- * userdir ;
- * - if not starting with a slash, maps a request path of /~user/ to a docroot of ~user//
- * - if starting with a slash, maps a request path of /~user/ to a docroot of
- * - * in is replaced by the requested username
- * - $1-9 are replace by the n-th letter of the requested username
- *
- * Example config:
- * userdir "public_html"; # maps /~lighty/ to ~lighty/public_html/ (e.g. /home/lighty/public_html/ on most systems)
- *
* Todo:
* - userdir.exclude / userdir.include options/setups to allow certain users to be excluded or included
*
diff --git a/src/modules/mod_vhost.c b/src/modules/mod_vhost.c
index 832f75a..2089832 100644
--- a/src/modules/mod_vhost.c
+++ b/src/modules/mod_vhost.c
@@ -1,36 +1,6 @@
/*
* mod_vhost - virtual hosting
*
- * Description:
- * mod_vhost offers various ways to implement virtual webhosts.
- * It can map hostnames to actions and offers multiple ways to do so.
- * These ways differ in the flexibility of mapping (what to map and what to map to) as well as performance.
- *
- * Setups:
- * none
- * Options:
- * vhost.debug = - enable debug output
- * Actions:
- * vhost.map ( "host1" => action1, "host2" => action2, "default" => action0 );
- * - lookup action by using the hostname as the key of the hashtable
- * - if not found, use default action
- * - fast and flexible but no matching on hostnames possible
- * vhost.map_regex ( "host1regex" => action1, "host2regex" => action2, "default" => action0 );
- * - lookup action by traversing the list and applying a regex match of the hostname on each entry
- * - uses first matching entry; if no match, use default action
- * - slowest method but the most flexible one
- *
- * Example config:
- *
- * mydom1 {...} mydom2 {...} defaultdom {...}
- * vhost.map ( "dom1.com" => mydom1, "dom2.tld" => mydom2, "default" => defaultdom );
- * vhost.map_regex ( "^(.+\.)?dom1\.com$" => mydom1, "^dom2\.(com|net|org)$" => mydom2, "default" => defaultdom );
- *
- * Tip:
- * You can combine vhost.map and vhost.map_regex to create a reasonably fast and flexible vhost mapping mechanism.
- * Just use a vhost.map_regex action as the default fallback action in vhost.map.
- * This way, the expensive vhost.map_regex is only used if the vhost was not found in vhost.map.
- *
* Todo:
* -
*