Add web interface module
This commit is contained in:
parent
0dd2ecdb0b
commit
ab9e9b2d7c
6
config
6
config
@ -116,6 +116,12 @@
|
||||
#hook =
|
||||
|
||||
|
||||
[web]
|
||||
|
||||
# Web interface backend
|
||||
#type = none
|
||||
|
||||
|
||||
[logging]
|
||||
|
||||
# Logging configuration file
|
||||
|
@ -50,7 +50,7 @@ from xml.etree import ElementTree as ET
|
||||
|
||||
import vobject
|
||||
|
||||
from . import auth, rights, storage, xmlutils
|
||||
from . import auth, rights, storage, web, xmlutils
|
||||
|
||||
VERSION = "2.0.0"
|
||||
|
||||
@ -211,6 +211,7 @@ class Application:
|
||||
self.Auth = auth.load(configuration, logger)
|
||||
self.Collection = storage.load(configuration, logger)
|
||||
self.authorized = rights.load(configuration, logger)
|
||||
self.web = web.load(configuration, logger)
|
||||
self.encoding = configuration.get("encoding", "request")
|
||||
|
||||
def headers_log(self, environ):
|
||||
@ -552,9 +553,18 @@ class Application:
|
||||
|
||||
def do_GET(self, environ, base_prefix, path, user):
|
||||
"""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("/"):
|
||||
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"):
|
||||
return NOT_ALLOWED
|
||||
with self.Collection.acquire_lock("r", user):
|
||||
|
@ -147,6 +147,11 @@ INITIAL_CONFIG = OrderedDict([
|
||||
"value": "",
|
||||
"help": "command that is run after changes to storage",
|
||||
"type": str})])),
|
||||
("web", OrderedDict([
|
||||
("type", {
|
||||
"value": "none",
|
||||
"help": "web interface backend",
|
||||
"type": str})])),
|
||||
("logging", OrderedDict([
|
||||
("config", {
|
||||
"value": "",
|
||||
|
@ -38,8 +38,8 @@ class BaseRequestsMixIn:
|
||||
def test_root(self):
|
||||
"""GET request at "/"."""
|
||||
status, headers, answer = self.request("GET", "/")
|
||||
assert status == 200
|
||||
assert "Radicale works!" in answer
|
||||
assert status == 303
|
||||
assert answer == "Redirected to .web"
|
||||
# Test the creation of the collection
|
||||
self.request("MKCOL", "/calendar.ics/")
|
||||
self.request(
|
||||
@ -48,6 +48,17 @@ class BaseRequestsMixIn:
|
||||
assert "BEGIN: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):
|
||||
"""Add an event."""
|
||||
self.request("MKCOL", "/calendar.ics/")
|
||||
@ -168,7 +179,7 @@ class BaseRequestsMixIn:
|
||||
|
||||
def test_head(self):
|
||||
status, headers, answer = self.request("HEAD", "/")
|
||||
assert status == 200
|
||||
assert status == 303
|
||||
|
||||
def test_options(self):
|
||||
status, headers, answer = self.request("OPTIONS", "/")
|
||||
@ -815,7 +826,7 @@ class BaseRequestsMixIn:
|
||||
"storage", "hook", "mkdir %s" % os.path.join(
|
||||
"collection-root", "created_by_hook"))
|
||||
status, headers, answer = self.request("GET", "/")
|
||||
assert status == 200
|
||||
assert status == 303
|
||||
status, headers, answer = self.request("GET", "/created_by_hook/")
|
||||
assert status == 404
|
||||
|
||||
@ -834,7 +845,7 @@ class BaseRequestsMixIn:
|
||||
"storage", "hook", "mkdir %s" % os.path.join(
|
||||
"collection-root", "created_by_hook"))
|
||||
status, headers, answer = self.request("GET", "/", REMOTE_USER="user")
|
||||
assert status == 200
|
||||
assert status == 303
|
||||
status, headers, answer = self.request("PROPFIND", "/created_by_hook/")
|
||||
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