/* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements . See the NOTICE file distributed with
* this work for additional information regarding copyright ownership .
* The ASF licenses this file to You under the Apache License , Version 2 . 0
* ( the " License " ) ; you may not use this file except in compliance with
* the License . You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an " AS IS " BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
*/
/*
* * DAV repository - independent lock functions
*/
#include "apr.h"
#include "apr_strings.h"
#include "mod_dav.h"
#include "http_log.h"
#include "http_config.h"
#include "http_protocol.h"
#include "http_core.h"
APLOG_USE_MODULE(dav);
/* ---------------------------------------------------------------
* *
* * Property - related lock functions
* *
*/
/*
* * dav_lock_get_activelock : Returns a < lockdiscovery > containing
* * an activelock element for every item in the lock_discovery tree
*/
DAV_DECLARE(const char *) dav_lock_get_activelock(request_rec *r,
dav_lock *lock,
dav_buffer *pbuf)
{
dav_lock *lock_scan;
const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r);
int count = 0 ;
dav_buffer work_buf = { 0 };
apr_pool_t *p = r->pool;
/* If no locks or no lock provider, there are no locks */
if (lock == NULL || hooks == NULL) {
/*
* * Since resourcediscovery is defined with ( activelock ) * ,
* * < D : activelock / > shouldn ' t be necessary for an empty lock .
*/
return "" ;
}
/*
* * Note : it could be interesting to sum the lengths of the owners
* * and locktokens during this loop . However , the buffer
* * mechanism provides some rough padding so that we don ' t
* * really need to have an exact size . Further , constructing
* * locktoken strings could be relatively expensive .
*/
for (lock_scan = lock; lock_scan != NULL; lock_scan = lock_scan->next)
count++;
/* if a buffer was not provided, then use an internal buffer */
if (pbuf == NULL)
pbuf = &work_buf;
/* reset the length before we start appending stuff */
pbuf->cur_len = 0 ;
/* prep the buffer with a "good" size */
dav_check_bufsize(p, pbuf, count * 300 );
for (; lock != NULL; lock = lock->next) {
char tmp[100 ];
#if DAV_DEBUG
if (lock->rectype == DAV_LOCKREC_INDIRECT_PARTIAL) {
/* ### crap. design error */
dav_buffer_append(p, pbuf,
"DESIGN ERROR: attempted to product an "
"activelock element from a partial, indirect "
"lock record. Creating an XML parsing error "
"to ease detection of this situation: <" );
}
#endif
dav_buffer_append(p, pbuf, "<D:activelock>" DEBUG_CR "<D:locktype>" );
switch (lock->type) {
case DAV_LOCKTYPE_WRITE:
dav_buffer_append(p, pbuf, "<D:write/>" );
break ;
default :
/* ### internal error. log something? */
break ;
}
dav_buffer_append(p, pbuf, "</D:locktype>" DEBUG_CR "<D:lockscope>" );
switch (lock->scope) {
case DAV_LOCKSCOPE_EXCLUSIVE:
dav_buffer_append(p, pbuf, "<D:exclusive/>" );
break ;
case DAV_LOCKSCOPE_SHARED:
dav_buffer_append(p, pbuf, "<D:shared/>" );
break ;
default :
/* ### internal error. log something? */
break ;
}
dav_buffer_append(p, pbuf, "</D:lockscope>" DEBUG_CR);
apr_snprintf(tmp, sizeof (tmp), "<D:depth>%s</D:depth>" DEBUG_CR,
lock->depth == DAV_INFINITY ? "infinity" : "0" );
dav_buffer_append(p, pbuf, tmp);
if (lock->owner) {
/*
* * This contains a complete , self - contained < DAV : owner > element ,
* * with namespace declarations and xml : lang handling . Just drop
* * it in .
*/
dav_buffer_append(p, pbuf, lock->owner);
}
dav_buffer_append(p, pbuf, "<D:timeout>" );
if (lock->timeout == DAV_TIMEOUT_INFINITE) {
dav_buffer_append(p, pbuf, "Infinite" );
}
else {
time_t now = time(NULL);
/*
* * Check if the timeout is not , for any reason , already elapsed .
* * ( e . g . , because of a large collection , or disk under heavy load . . . )
*/
if (now >= lock->timeout) {
dav_buffer_append(p, pbuf, "Second-0" );
}
else {
apr_snprintf(tmp, sizeof (tmp), "Second-%lu" , (long unsigned int )(lock->timeout - now));
dav_buffer_append(p, pbuf, tmp);
}
}
dav_buffer_append(p, pbuf,
"</D:timeout>" DEBUG_CR
"<D:locktoken>" DEBUG_CR
"<D:href>" );
dav_buffer_append(p, pbuf,
(*hooks->format_locktoken)(p, lock->locktoken));
dav_buffer_append(p, pbuf,
"</D:href>" DEBUG_CR
"</D:locktoken>" DEBUG_CR
"</D:activelock>" DEBUG_CR);
}
return pbuf->buf;
}
/*
* * dav_lock_parse_lockinfo : Validates the given xml_doc to contain a
* * lockinfo XML element , then populates a dav_lock structure
* * with its contents .
*/
DAV_DECLARE(dav_error *) dav_lock_parse_lockinfo(request_rec *r,
const dav_resource *resource,
dav_lockdb *lockdb,
const apr_xml_doc *doc,
dav_lock **lock_request)
{
apr_pool_t *p = r->pool;
dav_error *err;
apr_xml_elem *child;
dav_lock *lock;
if (!dav_validate_root(doc, "lockinfo" )) {
return dav_new_error(p, HTTP_BAD_REQUEST, 0 , 0 ,
"The request body contains an unexpected "
"XML root element." );
}
if ((err = (*lockdb->hooks->create_lock)(lockdb, resource,
&lock)) != NULL) {
return dav_push_error(p, err->status, 0 ,
"Could not parse the lockinfo due to an "
"internal problem creating a lock structure." ,
err);
}
lock->depth = dav_get_depth(r, DAV_INFINITY);
if (lock->depth == -1 ) {
return dav_new_error(p, HTTP_BAD_REQUEST, 0 , 0 ,
"An invalid Depth header was specified." );
}
lock->timeout = dav_get_timeout(r);
/* Parse elements in the XML body */
for (child = doc->root->first_child; child; child = child->next) {
if (strcmp(child->name, "locktype" ) == 0
&& child->first_child
&& lock->type == DAV_LOCKTYPE_UNKNOWN) {
if (strcmp(child->first_child->name, "write" ) == 0 ) {
lock->type = DAV_LOCKTYPE_WRITE;
continue ;
}
}
if (strcmp(child->name, "lockscope" ) == 0
&& child->first_child
&& lock->scope == DAV_LOCKSCOPE_UNKNOWN) {
if (strcmp(child->first_child->name, "exclusive" ) == 0 )
lock->scope = DAV_LOCKSCOPE_EXCLUSIVE;
else if (strcmp(child->first_child->name, "shared" ) == 0 )
lock->scope = DAV_LOCKSCOPE_SHARED;
if (lock->scope != DAV_LOCKSCOPE_UNKNOWN)
continue ;
}
if (strcmp(child->name, "owner" ) == 0 && lock->owner == NULL) {
const char *text;
/* quote all the values in the <DAV:owner> element */
apr_xml_quote_elem(p, child);
/*
* * Store a full < DAV : owner > element with namespace definitions
* * and an xml : lang definition , if applicable .
*/
apr_xml_to_text(p, child, APR_XML_X2T_FULL_NS_LANG, doc->namespaces,
NULL, &text, NULL);
lock->owner = text;
continue ;
}
return dav_new_error(p, HTTP_PRECONDITION_FAILED, 0 , 0 ,
apr_psprintf(p,
"The server cannot satisfy the "
"LOCK request due to an unknown XML "
"element (\" %s\") within the "
"DAV:lockinfo element." ,
child->name));
}
*lock_request = lock;
return NULL;
}
/* ---------------------------------------------------------------
* *
* * General lock functions
* *
*/
/* dav_lock_walker: Walker callback function to record indirect locks */
static dav_error * dav_lock_walker(dav_walk_resource *wres, int calltype)
{
dav_walker_ctx *ctx = wres->walk_ctx;
dav_error *err;
/* We don't want to set indirects on the target */
if ((*wres->resource->hooks->is_same_resource)(wres->resource,
ctx->w.root))
return NULL;
if ((err = (*ctx->w.lockdb->hooks->append_locks)(ctx->w.lockdb,
wres->resource, 1 ,
ctx->lock)) != NULL) {
if (ap_is_HTTP_SERVER_ERROR(err->status)) {
/* ### add a higher-level description? */
return err;
}
/* add to the multistatus response */
dav_add_response(wres, err->status, NULL);
/*
* * # # # actually , this is probably wrong : we want to fail the whole
* * # # # LOCK process if something goes bad . maybe the caller should
* * # # # do a dav_unlock ( ) ( e . g . a rollback ) if any errors occurred .
*/
}
return NULL;
}
/*
* * dav_add_lock : Add a direct lock for resource , and indirect locks for
* * all children , bounded by depth .
* * # # # assume request only contains one lock
*/
DAV_DECLARE(dav_error *) dav_add_lock(request_rec *r,
const dav_resource *resource,
dav_lockdb *lockdb, dav_lock *lock,
dav_response **response)
{
dav_error *err;
int depth = lock->depth;
*response = NULL;
/* Requested lock can be:
* Depth : 0 for null resource , existing resource , or existing collection
* Depth : Inf for existing collection
*/
/*
* * 2518 9 . 2 says to ignore depth if target is not a collection ( it has
* * no internal children ) ; pretend the client gave the correct depth .
*/
if (!resource->collection) {
depth = 0 ;
}
/* In all cases, first add direct entry in lockdb */
/*
* * Append the new ( direct ) lock to the resource ' s existing locks .
* *
* * Note : this also handles locknull resources
*/
if ((err = (*lockdb->hooks->append_locks)(lockdb, resource, 0 ,
lock)) != NULL) {
/* ### maybe add a higher-level description */
return err;
}
if (depth > 0 ) {
/* Walk existing collection and set indirect locks */
dav_walker_ctx ctx = { { 0 } };
dav_response *multi_status;
ctx.w.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_AUTH;
ctx.w.func = dav_lock_walker;
ctx.w.walk_ctx = &ctx;
ctx.w.pool = r->pool;
ctx.w.root = resource;
ctx.w.lockdb = lockdb;
ctx.r = r;
ctx.lock = lock;
err = (*resource->hooks->walk)(&ctx.w, DAV_INFINITY, &multi_status);
if (err != NULL) {
/* implies a 5xx status code occurred. screw the multistatus */
return err;
}
if (multi_status != NULL) {
/* manufacture a 207 error for the multistatus response */
*response = multi_status;
return dav_new_error(r->pool, HTTP_MULTI_STATUS, 0 , 0 ,
"Error(s) occurred on resources during the "
"addition of a depth lock." );
}
}
return NULL;
}
/*
* * dav_lock_query : Opens the lock database . Returns a linked list of
* * dav_lock structures for all direct locks on path .
*/
DAV_DECLARE(dav_error*) dav_lock_query(dav_lockdb *lockdb,
const dav_resource *resource,
dav_lock **locks)
{
/* If no lock database, return empty result */
if (lockdb == NULL) {
*locks = NULL;
return NULL;
}
/* ### insert a higher-level description? */
return (*lockdb->hooks->get_locks)(lockdb, resource,
DAV_GETLOCKS_RESOLVED,
locks);
}
/* dav_unlock_walker: Walker callback function to remove indirect locks */
static dav_error * dav_unlock_walker(dav_walk_resource *wres, int calltype)
{
dav_walker_ctx *ctx = wres->walk_ctx;
dav_error *err;
/* Before removing the lock, do any auto-checkin required */
if (wres->resource->working) {
/* ### get rid of this typecast */
if ((err = dav_auto_checkin(ctx->r, (dav_resource *) wres->resource,
0 /*undo*/, 1 /*unlock*/, NULL))
!= NULL) {
return err;
}
}
if ((err = (*ctx->w.lockdb->hooks->remove_lock)(ctx->w.lockdb,
wres->resource,
ctx->locktoken)) != NULL) {
/* ### should we stop or return a multistatus? looks like STOP */
/* ### add a higher-level description? */
return err;
}
return NULL;
}
/*
* * dav_get_direct_resource :
* *
* * Find a lock on the specified resource , then return the resource the
* * lock was applied to ( in other words , given a ( possibly ) indirect lock ,
* * return the direct lock ' s corresponding resource ) .
* *
* * If the lock is an indirect lock , this usually means traversing up the
* * namespace [ repository ] hierarchy . Note that some lock providers may be
* * able to return this information with a traversal .
*/
static dav_error * dav_get_direct_resource(apr_pool_t *p,
dav_lockdb *lockdb,
const dav_locktoken *locktoken,
const dav_resource *resource,
const dav_resource **direct_resource)
{
if (lockdb->hooks->lookup_resource != NULL) {
return (*lockdb->hooks->lookup_resource)(lockdb, locktoken,
resource, direct_resource);
}
*direct_resource = NULL;
/* Find the top of this lock-
* If r - > filename ' s direct locks include locktoken , use r - > filename .
* If r - > filename ' s indirect locks include locktoken , retry r - > filename / . .
* Else fail .
*/
while (resource != NULL) {
dav_error *err;
dav_lock *lock;
dav_resource *parent;
/*
* * Find the lock specified by < locktoken > on < resource > . If it is
* * an indirect lock , then partial results are okay . We ' re just
* * trying to find the thing and know whether it is a direct or
* * an indirect lock .
*/
if ((err = (*lockdb->hooks->find_lock)(lockdb, resource, locktoken,
1 , &lock)) != NULL) {
/* ### add a higher-level desc? */
return err;
}
/* not found! that's an error. */
if (lock == NULL) {
return dav_new_error(p, HTTP_BAD_REQUEST, 0 , 0 ,
"The specified locktoken does not correspond "
"to an existing lock on this resource." );
}
if (lock->rectype == DAV_LOCKREC_DIRECT) {
/* we found the direct lock. return this resource. */
*direct_resource = resource;
return NULL;
}
/* the lock was indirect. move up a level in the URL namespace */
if ((err = (*resource->hooks->get_parent_resource)(resource,
&parent)) != NULL) {
/* ### add a higher-level desc? */
return err;
}
resource = parent;
}
return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0 , 0 ,
"The lock database is corrupt. A direct lock could "
"not be found for the corresponding indirect lock "
"on this resource." );
}
/*
* * dav_unlock : Removes all direct and indirect locks for r - > filename ,
* * with given locktoken . If locktoken = = null_locktoken , all locks
* * are removed . If r - > filename represents an indirect lock ,
* * we must unlock the appropriate direct lock .
* * Returns OK or appropriate HTTP_ * response and logs any errors .
* *
* * # # # We ' ve already crawled the tree to ensure everything was locked
* * by us ; there should be no need to incorporate a rollback .
*/
DAV_DECLARE(int ) dav_unlock(request_rec *r, const dav_resource *resource,
const dav_locktoken *locktoken)
{
int result;
dav_lockdb *lockdb;
const dav_resource *lock_resource = resource;
const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r);
const dav_hooks_repository *repos_hooks = resource->hooks;
dav_walker_ctx ctx = { { 0 } };
dav_response *multi_status;
dav_error *err;
/* If no locks provider, then there is nothing to unlock. */
if (hooks == NULL) {
return OK;
}
/* 2518 requires the entire lock to be removed if resource/locktoken
* point to an indirect lock . We need resource of the _ direct_
* lock in order to walk down the tree and remove the locks . So ,
* If locktoken ! = null_locktoken ,
* Walk up the resource hierarchy until we see a direct lock .
* Or , we could get the direct lock ' s db / key , pick out the URL
* and do a subrequest . I think walking up is faster and will work
* all the time .
* Else
* Just start removing all locks at and below resource .
*/
if ((err = (*hooks->open_lockdb)(r, 0 , 1 , &lockdb)) != NULL) {
/* ### return err! maybe add a higher-level desc */
/* ### map result to something nice; log an error */
return HTTP_INTERNAL_SERVER_ERROR;
}
if (locktoken != NULL
&& (err = dav_get_direct_resource(r->pool, lockdb,
locktoken, resource,
&lock_resource)) != NULL) {
/* ### add a higher-level desc? */
/* ### should return err! */
return err->status;
}
/* At this point, lock_resource/locktoken refers to a direct lock (key), ie
* the root of a depth > 0 lock , or locktoken is null .
*/
ctx.w.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_LOCKNULL;
ctx.w.func = dav_unlock_walker;
ctx.w.walk_ctx = &ctx;
ctx.w.pool = r->pool;
ctx.w.root = lock_resource;
ctx.w.lockdb = lockdb;
ctx.r = r;
ctx.locktoken = locktoken;
err = (*repos_hooks->walk)(&ctx.w, DAV_INFINITY, &multi_status);
/* ### fix this! */
/* ### do something with multi_status */
result = err == NULL ? OK : err->status;
(*hooks->close_lockdb)(lockdb);
return result;
}
/* dav_inherit_walker: Walker callback function to inherit locks */
static dav_error * dav_inherit_walker(dav_walk_resource *wres, int calltype)
{
dav_walker_ctx *ctx = wres->walk_ctx;
if (ctx->skip_root
&& (*wres->resource->hooks->is_same_resource)(wres->resource,
ctx->w.root)) {
return NULL;
}
/* ### maybe add a higher-level desc */
return (*ctx->w.lockdb->hooks->append_locks)(ctx->w.lockdb,
wres->resource, 1 ,
ctx->lock);
}
/*
* * dav_inherit_locks : When a resource or collection is added to a collection ,
* * locks on the collection should be inherited to the resource / collection .
* * ( MOVE , MKCOL , etc ) Here we propagate any direct or indirect locks from
* * parent of resource to resource and below .
*/
static dav_error * dav_inherit_locks(request_rec *r, dav_lockdb *lockdb,
const dav_resource *resource,
int use_parent)
{
dav_error *err;
const dav_resource *which_resource;
dav_lock *locks;
dav_lock *scan;
dav_lock *prev;
dav_walker_ctx ctx = { { 0 } };
const dav_hooks_repository *repos_hooks = resource->hooks;
dav_response *multi_status;
if (use_parent) {
dav_resource *parent;
if ((err = (*repos_hooks->get_parent_resource)(resource,
&parent)) != NULL) {
/* ### add a higher-level desc? */
return err;
}
if (parent == NULL) {
/* ### map result to something nice; log an error */
return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0 , 0 ,
"Could not fetch parent resource. Unable to "
"inherit locks from the parent and apply "
"them to this resource." );
}
which_resource = parent;
}
else {
which_resource = resource;
}
if ((err = (*lockdb->hooks->get_locks)(lockdb, which_resource,
DAV_GETLOCKS_PARTIAL,
&locks)) != NULL) {
/* ### maybe add a higher-level desc */
return err;
}
if (locks == NULL) {
/* No locks to propagate, just return */
return NULL;
}
/*
* * ( 1 ) Copy all indirect locks from our parent ;
* * ( 2 ) Create indirect locks for the depth infinity , direct locks
* * in our parent .
* *
* * The append_locks call in the walker callback will do the indirect
* * conversion , but we need to remove any direct locks that are NOT
* * depth " infinity " .
*/
for (scan = locks, prev = NULL;
scan != NULL;
prev = scan, scan = scan->next) {
if (scan->rectype == DAV_LOCKREC_DIRECT
&& scan->depth != DAV_INFINITY) {
if (prev == NULL)
locks = scan->next;
else
prev->next = scan->next;
}
}
/* <locks> has all our new locks. Walk down and propagate them. */
ctx.w.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_LOCKNULL;
ctx.w.func = dav_inherit_walker;
ctx.w.walk_ctx = &ctx;
ctx.w.pool = r->pool;
ctx.w.root = resource;
ctx.w.lockdb = lockdb;
ctx.r = r;
ctx.lock = locks;
ctx.skip_root = !use_parent;
/* ### do something with multi_status */
return (*repos_hooks->walk)(&ctx.w, DAV_INFINITY, &multi_status);
}
/* ---------------------------------------------------------------
* *
* * Functions dealing with lock - null resources
* *
*/
/*
* * dav_get_resource_state : Returns the state of the resource
* * r - > filename : DAV_RESOURCE_NULL , DAV_RESOURCE_LOCK_NULL ,
* * or DAV_RESOURCE_EXIST .
* *
* * Returns DAV_RESOURCE_ERROR if an error occurs .
*/
DAV_DECLARE(int ) dav_get_resource_state(request_rec *r,
const dav_resource *resource)
{
const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r);
if (resource->exists)
return DAV_RESOURCE_EXISTS;
if (hooks != NULL) {
dav_error *err;
dav_lockdb *lockdb;
int locks_present;
/*
* * A locknull resource has the form :
* *
* * known - dir " / " locknull - file
* *
* * It would be nice to look into < resource > to verify this form ,
* * but it does not have enough information for us . Instead , we
* * can look at the path_info . If the form does not match , then
* * there is no way we could have a locknull resource - - it must
* * be a plain , null resource .
* *
* * Apache sets r - > filename to known - dir / unknown - file and r - > path_info
* * to " " for the " proper " case . If anything is in path_info , then
* * it can ' t be a locknull resource .
* *
* * # # # I bet this path_info hack doesn ' t work for repositories .
* * # # # Need input from repository implementors ! What kind of
* * # # # restructure do we need ? New provider APIs ?
*/
if (r->path_info != NULL && *r->path_info != '\0' ) {
return DAV_RESOURCE_NULL;
}
if ((err = (*hooks->open_lockdb)(r, 1 , 1 , &lockdb)) == NULL) {
/* note that we might see some expired locks... *shrug* */
err = (*hooks->has_locks)(lockdb, resource, &locks_present);
(*hooks->close_lockdb)(lockdb);
}
if (err != NULL) {
/* ### don't log an error. return err. add higher-level desc. */
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0 , r, APLOGNO(00623 )
"Failed to query lock-null status for %s" ,
r->filename);
return DAV_RESOURCE_ERROR;
}
if (locks_present)
return DAV_RESOURCE_LOCK_NULL;
}
return DAV_RESOURCE_NULL;
}
DAV_DECLARE(dav_error *) dav_notify_created(request_rec *r,
dav_lockdb *lockdb,
const dav_resource *resource,
int resource_state,
int depth)
{
dav_error *err;
if (resource_state == DAV_RESOURCE_LOCK_NULL) {
/*
* * The resource is no longer a locknull resource . This will remove
* * the special marker .
* *
* * Note that a locknull resource has already inherited all of the
* * locks from the parent . We do not need to call dav_inherit_locks .
* *
* * NOTE : some lock providers record locks for locknull resources using
* * a different key than for regular resources . this will shift
* * the lock information between the two key types .
*/
(void )(*lockdb->hooks->remove_locknull_state)(lockdb, resource);
/*
* * There are resources under this one , which are new . We must
* * propagate the locks down to the new resources .
*/
if (depth > 0 &&
(err = dav_inherit_locks(r, lockdb, resource, 0 )) != NULL) {
/* ### add a higher level desc? */
return err;
}
}
else if (resource_state == DAV_RESOURCE_NULL) {
/* ### should pass depth to dav_inherit_locks so that it can
* * # # # optimize for the depth = = 0 case .
*/
/* this resource should inherit locks from its parent */
if ((err = dav_inherit_locks(r, lockdb, resource, 1 )) != NULL) {
err = dav_push_error(r->pool, err->status, 0 ,
"The resource was created successfully, but "
"there was a problem inheriting locks from "
"the parent resource." ,
err);
return err;
}
}
/* else the resource already exists and its locks are correct. */
return NULL;
}
Messung V0.5 in Prozent C=90 H=95 G=92
¤ Dauer der Verarbeitung: 0.20 Sekunden
(vorverarbeitet am 2026-09-28)
¤
*© Formatika GbR, Deutschland