# Copyright (C) 2016-2017 by the Free Software Foundation, Inc.
#
# This file is part of GNU Mailman.
#
# GNU Mailman 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.
#
# GNU Mailman 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
# GNU Mailman. If not, see .
"""REST API for a mailing list's header matches."""
from mailman.interfaces.action import Action
from mailman.interfaces.mailinglist import IHeaderMatchList
from mailman.rest.helpers import (
CollectionMixin, bad_request, child, created, etag, no_content, not_found,
okay)
from mailman.rest.validator import Validator, enum_validator
from public import public
def lowercase(value):
return str(value).lower()
class _HeaderMatchBase:
"""Common base class."""
def __init__(self, mlist):
self._mlist = mlist
self.header_matches = IHeaderMatchList(self._mlist)
def _location(self, position):
return self.api.path_to('lists/{}/header-matches/{}'.format(
self._mlist.list_id, position))
def _resource_as_dict(self, header_match):
"""See `CollectionMixin`."""
resource = dict(
position=header_match.position,
header=header_match.header,
pattern=header_match.pattern,
self_link=self._location(header_match.position),
)
if header_match.chain is not None:
resource['action'] = header_match.chain
return resource
@public
class HeaderMatch(_HeaderMatchBase):
"""A header match."""
def __init__(self, mlist, position):
super().__init__(mlist)
self._position = position
def on_get(self, request, response):
"""Get a header match."""
try:
header_match = self.header_matches[self._position]
except IndexError:
not_found(response, 'No header match at this position: {}'.format(
self._position))
else:
okay(response, etag(self._resource_as_dict(header_match)))
def on_delete(self, request, response):
"""Remove a header match."""
try:
del self.header_matches[self._position]
except IndexError:
not_found(response, 'No header match at this position: {}'.format(
self._position))
else:
no_content(response)
def _patch_put(self, request, response, is_optional):
"""Update the header match."""
try:
header_match = self.header_matches[self._position]
except IndexError:
not_found(response, 'No header match at this position: {}'.format(
self._position))
return
kws = dict(
header=lowercase,
pattern=str,
position=int,
action=enum_validator(Action),
)
if is_optional:
# For a PATCH, all attributes are optional.
kws['_optional'] = kws.keys()
else:
# For a PUT, position can remain unchanged and action can be None.
kws['_optional'] = ('action', 'position')
validator = Validator(**kws)
try:
arguments = validator(request)
action = arguments.pop('action', None)
if action is not None:
arguments['chain'] = action.name
for key, value in arguments.items():
setattr(header_match, key, value)
except ValueError as error:
bad_request(response, str(error))
return
else:
no_content(response)
def on_put(self, request, response):
"""Full update of the header match."""
self._patch_put(request, response, is_optional=False)
def on_patch(self, request, response):
"""Partial update of the header match."""
self._patch_put(request, response, is_optional=True)
@public
class HeaderMatches(_HeaderMatchBase, CollectionMixin):
"""The list of all header matches."""
def _get_collection(self, request):
"""See `CollectionMixin`."""
return list(self.header_matches)
def on_get(self, request, response):
"""/header-matches"""
resource = self._make_collection(request)
okay(response, etag(resource))
def on_post(self, request, response):
"""Add a header match."""
validator = Validator(
header=str,
pattern=str,
action=enum_validator(Action),
_optional=('action',)
)
try:
arguments = validator(request)
except ValueError as error:
bad_request(response, str(error))
return
action = arguments.pop('action', None)
if action is not None:
arguments['chain'] = action.name
try:
self.header_matches.append(**arguments)
except ValueError:
bad_request(response, b'This header match already exists')
else:
header_match = self.header_matches[-1]
created(response, self._location(header_match.position))
def on_delete(self, request, response):
"""Delete all header matches for this mailing list."""
self.header_matches.clear()
no_content(response)
@child(r'^(?P\d+)')
def header_match(self, context, segments, **kw):
return HeaderMatch(self._mlist, int(kw['position']))