1 -- Copyright 2015-2016 Christian Schoenebeck <christian dot schoenebeck at gmail dot com>
2 -- Licensed under the Apache License, Version 2.0
4 local NXFS = require("nixio.fs")
5 local DISP = require("luci.dispatcher")
6 local DTYP = require("luci.cbi.datatypes")
7 local HTTP = require("luci.http")
8 local UTIL = require("luci.util")
9 local UCI = require("luci.model.uci")
10 local SYS = require("luci.sys")
11 local WADM = require("luci.tools.webadmin")
12 local CTRL = require("luci.controller.radicale") -- this application's controller and multiused functions
14 -- #################################################################################################
15 -- Error handling if not installed or wrong version -- #########################
16 if not CTRL.service_ok() then
17 local f = SimpleForm("__sf")
18 f.title = CTRL.app_title_main()
19 f.description = CTRL.app_description()
24 local s = f:section(SimpleSection)
25 s.title = [[<font color="red">]] .. [[<strong>]]
26 .. translate("Software update required")
27 .. [[</strong>]] .. [[</font>]]
29 local v = s:option(DummyValue, "_dv")
31 v.value = CTRL.app_err_value
36 -- #################################################################################################
37 -- Error handling if no config, create an empty one -- #########################
38 if not NXFS.access("/etc/config/radicale") then
39 NXFS.writefile("/etc/config/radicale", "")
42 -- #################################################################################################
43 -- takeover arguments if any -- ################################################
44 -- then show/edit selected file
46 local argument = arg[1]
49 -- SimpleForm ------------------------------------------------
50 local ft = SimpleForm("_text")
51 ft.title = CTRL.app_title_back()
52 ft.description = CTRL.app_description()
53 ft.redirect = DISP.build_url("admin", "services", "radicale") .. "#cbi-radicale-" .. argument
54 if argument == "logger" then
56 ft.submit = translate("Reload")
57 local uci = UCI.cursor()
58 filename = uci:get("radicale", "logger", "file_path") or "/var/log/radicale"
59 uci:unload("radicale")
60 filename = filename .. "/radicale"
61 elseif argument == "auth" then
62 ft.submit = translate("Save")
63 filename = "/etc/radicale/users"
64 elseif argument == "rights" then
65 ft.submit = translate("Save")
66 filename = "/etc/radicale/rights"
68 error("Invalid argument given as section")
70 if argument ~= "logger" and not NXFS.access(filename) then
71 NXFS.writefile(filename, "")
74 -- SimpleSection ---------------------------------------------
75 local fs = ft:section(SimpleSection)
76 if argument == "logger" then
77 fs.title = translate("Log-file Viewer")
78 fs.description = translate("Please press [Reload] button below to reread the file.")
79 elseif argument == "auth" then
80 fs.title = translate("Authentication")
81 fs.description = translate("Place here the 'user:password' pairs for your users which should have access to Radicale.")
83 .. translate("Keep in mind to use the correct hashing algorithm !")
86 fs.title = translate("Rights")
87 fs.description = translate("Authentication login is matched against the 'user' key, "
88 .. "and collection's path is matched against the 'collection' key.") .. " "
89 .. translate("You can use Python's ConfigParser interpolation values %(login)s and %(path)s.") .. " "
90 .. translate("You can also get groups from the user regex in the collection with {0}, {1}, etc.")
92 .. translate("For example, for the 'user' key, '.+' means 'authenticated user'" .. " "
93 .. "and '.*' means 'anybody' (including anonymous users).")
95 .. translate("Section names are only used for naming the rule.")
97 .. translate("Leading or ending slashes are trimmed from collection's path.")
100 -- TextValue -------------------------------------------------
101 local tt = fs:option(TextValue, "_textvalue")
103 if argument == "logger" then
107 HTTP.redirect(DISP.build_url("admin", "services", "radicale", "edit", argument))
111 function tt.write(self, section, value)
112 if not value then value = "" end
113 NXFS.writefile(filename, value:gsub("\r\n", "\n"))
114 return true --HTTP.redirect(DISP.build_url("admin", "services", "radicale", "edit") .. "#cbi-radicale-" .. argument)
118 function tt.cfgvalue()
119 return NXFS.readfile(filename) or
120 string.format(translate("File '%s' not found !"), filename)
127 -- cbi-map -- ##################################################################
128 local m = Map("radicale")
129 m.title = CTRL.app_title_main()
130 m.description = CTRL.app_description()
131 m.template = "radicale/tabmap_nsections"
133 function m.commit_handler(self)
134 if self.changed then -- changes ?
135 os.execute("/etc/init.d/radicale reload &") -- reload configuration
139 -- cbi-section "System" -- #####################################################
140 local sys = m:section( NamedSection, "system", "system" )
141 sys.title = translate("System")
142 sys.description = nil
143 function sys.cfgvalue(self, section)
144 if not self.map:get(section) then -- section might not exist
145 self.map:set(section, nil, self.sectiontype)
147 return self.map:get(section)
150 -- start/stop button -----------------------------------------------------------
151 local btn = sys:option(DummyValue, "_startstop")
152 btn.template = "radicale/btn_startstop"
155 btn.title = translate("Start / Stop")
156 btn.description = translate("Start/Stop Radicale server")
157 function btn.cfgvalue(self, section)
158 local pid = CTRL.get_pid(true)
160 btn.inputtitle = "PID: " .. pid
161 btn.inputstyle = "reset"
164 btn.inputtitle = translate("Start")
165 btn.inputstyle = "apply"
171 -- enabled ---------------------------------------------------------------------
172 local ena = sys:option(Flag, "_enabled")
173 ena.title = translate("Auto-start")
174 ena.description = translate("Enable/Disable auto-start of Radicale on system start-up and interface events")
175 ena.orientation = "horizontal" -- put description under the checkbox
176 ena.rmempty = false -- force write() function
177 function ena.cfgvalue(self, section)
178 return (SYS.init.enabled("radicale")) and self.enabled or self.disabled
180 function ena.write(self, section, value)
181 if value == self.enabled then
182 return SYS.init.enable("radicale")
184 return SYS.init.disable("radicale")
188 -- boot_delay ------------------------------------------------------------------
189 local bd = sys:option(Value, "boot_delay")
190 bd.title = translate("Boot delay")
191 bd.description = translate("Delay (in seconds) during system boot before Radicale start")
193 .. translate("During delay ifup-events are not monitored !")
195 function bd.parse(self, section, novld)
196 CTRL.value_parse(self, section, novld)
198 function bd.validate(self, value)
199 local val = tonumber(value)
201 return nil, self.title .. ": " .. translate("Value is not a number")
202 elseif val < 0 or val > 300 then
203 return nil, self.title .. ": " .. translate("Value not between 0 and 300")
209 -- cbi-section "Server" -- #####################################################
210 local srv = m:section( NamedSection, "server", "setting" )
211 srv.title = translate("Server")
212 srv.description = nil
213 function srv.cfgvalue(self, section)
214 if not self.map:get(section) then -- section might not exist
215 self.map:set(section, nil, self.sectiontype)
217 return self.map:get(section)
220 -- hosts -----------------------------------------------------------------------
221 local sh = srv:option( DynamicList, "hosts" )
222 sh.title = translate("Address:Port")
223 sh.description = translate("'Hostname:Port' or 'IPv4:Port' or '[IPv6]:Port' Radicale should listen on")
224 .. [[<br /><strong>]]
225 .. translate("Port numbers below 1024 (Privileged ports) are not supported")
227 sh.placeholder = "0.0.0.0:5232"
229 function sh.parse(self, section, novld)
230 CTRL.value_parse(self, section, novld)
233 -- realm -----------------------------------------------------------------------
234 local alm = srv:option( Value, "realm" )
235 alm.title = translate("Logon message")
236 alm.description = translate("Message displayed in the client when a password is needed.")
237 alm.default = "Radicale - Password Required"
238 function alm.parse(self, section, novld)
239 CTRL.value_parse(self, section, novld)
241 function alm.validate(self, value)
249 -- ssl -------------------------------------------------------------------------
250 local ssl = srv:option( Flag, "ssl" )
251 ssl.title = translate("Enable HTTPS")
252 ssl.description = nil
253 function ssl.write(self, section, value)
254 if value == "0" then -- delete all if not https enabled
255 self.map:del(section, "protocol") -- protocol
256 self.map:del(section, "certificate") -- certificate
257 self.map:del(section, "key") -- private key
258 self.map:del(section, "ciphers") -- ciphers
259 return self.map:del(section, self.option)
261 return self.map:set(section, self.option, value)
265 -- protocol --------------------------------------------------------------------
266 local prt = srv:option( ListValue, "protocol" )
267 prt.title = translate("SSL Protocol")
268 prt.description = translate("'AUTO' selects the highest protocol version that client and server support.")
269 prt.widget = "select"
270 prt.default = "PROTOCOL_SSLv23"
271 prt:depends ("ssl", "1")
272 prt:value ("PROTOCOL_SSLv23", translate("AUTO"))
273 prt:value ("PROTOCOL_SSLv2", "SSL v2")
274 prt:value ("PROTOCOL_SSLv3", "SSL v3")
275 prt:value ("PROTOCOL_TLSv1", "TLS v1")
276 prt:value ("PROTOCOL_TLSv1_1", "TLS v1.1")
277 prt:value ("PROTOCOL_TLSv1_2", "TLS v1.2")
278 function prt.parse(self, section, novld)
279 CTRL.value_parse(self, section, novld)
282 -- certificate -----------------------------------------------------------------
283 local crt = srv:option( Value, "certificate" )
284 crt.title = translate("Certificate file")
285 crt.description = translate("Full path and file name of certificate")
286 crt.placeholder = "/etc/radicale/ssl/server.crt"
287 crt:depends ("ssl", "1")
288 function crt.parse(self, section, novld)
289 CTRL.value_parse(self, section, novld)
291 function crt.validate(self, value)
292 local _ssl = ssl:formvalue(srv.section) or "0"
294 return "" -- ignore if not https enabled
296 if value then -- otherwise errors in datatype check
297 if DTYP.file(value) then
300 return nil, self.title .. ": " .. translate("File not found !")
303 return nil, self.title .. ": " .. translate("Path/File required !")
307 -- key -------------------------------------------------------------------------
308 local key = srv:option( Value, "key" )
309 key.title = translate("Private key file")
310 key.description = translate("Full path and file name of private key")
311 key.placeholder = "/etc/radicale/ssl/server.key"
312 key:depends ("ssl", "1")
313 function key.parse(self, section, novld)
314 CTRL.value_parse(self, section, novld)
316 function key.validate(self, value)
317 local _ssl = ssl:formvalue(srv.section) or "0"
319 return "" -- ignore if not https enabled
321 if value then -- otherwise errors in datatype check
322 if DTYP.file(value) then
325 return nil, self.title .. ": " .. translate("File not found !")
328 return nil, self.title .. ": " .. translate("Path/File required !")
332 -- ciphers ---------------------------------------------------------------------
333 --local cip = srv:option( Value, "ciphers" )
334 --cip.title = translate("Ciphers")
335 --cip.description = translate("OPTIONAL: See python's ssl module for available ciphers")
337 --cip:depends ("ssl", "1")
339 -- cbi-section "Authentication" -- #############################################
340 local aut = m:section( NamedSection, "auth", "setting" )
341 aut.title = translate("Authentication")
342 aut.description = translate("Authentication method to allow access to Radicale server.")
343 function aut.cfgvalue(self, section)
344 if not self.map:get(section) then -- section might not exist
345 self.map:set(section, nil, self.sectiontype)
347 return self.map:get(section)
350 -- type -----------------------------------------------------------------------
351 local aty = aut:option( ListValue, "type" )
352 aty.title = translate("Authentication method")
353 aty.description = nil
354 aty.widget = "select"
356 aty:value ("None", translate("None"))
357 aty:value ("htpasswd", translate("htpasswd file"))
358 --aty:value ("IMAP", "IMAP") -- The IMAP authentication module relies on the imaplib module.
359 --aty:value ("LDAP", "LDAP") -- The LDAP authentication module relies on the python-ldap module.
360 --aty:value ("PAM", "PAM") -- The PAM authentication module relies on the python-pam module.
361 --aty:value ("courier", "courier")
362 --aty:value ("HTTP", "HTTP") -- The HTTP authentication module relies on the requests module
363 --aty:value ("remote_user", "remote_user")
364 --aty:value ("custom", translate("custom"))
365 function aty.parse(self, section, novld)
366 CTRL.value_parse(self, section, novld)
368 function aty.write(self, section, value)
369 if value ~= "htpasswd" then
370 self.map:del(section, "htpasswd_encryption")
371 elseif value ~= "IMAP" then
372 self.map:del(section, "imap_hostname")
373 self.map:del(section, "imap_port")
374 self.map:del(section, "imap_ssl")
376 if value ~= self.default then
377 return self.map:set(section, self.option, value)
379 return self.map:del(section, self.option)
383 -- htpasswd_encryption ---------------------------------------------------------
384 local hte = aut:option( ListValue, "htpasswd_encryption" )
385 hte.title = translate("Encryption method")
386 hte.description = nil
387 hte.widget = "select"
388 hte.default = "crypt"
389 hte:depends ("type", "htpasswd")
390 hte:value ("crypt", translate("crypt"))
391 hte:value ("plain", translate("plain"))
392 hte:value ("sha1", translate("SHA-1"))
393 hte:value ("ssha", translate("salted SHA-1"))
394 function hte.parse(self, section, novld)
395 CTRL.value_parse(self, section, novld)
398 -- htpasswd_file (dummy) -------------------------------------------------------
399 local htf = aut:option( Value, "_htf" )
400 htf.title = translate("htpasswd file")
401 htf.description = [[<strong>]]
402 .. translate("Read only!")
404 .. translate("Radicale uses '/etc/radicale/users' as htpasswd file.")
405 .. [[<br /><a href="]]
406 .. DISP.build_url("admin", "services", "radicale", "edit") .. [[/auth]]
408 .. translate("To edit the file follow this link!")
411 htf:depends ("type", "htpasswd")
412 function htf.cfgvalue()
413 return "/etc/radicale/users"
416 -- cbi-section "Rights" -- #####################################################
417 local rig = m:section( NamedSection, "rights", "setting" )
418 rig.title = translate("Rights")
419 rig.description = translate("Control the access to data collections.")
420 function rig.cfgvalue(self, section)
421 if not self.map:get(section) then -- section might not exist
422 self.map:set(section, nil, self.sectiontype)
424 return self.map:get(section)
427 -- type -----------------------------------------------------------------------
428 local rty = rig:option( ListValue, "type" )
429 rty.title = translate("Rights backend")
430 rty.description = nil
431 rty.widget = "select"
433 rty:value ("None", translate("Full access for everybody (including anonymous)"))
434 rty:value ("authenticated", translate("Full access for authenticated Users") )
435 rty:value ("owner_only", translate("Full access for Owner only") )
436 rty:value ("owner_write", translate("Owner allow write, authenticated users allow read") )
437 rty:value ("from_file", translate("Rights are based on a regexp-based file") )
438 --rty:value ("custom", "Custom handler")
439 function rty.parse(self, section, novld)
440 CTRL.value_parse(self, section, novld)
442 function rty.write(self, section, value)
443 if value ~= "custom" then
444 self.map:del(section, "custom_handler")
446 if value ~= self.default then
447 return self.map:set(section, self.option, value)
449 return self.map:del(section, self.option)
453 -- from_file (dummy) -----------------------------------------------------------
454 local rtf = rig:option( Value, "_rtf" )
455 rtf.title = translate("RegExp file")
456 rtf.description = [[<strong>]]
457 .. translate("Read only!")
459 .. translate("Radicale uses '/etc/radicale/rights' as regexp-based file.")
460 .. [[<br /><a href="]]
461 .. DISP.build_url("admin", "services", "radicale", "edit") .. [[/rights]]
463 .. translate("To edit the file follow this link!")
466 rtf:depends ("type", "from_file")
467 function rtf.cfgvalue()
468 return "/etc/radicale/rights"
471 -- cbi-section "Storage" -- ####################################################
472 local sto = m:section( NamedSection, "storage", "setting" )
473 sto.title = translate("Storage")
474 sto.description = nil
475 function sto.cfgvalue(self, section)
476 if not self.map:get(section) then -- section might not exist
477 self.map:set(section, nil, self.sectiontype)
479 return self.map:get(section)
482 -- type -----------------------------------------------------------------------
483 local sty = sto:option( ListValue, "type" )
484 sty.title = translate("Storage backend")
485 sty.description = translate("WARNING: Only 'File-system' is documented and tested by Radicale development")
486 sty.widget = "select"
487 sty.default = "filesystem"
488 sty:value ("filesystem", translate("File-system"))
489 --sty:value ("multifilesystem", translate("") )
490 --sty:value ("database", translate("Database") )
491 --sty:value ("custom", translate("Custom") )
492 function sty.parse(self, section, novld)
493 CTRL.value_parse(self, section, novld)
495 function sty.write(self, section, value)
496 if value ~= "filesystem" then
497 self.map:del(section, "filesystem_folder")
499 if value ~= self.default then
500 return self.map:set(section, self.option, value)
502 return self.map:del(section, self.option)
506 --filesystem_folder ------------------------------------------------------------
507 local sfi = sto:option( Value, "filesystem_folder" )
508 sfi.title = translate("Directory")
509 sfi.description = nil
510 sfi.placeholder = "/srv/radicale"
511 sfi:depends ("type", "filesystem")
512 function sfi.parse(self, section, novld)
513 CTRL.value_parse(self, section, novld)
515 function sfi.validate(self, value)
516 local _typ = sty:formvalue(sto.section) or ""
517 if _typ ~= "filesystem" then
518 return "" -- ignore if not htpasswd
520 if value then -- otherwise errors in datatype check
521 if DTYP.directory(value) then
524 return nil, self.title .. ": " .. translate("Directory not exists/found !")
527 return nil, self.title .. ": " .. translate("Directory required !")
531 -- cbi-section "Logging" -- ####################################################
532 local log = m:section( NamedSection, "logger", "logging" )
533 log.title = translate("Logging")
534 log.description = nil
535 function log.cfgvalue(self, section)
536 if not self.map:get(section) then -- section might not exist
537 self.map:set(section, nil, self.sectiontype)
539 return self.map:get(section)
542 -- console_level ---------------------------------------------------------------
543 local lco = log:option( ListValue, "console_level" )
544 lco.title = translate("Console Log level")
545 lco.description = nil
546 lco.widget = "select"
547 lco.default = "ERROR"
548 lco:value ("DEBUG", translate("Debug"))
549 lco:value ("INFO", translate("Info") )
550 lco:value ("WARNING", translate("Warning") )
551 lco:value ("ERROR", translate("Error") )
552 lco:value ("CRITICAL", translate("Critical") )
553 function lco.parse(self, section, novld)
554 CTRL.value_parse(self, section, novld)
556 function lco.write(self, section, value)
557 if value ~= self.default then
558 return self.map:set(section, self.option, value)
560 return self.map:del(section, self.option)
564 -- syslog_level ----------------------------------------------------------------
565 local lsl = log:option( ListValue, "syslog_level" )
566 lsl.title = translate("Syslog Log level")
567 lsl.description = nil
568 lsl.widget = "select"
569 lsl.default = "WARNING"
570 lsl:value ("DEBUG", translate("Debug"))
571 lsl:value ("INFO", translate("Info") )
572 lsl:value ("WARNING", translate("Warning") )
573 lsl:value ("ERROR", translate("Error") )
574 lsl:value ("CRITICAL", translate("Critical") )
575 function lsl.parse(self, section, novld)
576 CTRL.value_parse(self, section, novld)
578 function lsl.write(self, section, value)
579 if value ~= self.default then
580 return self.map:set(section, self.option, value)
582 return self.map:del(section, self.option)
586 -- file_level ------------------------------------------------------------------
587 local lfi = log:option( ListValue, "file_level" )
588 lfi.title = translate("File Log level")
589 lfi.description = nil
590 lfi.widget = "select"
592 lfi:value ("DEBUG", translate("Debug"))
593 lfi:value ("INFO", translate("Info") )
594 lfi:value ("WARNING", translate("Warning") )
595 lfi:value ("ERROR", translate("Error") )
596 lfi:value ("CRITICAL", translate("Critical") )
597 function lfi.parse(self, section, novld)
598 CTRL.value_parse(self, section, novld)
600 function lfi.write(self, section, value)
601 if value ~= self.default then
602 return self.map:set(section, self.option, value)
604 return self.map:del(section, self.option)
608 -- file_path -------------------------------------------------------------------
609 local lfp = log:option( Value, "file_path" )
610 lfp.title = translate("Log-file directory")
611 lfp.description = translate("Directory where the rotating log-files are stored")
612 .. [[<br /><a href="]]
613 .. DISP.build_url("admin", "services", "radicale", "edit") .. [[/logger]]
615 .. translate("To view latest log file follow this link!")
617 lfp.default = "/var/log/radicale"
618 function lfp.parse(self, section, novld)
619 CTRL.value_parse(self, section, novld)
621 function lfp.validate(self, value)
622 if not value or (#value < 1) or (value:find("/") ~= 1) then
623 return nil, self.title .. ": " .. translate("no valid path given!")
628 -- file_maxbytes ---------------------------------------------------------------
629 local lmb = log:option( Value, "file_maxbytes" )
630 lmb.title = translate("Log-file size")
631 lmb.description = translate("Maximum size of each rotation log-file.")
632 .. [[<br /><strong>]]
633 .. translate("Setting this parameter to '0' will disable rotation of log-file.")
636 function lmb.parse(self, section, novld)
637 CTRL.value_parse(self, section, novld)
639 function lmb.validate(self, value)
640 if value then -- otherwise errors in datatype check
641 if DTYP.uinteger(value) then
644 return nil, self.title .. ": " .. translate("Value is not an Integer >= 0 !")
647 return nil, self.title .. ": " .. translate("Value required ! Integer >= 0 !")
651 -- file_backupcount ------------------------------------------------------------
652 local lbc = log:option( Value, "file_backupcount" )
653 lbc.title = translate("Log-backup Count")
654 lbc.description = translate("Number of backup files of log to create.")
655 .. [[<br /><strong>]]
656 .. translate("Setting this parameter to '0' will disable rotation of log-file.")
659 function lbc.parse(self, section, novld)
660 CTRL.value_parse(self, section, novld)
662 function lbc.validate(self, value)
663 if value then -- otherwise errors in datatype check
664 if DTYP.uinteger(value) then
667 return nil, self.title .. ": " .. translate("Value is not an Integer >= 0 !")
670 return nil, self.title .. ": " .. translate("Value required ! Integer >= 0 !")
674 -- cbi-section "Encoding" -- ###################################################
675 local enc = m:section( NamedSection, "encoding", "setting" )
676 enc.title = translate("Encoding")
677 enc.description = translate("Change here the encoding Radicale will use instead of 'UTF-8' "
678 .. "for responses to the client and/or to store data inside collections.")
679 function enc.cfgvalue(self, section)
680 if not self.map:get(section) then -- section might not exist
681 self.map:set(section, nil, self.sectiontype)
683 return self.map:get(section)
686 -- request ---------------------------------------------------------------------
687 local enr = enc:option( Value, "request" )
688 enr.title = translate("Response Encoding")
689 enr.description = translate("Encoding for responding requests.")
690 enr.default = "utf-8"
691 function enr.parse(self, section, novld)
692 CTRL.value_parse(self, section, novld)
695 -- stock -----------------------------------------------------------------------
696 local ens = enc:option( Value, "stock" )
697 ens.title = translate("Storage Encoding")
698 ens.description = translate("Encoding for storing local collections.")
699 ens.default = "utf-8"
700 function ens.parse(self, section, novld)
701 CTRL.value_parse(self, section, novld)
704 -- cbi-section "Headers" -- ####################################################
705 local hea = m:section( NamedSection, "headers", "setting" )
706 hea.title = translate("Additional HTTP headers")
707 hea.description = translate("Cross-origin resource sharing (CORS) is a mechanism that allows restricted resources (e.g. fonts, JavaScript, etc.) "
708 .. "on a web page to be requested from another domain outside the domain from which the resource originated.")
709 function hea.cfgvalue(self, section)
710 if not self.map:get(section) then -- section might not exist
711 self.map:set(section, nil, self.sectiontype)
713 return self.map:get(section)
716 -- Access_Control_Allow_Origin -------------------------------------------------
717 local heo = hea:option( DynamicList, "Access_Control_Allow_Origin" )
718 heo.title = translate("Access-Control-Allow-Origin")
719 heo.description = nil
720 function heo.parse(self, section, novld)
721 CTRL.value_parse(self, section, novld)
724 -- Access_Control_Allow_Methods ------------------------------------------------
725 local hem = hea:option( DynamicList, "Access_Control_Allow_Methods" )
726 hem.title = translate("Access-Control-Allow-Methods")
727 hem.description = nil
728 function hem.parse(self, section, novld)
729 CTRL.value_parse(self, section, novld)
732 -- Access_Control_Allow_Headers ------------------------------------------------
733 local heh = hea:option( DynamicList, "Access_Control_Allow_Headers" )
734 heh.title = translate("Access-Control-Allow-Headers")
735 heh.description = nil
736 function heh.parse(self, section, novld)
737 CTRL.value_parse(self, section, novld)
740 -- Access_Control_Expose_Headers -----------------------------------------------
741 local hee = hea:option( DynamicList, "Access_Control_Expose_Headers" )
742 hee.title = translate("Access-Control-Expose-Headers")
743 hee.description = nil
744 function hee.parse(self, section, novld)
745 CTRL.value_parse(self, section, novld)