From: Jo-Philipp Wich Date: Fri, 15 Jan 2016 14:20:41 +0000 (+0100) Subject: Merge pull request #563 from cshore/pull-request-app-uhttpd X-Git-Url: https://git.archive.openwrt.org/?p=project%2Fluci.git;a=commitdiff_plain;h=ad064f0b039839dd333bfe8e9631cbc5218410b0;hp=1a7b0b22a85d8294d7a2c196b708266c872dd054 Merge pull request #563 from cshore/pull-request-app-uhttpd Pull request app uhttpd --- diff --git a/applications/luci-app-uhttpd/Makefile b/applications/luci-app-uhttpd/Makefile new file mode 100644 index 000000000..9a2cf462e --- /dev/null +++ b/applications/luci-app-uhttpd/Makefile @@ -0,0 +1,28 @@ +# +# Copyright (C) 2015 OpenWrt.org +# +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. +# + +include $(TOPDIR)/rules.mk + +LUCI_TITLE:=uHTTPd Webserver Configuration +LUCI_DEPENDS:=+uhttpd +LUCI_PKGARCH:=all + +PKG_NAME:=luci-app-uhttpd +PKG_VERSION:=1.0.0 +PKG_RELEASE:=1 +PKG_LICENSE:=Apache-2.0 +PKG_MAINTAINER:=Daniel Dickinson + +PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME) + +include $(INCLUDE_DIR)/package.mk + +LUA_TARGET:=source + +include ../../luci.mk + +# call BuildPackage - OpenWrt buildroot signature diff --git a/applications/luci-app-uhttpd/luasrc/controller/uhttpd/uhttpd.lua b/applications/luci-app-uhttpd/luasrc/controller/uhttpd/uhttpd.lua new file mode 100644 index 000000000..2e80dd63b --- /dev/null +++ b/applications/luci-app-uhttpd/luasrc/controller/uhttpd/uhttpd.lua @@ -0,0 +1,17 @@ +-- Copyright 2015 Daniel Dickinson +-- Licensed to the public under the Apache License 2.0. + +module("luci.controller.uhttpd.uhttpd", package.seeall) + +function index() + if not nixio.fs.access("/etc/config/uhttpd") then + return + end + + local page + + page = entry({"admin", "services", "uhttpd"}, cbi("uhttpd/uhttpd"), _("uHTTPd")) + page.leaf = true + +end + diff --git a/applications/luci-app-uhttpd/luasrc/model/cbi/uhttpd/uhttpd.lua b/applications/luci-app-uhttpd/luasrc/model/cbi/uhttpd/uhttpd.lua new file mode 100644 index 000000000..03821ad9b --- /dev/null +++ b/applications/luci-app-uhttpd/luasrc/model/cbi/uhttpd/uhttpd.lua @@ -0,0 +1,227 @@ +-- Copyright 2015 Daniel Dickinson +-- Licensed to the public under the Apache License 2.0. + +local fs = require("nixio.fs") + +local m = Map("uhttpd", translate("uHTTPd"), + translate("A lightweight single-threaded HTTP(S) server")) + +local ucs = m:section(TypedSection, "uhttpd", "") +ucs.addremove = true +ucs.anonymous = false + +local lhttp = nil +local lhttps = nil +local cert_file = nil +local key_file = nil + +ucs:tab("general", translate("General Settings")) +ucs:tab("server", translate("Full Web Server Settings"), translate("For settings primarily geared to serving more than the web UI")) +ucs:tab("advanced", translate("Advanced Settings"), translate("Settings which are either rarely needed or which affect serving the WebUI")) + +lhttp = ucs:taboption("general", DynamicList, "listen_http", translate("HTTP listeners (address:port)"), translate("Bind to specific interface:port (by specifying interface address")) +lhttp.datatype = "list(ipaddrport(1))" + +function lhttp.validate(self, value, section) + local have_https_listener = false + local have_http_listener = false + if lhttp and lhttp:formvalue(section) and (#(lhttp:formvalue(section)) > 0) then + for k, v in pairs(lhttp:formvalue(section)) do + if v and (v ~= "") then + have_http_listener = true + break + end + end + end + if lhttps and lhttps:formvalue(section) and (#(lhttps:formvalue(section)) > 0) then + for k, v in pairs(lhttps:formvalue(section)) do + if v and (v ~= "") then + have_https_listener = true + break + end + end + end + if not (have_http_listener or have_https_listener) then + return nil, "must listen on at list one address:port" + end + return DynamicList.validate(self, value, section) +end + +lhttps = ucs:taboption("general", DynamicList, "listen_https", translate("HTTPS listener (address:port)"), translate("Bind to specific interface:port (by specifying interface address")) +lhttps.datatype = "list(ipaddrport(1))" +lhttps:depends("cert") +lhttps:depends("key") + +function lhttps.validate(self, value, section) + local have_https_listener = false + local have_http_listener = false + if lhttps and lhttps:formvalue(section) and (#(lhttps:formvalue(section)) > 0) then + for k, v in pairs(lhttps:formvalue(section)) do + if v and (v ~= "") then + have_https_listener = true + break + end + end + if have_https_listener and ((not cert_file) or (not cert_file:formvalue(section)) or (cert_file:formvalue(section) == "")) then + return nil, "must have certificate when using https" + end + if have_https_listener and ((not key_file) or (not key_file:formvalue(section)) or (key_file:formvalue(section) == "")) then + return nil, "must have key when using https" + end + end + if lhttp and (lhttp:formvalue(section)) and (#lhttp:formvalue(section) > 0) then + for k, v in pairs(lhttp:formvalue(section)) do + if v and (v ~= "") then + have_http_listener = true + break + end + end + end + if not (have_http_listener or have_https_listener) then + return nil, "must listen on at list one address:port" + end + return DynamicList.validate(self, value, section) +end + +o = ucs:taboption("general", Flag, "redirect_https", translate("Redirect all HTTP to HTTPS")) +o.default = o.enabled +o.rmempty = false + +o = ucs:taboption("general", Flag, "rfc1918_filter", translate("Ignore private IPs on public interface"), translate("Prevent access from private (RFC1918) IPs on an interface if it has an public IP address")) +o.default = o.enabled +o.rmempty = false + +cert_file = ucs:taboption("general", FileUpload, "cert", translate("HTTPS Certificate (DER Encoded)")) + +key_file = ucs:taboption("general", FileUpload, "key", translate("HTTPS Private Key (DER Encoded)")) + +o = ucs:taboption("general", Button, "remove_old", translate("Remove old certificate and key"), + translate("uHTTPd will generate a new self-signed certificate using the configuration shown below.")) +o.inputstyle = "remove" + +function o.write(self, section) + if cert_file:cfgvalue(section) and fs.access(cert_file:cfgvalue(section)) then fs.unlink(cert_file:cfgvalue(section)) end + if key_file:cfgvalue(section) and fs.access(key_file:cfgvalue(section)) then fs.unlink(key_file:cfgvalue(section)) end + luci.sys.call("/etc/init.d/uhttpd restart") + luci.http.redirect(luci.dispatcher.build_url("admin", "services", "uhttpd")) +end + +o = ucs:taboption("general", Button, "remove_conf", translate("Remove configuration for certificate and key"), + translate("This permanently deletes the cert, key, and configuration to use same.")) +o.inputstyle = "remove" + +function o.write(self, section) + if cert_file:cfgvalue(section) and fs.access(cert_file:cfgvalue(section)) then fs.unlink(cert_file:cfgvalue(section)) end + if key_file:cfgvalue(section) and fs.access(key_file:cfgvalue(section)) then fs.unlink(key_file:cfgvalue(section)) end + self.map:del(section, "cert") + self.map:del(section, "key") + self.map:del(section, "listen_https") + luci.http.redirect(luci.dispatcher.build_url("admin", "services", "uhttpd")) +end + +o = ucs:taboption("server", DynamicList, "index_page", translate("Index page(s)"), translate("E.g specify with index.html and index.php when using PHP")) +o.optional = true +o.placeholder = "index.html" + +o = ucs:taboption("server", DynamicList, "interpreter", translate("CGI filetype handler"), translate("Interpreter to associate with file endings ('suffix=handler', e.g. '.php=/usr/bin/php-cgi')")) +o.optional = true + +o = ucs:taboption("server", Flag, "no_symlinks", translate("Do not follow symlinks outside document root")) +o.optional = true + +o = ucs:taboption("server", Flag, "no_dirlists", translate("Do not generate directory listings.")) +o.default = o.disabled + +o = ucs:taboption("server", DynamicList, "alias", translate("Aliases"), translate("(/old/path=/new/path) or (just /old/path which becomes /cgi-prefix/old/path)")) +o.optional = true + +o = ucs:taboption("server", Value, "realm", translate("Realm for Basic Auth")) +o.optional = true +o.placeholder = luci.sys.hostname() or "OpenWrt" + +local httpconfig = ucs:taboption("server", Value, "config", translate("Config file (e.g. for credentials for Basic Auth)"), translate("Will not use HTTP authentication if not present")) +httpconfig.optional = true + +o = ucs:taboption("server", Value, "error_page", translate("404 Error"), translate("Virtual URL or CGI script to display on status '404 Not Found'. Must begin with '/'")) +o.optional = true + +o = ucs:taboption("advanced", Value, "home", translate("Document root"), + translate("Base directory for files to be served")) +o.default = "/www" +o.datatype = "directory" + +o = ucs:taboption("advanced", Value, "cgi_prefix", translate("Path prefix for CGI scripts"), translate("CGI is disabled if not present.")) +o.optional = true + +o = ucs:taboption("advanced", Value, "lua_prefix", translate("Virtual path prefix for Lua scripts")) +o.placeholder = "/lua" +o.optional = true + +o = ucs:taboption("advanced", Value, "lua_handler", translate("Full real path to handler for Lua scripts"), translate("Embedded Lua interpreter is disabled if not present.")) +o.optional = true + +o = ucs:taboption("advanced", Value, "ubus_prefix", translate("Virtual path prefix for ubus via JSON-RPC integration"), translate("ubus integration is disabled if not present")) +o.optional = true + +o = ucs:taboption("advanced", Value, "ubus_socket", translate("Override path for ubus socket")) +o.optional = true + +o = ucs:taboption("advanced", Flag, "ubus_cors", translate("Enable JSON-RPC Cross-Origin Resource Support")) +o.default = o.disabled +o.optional = true + +o = ucs:taboption("advanced", Flag, "no_ubusauth", translate("Disable JSON-RPC authorization via ubus session API")) +o.optional= true +o.default = o.disabled + +o = ucs:taboption("advanced", Value, "script_timeout", translate("Maximum wait time for Lua, CGI, or ubus execution")) +o.placeholder = 60 +o.datatype = "uinteger" +o.optional = true + +o = ucs:taboption("advanced", Value, "network_timeout", translate("Maximum wait time for network activity")) +o.placeholder = 30 +o.datatype = "uinteger" +o.optional = true + +o = ucs:taboption("advanced", Value, "http_keepalive", translate("Connection reuse")) +o.placeholder = 20 +o.datatype = "uinteger" +o.optional = true + +o = ucs:taboption("advanced", Value, "tcp_keepalive", translate("TCP Keepalive")) +o.optional = true +o.datatype = "uinteger" +o.default = 1 + +o = ucs:taboption("advanced", Value, "max_connections", translate("Maximum number of connections")) +o.optional = true +o.datatype = "uinteger" + +o = ucs:taboption("advanced", Value, "max_requests", translate("Maximum number of script requests")) +o.optional = true +o.datatype = "uinteger" + +local s = m:section(NamedSection, "px5g", "cert", translate("uHTTPd Self-signed Certificate Parameters")) + +o = s:option(Value, "days", translate("Valid for # of Days")) +o.default = 730 +o.datatype = "uinteger" + +o = s:option(Value, "bits", translate("Length of key in bits")) +o.default = 1024 +o.datatype = "min(1024)" + +o = s:option(Value, "commonname", translate("Server Hostname"), translate("a.k.a CommonName")) +o.default = luci.sys.hostname() + +o = s:option(Value, "country", translate("Country")) +o.default = "ZZ" + +o = s:option(Value, "state", translate("State")) +o.default = "Unknown" + +o = s:option(Value, "location", translate("Location")) +o.default = "Somewhere" + +return m diff --git a/libs/luci-lib-nixio/src/file.c b/libs/luci-lib-nixio/src/file.c index b86e040e1..cfa35dfd1 100644 --- a/libs/luci-lib-nixio/src/file.c +++ b/libs/luci-lib-nixio/src/file.c @@ -79,6 +79,38 @@ static int nixio_open(lua_State *L) { return 1; } +static int nixio_mkstemp(lua_State *L) { + const char *intemplate = luaL_checklstring(L, 1, NULL); + size_t len = lua_strlen(L, 1); + char *template = (char *)lua_newuserdata(L, 13 + len); + if (!template) { + return luaL_error(L, "out of memory"); + } + snprintf(template, 13 + len, "/tmp/%s.XXXXXX", intemplate); + + int fd; + + do { + fd = mkstemp(template); + } while (fd == -1 && errno == EINTR); + if (fd == -1) { + return nixio__perror(L); + } + unlink(template); + + int *udata = lua_newuserdata(L, sizeof(int)); + if (!udata) { + return luaL_error(L, "out of memory"); + } + + *udata = fd; + + luaL_getmetatable(L, NIXIO_FILE_META); + lua_setmetatable(L, -2); + + return 1; +} + static int nixio_open_flags(lua_State *L) { int mode = 0; const int j = lua_gettop(L); @@ -366,6 +398,7 @@ static const luaL_reg R[] = { {"dup", nixio_dup}, {"open", nixio_open}, {"open_flags", nixio_open_flags}, + {"mkstemp", nixio_mkstemp}, {"pipe", nixio_pipe}, {NULL, NULL} }; diff --git a/modules/luci-base/luasrc/http.lua b/modules/luci-base/luasrc/http.lua index 4b3573172..8795dfc4b 100644 --- a/modules/luci-base/luasrc/http.lua +++ b/modules/luci-base/luasrc/http.lua @@ -89,6 +89,37 @@ end function Request.setfilehandler(self, callback) self.filehandler = callback + + -- If input has already been parsed then any files are either in temporary files + -- or are in self.message.params[key] + if self.parsed_input then + for param, value in pairs(self.message.params) do + repeat + -- We're only interested in files + if (not value["file"]) then break end + -- If we were able to write to temporary file + if (value["fd"]) then + fd = value["fd"] + local eof = false + repeat + filedata = fd:read(1024) + if (filedata:len() < 1024) then + eof = true + end + callback({ name=value["name"], file=value["file"] }, filedata, eof) + until (eof) + fd:close() + value["fd"] = nil + -- We had to read into memory + else + -- There should only be one numbered value in table - the data + for k, v in ipairs(value) do + callback({ name=value["name"], file=value["file"] }, v, true) + end + end + until true + end + end end function Request._parse_input(self) diff --git a/modules/luci-base/luasrc/http/protocol.lua b/modules/luci-base/luasrc/http/protocol.lua index 0cb62aeec..061c6ad54 100644 --- a/modules/luci-base/luasrc/http/protocol.lua +++ b/modules/luci-base/luasrc/http/protocol.lua @@ -114,6 +114,16 @@ local function __initval( tbl, key ) end -- (Internal function) +-- Initialize given file parameter. +local function __initfileval( tbl, key, filename, fd ) + if tbl[key] == nil then + tbl[key] = { file=filename, fd=fd, name=key, "" } + else + table.insert( tbl[key], "" ) + end +end + +-- (Internal function) -- Append given data to given parameter, either by extending the string value -- or by appending it to the last string in the parameter's value table. local function __appendval( tbl, key, chunk ) @@ -313,6 +323,22 @@ function mimedecode_message_body( src, msg, filecb ) __appendval( msg.params, field.name, field.file ) store = filecb + elseif field.name and field.file then + local nxf = require "nixio" + local fd = nxf.mkstemp(field.name) + __initfileval ( msg.params, field.name, field.file, fd ) + if fd then + store = function(hdr, buf, eof) + fd:write(buf) + if (eof) then + fd:seek(0, "set") + end + end + else + store = function( hdr, buf, eof ) + __appendval( msg.params, field.name, buf ) + end + end elseif field.name then __initval( msg.params, field.name )