cscg24-guacamole

CSCG 2024 Challenge 'Guacamole Mashup'
git clone https://git.sinitax.com/sinitax/cscg24-guacamole
Log | Files | Refs | sfeed.txt

argv.c (9961B)


      1/*
      2 * Licensed to the Apache Software Foundation (ASF) under one
      3 * or more contributor license agreements.  See the NOTICE file
      4 * distributed with this work for additional information
      5 * regarding copyright ownership.  The ASF licenses this file
      6 * to you under the Apache License, Version 2.0 (the
      7 * "License"); you may not use this file except in compliance
      8 * with the License.  You may obtain a copy of the License at
      9 *
     10 *   http://www.apache.org/licenses/LICENSE-2.0
     11 *
     12 * Unless required by applicable law or agreed to in writing,
     13 * software distributed under the License is distributed on an
     14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     15 * KIND, either express or implied.  See the License for the
     16 * specific language governing permissions and limitations
     17 * under the License.
     18 */
     19
     20#include "config.h"
     21
     22#include "guacamole/mem.h"
     23#include "guacamole/argv.h"
     24#include "guacamole/client.h"
     25#include "guacamole/protocol.h"
     26#include "guacamole/socket.h"
     27#include "guacamole/stream.h"
     28#include "guacamole/string.h"
     29#include "guacamole/user.h"
     30
     31#include <pthread.h>
     32#include <stdlib.h>
     33#include <string.h>
     34
     35/**
     36 * The state of an argument that will be automatically processed. Note that
     37 * this is distinct from the state of an argument value that is currently being
     38 * processed. Argument value states are dynamically-allocated and scoped by the
     39 * associated guac_stream.
     40 */
     41typedef struct guac_argv_state {
     42
     43    /**
     44     * The name of the argument.
     45     */
     46    char name[GUAC_ARGV_MAX_NAME_LENGTH];
     47
     48    /**
     49     * Whether at least one value for this argument has been received since it
     50     * was registered.
     51     */
     52    int received;
     53
     54    /**
     55     * Bitwise OR of all option flags that should affect processing of this
     56     * argument.
     57     */
     58    int options;
     59
     60    /**
     61     * The callback that should be invoked when a new value for the associated
     62     * argument has been received. If the GUAC_ARGV_OPTION_ONCE flag is set,
     63     * the callback will be invoked at most once.
     64     */
     65    guac_argv_callback* callback;
     66
     67    /**
     68     * The arbitrary data that should be passed to the callback.
     69     */
     70    void* data;
     71
     72} guac_argv_state;
     73
     74/**
     75 * The current state of automatic processing of "argv" streams.
     76 */
     77typedef struct guac_argv_await_state {
     78
     79    /**
     80     * Whether automatic argument processing has been stopped via a call to
     81     * guac_argv_stop().
     82     */
     83    int stopped;
     84
     85    /**
     86     * The total number of arguments registered.
     87     */
     88    unsigned int num_registered;
     89
     90    /**
     91     * All registered arguments and their corresponding callbacks.
     92     */
     93    guac_argv_state registered[GUAC_ARGV_MAX_REGISTERED];
     94
     95    /**
     96     * Lock which protects multi-threaded access to this entire state
     97     * structure, including the condition that signals specific modifications
     98     * to the structure.
     99     */
    100    pthread_mutex_t lock;
    101
    102    /**
    103     * Condition which is signalled whenever the overall state of "argv"
    104     * processing changes, either through the receipt of a new argument value
    105     * or due to a call to guac_argv_stop().
    106     */
    107    pthread_cond_t changed;
    108
    109} guac_argv_await_state;
    110
    111/**
    112 * The value or current status of a connection parameter received over an
    113 * "argv" stream.
    114 */
    115typedef struct guac_argv {
    116
    117    /**
    118     * The state of the specific setting being updated.
    119     */
    120    guac_argv_state* state;
    121
    122    /**
    123     * The mimetype of the data being received.
    124     */
    125    char mimetype[GUAC_ARGV_MAX_MIMETYPE_LENGTH];
    126
    127    /**
    128     * Buffer space for containing the received argument value.
    129     */
    130    char buffer[GUAC_ARGV_MAX_LENGTH];
    131
    132    /**
    133     * The number of bytes received so far.
    134     */
    135    int length;
    136
    137} guac_argv;
    138
    139/**
    140 * Statically-allocated, shared state of the guac_argv_*() family of functions.
    141 */
    142static guac_argv_await_state await_state = {
    143    .lock = PTHREAD_MUTEX_INITIALIZER,
    144    .changed = PTHREAD_COND_INITIALIZER
    145};
    146
    147/**
    148 * Returns whether at least one value for each of the provided arguments has
    149 * been received.
    150 *
    151 * @param args
    152 *     A NULL-terminated array of the names of all arguments to test.
    153 *
    154 * @return
    155 *     Non-zero if at least one value has been received for each of the
    156 *     provided arguments, zero otherwise.
    157 */
    158static int guac_argv_is_received(const char** args) {
    159
    160    for (int i = 0; i < await_state.num_registered; i++) {
    161
    162        /* Ignore all received arguments */
    163        guac_argv_state* state = &await_state.registered[i];
    164        if (state->received)
    165            continue;
    166
    167        /* Fail immediately for any matching non-received arguments */
    168        for (const char** arg = args; *arg != NULL; arg++) {
    169            if (strcmp(state->name, *arg) == 0)
    170                return 0;
    171        }
    172
    173    }
    174
    175    /* All arguments were received */
    176    return 1;
    177
    178}
    179
    180int guac_argv_register(const char* name, guac_argv_callback* callback, void* data, int options) {
    181
    182    pthread_mutex_lock(&await_state.lock);
    183
    184    if (await_state.num_registered == GUAC_ARGV_MAX_REGISTERED) {
    185        pthread_mutex_unlock(&await_state.lock);
    186        return 1;
    187    }
    188
    189    guac_argv_state* state = &await_state.registered[await_state.num_registered++];
    190    guac_strlcpy(state->name, name, sizeof(state->name));
    191    state->options = options;
    192    state->callback = callback;
    193    state->data = data;
    194
    195    pthread_mutex_unlock(&await_state.lock);
    196    return 0;
    197
    198}
    199
    200int guac_argv_await(const char** args) {
    201
    202    /* Wait for all requested arguments to be received (or for receipt to be
    203     * stopped) */
    204    pthread_mutex_lock(&await_state.lock);
    205    while (!await_state.stopped && !guac_argv_is_received(args))
    206        pthread_cond_wait(&await_state.changed, &await_state.lock);
    207
    208    /* Arguments were successfully received only if receipt was not stopped */
    209    int retval = await_state.stopped;
    210    pthread_mutex_unlock(&await_state.lock);
    211    return retval;
    212
    213}
    214
    215/**
    216 * Handler for "blob" instructions which appends the data from received blobs
    217 * to the end of the in-progress argument value buffer.
    218 *
    219 * @see guac_user_blob_handler
    220 */
    221static int guac_argv_blob_handler(guac_user* user, guac_stream* stream,
    222        void* data, int length) {
    223
    224    guac_argv* argv = (guac_argv*) stream->data;
    225
    226    /* Calculate buffer size remaining, including space for null terminator,
    227     * adjusting received length accordingly */
    228    int remaining = sizeof(argv->buffer) - argv->length - 1;
    229    if (length > remaining)
    230        length = remaining;
    231
    232    /* Append received data to end of buffer */
    233    memcpy(argv->buffer + argv->length, data, length);
    234    argv->length += length;
    235
    236    return 0;
    237
    238}
    239
    240/**
    241 * Handler for "end" instructions which applies the changes specified by the
    242 * argument value buffer associated with the stream.
    243 *
    244 * @see guac_user_end_handler
    245 */
    246static int guac_argv_end_handler(guac_user* user, guac_stream* stream) {
    247
    248    int result = 0;
    249
    250    /* Append null terminator to value */
    251    guac_argv* argv = (guac_argv*) stream->data;
    252    argv->buffer[argv->length] = '\0';
    253
    254    pthread_mutex_lock(&await_state.lock);
    255
    256    /* Invoke callback, limiting to a single invocation if
    257     * GUAC_ARGV_OPTION_ONCE applies */
    258    guac_argv_state* state = argv->state;
    259    if (!(state->options & GUAC_ARGV_OPTION_ONCE) || !state->received) {
    260        if (state->callback != NULL)
    261            result = state->callback(user, argv->mimetype, state->name, argv->buffer, state->data);
    262    }
    263
    264    /* Alert connected clients regarding newly-accepted values if echo is
    265     * enabled */
    266    if (!result && (state->options & GUAC_ARGV_OPTION_ECHO)) {
    267        guac_client* client = user->client;
    268        guac_client_stream_argv(client, client->socket, argv->mimetype, state->name, argv->buffer);
    269    }
    270
    271    /* Notify that argument has been received */
    272    state->received = 1;
    273    pthread_cond_broadcast(&await_state.changed);
    274
    275    pthread_mutex_unlock(&await_state.lock);
    276
    277    guac_mem_free(argv);
    278    return 0;
    279
    280}
    281
    282int guac_argv_received(guac_stream* stream, const char* mimetype, const char* name) {
    283
    284    pthread_mutex_lock(&await_state.lock);
    285
    286    for (int i = 0; i < await_state.num_registered; i++) {
    287
    288        /* Ignore any arguments that have already been received if they are
    289         * declared as being acceptable only once */
    290        guac_argv_state* state = &await_state.registered[i];
    291        if ((state->options & GUAC_ARGV_OPTION_ONCE) && state->received)
    292            continue;
    293
    294        /* Argument matched */
    295        if (strcmp(state->name, name) == 0) {
    296
    297            guac_argv* argv = guac_mem_alloc(sizeof(guac_argv));
    298            guac_strlcpy(argv->mimetype, mimetype, sizeof(argv->mimetype));
    299            argv->state = state;
    300            argv->length = 0;
    301
    302            stream->data = argv;
    303            stream->blob_handler = guac_argv_blob_handler;
    304            stream->end_handler = guac_argv_end_handler;
    305
    306            pthread_mutex_unlock(&await_state.lock);
    307            return 0;
    308
    309        }
    310
    311    }
    312
    313    /* No such argument awaiting processing */
    314    pthread_mutex_unlock(&await_state.lock);
    315    return 1;
    316
    317}
    318
    319void guac_argv_stop() {
    320    pthread_mutex_lock(&await_state.lock);
    321
    322    /* Signal any waiting threads that no further argument values will be
    323     * received */
    324    if (!await_state.stopped) {
    325        await_state.stopped = 1;
    326        pthread_cond_broadcast(&await_state.changed);
    327
    328    }
    329
    330    pthread_mutex_unlock(&await_state.lock);
    331}
    332
    333int guac_argv_handler(guac_user* user, guac_stream* stream,
    334        char* mimetype, char* name) {
    335
    336    /* Refuse stream if argument is not registered */
    337    if (guac_argv_received(stream, mimetype, name)) {
    338        guac_protocol_send_ack(user->socket, stream, "Not allowed.",
    339                GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN);
    340        guac_socket_flush(user->socket);
    341        return 0;
    342    }
    343
    344    /* Signal stream is ready */
    345    guac_protocol_send_ack(user->socket, stream, "Ready for updated "
    346            "parameter.", GUAC_PROTOCOL_STATUS_SUCCESS);
    347    guac_socket_flush(user->socket);
    348    return 0;
    349
    350}
    351