GUAC-1172: Implement directory listing, file upload, and file download.

This commit is contained in:
Michael Jumper 2015-07-05 23:14:18 -07:00
parent fef6cd212b
commit 8a36358e94
4 changed files with 409 additions and 1 deletions

View File

@ -24,6 +24,7 @@
#include "rdp_fs.h"
#include "rdp_status.h"
#include "rdp_stream.h"
#include <dirent.h>
#include <errno.h>
@ -45,6 +46,8 @@ guac_rdp_fs* guac_rdp_fs_alloc(guac_client* client, const char* drive_path) {
fs->client = client;
fs->object = guac_client_alloc_object(client);
fs->object->get_handler = guac_rdp_download_get_handler;
fs->object->put_handler = guac_rdp_upload_put_handler;
fs->drive_path = strdup(drive_path);
fs->file_id_pool = guac_pool_alloc(0);
@ -180,7 +183,7 @@ int guac_rdp_fs_open(guac_rdp_fs* fs, const char* path,
path = "\\";
/* If path is relative, the file does not exist */
else if (path[0] != '\\') {
else if (path[0] != '\\' && path[0] != '/') {
guac_client_log(fs->client, GUAC_LOG_DEBUG,
"%s: Access denied - supplied path \"%s\" is relative.",
__func__, path);
@ -692,3 +695,65 @@ int guac_rdp_fs_get_info(guac_rdp_fs* fs, guac_rdp_fs_info* info) {
}
int guac_rdp_fs_append_filename(char* fullpath, const char* path,
const char* filename) {
int i;
/* Disallow "." as a filename */
if (strcmp(filename, ".") == 0)
return 0;
/* Disallow ".." as a filename */
if (strcmp(filename, "..") == 0)
return 0;
/* Copy path, append trailing slash */
for (i=0; i<GUAC_RDP_FS_MAX_PATH; i++) {
/*
* Append trailing slash only if:
* 1) Trailing slash is not already present
* 2) Path is non-empty
*/
char c = path[i];
if (c == '\0') {
if (i > 0 && path[i-1] != '/' && path[i-1] != '\\')
fullpath[i++] = '/';
break;
}
/* Copy character if not end of string */
fullpath[i] = c;
}
/* Append filename */
for (; i<GUAC_RDP_FS_MAX_PATH; i++) {
char c = *(filename++);
if (c == '\0')
break;
/* Filenames may not contain slashes */
if (c == '\\' || c == '/')
return 0;
/* Append each character within filename */
fullpath[i] = c;
}
/* Verify path length is within maximum */
if (i == GUAC_RDP_FS_MAX_PATH)
return 0;
/* Terminate path string */
fullpath[i] = '\0';
/* Append was successful */
return 1;
}

View File

@ -428,5 +428,27 @@ int guac_rdp_fs_matches(const char* filename, const char* pattern);
*/
int guac_rdp_fs_get_info(guac_rdp_fs* fs, guac_rdp_fs_info* info);
/**
* Concatenates the given filename with the given path, separating the two
* with a single forward slash. The full result must be no more than
* GUAC_RDP_FS_MAX_PATH bytes long, counting null terminator.
*
* @param fullpath
* The buffer to store the result within. This buffer must be at least
* GUAC_RDP_FS_MAX_PATH bytes long.
*
* @param path
* The path to append the filename to.
*
* @param filename
* The filename to append to the path.
*
* @return
* Non-zero if the filename is valid and was successfully appended to the
* path, zero otherwise.
*/
int guac_rdp_fs_append_filename(char* fullpath, const char* path,
const char* filename);
#endif

View File

@ -356,4 +356,216 @@ int guac_rdp_download_ack_handler(guac_client* client, guac_stream* stream,
}
int guac_rdp_ls_ack_handler(guac_client* client, guac_stream* stream,
char* message, guac_protocol_status status) {
int blob_written = 0;
const char* filename;
guac_rdp_stream* rdp_stream = (guac_rdp_stream*) stream->data;
/* If unsuccessful, free stream and abort */
if (status != GUAC_PROTOCOL_STATUS_SUCCESS) {
guac_rdp_fs_close(rdp_stream->ls_status.fs,
rdp_stream->ls_status.file_id);
guac_client_free_stream(client, stream);
free(rdp_stream);
return 0;
}
/* While directory entries remain */
while ((filename = guac_rdp_fs_read_dir(rdp_stream->ls_status.fs,
rdp_stream->ls_status.file_id)) != NULL
&& !blob_written) {
char absolute_path[GUAC_RDP_FS_MAX_PATH];
/* Skip current and parent directory entries */
if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0)
continue;
/* Concatenate into absolute path - skip if invalid */
if (!guac_rdp_fs_append_filename(absolute_path,
rdp_stream->ls_status.directory_name, filename)) {
guac_client_log(client, GUAC_LOG_DEBUG,
"Skipping filename \"%s\" - filename is invalid or "
"resulting path is too long", filename);
continue;
}
/* Attempt to open file to determine type */
int file_id = guac_rdp_fs_open(rdp_stream->ls_status.fs, absolute_path,
ACCESS_GENERIC_READ, 0, DISP_FILE_OPEN, 0);
if (file_id < 0)
continue;
/* Get opened file */
guac_rdp_fs_file* file = guac_rdp_fs_get_file(rdp_stream->ls_status.fs,
file_id);
if (file == NULL) {
guac_client_log(rdp_stream->ls_status.fs->client, GUAC_LOG_DEBUG,
"%s: Successful open produced bad file_id: %i",
__func__, file_id);
return 0;
}
/* Determine mimetype */
const char* mimetype;
if (file->attributes & FILE_ATTRIBUTE_DIRECTORY)
mimetype = GUAC_CLIENT_STREAM_INDEX_MIMETYPE;
else
mimetype = "application/octet-stream";
/* Write entry */
blob_written |= guac_common_json_write_property(client, stream,
&rdp_stream->ls_status.json_state, absolute_path, mimetype);
guac_rdp_fs_close(rdp_stream->ls_status.fs, file_id);
}
/* Complete JSON and cleanup at end of directory */
if (filename == NULL) {
/* Complete JSON object */
guac_common_json_end_object(client, stream,
&rdp_stream->ls_status.json_state);
guac_common_json_flush(client, stream,
&rdp_stream->ls_status.json_state);
/* Clean up resources */
guac_rdp_fs_close(rdp_stream->ls_status.fs,
rdp_stream->ls_status.file_id);
free(rdp_stream);
/* Signal of stream */
guac_protocol_send_end(client->socket, stream);
guac_client_free_stream(client, stream);
}
guac_socket_flush(client->socket);
return 0;
}
int guac_rdp_download_get_handler(guac_client* client, guac_object* object,
char* name) {
/* Get filesystem, ignore request if no filesystem */
guac_rdp_fs* fs = ((rdp_guac_client_data*) client->data)->filesystem;
if (fs == NULL)
return 0;
/* Attempt to open file for reading */
int file_id = guac_rdp_fs_open(fs, name, ACCESS_GENERIC_READ, 0,
DISP_FILE_OPEN, 0);
if (file_id < 0) {
guac_client_log(client, GUAC_LOG_INFO, "Unable to read file \"%s\"",
name);
return 0;
}
/* Get opened file */
guac_rdp_fs_file* file = guac_rdp_fs_get_file(fs, file_id);
if (file == NULL) {
guac_client_log(fs->client, GUAC_LOG_DEBUG,
"%s: Successful open produced bad file_id: %i",
__func__, file_id);
return 0;
}
/* If directory, send contents of directory */
if (file->attributes & FILE_ATTRIBUTE_DIRECTORY) {
/* Create stream data */
guac_rdp_stream* rdp_stream = malloc(sizeof(guac_rdp_stream));
rdp_stream->type = GUAC_RDP_LS_STREAM;
rdp_stream->ls_status.fs = fs;
rdp_stream->ls_status.file_id = file_id;
strncpy(rdp_stream->ls_status.directory_name, name,
sizeof(rdp_stream->ls_status.directory_name));
/* Allocate stream for body */
guac_stream* stream = guac_client_alloc_stream(client);
stream->ack_handler = guac_rdp_ls_ack_handler;
stream->data = rdp_stream;
/* Init JSON object state */
guac_common_json_begin_object(client, stream,
&rdp_stream->ls_status.json_state);
/* Associate new stream with get request */
guac_protocol_send_body(client->socket, object, stream,
GUAC_CLIENT_STREAM_INDEX_MIMETYPE, name);
}
/* Otherwise, send file contents */
else {
/* Create stream data */
guac_rdp_stream* rdp_stream = malloc(sizeof(guac_rdp_stream));
rdp_stream->type = GUAC_RDP_DOWNLOAD_STREAM;
rdp_stream->download_status.file_id = file_id;
rdp_stream->download_status.offset = 0;
/* Allocate stream for body */
guac_stream* stream = guac_client_alloc_stream(client);
stream->data = rdp_stream;
stream->ack_handler = guac_rdp_download_ack_handler;
/* Associate new stream with get request */
guac_protocol_send_body(client->socket, object, stream,
"application/octet-stream", name);
}
guac_socket_flush(client->socket);
return 0;
}
int guac_rdp_upload_put_handler(guac_client* client, guac_object* object,
guac_stream* stream, char* mimetype, char* name) {
/* 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_SERVER_ERROR);
guac_socket_flush(client->socket);
return 0;
}
/* Open file */
int file_id = guac_rdp_fs_open(fs, name, ACCESS_GENERIC_WRITE, 0,
DISP_FILE_OVERWRITE_IF, 0);
/* Abort on failure */
if (file_id < 0) {
guac_protocol_send_ack(client->socket, stream, "FAIL (CANNOT OPEN)",
GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN);
guac_socket_flush(client->socket);
return 0;
}
/* Init upload stream data */
guac_rdp_stream* 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;
/* Allocate stream, init for file upload */
stream->data = rdp_stream;
stream->blob_handler = guac_rdp_upload_blob_handler;
stream->end_handler = guac_rdp_upload_end_handler;
/* Acknowledge stream creation */
guac_protocol_send_ack(client->socket, stream, "OK (STREAM BEGIN)",
GUAC_PROTOCOL_STATUS_SUCCESS);
guac_socket_flush(client->socket);
return 0;
}

View File

@ -25,6 +25,7 @@
#define _GUAC_RDP_STREAM_H
#include "config.h"
#include "guac_json.h"
#include "rdp_svc.h"
#include <guacamole/client.h>
@ -68,6 +69,33 @@ typedef struct guac_rdp_upload_status {
} guac_rdp_upload_status;
/**
* The current state of a directory listing operation.
*/
typedef struct guac_rdp_ls_status {
/**
* The filesystem associated with the directory being listed.
*/
guac_rdp_fs* fs;
/**
* The file ID of the directory being listed.
*/
int file_id;
/**
* The absolute path of the directory being listed.
*/
char directory_name[GUAC_RDP_FS_MAX_PATH];
/**
* The current state of the JSON directory object being written.
*/
guac_common_json_state json_state;
} guac_rdp_ls_status;
/**
* All available stream types.
*/
@ -83,6 +111,11 @@ typedef enum guac_rdp_stream_type {
*/
GUAC_RDP_DOWNLOAD_STREAM,
/**
* An in-progress stream of a directory listing.
*/
GUAC_RDP_LS_STREAM,
/**
* The inbound half of a static virtual channel.
*/
@ -115,6 +148,11 @@ typedef struct guac_rdp_stream {
*/
guac_rdp_download_status download_status;
/**
* The directory list status. Only valid for GUAC_RDP_LS_STREAM.
*/
guac_rdp_ls_status ls_status;
/**
* Associated SVC instance. Only valid for GUAC_RDP_INBOUND_SVC_STREAM.
*/
@ -174,5 +212,76 @@ int guac_rdp_clipboard_end_handler(guac_client* client, guac_stream* stream);
int guac_rdp_download_ack_handler(guac_client* client, guac_stream* stream,
char* message, guac_protocol_status status);
/**
* Handler for ack messages received due to receipt of a "body" or "blob"
* instruction associated with a directory list operation.
*
* @param client
* The client receiving the ack message.
*
* @param stream
* The Guacamole protocol stream associated with the received ack message.
*
* @param message
* An arbitrary human-readable message describing the nature of the
* success or failure denoted by this ack message.
*
* @param status
* The status code associated with this ack message, which may indicate
* success or an error.
*
* @return
* Zero on success, non-zero on error.
*/
int guac_rdp_ls_ack_handler(guac_client* client, guac_stream* stream,
char* message, guac_protocol_status status);
/**
* Handler for get messages. In context of downloads and the filesystem exposed
* via the Guacamole protocol, get messages request the body of a file within
* the filesystem.
*
* @param client
* The client receiving the get message.
*
* @param object
* The Guacamole protocol object associated with the get request itself.
*
* @param name
* The name of the input stream (file) being requested.
*
* @return
* Zero on success, non-zero on error.
*/
int guac_rdp_download_get_handler(guac_client* client, guac_object* object,
char* name);
/**
* Handler for put messages. In context of uploads and the filesystem exposed
* via the Guacamole protocol, put messages request write access to a file
* within the filesystem.
*
* @param client
* The client receiving the put message.
*
* @param object
* The Guacamole protocol object associated with the put request itself.
*
* @param stream
* The Guacamole protocol stream along which the client will be sending
* file data.
*
* @param mimetype
* The mimetype of the data being send along the stream.
*
* @param name
* The name of the input stream (file) being requested.
*
* @return
* Zero on success, non-zero on error.
*/
int guac_rdp_upload_put_handler(guac_client* client, guac_object* object,
guac_stream* stream, char* mimetype, char* name);
#endif