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