diff options
Diffstat (limited to '')
-rw-r--r-- | src/mds-server/interceptors.c | 291 |
1 files changed, 291 insertions, 0 deletions
diff --git a/src/mds-server/interceptors.c b/src/mds-server/interceptors.c new file mode 100644 index 0000000..9d602c7 --- /dev/null +++ b/src/mds-server/interceptors.c @@ -0,0 +1,291 @@ +/** + * mds — A micro-display server + * Copyright © 2014 Mattias Andrée (maandree@member.fsf.org) + * + * This program 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 program 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 this program. If not, see <http://www.gnu.org/licenses/>. + */ +#include "interceptors.h" + +#include "globals.h" +#include "interception_condition.h" +#include "client.h" +#include "queued_interception.h" + +#include <libmdsserver/macros.h> +#include <libmdsserver/hash-help.h> + +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> + + +/** + * Remove interception condition by index + * + * @param client The intercepting client + * @param index The index of the condition + */ +static void remove_intercept_condition(client_t* client, size_t index) +{ + interception_condition_t* conds = client->interception_conditions; + size_t n = client->interception_conditions_count; + + /* Remove the condition from the list. */ + memmove(conds + index, conds + index + 1, --n - index); + client->interception_conditions_count--; + + /* Shrink the list. */ + if (client->interception_conditions_count == 0) + { + free(conds); + client->interception_conditions = NULL; + } + else + if (xrealloc(conds, n, interception_condition_t)) + perror(*argv); + else + client->interception_conditions = conds; +} + + +/** + * Add an interception condition for a client + * + * @param client The client + * @param condition The header, optionally with value, to look for, or empty (not NULL) for all messages + * @param priority Interception priority + * @param modifying Whether the client may modify the messages + * @param stop Whether the condition should be removed rather than added + */ +void add_intercept_condition(client_t* client, char* condition, int64_t priority, int modifying, int stop) +{ + size_t n = client->interception_conditions_count; + interception_condition_t* conds = client->interception_conditions; + ssize_t nonmodifying = -1; + char* header = condition; + char* value; + size_t hash; + size_t i; + + /* Split header and value apart. */ + if ((value = strchr(header, ':')) != NULL) + { + *value = '\0'; /* NUL-terminate header. */ + value += 2; /* Skip over delimiter. */ + } + + /* Calcuate header hash (comparison optimisation) */ + hash = string_hash(header); + + /* Remove of update condition of already registered, + also look for non-modifying condition to swap position + with for optimisation. */ + for (i = 0; i < n; i++) + { + if ((conds[i].header_hash != hash) || !strequals(conds[i].condition, condition)) + { + /* Look for the first non-modifying, this is a part of the + optimisation where we put all modifying conditions at the + beginning. */ + if ((nonmodifying < 0) && conds[i].modifying) + nonmodifying = (ssize_t)i; + continue; + } + + if (stop) + remove_intercept_condition(client, i); + else + { + /* Update parameters. */ + conds[i].priority = priority; + conds[i].modifying = modifying; + + if (modifying && (nonmodifying >= 0)) + { + /* Optimisation: put conditions that are modifying + at the beginning. When a client is intercepting + we most know if any satisfying condition is + modifying. With this optimisation the first + satisfying condition will tell us if there is + any satisfying condition that is modifying. */ + interception_condition_t temp = conds[nonmodifying]; + conds[nonmodifying] = conds[i]; + conds[i] = temp; + } + } + return; + } + + if (stop) + eprint("client tried to stop intercepting messages that it does not intercept."); + else + { + /* Duplicate condition string. */ + if ((condition = strdup(condition)) == NULL) + { + perror(*argv); + return; + } + + /* Grow the interception condition list. */ + if (xrealloc(conds, n + 1, interception_condition_t)) + { + perror(*argv); + free(condition); + return; + } + client->interception_conditions = conds; + /* Store condition. */ + client->interception_conditions_count++; + conds[n].condition = condition; + conds[n].header_hash = hash; + conds[n].priority = priority; + conds[n].modifying = modifying; + + if (modifying && (nonmodifying >= 0)) + { + /* Optimisation: put conditions that are modifying + at the beginning. When a client is intercepting + we most know if any satisfying condition is + modifying. With this optimisation the first + satisfying condition will tell us if there is + any satisfying condition that is modifying. */ + interception_condition_t temp = conds[nonmodifying]; + conds[nonmodifying] = conds[n]; + conds[n] = temp; + } + } +} + + +/** + * Check if a condition matches any of a set of accepted patterns + * + * @param cond The condition + * @param hashes The hashes of the accepted header names + * @param keys The header names + * @param headers The header name–value pairs + * @param count The number of accepted patterns + * @return Evaluates to true if and only if a matching pattern was found + */ +int is_condition_matching(interception_condition_t* cond, size_t* hashes, + char** keys, char** headers, size_t count) +{ + size_t i; + for (i = 0; i < count; i++) + if (*(cond->condition) == '\0') + return 1; + else if ((cond->header_hash == hashes[i]) && + (strequals(cond->condition, keys[i]) || + strequals(cond->condition, headers[i]))) + return 1; + return 0; +} + + +/** + * Find a matching condition to any of a set of acceptable conditions + * + * @param client The intercepting client + * @param hashes The hashes of the accepted header names + * @param keys The header names + * @param headers The header name–value pairs + * @param count The number of accepted patterns + * @param interception_out Storage slot for found interception + * @return -1 on error, otherwise: evalutes to true iff a matching condition was found + */ +int find_matching_condition(client_t* client, size_t* hashes, char** keys, char** headers, + size_t count, queued_interception_t* interception_out) +{ + pthread_mutex_t mutex = client->mutex; + interception_condition_t* conds = client->interception_conditions; + size_t n = 0, i; + + errno = pthread_mutex_lock(&(mutex)); + if (errno) + return -1; + + /* Look for a matching condition. */ + if (client->open) + n = client->interception_conditions_count; + for (i = 0; i < n; i++) + if (is_condition_matching(conds + i, hashes, keys, headers, count)) + { + /* Report matching condition. */ + interception_out->client = client; + interception_out->priority = conds[i].priority; + interception_out->modifying = conds[i].modifying; + break; + } + + pthread_mutex_unlock(&(mutex)); + + return i < n; +} + + +/** + * Get all interceptors who have at least one condition matching any of a set of acceptable patterns + * + * @param sender The original sender of the message + * @param hashes The hashes of the accepted header names + * @param keys The header names + * @param headers The header name–value pairs + * @param count The number of accepted patterns + * @param interceptions_count_out Slot at where to store the number of found interceptors + * @return The found interceptors, NULL on error + */ +queued_interception_t* get_interceptors(client_t* sender, size_t* hashes, char** keys, char** headers, + size_t count, size_t* interceptions_count_out) +{ + queued_interception_t* interceptions = NULL; + size_t interceptions_count = 0; + size_t n = 0; + ssize_t node; + + /* Count clients. */ + foreach_linked_list_node (client_list, node) + n++; + + /* Allocate interceptor list. */ + if (xmalloc(interceptions, n, queued_interception_t)) + return NULL; + + /* Search clients. */ + foreach_linked_list_node (client_list, node) + { + client_t* client = (client_t*)(void*)(client_list.values[node]); + + /* Look for and list a matching condition. */ + if (client->open && (client != sender)) + { + int r = find_matching_condition(client, hashes, keys, headers, count, + interceptions + interceptions_count); + if (r == -1) + { + free(interceptions); + return NULL; + } + if (r) + /* List client of there was a matching condition. */ + interceptions_count++; + } + } + + *interceptions_count_out = interceptions_count; + return interceptions; +} + |