2016-03-01 05:35:32 +00:00
|
|
|
/*
|
2016-03-25 19:59:40 +00:00
|
|
|
* 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
|
2016-03-01 05:35:32 +00:00
|
|
|
*
|
2016-03-25 19:59:40 +00:00
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
2016-03-01 05:35:32 +00:00
|
|
|
*
|
2016-03-25 19:59:40 +00:00
|
|
|
* 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.
|
2016-03-01 05:35:32 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
|
|
|
#include "encode-jpeg.h"
|
|
|
|
#include "encode-png.h"
|
|
|
|
#include "encode-webp.h"
|
2018-10-19 16:30:20 +00:00
|
|
|
#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"
|
2016-03-01 05:35:32 +00:00
|
|
|
#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;
|
|
|
|
|
|
|
|
}
|
|
|
|
|