GUAC-1172: Implement directory listing, file upload, and file download.
This commit is contained in:
parent
fef6cd212b
commit
8a36358e94
@ -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;
|
||||
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user