Add web interface module
This commit is contained in:
parent
0dd2ecdb0b
commit
ab9e9b2d7c
6
config
6
config
@ -116,6 +116,12 @@
|
|||||||
#hook =
|
#hook =
|
||||||
|
|
||||||
|
|
||||||
|
[web]
|
||||||
|
|
||||||
|
# Web interface backend
|
||||||
|
#type = none
|
||||||
|
|
||||||
|
|
||||||
[logging]
|
[logging]
|
||||||
|
|
||||||
# Logging configuration file
|
# Logging configuration file
|
||||||
|
@ -50,7 +50,7 @@ from xml.etree import ElementTree as ET
|
|||||||
|
|
||||||
import vobject
|
import vobject
|
||||||
|
|
||||||
from . import auth, rights, storage, xmlutils
|
from . import auth, rights, storage, web, xmlutils
|
||||||
|
|
||||||
VERSION = "2.0.0"
|
VERSION = "2.0.0"
|
||||||
|
|
||||||
@ -211,6 +211,7 @@ class Application:
|
|||||||
self.Auth = auth.load(configuration, logger)
|
self.Auth = auth.load(configuration, logger)
|
||||||
self.Collection = storage.load(configuration, logger)
|
self.Collection = storage.load(configuration, logger)
|
||||||
self.authorized = rights.load(configuration, logger)
|
self.authorized = rights.load(configuration, logger)
|
||||||
|
self.web = web.load(configuration, logger)
|
||||||
self.encoding = configuration.get("encoding", "request")
|
self.encoding = configuration.get("encoding", "request")
|
||||||
|
|
||||||
def headers_log(self, environ):
|
def headers_log(self, environ):
|
||||||
@ -552,9 +553,18 @@ class Application:
|
|||||||
|
|
||||||
def do_GET(self, environ, base_prefix, path, user):
|
def do_GET(self, environ, base_prefix, path, user):
|
||||||
"""Manage GET request."""
|
"""Manage GET request."""
|
||||||
# Display a "Radicale works!" message if the root URL is requested
|
# Redirect to .web if the root URL is requested
|
||||||
if not path.strip("/"):
|
if not path.strip("/"):
|
||||||
return client.OK, {"Content-Type": "text/plain"}, "Radicale works!"
|
web_path = ".web"
|
||||||
|
if not path.endswith("/"):
|
||||||
|
web_path = posixpath.join(posixpath.basename(base_prefix),
|
||||||
|
web_path)
|
||||||
|
return (client.SEE_OTHER,
|
||||||
|
{"Location": web_path, "Content-Type": "text/plain"},
|
||||||
|
"Redirected to %s" % web_path)
|
||||||
|
# Dispatch .web URL to web module
|
||||||
|
if path == "/.web" or path.startswith("/.web/"):
|
||||||
|
return self.web.get(environ, base_prefix, path, user)
|
||||||
if not self._access(user, path, "r"):
|
if not self._access(user, path, "r"):
|
||||||
return NOT_ALLOWED
|
return NOT_ALLOWED
|
||||||
with self.Collection.acquire_lock("r", user):
|
with self.Collection.acquire_lock("r", user):
|
||||||
|
@ -147,6 +147,11 @@ INITIAL_CONFIG = OrderedDict([
|
|||||||
"value": "",
|
"value": "",
|
||||||
"help": "command that is run after changes to storage",
|
"help": "command that is run after changes to storage",
|
||||||
"type": str})])),
|
"type": str})])),
|
||||||
|
("web", OrderedDict([
|
||||||
|
("type", {
|
||||||
|
"value": "none",
|
||||||
|
"help": "web interface backend",
|
||||||
|
"type": str})])),
|
||||||
("logging", OrderedDict([
|
("logging", OrderedDict([
|
||||||
("config", {
|
("config", {
|
||||||
"value": "",
|
"value": "",
|
||||||
|
@ -38,8 +38,8 @@ class BaseRequestsMixIn:
|
|||||||
def test_root(self):
|
def test_root(self):
|
||||||
"""GET request at "/"."""
|
"""GET request at "/"."""
|
||||||
status, headers, answer = self.request("GET", "/")
|
status, headers, answer = self.request("GET", "/")
|
||||||
assert status == 200
|
assert status == 303
|
||||||
assert "Radicale works!" in answer
|
assert answer == "Redirected to .web"
|
||||||
# Test the creation of the collection
|
# Test the creation of the collection
|
||||||
self.request("MKCOL", "/calendar.ics/")
|
self.request("MKCOL", "/calendar.ics/")
|
||||||
self.request(
|
self.request(
|
||||||
@ -48,6 +48,17 @@ class BaseRequestsMixIn:
|
|||||||
assert "BEGIN:VCALENDAR" in answer
|
assert "BEGIN:VCALENDAR" in answer
|
||||||
assert "END:VCALENDAR" in answer
|
assert "END:VCALENDAR" in answer
|
||||||
|
|
||||||
|
def test_script_name(self):
|
||||||
|
"""GET request at "/" with SCRIPT_NAME."""
|
||||||
|
status, headers, answer = self.request(
|
||||||
|
"GET", "/", SCRIPT_NAME="/radicale")
|
||||||
|
assert status == 303
|
||||||
|
assert answer == "Redirected to .web"
|
||||||
|
status, headers, answer = self.request(
|
||||||
|
"GET", "", SCRIPT_NAME="/radicale")
|
||||||
|
assert status == 303
|
||||||
|
assert answer == "Redirected to radicale/.web"
|
||||||
|
|
||||||
def test_add_event(self):
|
def test_add_event(self):
|
||||||
"""Add an event."""
|
"""Add an event."""
|
||||||
self.request("MKCOL", "/calendar.ics/")
|
self.request("MKCOL", "/calendar.ics/")
|
||||||
@ -168,7 +179,7 @@ class BaseRequestsMixIn:
|
|||||||
|
|
||||||
def test_head(self):
|
def test_head(self):
|
||||||
status, headers, answer = self.request("HEAD", "/")
|
status, headers, answer = self.request("HEAD", "/")
|
||||||
assert status == 200
|
assert status == 303
|
||||||
|
|
||||||
def test_options(self):
|
def test_options(self):
|
||||||
status, headers, answer = self.request("OPTIONS", "/")
|
status, headers, answer = self.request("OPTIONS", "/")
|
||||||
@ -815,7 +826,7 @@ class BaseRequestsMixIn:
|
|||||||
"storage", "hook", "mkdir %s" % os.path.join(
|
"storage", "hook", "mkdir %s" % os.path.join(
|
||||||
"collection-root", "created_by_hook"))
|
"collection-root", "created_by_hook"))
|
||||||
status, headers, answer = self.request("GET", "/")
|
status, headers, answer = self.request("GET", "/")
|
||||||
assert status == 200
|
assert status == 303
|
||||||
status, headers, answer = self.request("GET", "/created_by_hook/")
|
status, headers, answer = self.request("GET", "/created_by_hook/")
|
||||||
assert status == 404
|
assert status == 404
|
||||||
|
|
||||||
@ -834,7 +845,7 @@ class BaseRequestsMixIn:
|
|||||||
"storage", "hook", "mkdir %s" % os.path.join(
|
"storage", "hook", "mkdir %s" % os.path.join(
|
||||||
"collection-root", "created_by_hook"))
|
"collection-root", "created_by_hook"))
|
||||||
status, headers, answer = self.request("GET", "/", REMOTE_USER="user")
|
status, headers, answer = self.request("GET", "/", REMOTE_USER="user")
|
||||||
assert status == 200
|
assert status == 303
|
||||||
status, headers, answer = self.request("PROPFIND", "/created_by_hook/")
|
status, headers, answer = self.request("PROPFIND", "/created_by_hook/")
|
||||||
assert status == 207
|
assert status == 207
|
||||||
|
|
||||||
|
66
radicale/web.py
Normal file
66
radicale/web.py
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
# This file is part of Radicale Server - Calendar Server
|
||||||
|
# Copyright (C) 2017 Unrud <unrud@openaliasbox.org>
|
||||||
|
#
|
||||||
|
# This library is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This library is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from http import client
|
||||||
|
from importlib import import_module
|
||||||
|
|
||||||
|
NOT_FOUND = (
|
||||||
|
client.NOT_FOUND, (("Content-Type", "text/plain"),),
|
||||||
|
"The requested resource could not be found.")
|
||||||
|
|
||||||
|
MIMETYPES = {
|
||||||
|
".css": "text/css",
|
||||||
|
".eot": "application/vnd.ms-fontobject",
|
||||||
|
".gif": "image/gif",
|
||||||
|
".html": "text/html",
|
||||||
|
".js": "application/javascript",
|
||||||
|
".manifest": "text/cache-manifest",
|
||||||
|
".png": "image/png",
|
||||||
|
".svg": "image/svg+xml",
|
||||||
|
".ttf": "application/font-sfnt",
|
||||||
|
".txt": "text/plain",
|
||||||
|
".woff": "application/font-woff",
|
||||||
|
".woff2": "font/woff2",
|
||||||
|
".xml": "text/xml"}
|
||||||
|
FALLBACK_MIMETYPE = "application/octet-stream"
|
||||||
|
|
||||||
|
|
||||||
|
def load(configuration, logger):
|
||||||
|
"""Load the web module chosen in configuration."""
|
||||||
|
web_type = configuration.get("web", "type")
|
||||||
|
if web_type in ("None", "none"): # DEPRECATED: use "none"
|
||||||
|
web_class = NoneWeb
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
web_class = import_module(web_type).Web
|
||||||
|
except ImportError as e:
|
||||||
|
raise RuntimeError("Web module %r not found" %
|
||||||
|
web_type) from e
|
||||||
|
logger.info("Web type is %r", web_type)
|
||||||
|
return web_class(configuration, logger)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseWeb:
|
||||||
|
def __init__(self, configuration, logger):
|
||||||
|
self.configuration = configuration
|
||||||
|
self.logger = logger
|
||||||
|
|
||||||
|
|
||||||
|
class NoneWeb(BaseWeb):
|
||||||
|
def get(self, environ, base_prefix, path, user):
|
||||||
|
if path != "/.web":
|
||||||
|
return NOT_FOUND
|
||||||
|
return client.OK, {"Content-Type": "text/plain"}, "Radicale works!"
|
Loading…
Reference in New Issue
Block a user