diff --git a/src/protocols/rdp/Makefile.am b/src/protocols/rdp/Makefile.am index 192fede2..bed84993 100644 --- a/src/protocols/rdp/Makefile.am +++ b/src/protocols/rdp/Makefile.am @@ -40,6 +40,7 @@ libguac_client_rdp_la_SOURCES = \ rdp_pointer.c \ rdp_rail.c \ rdp_settings.c \ + rdp_stream.c \ rdp_svc.c \ unicode.c @@ -89,6 +90,7 @@ noinst_HEADERS = \ rdp_rail.h \ rdp_settings.h \ rdp_status.h \ + rdp_stream.h \ rdp_svc.h \ unicode.h diff --git a/src/protocols/rdp/client.h b/src/protocols/rdp/client.h index 89bd0d8e..28b3902f 100644 --- a/src/protocols/rdp/client.h +++ b/src/protocols/rdp/client.h @@ -30,7 +30,6 @@ #include "rdp_fs.h" #include "rdp_keymap.h" #include "rdp_settings.h" -#include "rdp_svc.h" #include @@ -203,90 +202,6 @@ typedef struct rdp_freerdp_context { } rdp_freerdp_context; -/** - * The transfer status of a file being downloaded. - */ -typedef struct guac_rdp_download_status { - - /** - * The file ID of the file being downloaded. - */ - int file_id; - - /** - * The current position within the file. - */ - uint64_t offset; - -} guac_rdp_download_status; - -/** - * Structure which represents the current state of an upload. - */ -typedef struct guac_rdp_upload_status { - - /** - * The overall offset within the file that the next write should - * occur at. - */ - int offset; - - /** - * The ID of the file being written to. - */ - int file_id; - -} guac_rdp_upload_status; - -/** - * All available stream types. - */ -typedef enum guac_rdp_stream_type { - - /** - * An in-progress file upload. - */ - GUAC_RDP_UPLOAD_STREAM, - - /** - * An in-progress file download. - */ - GUAC_RDP_DOWNLOAD_STREAM, - - /** - * The inbound half of a static virtual channel. - */ - GUAC_RDP_INBOUND_SVC_STREAM - -} guac_rdp_stream_type; - -/** - * Variable-typed stream data. - */ -typedef struct guac_rdp_stream { - - /** - * The type of this stream. - */ - guac_rdp_stream_type type; - - /** - * The file upload status. Only valid for GUAC_RDP_UPLOAD_STREAM. - */ - guac_rdp_upload_status upload_status; - - /** - * The file upload status. Only valid for GUAC_RDP_DOWNLOAD_STREAM. - */ - guac_rdp_download_status download_status; - - /** - * Associated SVC instance. Only valid for GUAC_RDP_INBOUND_SVC_STREAM. - */ - guac_rdp_svc* svc; - -} guac_rdp_stream; - /** * Given the coordinates and dimensions of a rectangle, clips the rectangle to be * within the clipping bounds of the client data, if clipping is active. diff --git a/src/protocols/rdp/guac_handlers.c b/src/protocols/rdp/guac_handlers.c index 40813fe5..009fd5ff 100644 --- a/src/protocols/rdp/guac_handlers.c +++ b/src/protocols/rdp/guac_handlers.c @@ -28,7 +28,7 @@ #include "rdp_cliprdr.h" #include "rdp_keymap.h" #include "rdp_rail.h" -#include "rdp_svc.h" +#include "rdp_stream.h" #include #include @@ -492,164 +492,48 @@ int rdp_guac_client_clipboard_handler(guac_client* client, char* data) { } -/** - * Writes the given filename to the given upload path, sanitizing the filename - * and translating the filename to the root directory. - */ -static void __generate_upload_path(const char* filename, char* path) { - - int i; - - /* Add initial backslash */ - *(path++) = '\\'; - - for (i=1; idata)->filesystem; - if (fs == NULL) { - guac_protocol_send_ack(client->socket, stream, "FAIL (NO FS)", - GUAC_PROTOCOL_STATUS_INTERNAL_ERROR); - guac_socket_flush(client->socket); - return 0; - } - - /* Translate name */ - __generate_upload_path(filename, file_path); - - /* Open file */ - file_id = guac_rdp_fs_open(fs, file_path, ACCESS_GENERIC_WRITE, 0, - DISP_FILE_OVERWRITE_IF, 0); - if (file_id < 0) { - guac_protocol_send_ack(client->socket, stream, "FAIL (CANNOT OPEN)", - GUAC_PROTOCOL_STATUS_PERMISSION_DENIED); - guac_socket_flush(client->socket); - return 0; - } - - /* Init upload status */ - rdp_stream = malloc(sizeof(guac_rdp_stream)); - rdp_stream->type = GUAC_RDP_UPLOAD_STREAM; - rdp_stream->upload_status.offset = 0; - rdp_stream->upload_status.file_id = file_id; - stream->data = rdp_stream; - - guac_protocol_send_ack(client->socket, stream, "OK (STREAM BEGIN)", - GUAC_PROTOCOL_STATUS_SUCCESS); - guac_socket_flush(client->socket); - return 0; + /* All inbound files are file uploads */ + return guac_rdp_upload_file_handler(client, stream, mimetype, filename); } int rdp_guac_client_pipe_handler(guac_client* client, guac_stream* stream, char* mimetype, char* name) { - guac_rdp_stream* rdp_stream; - guac_rdp_svc* svc = guac_rdp_get_svc(client, name); - - /* Fail if no such SVC */ - if (svc == NULL) { - guac_client_log_error(client, - "Requested non-existent pipe: \"%s\".", - name); - guac_protocol_send_ack(client->socket, stream, "FAIL (NO SUCH PIPE)", - GUAC_PROTOCOL_STATUS_INVALID_PARAMETER); - guac_socket_flush(client->socket); - return 0; - } - else - guac_client_log_error(client, - "Inbound half of channel \"%s\" connected.", - name); - - /* Init stream data */ - stream->data = rdp_stream = malloc(sizeof(guac_rdp_stream)); - rdp_stream->type = GUAC_RDP_INBOUND_SVC_STREAM; - rdp_stream->svc = svc; - svc->input_pipe = stream; - - return 0; + /* All inbound pipes are SVC-related */ + return guac_rdp_svc_pipe_handler(client, stream, mimetype, name); } int rdp_guac_client_blob_handler(guac_client* client, guac_stream* stream, void* data, int length) { - int bytes_written; guac_rdp_stream* rdp_stream = (guac_rdp_stream*) stream->data; - /* Write any received data if upload stream */ - if (rdp_stream->type == GUAC_RDP_UPLOAD_STREAM) { + /* Handle based on stream type */ + switch (rdp_stream->type) { + + /* Inbound file stream */ + case GUAC_RDP_UPLOAD_STREAM: + return guac_rdp_upload_blob_handler(client, stream, data, length); + + /* SVC stream */ + case GUAC_RDP_INBOUND_SVC_STREAM: + return guac_rdp_svc_blob_handler(client, stream, data, length); + + /* Other streams do not accept blobs */ + default: + guac_protocol_send_ack(client->socket, stream, + "FAIL (BLOB NOT EXPECTED)", + GUAC_PROTOCOL_STATUS_INVALID_PARAMETER); - /* Get filesystem, return error if no filesystem 0*/ - guac_rdp_fs* fs = ((rdp_guac_client_data*) client->data)->filesystem; - if (fs == NULL) { - guac_protocol_send_ack(client->socket, stream, "FAIL (NO FS)", - GUAC_PROTOCOL_STATUS_INTERNAL_ERROR); guac_socket_flush(client->socket); return 0; - } - /* Write entire block */ - while (length > 0) { - - /* Attempt write */ - bytes_written = guac_rdp_fs_write(fs, - rdp_stream->upload_status.file_id, - rdp_stream->upload_status.offset, - data, length); - - /* On error, abort */ - if (bytes_written < 0) { - guac_protocol_send_ack(client->socket, stream, - "FAIL (BAD WRITE)", - GUAC_PROTOCOL_STATUS_PERMISSION_DENIED); - guac_socket_flush(client->socket); - return 0; - } - - /* Update counters */ - rdp_stream->upload_status.offset += bytes_written; - data += bytes_written; - length -= bytes_written; - - } - - } /* end if upload stream */ - - /* Handle received SVC data */ - else if (rdp_stream->type == GUAC_RDP_INBOUND_SVC_STREAM) - guac_rdp_svc_write(rdp_stream->svc, data, length); - - guac_protocol_send_ack(client->socket, stream, "OK (DATA RECEIVED)", - GUAC_PROTOCOL_STATUS_SUCCESS); - guac_socket_flush(client->socket); - return 0; + } } @@ -657,31 +541,24 @@ int rdp_guac_client_end_handler(guac_client* client, guac_stream* stream) { guac_rdp_stream* rdp_stream = (guac_rdp_stream*) stream->data; - /* Close file for upload streams */ - if (rdp_stream->type == GUAC_RDP_UPLOAD_STREAM) { + /* Handle based on stream type */ + switch (rdp_stream->type) { + + /* Inbound file stream */ + case GUAC_RDP_UPLOAD_STREAM: + return guac_rdp_upload_end_handler(client, stream); + + /* Other streams do not accept explicit closure */ + default: + guac_protocol_send_ack(client->socket, stream, + "FAIL (END NOT EXPECTED)", + GUAC_PROTOCOL_STATUS_INVALID_PARAMETER); - /* Get filesystem, return error if no filesystem */ - guac_rdp_fs* fs = ((rdp_guac_client_data*) client->data)->filesystem; - if (fs == NULL) { - guac_protocol_send_ack(client->socket, stream, "FAIL (NO FS)", - GUAC_PROTOCOL_STATUS_INTERNAL_ERROR); guac_socket_flush(client->socket); return 0; - } - - /* Close file */ - guac_rdp_fs_close(fs, rdp_stream->upload_status.file_id); } - /* Acknowledge stream end */ - guac_protocol_send_ack(client->socket, stream, "OK (STREAM END)", - GUAC_PROTOCOL_STATUS_SUCCESS); - guac_socket_flush(client->socket); - - free(rdp_stream); - return 0; - } int rdp_guac_client_ack_handler(guac_client* client, guac_stream* stream, @@ -693,61 +570,19 @@ int rdp_guac_client_ack_handler(guac_client* client, guac_stream* stream, if (rdp_stream == NULL) return 0; - /* Send more data when download blobs are acknowledged */ - if (rdp_stream->type == GUAC_RDP_DOWNLOAD_STREAM) { + /* Handle other acks based on stream type */ + switch (rdp_stream->type) { - /* Get filesystem, return error if no filesystem */ - guac_rdp_fs* fs = ((rdp_guac_client_data*) client->data)->filesystem; - if (fs == NULL) { - guac_protocol_send_ack(client->socket, stream, "FAIL (NO FS)", - GUAC_PROTOCOL_STATUS_INTERNAL_ERROR); - guac_socket_flush(client->socket); + /* Inbound file stream */ + case GUAC_RDP_DOWNLOAD_STREAM: + return guac_rdp_download_ack_handler( + client, stream, message, status); + + /* Ignore acks on all other streams */ + default: return 0; - } - - /* If successful, read data */ - if (status == GUAC_PROTOCOL_STATUS_SUCCESS) { - - /* Attempt read into buffer */ - char buffer[4096]; - int bytes_read = guac_rdp_fs_read(fs, - rdp_stream->download_status.file_id, - rdp_stream->download_status.offset, buffer, sizeof(buffer)); - - /* If bytes read, send as blob */ - if (bytes_read > 0) { - rdp_stream->download_status.offset += bytes_read; - guac_protocol_send_blob(client->socket, stream, - buffer, bytes_read); - } - - /* If EOF, send end */ - else if (bytes_read == 0) { - guac_protocol_send_end(client->socket, stream); - guac_client_free_stream(client, stream); - free(rdp_stream); - } - - /* Otherwise, fail stream */ - else { - guac_client_log_error(client, - "Error reading file for download"); - guac_protocol_send_end(client->socket, stream); - guac_client_free_stream(client, stream); - free(rdp_stream); - } - - guac_socket_flush(client->socket); - - } - - /* Otherwise, return stream to client */ - else - guac_client_free_stream(client, stream); } - return 0; - } diff --git a/src/protocols/rdp/guac_rdpdr/rdpdr_service.c b/src/protocols/rdp/guac_rdpdr/rdpdr_service.c index f04cc631..47891719 100644 --- a/src/protocols/rdp/guac_rdpdr/rdpdr_service.c +++ b/src/protocols/rdp/guac_rdpdr/rdpdr_service.c @@ -24,6 +24,7 @@ #include "client.h" #include "debug.h" +#include "rdp_stream.h" #include "rdpdr_fs_service.h" #include "rdpdr_messages.h" #include "rdpdr_printer.h" diff --git a/src/protocols/rdp/rdp_stream.c b/src/protocols/rdp/rdp_stream.c new file mode 100644 index 00000000..0569de8c --- /dev/null +++ b/src/protocols/rdp/rdp_stream.c @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2013 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +#include "config.h" +#include "client.h" +#include "rdp_fs.h" +#include "rdp_svc.h" +#include "rdp_stream.h" + +#include +#include +#include +#include + +#ifdef ENABLE_WINPR +#include +#else +#include "compat/winpr-wtypes.h" +#endif + +/** + * Writes the given filename to the given upload path, sanitizing the filename + * and translating the filename to the root directory. + */ +static void __generate_upload_path(const char* filename, char* path) { + + int i; + + /* Add initial backslash */ + *(path++) = '\\'; + + for (i=1; idata)->filesystem; + if (fs == NULL) { + guac_protocol_send_ack(client->socket, stream, "FAIL (NO FS)", + GUAC_PROTOCOL_STATUS_INTERNAL_ERROR); + guac_socket_flush(client->socket); + return 0; + } + + /* Translate name */ + __generate_upload_path(filename, file_path); + + /* Open file */ + file_id = guac_rdp_fs_open(fs, file_path, ACCESS_GENERIC_WRITE, 0, + DISP_FILE_OVERWRITE_IF, 0); + if (file_id < 0) { + guac_protocol_send_ack(client->socket, stream, "FAIL (CANNOT OPEN)", + GUAC_PROTOCOL_STATUS_PERMISSION_DENIED); + guac_socket_flush(client->socket); + return 0; + } + + /* Init upload status */ + rdp_stream = malloc(sizeof(guac_rdp_stream)); + rdp_stream->type = GUAC_RDP_UPLOAD_STREAM; + rdp_stream->upload_status.offset = 0; + rdp_stream->upload_status.file_id = file_id; + stream->data = rdp_stream; + + guac_protocol_send_ack(client->socket, stream, "OK (STREAM BEGIN)", + GUAC_PROTOCOL_STATUS_SUCCESS); + guac_socket_flush(client->socket); + return 0; + +} + +int guac_rdp_svc_pipe_handler(guac_client* client, guac_stream* stream, + char* mimetype, char* name) { + + guac_rdp_stream* rdp_stream; + guac_rdp_svc* svc = guac_rdp_get_svc(client, name); + + /* Fail if no such SVC */ + if (svc == NULL) { + guac_client_log_error(client, + "Requested non-existent pipe: \"%s\".", + name); + guac_protocol_send_ack(client->socket, stream, "FAIL (NO SUCH PIPE)", + GUAC_PROTOCOL_STATUS_INVALID_PARAMETER); + guac_socket_flush(client->socket); + return 0; + } + else + guac_client_log_error(client, + "Inbound half of channel \"%s\" connected.", + name); + + /* Init stream data */ + stream->data = rdp_stream = malloc(sizeof(guac_rdp_stream)); + rdp_stream->type = GUAC_RDP_INBOUND_SVC_STREAM; + rdp_stream->svc = svc; + svc->input_pipe = stream; + + return 0; + +} + +int guac_rdp_upload_blob_handler(guac_client* client, guac_stream* stream, + void* data, int length) { + + int bytes_written; + guac_rdp_stream* rdp_stream = (guac_rdp_stream*) stream->data; + + /* Get filesystem, return error if no filesystem 0*/ + guac_rdp_fs* fs = ((rdp_guac_client_data*) client->data)->filesystem; + if (fs == NULL) { + guac_protocol_send_ack(client->socket, stream, "FAIL (NO FS)", + GUAC_PROTOCOL_STATUS_INTERNAL_ERROR); + guac_socket_flush(client->socket); + return 0; + } + + /* Write entire block */ + while (length > 0) { + + /* Attempt write */ + bytes_written = guac_rdp_fs_write(fs, + rdp_stream->upload_status.file_id, + rdp_stream->upload_status.offset, + data, length); + + /* On error, abort */ + if (bytes_written < 0) { + guac_protocol_send_ack(client->socket, stream, + "FAIL (BAD WRITE)", + GUAC_PROTOCOL_STATUS_PERMISSION_DENIED); + guac_socket_flush(client->socket); + return 0; + } + + /* Update counters */ + rdp_stream->upload_status.offset += bytes_written; + data += bytes_written; + length -= bytes_written; + + } + + guac_protocol_send_ack(client->socket, stream, "OK (DATA RECEIVED)", + GUAC_PROTOCOL_STATUS_SUCCESS); + guac_socket_flush(client->socket); + return 0; + +} + +int guac_rdp_svc_blob_handler(guac_client* client, guac_stream* stream, + void* data, int length) { + + guac_rdp_stream* rdp_stream = (guac_rdp_stream*) stream->data; + + /* Write blob data to SVC directly */ + guac_rdp_svc_write(rdp_stream->svc, data, length); + + guac_protocol_send_ack(client->socket, stream, "OK (DATA RECEIVED)", + GUAC_PROTOCOL_STATUS_SUCCESS); + guac_socket_flush(client->socket); + return 0; + +} + +int guac_rdp_upload_end_handler(guac_client* client, guac_stream* stream) { + + guac_rdp_stream* rdp_stream = (guac_rdp_stream*) stream->data; + + /* Get filesystem, return error if no filesystem */ + guac_rdp_fs* fs = ((rdp_guac_client_data*) client->data)->filesystem; + if (fs == NULL) { + guac_protocol_send_ack(client->socket, stream, "FAIL (NO FS)", + GUAC_PROTOCOL_STATUS_INTERNAL_ERROR); + guac_socket_flush(client->socket); + return 0; + } + + /* Close file */ + guac_rdp_fs_close(fs, rdp_stream->upload_status.file_id); + + /* Acknowledge stream end */ + guac_protocol_send_ack(client->socket, stream, "OK (STREAM END)", + GUAC_PROTOCOL_STATUS_SUCCESS); + guac_socket_flush(client->socket); + + free(rdp_stream); + return 0; + +} + +int guac_rdp_download_ack_handler(guac_client* client, guac_stream* stream, + char* message, guac_protocol_status status) { + + guac_rdp_stream* rdp_stream = (guac_rdp_stream*) stream->data; + + /* Get filesystem, return error if no filesystem */ + guac_rdp_fs* fs = ((rdp_guac_client_data*) client->data)->filesystem; + if (fs == NULL) { + guac_protocol_send_ack(client->socket, stream, "FAIL (NO FS)", + GUAC_PROTOCOL_STATUS_INTERNAL_ERROR); + guac_socket_flush(client->socket); + return 0; + } + + /* If successful, read data */ + if (status == GUAC_PROTOCOL_STATUS_SUCCESS) { + + /* Attempt read into buffer */ + char buffer[4096]; + int bytes_read = guac_rdp_fs_read(fs, + rdp_stream->download_status.file_id, + rdp_stream->download_status.offset, buffer, sizeof(buffer)); + + /* If bytes read, send as blob */ + if (bytes_read > 0) { + rdp_stream->download_status.offset += bytes_read; + guac_protocol_send_blob(client->socket, stream, + buffer, bytes_read); + } + + /* If EOF, send end */ + else if (bytes_read == 0) { + guac_protocol_send_end(client->socket, stream); + guac_client_free_stream(client, stream); + free(rdp_stream); + } + + /* Otherwise, fail stream */ + else { + guac_client_log_error(client, + "Error reading file for download"); + guac_protocol_send_end(client->socket, stream); + guac_client_free_stream(client, stream); + free(rdp_stream); + } + + guac_socket_flush(client->socket); + + } + + /* Otherwise, return stream to client */ + else + guac_client_free_stream(client, stream); + + return 0; + +} + + diff --git a/src/protocols/rdp/rdp_stream.h b/src/protocols/rdp/rdp_stream.h new file mode 100644 index 00000000..e7cf6958 --- /dev/null +++ b/src/protocols/rdp/rdp_stream.h @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2013 Glyptodon LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +#ifndef _GUAC_RDP_STREAM_H +#define _GUAC_RDP_STREAM_H + +#include "config.h" +#include "rdp_svc.h" + +#include + +/** + * The transfer status of a file being downloaded. + */ +typedef struct guac_rdp_download_status { + + /** + * The file ID of the file being downloaded. + */ + int file_id; + + /** + * The current position within the file. + */ + uint64_t offset; + +} guac_rdp_download_status; + +/** + * Structure which represents the current state of an upload. + */ +typedef struct guac_rdp_upload_status { + + /** + * The overall offset within the file that the next write should + * occur at. + */ + int offset; + + /** + * The ID of the file being written to. + */ + int file_id; + +} guac_rdp_upload_status; + +/** + * All available stream types. + */ +typedef enum guac_rdp_stream_type { + + /** + * An in-progress file upload. + */ + GUAC_RDP_UPLOAD_STREAM, + + /** + * An in-progress file download. + */ + GUAC_RDP_DOWNLOAD_STREAM, + + /** + * The inbound half of a static virtual channel. + */ + GUAC_RDP_INBOUND_SVC_STREAM + +} guac_rdp_stream_type; + +/** + * Variable-typed stream data. + */ +typedef struct guac_rdp_stream { + + /** + * The type of this stream. + */ + guac_rdp_stream_type type; + + /** + * The file upload status. Only valid for GUAC_RDP_UPLOAD_STREAM. + */ + guac_rdp_upload_status upload_status; + + /** + * The file upload status. Only valid for GUAC_RDP_DOWNLOAD_STREAM. + */ + guac_rdp_download_status download_status; + + /** + * Associated SVC instance. Only valid for GUAC_RDP_INBOUND_SVC_STREAM. + */ + guac_rdp_svc* svc; + +} guac_rdp_stream; + +/** + * Handler for inbound files related to file uploads. + */ +int guac_rdp_upload_file_handler(guac_client* client, guac_stream* stream, + char* mimetype, char* filename); + +/** + * Handler for inbound pipes related to static virtual channels. + */ +int guac_rdp_svc_pipe_handler(guac_client* client, guac_stream* stream, + char* mimetype, char* name); + +/** + * Handler for stream data related to file uploads. + */ +int guac_rdp_upload_blob_handler(guac_client* client, guac_stream* stream, + void* data, int length); + +/** + * Handler for stream data related to static virtual channels. + */ +int guac_rdp_svc_blob_handler(guac_client* client, guac_stream* stream, + void* data, int length); + +/** + * Handler for end-of-stream related to file uploads. + */ +int guac_rdp_upload_end_handler(guac_client* client, guac_stream* stream); + +/** + * Handler for acknowledgements of receipt of data related to file downloads. + */ +int guac_rdp_download_ack_handler(guac_client* client, guac_stream* stream, + char* message, guac_protocol_status status); + +#endif +