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