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