| /* |
| * 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. |
| */ |
| |
| #include "config.h" |
| |
| #include "encode-jpeg.h" |
| #include "encode-png.h" |
| #include "encode-webp.h" |
| #include "guacamole/client.h" |
| #include "guacamole/object.h" |
| #include "guacamole/pool.h" |
| #include "guacamole/protocol.h" |
| #include "guacamole/socket.h" |
| #include "guacamole/stream.h" |
| #include "guacamole/timestamp.h" |
| #include "guacamole/user.h" |
| #include "id.h" |
| #include "user-handlers.h" |
| |
| #include <errno.h> |
| #include <limits.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| guac_user* guac_user_alloc() { |
| |
| guac_user* user = calloc(1, sizeof(guac_user)); |
| int i; |
| |
| /* Generate ID */ |
| user->user_id = guac_generate_id(GUAC_USER_ID_PREFIX); |
| if (user->user_id == NULL) { |
| free(user); |
| return NULL; |
| } |
| |
| user->last_received_timestamp = guac_timestamp_current(); |
| user->last_frame_duration = 0; |
| user->processing_lag = 0; |
| user->active = 1; |
| |
| /* Allocate stream pool */ |
| user->__stream_pool = guac_pool_alloc(0); |
| |
| /* Initialze streams */ |
| user->__input_streams = malloc(sizeof(guac_stream) * GUAC_USER_MAX_STREAMS); |
| user->__output_streams = malloc(sizeof(guac_stream) * GUAC_USER_MAX_STREAMS); |
| |
| for (i=0; i<GUAC_USER_MAX_STREAMS; i++) { |
| user->__input_streams[i].index = GUAC_USER_CLOSED_STREAM_INDEX; |
| user->__output_streams[i].index = GUAC_USER_CLOSED_STREAM_INDEX; |
| } |
| |
| /* Allocate object pool */ |
| user->__object_pool = guac_pool_alloc(0); |
| |
| /* Initialize objects */ |
| user->__objects = malloc(sizeof(guac_object) * GUAC_USER_MAX_OBJECTS); |
| for (i=0; i<GUAC_USER_MAX_OBJECTS; i++) |
| user->__objects[i].index = GUAC_USER_UNDEFINED_OBJECT_INDEX; |
| |
| return user; |
| |
| } |
| |
| void guac_user_free(guac_user* user) { |
| |
| /* Free streams */ |
| free(user->__input_streams); |
| free(user->__output_streams); |
| |
| /* Free stream pool */ |
| guac_pool_free(user->__stream_pool); |
| |
| /* Free objects */ |
| free(user->__objects); |
| |
| /* Free object pool */ |
| guac_pool_free(user->__object_pool); |
| |
| /* Clean up user */ |
| free(user->user_id); |
| free(user); |
| |
| } |
| |
| guac_stream* guac_user_alloc_stream(guac_user* user) { |
| |
| guac_stream* allocd_stream; |
| int stream_index; |
| |
| /* Refuse to allocate beyond maximum */ |
| if (user->__stream_pool->active == GUAC_USER_MAX_STREAMS) |
| return NULL; |
| |
| /* Allocate stream */ |
| stream_index = guac_pool_next_int(user->__stream_pool); |
| |
| /* Initialize stream with even index (odd indices are client-level) */ |
| allocd_stream = &(user->__output_streams[stream_index]); |
| allocd_stream->index = stream_index * 2; |
| allocd_stream->data = NULL; |
| allocd_stream->ack_handler = NULL; |
| allocd_stream->blob_handler = NULL; |
| allocd_stream->end_handler = NULL; |
| |
| return allocd_stream; |
| |
| } |
| |
| void guac_user_free_stream(guac_user* user, guac_stream* stream) { |
| |
| /* Release index to pool */ |
| guac_pool_free_int(user->__stream_pool, stream->index / 2); |
| |
| /* Mark stream as closed */ |
| stream->index = GUAC_USER_CLOSED_STREAM_INDEX; |
| |
| } |
| |
| guac_object* guac_user_alloc_object(guac_user* user) { |
| |
| guac_object* allocd_object; |
| int object_index; |
| |
| /* Refuse to allocate beyond maximum */ |
| if (user->__object_pool->active == GUAC_USER_MAX_OBJECTS) |
| return NULL; |
| |
| /* Allocate object */ |
| object_index = guac_pool_next_int(user->__object_pool); |
| |
| /* Initialize object */ |
| allocd_object = &(user->__objects[object_index]); |
| allocd_object->index = object_index; |
| allocd_object->data = NULL; |
| allocd_object->get_handler = NULL; |
| allocd_object->put_handler = NULL; |
| |
| return allocd_object; |
| |
| } |
| |
| void guac_user_free_object(guac_user* user, guac_object* object) { |
| |
| /* Release index to pool */ |
| guac_pool_free_int(user->__object_pool, object->index); |
| |
| /* Mark object as undefined */ |
| object->index = GUAC_USER_UNDEFINED_OBJECT_INDEX; |
| |
| } |
| |
| int guac_user_handle_instruction(guac_user* user, const char* opcode, int argc, char** argv) { |
| |
| /* For each defined instruction */ |
| __guac_instruction_handler_mapping* current = __guac_instruction_handler_map; |
| while (current->opcode != NULL) { |
| |
| /* If recognized, call handler */ |
| if (strcmp(opcode, current->opcode) == 0) |
| return current->handler(user, argc, argv); |
| |
| current++; |
| } |
| |
| /* If unrecognized, ignore */ |
| return 0; |
| |
| } |
| |
| void guac_user_stop(guac_user* user) { |
| user->active = 0; |
| } |
| |
| void vguac_user_abort(guac_user* user, guac_protocol_status status, |
| const char* format, va_list ap) { |
| |
| /* Only relevant if user is active */ |
| if (user->active) { |
| |
| /* Log detail of error */ |
| vguac_user_log(user, GUAC_LOG_ERROR, format, ap); |
| |
| /* Send error immediately, limit information given */ |
| guac_protocol_send_error(user->socket, "Aborted. See logs.", status); |
| guac_socket_flush(user->socket); |
| |
| /* Stop user */ |
| guac_user_stop(user); |
| |
| } |
| |
| } |
| |
| void guac_user_abort(guac_user* user, guac_protocol_status status, |
| const char* format, ...) { |
| |
| va_list args; |
| va_start(args, format); |
| |
| vguac_user_abort(user, status, format, args); |
| |
| va_end(args); |
| |
| } |
| |
| void vguac_user_log(guac_user* user, guac_client_log_level level, |
| const char* format, va_list ap) { |
| |
| vguac_client_log(user->client, level, format, ap); |
| |
| } |
| |
| void guac_user_log(guac_user* user, guac_client_log_level level, |
| const char* format, ...) { |
| |
| va_list args; |
| va_start(args, format); |
| |
| vguac_client_log(user->client, level, format, args); |
| |
| va_end(args); |
| |
| } |
| |
| void guac_user_stream_png(guac_user* user, guac_socket* socket, |
| guac_composite_mode mode, const guac_layer* layer, int x, int y, |
| cairo_surface_t* surface) { |
| |
| /* Allocate new stream for image */ |
| guac_stream* stream = guac_user_alloc_stream(user); |
| |
| /* Declare stream as containing image data */ |
| guac_protocol_send_img(socket, stream, mode, layer, "image/png", x, y); |
| |
| /* Write PNG data */ |
| guac_png_write(socket, stream, surface); |
| |
| /* Terminate stream */ |
| guac_protocol_send_end(socket, stream); |
| |
| /* Free allocated stream */ |
| guac_user_free_stream(user, stream); |
| |
| } |
| |
| void guac_user_stream_jpeg(guac_user* user, guac_socket* socket, |
| guac_composite_mode mode, const guac_layer* layer, int x, int y, |
| cairo_surface_t* surface, int quality) { |
| |
| /* Allocate new stream for image */ |
| guac_stream* stream = guac_user_alloc_stream(user); |
| |
| /* Declare stream as containing image data */ |
| guac_protocol_send_img(socket, stream, mode, layer, "image/jpeg", x, y); |
| |
| /* Write JPEG data */ |
| guac_jpeg_write(socket, stream, surface, quality); |
| |
| /* Terminate stream */ |
| guac_protocol_send_end(socket, stream); |
| |
| /* Free allocated stream */ |
| guac_user_free_stream(user, stream); |
| |
| } |
| |
| void guac_user_stream_webp(guac_user* user, guac_socket* socket, |
| guac_composite_mode mode, const guac_layer* layer, int x, int y, |
| cairo_surface_t* surface, int quality, int lossless) { |
| |
| #ifdef ENABLE_WEBP |
| /* Allocate new stream for image */ |
| guac_stream* stream = guac_user_alloc_stream(user); |
| |
| /* Declare stream as containing image data */ |
| guac_protocol_send_img(socket, stream, mode, layer, "image/webp", x, y); |
| |
| /* Write WebP data */ |
| guac_webp_write(socket, stream, surface, quality, lossless); |
| |
| /* Terminate stream */ |
| guac_protocol_send_end(socket, stream); |
| |
| /* Free allocated stream */ |
| guac_user_free_stream(user, stream); |
| #else |
| /* Do nothing if WebP support is not built in */ |
| #endif |
| |
| } |
| |
| int guac_user_supports_webp(guac_user* user) { |
| |
| #ifdef ENABLE_WEBP |
| const char** mimetype = user->info.image_mimetypes; |
| |
| /* Search for WebP mimetype in list of supported image mimetypes */ |
| while (*mimetype != NULL) { |
| |
| /* If WebP mimetype found, no need to search further */ |
| if (strcmp(*mimetype, "image/webp") == 0) |
| return 1; |
| |
| /* Next mimetype */ |
| mimetype++; |
| |
| } |
| |
| /* User does not support WebP */ |
| return 0; |
| #else |
| /* Support for WebP is completely absent */ |
| return 0; |
| #endif |
| |
| } |
| |
| char* guac_user_parse_args_string(guac_user* user, const char** arg_names, |
| const char** argv, int index, const char* default_value) { |
| |
| /* Pull parameter value from argv */ |
| const char* value = argv[index]; |
| |
| /* Use default value if blank */ |
| if (value[0] == 0) { |
| |
| /* NULL is a completely legal default value */ |
| if (default_value == NULL) |
| return NULL; |
| |
| /* Log use of default */ |
| guac_user_log(user, GUAC_LOG_DEBUG, "Parameter \"%s\" omitted. Using " |
| "default value of \"%s\".", arg_names[index], default_value); |
| |
| return strdup(default_value); |
| |
| } |
| |
| /* Otherwise use provided value */ |
| return strdup(value); |
| |
| } |
| |
| int guac_user_parse_args_int(guac_user* user, const char** arg_names, |
| const char** argv, int index, int default_value) { |
| |
| char* parse_end; |
| long parsed_value; |
| |
| /* Pull parameter value from argv */ |
| const char* value = argv[index]; |
| |
| /* Use default value if blank */ |
| if (value[0] == 0) { |
| |
| /* Log use of default */ |
| guac_user_log(user, GUAC_LOG_DEBUG, "Parameter \"%s\" omitted. Using " |
| "default value of %i.", arg_names[index], default_value); |
| |
| return default_value; |
| |
| } |
| |
| /* Parse value, checking for errors */ |
| errno = 0; |
| parsed_value = strtol(value, &parse_end, 10); |
| |
| /* Ensure parsed value is within the legal range of an int */ |
| if (parsed_value < INT_MIN || parsed_value > INT_MAX) |
| errno = ERANGE; |
| |
| /* Resort to default if input is invalid */ |
| if (errno != 0 || *parse_end != '\0') { |
| |
| /* Log use of default */ |
| guac_user_log(user, GUAC_LOG_WARNING, "Specified value \"%s\" for " |
| "parameter \"%s\" is not a valid integer. Using default value " |
| "of %i.", value, arg_names[index], default_value); |
| |
| return default_value; |
| |
| } |
| |
| /* Parsed successfully */ |
| return parsed_value; |
| |
| } |
| |
| int guac_user_parse_args_boolean(guac_user* user, const char** arg_names, |
| const char** argv, int index, int default_value) { |
| |
| /* Pull parameter value from argv */ |
| const char* value = argv[index]; |
| |
| /* Use default value if blank */ |
| if (value[0] == 0) { |
| |
| /* Log use of default */ |
| guac_user_log(user, GUAC_LOG_DEBUG, "Parameter \"%s\" omitted. Using " |
| "default value of %i.", arg_names[index], default_value); |
| |
| return default_value; |
| |
| } |
| |
| /* Parse string "true" as true */ |
| if (strcmp(value, "true") == 0) |
| return 1; |
| |
| /* Parse string "false" as false */ |
| if (strcmp(value, "false") == 0) |
| return 0; |
| |
| /* All other values are invalid */ |
| guac_user_log(user, GUAC_LOG_WARNING, "Parameter \"%s\" must be either " |
| "\"true\" or \"false\". Using default value.", arg_names[index]); |
| |
| return default_value; |
| |
| } |
| |