luci-app-privoxy: change to tabbed display
[project/luci.git] / applications / luci-app-privoxy / luasrc / model / cbi / privoxy.lua
1 -- Copyright 2014-2015 Christian Schoenebeck <christian dot schoenebeck at gmail dot com>
2 -- Licensed under the Apache License, Version 2.0
3
4 local NXFS = require "nixio.fs"
5 local SYS  = require "luci.sys"
6 local UTIL = require "luci.util"
7 local DISP = require "luci.dispatcher"
8 local DTYP = require "luci.cbi.datatypes"
9 local CTRL = require "luci.controller.privoxy"  -- this application's controller
10
11 local HELP = [[<a href="http://www.privoxy.org/user-manual/config.html#%s" target="_blank">%s</a>]]
12
13 -- Error handling if wrong privoxy version installed -- ########################
14 if not CTRL.service_ok() then
15         local f         = SimpleForm("_sf")
16         f.title         = CTRL.app_title_main()
17         f.description   = CTRL.app_description()
18         f.embedded      = true
19         f.submit        = false
20         f.reset         = false
21
22         local s    = f:section(SimpleSection)
23         local v    = s:option(DummyValue, "_dv")
24         v.titleref = DISP.build_url("admin", "system", "packages")
25         v.rawhtml  = true
26         v.value    = CTRL.service_update()
27         return f
28 end
29
30 -- #################################################################################################
31 -- Error handling if no config, create an empty one -- #########################
32 if not NXFS.access("/etc/config/privoxy") then
33         NXFS.writefile("/etc/config/privoxy", "")
34 end
35
36 -- cbi-map -- ##################################################################
37 local m = Map("privoxy")
38 m.title = CTRL.app_title_main()
39 m.description = CTRL.app_description()
40 function m.commit_handler(self)
41         if self.changed then    -- changes ?
42                 os.execute("/etc/init.d/privoxy reload &")      -- reload configuration
43         end
44 end
45
46 -- cbi-section -- ##############################################################
47 local ns = m:section( NamedSection, "privoxy", "privoxy")
48 function ns.cfgvalue(self, section)
49         if not self.map:get("system") then      -- section might not exist
50                 self.map:set("system", nil, "system")
51         end
52         if not self.map:get(section) then       -- section might not exist
53                 self.map:set(section, nil, self.sectiontype)
54         end
55         return self.map:get(section)
56 end
57
58 ns:tab("sys",
59         translate("System"),
60         nil )
61 local function err_tab_sys(title, msg)
62         return string.format(translate("System") .. " - %s: %s", title, msg )
63 end
64
65 ns:tab("doc",
66         translate("Documentation"),
67         translate("If you intend to operate Privoxy for more users than just yourself, "
68                 .. "it might be a good idea to let them know how to reach you, what you block "
69                 .. "and why you do that, your policies, etc.") )
70 local function err_tab_doc(title, msg)
71         return string.format(translate("Documentation") .. " - %s: %s", title, msg )
72 end
73
74 ns:tab("filter",
75         translate("Files and Directories"),
76         translate("Privoxy can (and normally does) use a number of other files "
77                 .. "for additional configuration, help and logging. This section of "
78                 .. "the configuration file tells Privoxy where to find those other files.") )
79 local function err_tab_filter(title, msg)
80         return string.format(translate("Files and Directories") .. " - %s: %s", title, msg )
81 end
82
83 ns:tab("access",
84         translate("Access Control"),
85         translate("This tab controls the security-relevant aspects of Privoxy's configuration.") )
86 local function err_tab_access(title, msg)
87         return string.format(translate("Access Control") .. " - %s: %s", title, msg )
88 end
89
90 ns:tab("forward",
91         translate("Forwarding"),
92         translate("Configure here the routing of HTTP requests through a chain of multiple proxies. "
93                 .. "Note that parent proxies can severely decrease your privacy level. "
94                 .. "Also specified here are SOCKS proxies.") )
95
96 ns:tab("misc",
97         translate("Miscellaneous"),
98         nil)
99 local function err_tab_misc(self, msg)
100         return string.format(translate("Miscellaneous") .. " - %s: %s", self.title_base, msg )
101 end
102
103 ns:tab("debug",
104         translate("Logging"),
105         nil )
106
107 ns:tab("logview",
108         translate("Log File Viewer"),
109         nil )
110
111 -- tab: local -- ###############################################################
112
113 -- start/stop button -----------------------------------------------------------
114 local btn       = ns:taboption("sys", Button, "_startstop")
115 btn.title       = translate("Start / Stop")
116 btn.description = translate("Start/Stop Privoxy WEB Proxy")
117 btn.template    = "privoxy/detail_startstop"
118 function btn.cfgvalue(self, section)
119         local pid = CTRL.get_pid(true)
120         if pid > 0 then
121                 btn.inputtitle  = "PID: " .. pid
122                 btn.inputstyle  = "reset"
123                 btn.disabled    = false
124         else
125                 btn.inputtitle  = translate("Start")
126                 btn.inputstyle  = "apply"
127                 btn.disabled    = false
128         end
129         return true
130 end
131
132 -- enabled ---------------------------------------------------------------------
133 local ena       = ns:taboption("sys", Flag, "_enabled")
134 ena.title       = translate("Enabled")
135 ena.description = translate("Enable/Disable autostart of Privoxy on system startup and interface events")
136 ena.orientation = "horizontal" -- put description under the checkbox
137 ena.rmempty     = false
138 function ena.cfgvalue(self, section)
139         return (SYS.init.enabled("privoxy")) and "1" or "0"
140 end
141 function ena.write(self, section, value)
142         if value == "1" then
143                 return SYS.init.enable("privoxy")
144         else
145                 return SYS.init.disable("privoxy")
146         end
147 end
148
149 -- boot_delay ------------------------------------------------------------------
150 local bd        = ns:taboption("sys", Value, "boot_delay")
151 bd.title        = translate("Boot delay")
152 bd.description  = translate("Delay (in seconds) during system boot before Privoxy start")
153                 .. [[<br />]]
154                 .. translate("During delay ifup-events are not monitored !")
155 bd.default      = "10"
156 bd.rmempty      = false
157 -- value is in a separate section so we need to do by hand
158 function bd.cfgvalue(self, section)
159         local value = tonumber(self.map:get("system", "boot_delay") )
160         if not value then return nil end
161         return tostring(value)
162 end
163 function bd.validate(self, value)
164         local val = tonumber(value)
165         if not val then
166                 return nil, err_tab_sys(self.title, translate("Value is not a number") )
167         elseif val < 0 or val > 300 then
168                 return nil, err_tab_sys(self.title, translate("Value not between 0 and 300") )
169         end
170         return value
171 end
172 function bd.write(self, section, value)
173         local fvalue = self:formvalue(section)
174         local cvalue = self:cfgvalue(section)
175         if (fvalue ~= cvalue) then
176                 self.map:set("system", "boot_delay", value)
177         end
178 end
179
180 -- hostname --------------------------------------------------------------------
181 local hn        = ns:taboption("doc", Value, "hostname")
182 hn.title        = string.format(HELP, "HOSTNAME", "Hostname" )
183 hn.description  = translate("The hostname shown on the CGI pages.")
184 hn.placeholder  = SYS.hostname()
185 hn.optional     = true
186 hn.rmempty      = true
187 function hn.parse(self, section, novld)
188         CTRL.value_parse(self, section, novld)
189 end
190
191 -- user-manual -----------------------------------------------------------------
192 local um        = ns:taboption("doc", Value, "user_manual")
193 um.title        = string.format(HELP, "USER-MANUAL", "User Manual" )
194 um.description  = translate("Location of the Privoxy User Manual.")
195 um.placeholder  = "http://www.privoxy.org/user-manual/"
196 um.optional     = true
197 um.rmempty      = true
198 function um.parse(self, section, novld)
199         CTRL.value_parse(self, section, novld)
200 end
201
202 -- admin-address ---------------------------------------------------------------
203 local aa        = ns:taboption("doc", Value, "admin_address")
204 aa.title_base   = "Admin Email"
205 aa.title        = string.format(HELP, "ADMIN-ADDRESS", aa.title_base )
206 aa.description  = translate("An email address to reach the Privoxy administrator.")
207 aa.placeholder  = "privoxy.admin@example.com"
208 aa.optional     = true
209 aa.rmempty      = true
210 function aa.validate(self, value)
211         if not value or #value == 0 then
212                 return ""
213         end
214         if not (value:match("[A-Za-z0-9%.%%%+%-]+@[A-Za-z0-9%.%%%+%-]+%.%w%w%w?%w?")) then
215                 return nil, err_tab_doc(self.title_base, translate("Invalid email address") )
216         end
217         return value
218 end
219 function aa.parse(self, section, novld)
220         CTRL.value_parse(self, section, novld)
221 end
222
223 -- proxy-info-url --------------------------------------------------------------
224 local piu       = ns:taboption("doc", Value, "proxy_info_url")
225 piu.title       = string.format(HELP, "PROXY-INFO-URL", "Proxy Info URL" )
226 piu.description = translate("A URL to documentation about the local Privoxy setup, configuration or policies.")
227 piu.optional    = true
228 piu.rmempty     = true
229 function piu.parse(self, section, novld)
230         CTRL.value_parse(self, section, novld)
231 end
232
233 -- trust-info-url --------------------------------------------------------------
234 local tiu       = ns:taboption("doc", Value, "trust_info_url")
235 tiu.title       = string.format(HELP, "TRUST-INFO-URL", "Trust Info URLs" )
236 tiu.description = translate("A URL to be displayed in the error page that users will see if access to an untrusted page is denied.")
237                 .. [[<br /><strong>]]
238                 .. translate("The value of this option only matters if the experimental trust mechanism has been activated.")
239                 .. [[</strong>]]
240 tiu.optional    = true
241 tiu.rmepty      = true
242 function tiu.parse(self, section, novld)
243         CTRL.value_parse(self, section, novld)
244 end
245
246 -- tab: filter -- ##############################################################
247
248 -- logdir ----------------------------------------------------------------------
249 local ld        = ns:taboption("filter", Value, "logdir")
250 ld.title_base   = "Log Directory"
251 ld.title        = string.format(HELP, "LOGDIR", ld.title_base )
252 ld.description  = translate("The directory where all logging takes place (i.e. where the logfile is located).")
253                 .. [[<br />]]
254                 .. translate("No trailing '/', please.")
255 ld.default      = "/var/log"
256 ld.rmempty      = false
257 function ld.validate(self, value)
258         if not value or #value == 0 then
259                 return nil, err_tab_filter(self.title_base, translate("Mandatory Input: No Directory given!") )
260         elseif not NXFS.access(value) then
261                 return nil, err_tab_filter(self.title_base, translate("Directory does not exist!") )
262         else
263                 return value
264         end
265 end
266 function ld.parse(self, section, novld)
267         CTRL.value_parse(self, section, novld)
268 end
269
270 -- logfile ---------------------------------------------------------------------
271 local lf        = ns:taboption("filter", Value, "logfile")
272 lf.title_base   = "Log File"
273 lf.title        = string.format(HELP, "LOGFILE", lf.title_base )
274 lf.description  = translate("The log file to use. File name, relative to log directory.")
275 lf.default      = "privoxy.log"
276 lf.rmempty      = false
277 function lf.validate(self, value)
278         if not value or #value == 0 then
279                 return nil, err_tab_filter(self.title_base, translate("Mandatory Input: No File given!") )
280         else
281                 return value
282         end
283 end
284
285 -- confdir ---------------------------------------------------------------------
286 local cd        = ns:taboption("filter", Value, "confdir")
287 cd.title_base   = "Configuration Directory"
288 cd.title        = string.format(HELP, "CONFDIR", cd.title_base )
289 cd.description  = translate("The directory where the other configuration files are located.")
290                 .. [[<br />]]
291                 .. translate("No trailing '/', please.")
292 cd.default      = "/etc/privoxy"
293 cd.rmempty      = false
294 function cd.validate(self, value)
295         if not value or #value == 0 then
296                 return nil, err_tab_filter(self.title_base, translate("Mandatory Input: No Directory given!") )
297         elseif not NXFS.access(value) then
298                 return nil, err_tab_filter(self.title_base, translate("Directory does not exist!") )
299         else
300                 return value
301         end
302 end
303
304 -- templdir --------------------------------------------------------------------
305 local tld       = ns:taboption("filter", Value, "templdir")
306 tld.title_base  = "Template Directory"
307 tld.title       = string.format(HELP, "TEMPLDIR", tld.title_base )
308 tld.description = translate("An alternative directory where the templates are loaded from.")
309                 .. [[<br />]]
310                 .. translate("No trailing '/', please.")
311 tld.placeholder = "/etc/privoxy/templates"
312 tld.rmempty     = true
313 function tld.validate(self, value)
314         if not NXFS.access(value) then
315                 return nil, err_tab_filter(self.title_base, translate("Directory does not exist!") )
316         else
317                 return value
318         end
319 end
320
321 -- temporary-directory ---------------------------------------------------------
322 local td        = ns:taboption("filter", Value, "temporary_directory")
323 td.title_base   = "Temporary Directory"
324 td.title        = string.format(HELP, "TEMPORARY-DIRECTORY", td.title_base )
325 td.description  = translate("A directory where Privoxy can create temporary files.")
326                 .. [[<br /><strong>]]
327                 .. translate("Only when using 'external filters', Privoxy has to create temporary files.")
328                 .. [[</strong>]]
329 td.rmempty      = true
330
331 -- actionsfile -----------------------------------------------------------------
332 local af        = ns:taboption("filter", DynamicList, "actionsfile")
333 af.title_base   = "Action Files"
334 af.title        = string.format(HELP, "ACTIONSFILE", af.title_base)
335 af.description  = translate("The actions file(s) to use. Multiple actionsfile lines are permitted, and are in fact recommended!")
336                 .. [[<br /><strong>match-all.action := </strong>]]
337                 .. translate("Actions that are applied to all sites and maybe overruled later on.")
338                 .. [[<br /><strong>default.action := </strong>]]
339                 .. translate("Main actions file")
340                 .. [[<br /><strong>user.action := </strong>]]
341                 .. translate("User customizations")
342 af.rmempty      = false
343 function af.validate(self, value)
344         if not value or #value == 0 then
345                 return nil, err_tab_access(self.title_base, translate("Mandatory Input: No files given!") )
346         end
347         local confdir = cd:formvalue(ns.section)
348         local err     = false
349         local file    = ""
350         if type(value) == "table" then
351                 local x
352                 for _, x in ipairs(value) do
353                         if x and #x > 0 then
354                                 if not NXFS.access(confdir .."/".. x) then
355                                         err  = true
356                                         file = x
357                                         break   -- break/leave for on error
358                                 end
359                         end
360                 end
361         else
362                 if not NXFS.access(confdir .."/".. value) then
363                         err  = true
364                         file = value
365                 end
366         end
367         if err then
368                 return nil, string.format(err_tab_filter(self.title_base, translate("File '%s' not found inside Configuration Directory") ), file)
369         end
370         return value
371 end
372
373 -- filterfile ------------------------------------------------------------------
374 local ff        = ns:taboption("filter", DynamicList, "filterfile")
375 ff.title_base   = "Filter files"
376 ff.title        = string.format(HELP, "FILTERFILE", ff.title_base )
377 ff.description  = translate("The filter files contain content modification rules that use regular expressions.")
378 ff.rmempty      = false
379 function ff.validate(self, value)
380         if not value or #value == 0 then
381                 return nil, err_tab_access(self.title_base, translate("Mandatory Input: No files given!") )
382         end
383         local confdir = cd:formvalue(ns.section)
384         local err     = false
385         local file    = ""
386         if type(value) == "table" then
387                 local x
388                 for _, x in ipairs(value) do
389                         if x and #x > 0 then
390                                 if not NXFS.access(confdir .."/".. x) then
391                                         err  = true
392                                         file = x
393                                         break   -- break/leave for on error
394                                 end
395                         end
396                 end
397         else
398                 if not NXFS.access(confdir .."/".. value) then
399                         err  = true
400                         file = value
401                 end
402         end
403         if err then
404                 return nil, string.format(err_tab_filter(self.title_base, translate("File '%s' not found inside Configuration Directory") ), file )
405         end
406         return value
407 end
408
409 -- trustfile -------------------------------------------------------------------
410 local tf        = ns:taboption("filter", Value, "trustfile")
411 tf.title_base   = "Trust file"
412 tf.title        = string.format(HELP, "TRUSTFILE", tf.title_base )
413 tf.description  = translate("The trust mechanism is an experimental feature for building white-lists "
414                 .."and should be used with care.")
415                 .. [[<br /><strong>]]
416                 .. translate("It is NOT recommended for the casual user.")
417                 .. [[</strong>]]
418 tf.placeholder  = "user.trust"
419 tf.rmempty      = true
420 function tf.validate(self, value)
421         local confdir = cd:formvalue(ns.section)
422         local err     = false
423         local file    = ""
424         if type(value) == "table" then
425                 local x
426                 for _, x in ipairs(value) do
427                         if x and #x > 0 then
428                                 if not NCFS.access(confdir .."/".. x) then
429                                         err  = true
430                                         file = x
431                                         break   -- break/leave for on error
432                                 end
433                         end
434                 end
435         else
436                 if not NXFS.access(confdir .."/".. value) then
437                         err  = true
438                         file = value
439                 end
440         end
441         if err then
442                 return nil, string.format(err_tab_filter(self.title_base, translate("File '%s' not found inside Configuration Directory") ), file )
443         end
444         return value
445 end
446
447 -- tab: access -- ##############################################################
448
449 -- listen-address --------------------------------------------------------------
450 local la        = ns:taboption("access", DynamicList, "listen_address")
451 la.title_base   = "Listen addresses"
452 la.title        = string.format(HELP, "LISTEN-ADDRESS", la.title_base )
453 la.description  = translate("The address and TCP port on which Privoxy will listen for client requests.")
454                 .. [[<br />]]
455                 .. translate("Syntax: ")
456                 .. "IPv4:Port / [IPv6]:Port / Host:Port"
457 la.default      = "127.0.0.1:8118"
458 la.rmempty      = false
459 function la.validate(self, value)
460         if not value or #value == 0 then
461                 return nil, err_tab_access(self.title_base, translate("Mandatory Input: No Data given!") )
462         end
463
464         local function check_value(v)
465                 local _ret = UTIL.split(v, "]:")
466                 local _ip
467                 if _ret[2] then -- ip6 with port
468                         _ip   = string.gsub(_ret[1], "%[", "")  -- remove "[" at beginning
469                         if not DTYP.ip6addr(_ip) then
470                                 return translate("Mandatory Input: No valid IPv6 address given!")
471                         elseif not DTYP.port(_ret[2]) then
472                                 return translate("Mandatory Input: No valid Port given!")
473                         else
474                                 return nil
475                         end
476                 end
477                 _ret = UTIL.split(v, ":")
478                 if not _ret[2] then
479                         return translate("Mandatory Input: No Port given!")
480                 end
481                 if #_ret[1] > 0 and not DTYP.host(_ret[1]) then -- :8118 is valid address
482                         return translate("Mandatory Input: No valid IPv4 address or host given!")
483                 elseif not DTYP.port(_ret[2]) then
484                         return translate("Mandatory Input: No valid Port given!")
485                 else
486                         return nil
487                 end
488         end
489
490         local err   = ""
491         local entry = ""
492         if type(value) == "table" then
493                 local x
494                 for _, x in ipairs(value) do
495                         if x and #x > 0 then
496                                 err = check_value(x)
497                                 if err then
498                                         entry = x
499                                         break
500                                 end
501                         end
502                 end
503         else
504                 err = check_value(value)
505                 entry = value
506         end
507         if err then
508                 return nil, string.format(err_tab_access(self.title_base, err .. " - %s"), entry )
509         end
510         return value
511 end
512
513 -- permit-access ---------------------------------------------------------------
514 local pa        = ns:taboption("access", DynamicList, "permit_access")
515 pa.title        = string.format(HELP, "ACLS", "Permit access" )
516 pa.description  = translate("Who can access what.")
517                 .. [[<br /><strong>]]
518                 .. translate("Please read Privoxy manual for details!")
519                 .. [[</strong>]]
520 pa.rmempty      = true
521
522 -- deny-access -----------------------------------------------------------------
523 local da        = ns:taboption("access", DynamicList, "deny_access")
524 da.title        = string.format(HELP, "ACLS", "Deny Access" )
525 da.description  = translate("Who can access what.")
526                 .. [[<br /><strong>]]
527                 .. translate("Please read Privoxy manual for details!")
528                 .. [[</strong>]]
529 da.rmempty      = true
530
531 -- buffer-limit ----------------------------------------------------------------
532 local bl        = ns:taboption("access", Value, "buffer_limit")
533 bl.title_base   = "Buffer Limit"
534 bl.title        = string.format(HELP, "BUFFER-LIMIT", bl.title_base )
535 bl.description  = translate("Maximum size (in KB) of the buffer for content filtering.")
536                 .. [[<br />]]
537                 .. translate("Value range 1 to 4096, no entry defaults to 4096")
538 bl.default      = 4096
539 bl.rmempty      = true
540 function bl.validate(self, value)
541         local v = tonumber(value)
542         if not v then
543                 return nil, err_tab_access(self.title_base, translate("Value is not a number") )
544         elseif v < 1 or v > 4096 then
545                 return nil, err_tab_access(self.title_base, translate("Value not between 1 and 4096") )
546         elseif v == self.default then
547                 return ""       -- dont need to save default
548         end
549         return value
550 end
551
552 -- toggle ----------------------------------------------------------------------
553 local tgl       = ns:taboption("access", Flag, "toggle")
554 tgl.title       = string.format(HELP, "TOGGLE", "Toggle Status" )
555 tgl.description = translate("Enable/Disable filtering when Privoxy starts.")
556                 .. [[<br />]]
557                 .. translate("Disabled == Transparent Proxy Mode")
558 tgl.orientation = "horizontal"
559 tgl.default     = "1"
560 tgl.rmempty     = false
561
562 -- enable-remote-toggle --------------------------------------------------------
563 local ert       = ns:taboption("access", Flag, "enable_remote_toggle")
564 ert.title       = string.format(HELP, "ENABLE-REMOTE-TOGGLE", "Enable remote toggle" )
565 ert.description = translate("Whether or not the web-based toggle feature may be used.")
566 ert.orientation = "horizontal"
567 ert.rmempty     = true
568
569 -- enable-remote-http-toggle ---------------------------------------------------
570 local eht       = ns:taboption("access", Flag, "enable_remote_http_toggle")
571 eht.title       = string.format(HELP, "ENABLE-REMOTE-HTTP-TOGGLE", "Enable remote toggle via HTTP" )
572 eht.description = translate("Whether or not Privoxy recognizes special HTTP headers to change toggle state.")
573                 .. [[<br /><strong>]]
574                 .. translate("This option will be removed in future releases as it has been obsoleted by the more general header taggers.")
575                 .. [[</strong>]]
576 eht.orientation = "horizontal"
577 eht.rmempty     = true
578
579 -- enable-edit-actions ---------------------------------------------------------
580 local eea       = ns:taboption("access", Flag, "enable_edit_actions")
581 eea.title       = string.format(HELP, "ENABLE-EDIT-ACTIONS", "Enable action file editor" )
582 eea.description = translate("Whether or not the web-based actions file editor may be used.")
583 eea.orientation = "horizontal"
584 eea.rmempty     = true
585
586 -- enforce-blocks --------------------------------------------------------------
587 local eb        = ns:taboption("access", Flag, "enforce_blocks")
588 eb.title        = string.format(HELP, "ENFORCE-BLOCKS", "Enforce page blocking" )
589 eb.description  = translate("If enabled, Privoxy hides the 'go there anyway' link. "
590                 .. "The user obviously should not be able to bypass any blocks.")
591 eb.orientation  = "horizontal"
592 eb.rmempty      = true
593
594 -- tab: forward -- #############################################################
595
596 -- enable-proxy-authentication-forwarding --------------------------------------
597 local paf       = ns:taboption("forward", Flag, "enable_proxy_authentication_forwarding")
598 paf.title       = string.format(HELP, "ENABLE-PROXY-AUTHENTICATION-FORWARDING",
599                 translate("Enable proxy authentication forwarding") )
600 paf.description = translate("Whether or not proxy authentication through Privoxy should work.")
601                 .. [[<br /><strong>]]
602                 .. translate("Enabling this option is NOT recommended if there is no parent proxy that requires authentication!")
603                 .. [[</strong>]]
604 --paf.orientation       = "horizontal"
605 paf.rmempty     = true
606
607 -- forward ---------------------------------------------------------------------
608 local fwd       = ns:taboption("forward", DynamicList, "forward")
609 fwd.title       = string.format(HELP, "FORWARD", "Forward HTTP" )
610 fwd.description = translate("To which parent HTTP proxy specific requests should be routed.")
611                 .. [[<br />]]
612                 .. translate("Syntax: target_pattern http_parent[:port]")
613 fwd.rmempty     = true
614
615 -- forward-socks4 --------------------------------------------------------------
616 local fs4       = ns:taboption("forward", DynamicList, "forward_socks4")
617 fs4.title       = string.format(HELP, "SOCKS", "Forward SOCKS 4" )
618 fs4.description = translate("Through which SOCKS proxy (and optionally to which parent HTTP proxy) specific requests should be routed.")
619                 .. [[<br />]]
620                 .. translate("Syntax: target_pattern socks_proxy[:port] http_parent[:port]")
621 fs4.rmempty     = true
622
623 -- forward-socks4a -------------------------------------------------------------
624 local f4a       = ns:taboption("forward", DynamicList, "forward_socks4a")
625 f4a.title       = string.format(HELP, "SOCKS", "Forward SOCKS 4A" )
626 f4a.description = fs4.description
627 f4a.rmempty     = true
628
629 -- forward-socks5 --------------------------------------------------------------
630 local fs5       = ns:taboption("forward", DynamicList, "forward_socks5")
631 fs5.title       = string.format(HELP, "SOCKS", "Forward SOCKS 5" )
632 fs5.description = fs4.description
633 fs5.rmempty     = true
634
635 -- forward-socks5t -------------------------------------------------------------
636 local f5t       = ns:taboption("forward", DynamicList, "forward_socks5t")
637 f5t.title       = string.format(HELP, "SOCKS", "Forward SOCKS 5t" )
638 f5t.description = fs4.description
639 f5t.rmempty     = true
640
641 -- tab: misc -- ################################################################
642
643 -- accept-intercepted-requests -------------------------------------------------
644 local air       = ns:taboption("misc", Flag, "accept_intercepted_requests")
645 air.title       = string.format(HELP, "ACCEPT-INTERCEPTED-REQUESTS", "Accept intercepted requests" )
646 air.description = translate("Whether intercepted requests should be treated as valid.")
647 air.orientation = "horizontal"
648 air.rmempty     = true
649
650 -- allow-cgi-request-crunching -------------------------------------------------
651 local crc       = ns:taboption("misc", Flag, "allow_cgi_request_crunching")
652 crc.title       = string.format(HELP, "ALLOW-CGI-REQUEST-CRUNCHING", "Allow CGI request crunching" )
653 crc.description = translate("Whether requests to Privoxy's CGI pages can be blocked or redirected.")
654 crc.orientation = "horizontal"
655 crc.rmempty     = true
656
657 -- split-large-forms -----------------------------------------------------------
658 local slf       = ns:taboption("misc", Flag, "split_large_forms")
659 slf.title       = string.format(HELP, "SPLIT-LARGE-FORMS", "Split large forms" )
660 slf.description = translate("Whether the CGI interface should stay compatible with broken HTTP clients.")
661 slf.orientation = "horizontal"
662 slf.rmempty     = true
663
664 -- keep-alive-timeout ----------------------------------------------------------
665 local kat       = ns:taboption("misc", Value, "keep_alive_timeout")
666 kat.title_base  = "Keep-alive timeout"
667 kat.title       = string.format(HELP, "KEEP-ALIVE-TIMEOUT", kat.title_base)
668 kat.description = translate("Number of seconds after which an open connection will no longer be reused.")
669 kat.rmempty     = true
670 function kat.validate(self, value)
671         local v = tonumber(value)
672         if not v then
673                 return nil, err_tab_misc(self.title_base, translate("Value is not a number") )
674         elseif v < 1 then
675                 return nil, err_tab_misc(self.title_base, translate("Value not greater 0 or empty") )
676         end
677         return value
678 end
679
680 -- tolerate-pipelining ---------------------------------------------------------
681 local tp        = ns:taboption("misc", Flag, "tolerate_pipelining")
682 tp.title        = string.format(HELP, "TOLERATE-PIPELINING", "Tolerate pipelining" )
683 tp.description  = translate("Whether or not pipelined requests should be served.")
684 tp.orientation  = "horizontal"
685 tp.rmempty      = true
686
687 -- default-server-timeout ------------------------------------------------------
688 local dst       = ns:taboption("misc", Value, "default_server_timeout")
689 dst.title_base  = "Default server timeout"
690 dst.title       = string.format(HELP, "DEFAULT-SERVER-TIMEOUT", dst.title_base)
691 dst.description = translate("Assumed server-side keep-alive timeout (in seconds) if not specified by the server.")
692 dst.rmempty     = true
693 function dst.validate(self, value)
694         local v = tonumber(value)
695         if not v then
696                 return nil, err_tab_misc(self.title_base, translate("Value is not a number") )
697         elseif v < 1 then
698                 return nil, err_tab_misc(self.title_base, translate("Value not greater 0 or empty") )
699         end
700         return value
701 end
702
703 -- connection-sharing ----------------------------------------------------------
704 local cs        = ns:taboption("misc", Flag, "connection_sharing")
705 cs.title        = string.format(HELP, "CONNECTION-SHARING", "Connection sharing" )
706 cs.description  = translate("Whether or not outgoing connections that have been kept alive should be shared between different incoming connections.")
707 cs.orientation  = "horizontal"
708 cs.rmempty      = true
709
710 -- socket-timeout --------------------------------------------------------------
711 local st        = ns:taboption("misc", Value, "socket_timeout")
712 st.title_base   = "Socket timeout"
713 st.title        = string.format(HELP, "SOCKET-TIMEOUT", st.title_base )
714 st.description  = translate("Number of seconds after which a socket times out if no data is received.")
715 st.default      = 300
716 st.rmempty      = true
717 function st.validate(self, value)
718         local v = tonumber(value)
719         if not v then
720                 return nil, err_tab_misc(self.title_base, translate("Value is not a number") )
721         elseif v < 1 then
722                 return nil, err_tab_misc(self.title_base, translate("Value not greater 0 or empty") )
723         elseif v == self.default then
724                 return ""       -- dont need to save default
725         end
726         return value
727 end
728
729 -- max-client-connections ------------------------------------------------------
730 local mcc       = ns:taboption("misc", Value, "max_client_connections")
731 mcc.title_base  = "Max. client connections"
732 mcc.title       = string.format(HELP, "MAX-CLIENT-CONNECTIONS", mcc.title_base )
733 mcc.description = translate("Maximum number of client connections that will be served.")
734 mcc.default     = 128
735 mcc.rmempty     = true
736 function mcc.validate(self, value)
737         local v = tonumber(value)
738         if not v then
739                 return nil, err_tab_misc(self.title_base, translate("Value is not a number") )
740         elseif v < 1 then
741                 return nil, err_tab_misc(self.title_base, translate("Value not greater 0 or empty") )
742         elseif v == self.default then
743                 return ""       -- dont need to save default
744         end
745         return value
746 end
747
748 -- handle-as-empty-doc-returns-ok ----------------------------------------------
749 local her       = ns:taboption("misc", Flag, "handle_as_empty_doc_returns_ok")
750 her.title       = string.format(HELP, "HANDLE-AS-EMPTY-DOC-RETURNS-OK", "Handle as empty doc returns ok" )
751 her.description = translate("The status code Privoxy returns for pages blocked with +handle-as-empty-document.")
752 her.orientation = "horizontal"
753 her.rmempty     = true
754
755 -- enable-compression ----------------------------------------------------------
756 local ec        = ns:taboption("misc", Flag, "enable_compression")
757 ec.title        = string.format(HELP, "ENABLE-COMPRESSION", "Enable compression" )
758 ec.description  = translate("Whether or not buffered content is compressed before delivery.")
759 ec.orientation  = "horizontal"
760 ec.rmempty      = true
761
762 -- compression-level -----------------------------------------------------------
763 local cl        = ns:taboption("misc", Value, "compression_level")
764 cl.title_base   = "Compression level"
765 cl.title        = string.format(HELP, "COMPRESSION-LEVEL", cl.title_base )
766 cl.description  = translate("The compression level that is passed to the zlib library when compressing buffered content.")
767 cl.default      = 1
768 cl.rmempty      = true
769 function cl.validate(self, value)
770         local v = tonumber(value)
771         if not v then
772                 return nil, err_tab_misc(self.title_base, translate("Value is not a number") )
773         elseif v < 0 or v > 9 then
774                 return nil, err_tab_misc(self.title_base, translate("Value not between 0 and 9") )
775         elseif v == self.default then
776                 return ""       -- don't need to save default
777         end
778         return value
779 end
780
781 -- client-header-order ---------------------------------------------------------
782 local cho       = ns:taboption("misc", Value, "client_header_order")
783 cho.title       = string.format(HELP, "CLIENT-HEADER-ORDER", "Client header order" )
784 cho.description = translate("The order in which client headers are sorted before forwarding them.")
785                 .. [[<br />]]
786                 .. translate("Syntax: Client header names delimited by spaces.")
787 cho.rmempty     = true
788
789 -- "debug"-tab definition -- ###################################################
790
791 -- single-threaded -------------------------------------------------------------
792 local st        = ns:taboption("debug", Flag, "single_threaded")
793 st.title        = string.format(HELP, "SINGLE-THREADED", "Single Threaded" )
794 st.description  = translate("Whether to run only one server thread.")
795                 .. [[<br /><strong>]]
796                 .. translate("This option is only there for debugging purposes. It will drastically reduce performance.")
797                 .. [[</strong>]]
798 st.rmempty      = true
799
800 -- debug 1 ---------------------------------------------------------------------
801 local d0        = ns:taboption("debug", Flag, "debug_1")
802 d0.title        = string.format(HELP, "DEBUG", "Debug 1" )
803 d0.description  = translate("Log the destination for each request Privoxy let through. See also 'Debug 1024'.")
804 d0.rmempty      = true
805
806 -- debug 2 ---------------------------------------------------------------------
807 local d1        = ns:taboption("debug", Flag, "debug_2")
808 d1.title        = string.format(HELP, "DEBUG", "Debug 2" )
809 d1.description  = translate("Show each connection status")
810 d1.rmempty      = true
811
812 -- debug 4 ---------------------------------------------------------------------
813 local d2        = ns:taboption("debug", Flag, "debug_4")
814 d2.title        = string.format(HELP, "DEBUG", "Debug 4" )
815 d2.description  = translate("Show I/O status")
816 d2.rmempty      = true
817
818 -- debug 8 ---------------------------------------------------------------------
819 local d3        = ns:taboption("debug", Flag, "debug_8")
820 d3.title        = string.format(HELP, "DEBUG", "Debug 8" )
821 d3.description  = translate("Show header parsing")
822 d3.rmempty      = true
823
824 -- debug 16 --------------------------------------------------------------------
825 local d4        = ns:taboption("debug", Flag, "debug_16")
826 d4.title        = string.format(HELP, "DEBUG", "Debug 16" )
827 d4.description  = translate("Log all data written to the network")
828 d4.rmempty      = true
829
830 -- debug 32 --------------------------------------------------------------------
831 local d5        = ns:taboption("debug", Flag, "debug_32")
832 d5.title        = string.format(HELP, "DEBUG", "Debug 32" )
833 d5.description  = translate("Debug force feature")
834 d5.rmempty      = true
835
836 -- debug 64 --------------------------------------------------------------------
837 local d6        = ns:taboption("debug", Flag, "debug_64")
838 d6.title        = string.format(HELP, "DEBUG", "Debug 64" )
839 d6.description  = translate("Debug regular expression filters")
840 d6.rmempty      = true
841
842 -- debug 128 -------------------------------------------------------------------
843 local d7        = ns:taboption("debug", Flag, "debug_128")
844 d7.title        = string.format(HELP, "DEBUG", "Debug 128" )
845 d7.description  = translate("Debug redirects")
846 d7.rmempty      = true
847
848 -- debug 256 -------------------------------------------------------------------
849 local d8        = ns:taboption("debug", Flag, "debug_256")
850 d8.title        = string.format(HELP, "DEBUG", "Debug 256" )
851 d8.description  = translate("Debug GIF de-animation")
852 d8.rmempty      = true
853
854 -- debug 512 -------------------------------------------------------------------
855 local d9        = ns:taboption("debug", Flag, "debug_512")
856 d9.title        = string.format(HELP, "DEBUG", "Debug 512" )
857 d9.description  = translate("Common Log Format")
858 d9.rmempty      = true
859
860 -- debug 1024 ------------------------------------------------------------------
861 local d10       = ns:taboption("debug", Flag, "debug_1024")
862 d10.title       = string.format(HELP, "DEBUG", "Debug 1024" )
863 d10.description = translate("Log the destination for requests Privoxy didn't let through, and the reason why.")
864 d10.rmempty     = true
865
866 -- debug 2048 ------------------------------------------------------------------
867 local d11       = ns:taboption("debug", Flag, "debug_2048")
868 d11.title       = string.format(HELP, "DEBUG", "Debug 2048" )
869 d11.description = translate("CGI user interface")
870 d11.rmempty     = true
871
872 -- debug 4096 ------------------------------------------------------------------
873 local d12       = ns:taboption("debug", Flag, "debug_4096")
874 d12.title       = string.format(HELP, "DEBUG", "Debug 4096" )
875 d12.description = translate("Startup banner and warnings.")
876 d12.rmempty     = true
877
878 -- debug 8192 ------------------------------------------------------------------
879 local d13       = ns:taboption("debug", Flag, "debug_8192")
880 d13.title       = string.format(HELP, "DEBUG", "Debug 8192" )
881 d13.description = translate("Non-fatal errors - *we highly recommended enabling this*")
882 d13.rmempty     = true
883
884 -- debug 16384 -----------------------------------------------------------------
885 --[[ TODO ???
886 local d14       = ns:taboption("debug", Flag, "debug_16384")
887 d14.title       = string.format(HELP, "DEBUG", "Debug 16384" )
888 d14.description = translate("")
889 d14.rmempty     = true
890 ]]--
891
892 -- debug 32768 -----------------------------------------------------------------
893 local d15       = ns:taboption("debug", Flag, "debug_32768")
894 d15.title       = string.format(HELP, "DEBUG", "Debug 32768" )
895 d15.description = translate("Log all data read from the network")
896 d15.rmempty     = true
897
898 -- debug 65536 -----------------------------------------------------------------
899 local d16       = ns:taboption("debug", Flag, "debug_65536")
900 d16.title       = string.format(HELP, "DEBUG", "Debug 65536" )
901 d16.description = translate("Log the applying actions")
902 d16.rmempty     = true
903
904 -- tab: logview -- #############################################################
905
906 local lv        = ns:taboption("logview", DummyValue, "_logview")
907 lv.template     = "privoxy/detail_logview"
908 lv.inputtitle   = translate("Read / Reread log file")
909 lv.rows         = 50
910 function lv.cfgvalue(self, section)
911         local lfile=self.map:get(ns.section, "logdir") .. "/" .. self.map:get(ns.section, "logfile")
912         if NXFS.access(lfile) then
913                 return lfile .. "\n" .. translate("Please press [Read] button")
914         end
915         return lfile .. "\n" .. translate("File not found or empty")
916 end
917
918 return m