cscg24-guacamole

CSCG 2024 Challenge 'Guacamole Mashup'
git clone https://git.sinitax.com/sinitax/cscg24-guacamole
Log | Files | Refs | sfeed.txt

fs.c (23696B)


      1/*
      2 * Licensed to the Apache Software Foundation (ASF) under one
      3 * or more contributor license agreements.  See the NOTICE file
      4 * distributed with this work for additional information
      5 * regarding copyright ownership.  The ASF licenses this file
      6 * to you under the Apache License, Version 2.0 (the
      7 * "License"); you may not use this file except in compliance
      8 * with the License.  You may obtain a copy of the License at
      9 *
     10 *   http://www.apache.org/licenses/LICENSE-2.0
     11 *
     12 * Unless required by applicable law or agreed to in writing,
     13 * software distributed under the License is distributed on an
     14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     15 * KIND, either express or implied.  See the License for the
     16 * specific language governing permissions and limitations
     17 * under the License.
     18 */
     19
     20#include "fs.h"
     21#include "download.h"
     22#include "upload.h"
     23
     24#include <guacamole/client.h>
     25#include <guacamole/mem.h>
     26#include <guacamole/object.h>
     27#include <guacamole/pool.h>
     28#include <guacamole/protocol.h>
     29#include <guacamole/socket.h>
     30#include <guacamole/string.h>
     31#include <guacamole/user.h>
     32#include <winpr/file.h>
     33#include <winpr/nt.h>
     34
     35#include <dirent.h>
     36#include <errno.h>
     37#include <fcntl.h>
     38#include <fnmatch.h>
     39#include <stdio.h>
     40#include <stdlib.h>
     41#include <string.h>
     42#include <sys/stat.h>
     43#include <sys/statvfs.h>
     44#include <unistd.h>
     45
     46guac_rdp_fs* guac_rdp_fs_alloc(guac_client* client, const char* drive_path,
     47        int create_drive_path, int disable_download, int disable_upload) {
     48
     49    /* Create drive path if it does not exist */
     50    if (create_drive_path) {
     51        guac_client_log(client, GUAC_LOG_DEBUG,
     52               "%s: Creating directory \"%s\" if necessary.",
     53               __func__, drive_path);
     54
     55        /* Log error if directory creation fails */
     56        if (mkdir(drive_path, S_IRWXU) && errno != EEXIST) {
     57            guac_client_log(client, GUAC_LOG_ERROR,
     58                    "Unable to create directory \"%s\": %s",
     59                    drive_path, strerror(errno));
     60        }
     61    }
     62
     63    guac_rdp_fs* fs = guac_mem_alloc(sizeof(guac_rdp_fs));
     64
     65    fs->client = client;
     66    fs->drive_path = guac_strdup(drive_path);
     67    fs->file_id_pool = guac_pool_alloc(0);
     68    fs->open_files = 0;
     69    fs->disable_download = disable_download;
     70    fs->disable_upload = disable_upload;
     71
     72    return fs;
     73
     74}
     75
     76void guac_rdp_fs_free(guac_rdp_fs* fs) {
     77    guac_pool_free(fs->file_id_pool);
     78    guac_mem_free(fs->drive_path);
     79    guac_mem_free(fs);
     80}
     81
     82guac_object* guac_rdp_fs_alloc_object(guac_rdp_fs* fs, guac_user* user) {
     83    
     84    /* Init filesystem */
     85    guac_object* fs_object = guac_user_alloc_object(user);
     86    fs_object->get_handler = guac_rdp_download_get_handler;
     87    
     88    /* Assign handler only if uploads are not disabled. */
     89    if (!fs->disable_upload)
     90        fs_object->put_handler = guac_rdp_upload_put_handler;
     91    
     92    fs_object->data = fs;
     93
     94    /* Send filesystem to user */
     95    guac_protocol_send_filesystem(user->socket, fs_object, "Shared Drive");
     96    guac_socket_flush(user->socket);
     97
     98    return fs_object;
     99
    100}
    101
    102void* guac_rdp_fs_expose(guac_user* user, void* data) {
    103
    104    guac_rdp_fs* fs = (guac_rdp_fs*) data;
    105
    106    /* No need to expose if there is no filesystem or the user has left */
    107    if (user == NULL || fs == NULL)
    108        return NULL;
    109
    110    /* Allocate and expose filesystem object for user */
    111    return guac_rdp_fs_alloc_object(fs, user);
    112
    113}
    114
    115/**
    116 * Translates an absolute Windows path to an absolute path which is within the
    117 * "drive path" specified in the connection settings. No checking is performed
    118 * on the path provided, which is assumed to have already been normalized and
    119 * validated as absolute.
    120 *
    121 * @param fs
    122 *     The filesystem containing the file whose path is being translated.
    123 *
    124 * @param virtual_path
    125 *     The absolute path to the file on the simulated filesystem, relative to
    126 *     the simulated filesystem root.
    127 *
    128 * @param real_path
    129 *     The buffer in which to store the absolute path to the real file on the
    130 *     local filesystem.
    131 */
    132static void __guac_rdp_fs_translate_path(guac_rdp_fs* fs,
    133        const char* virtual_path, char* real_path) {
    134
    135    /* Get drive path */
    136    char* drive_path = fs->drive_path;
    137
    138    int i;
    139
    140    /* Start with path from settings */
    141    for (i=0; i<GUAC_RDP_FS_MAX_PATH-1; i++) {
    142
    143        /* Break on end-of-string */
    144        char c = *(drive_path++);
    145        if (c == 0)
    146            break;
    147
    148        /* Copy character */
    149        *(real_path++) = c;
    150
    151    }
    152
    153    /* Translate path */
    154    for (; i<GUAC_RDP_FS_MAX_PATH-1; i++) {
    155
    156        /* Stop at end of string */
    157        char c = *(virtual_path++);
    158        if (c == 0)
    159            break;
    160
    161        /* Translate backslashes to forward slashes */
    162        if (c == '\\')
    163            c = '/';
    164
    165        /* Store in real path buffer */
    166        *(real_path++)= c;
    167
    168    }
    169
    170    /* Null terminator */
    171    *real_path = 0;
    172
    173}
    174
    175int guac_rdp_fs_get_errorcode(int err) {
    176
    177    /* Translate errno codes to GUAC_RDP_FS codes */
    178    if (err == ENFILE)  return GUAC_RDP_FS_ENFILE;
    179    if (err == ENOENT)  return GUAC_RDP_FS_ENOENT;
    180    if (err == ENOTDIR) return GUAC_RDP_FS_ENOTDIR;
    181    if (err == ENOSPC)  return GUAC_RDP_FS_ENOSPC;
    182    if (err == EISDIR)  return GUAC_RDP_FS_EISDIR;
    183    if (err == EACCES)  return GUAC_RDP_FS_EACCES;
    184    if (err == EEXIST)  return GUAC_RDP_FS_EEXIST;
    185    if (err == EINVAL)  return GUAC_RDP_FS_EINVAL;
    186    if (err == ENOSYS)  return GUAC_RDP_FS_ENOSYS;
    187    if (err == ENOTSUP) return GUAC_RDP_FS_ENOTSUP;
    188
    189    /* Default to invalid parameter */
    190    return GUAC_RDP_FS_EINVAL;
    191
    192}
    193
    194int guac_rdp_fs_get_status(int err) {
    195
    196    /* Translate GUAC_RDP_FS error code to RDPDR status code */
    197    if (err == GUAC_RDP_FS_ENFILE)  return STATUS_NO_MORE_FILES;
    198    if (err == GUAC_RDP_FS_ENOENT)  return STATUS_NO_SUCH_FILE;
    199    if (err == GUAC_RDP_FS_ENOTDIR) return STATUS_NOT_A_DIRECTORY;
    200    if (err == GUAC_RDP_FS_ENOSPC)  return STATUS_DISK_FULL;
    201    if (err == GUAC_RDP_FS_EISDIR)  return STATUS_FILE_IS_A_DIRECTORY;
    202    if (err == GUAC_RDP_FS_EACCES)  return STATUS_ACCESS_DENIED;
    203    if (err == GUAC_RDP_FS_EEXIST)  return STATUS_OBJECT_NAME_COLLISION;
    204    if (err == GUAC_RDP_FS_EINVAL)  return STATUS_INVALID_PARAMETER;
    205    if (err == GUAC_RDP_FS_ENOSYS)  return STATUS_NOT_IMPLEMENTED;
    206    if (err == GUAC_RDP_FS_ENOTSUP) return STATUS_NOT_SUPPORTED;
    207
    208    /* Default to invalid parameter */
    209    return STATUS_INVALID_PARAMETER;
    210
    211}
    212
    213int guac_rdp_fs_open(guac_rdp_fs* fs, const char* path,
    214        int access, int file_attributes, int create_disposition,
    215        int create_options) {
    216
    217    char real_path[GUAC_RDP_FS_MAX_PATH];
    218    char normalized_path[GUAC_RDP_FS_MAX_PATH];
    219
    220    struct stat file_stat;
    221    int fd;
    222    int file_id;
    223    guac_rdp_fs_file* file;
    224
    225    int flags = 0;
    226
    227    guac_client_log(fs->client, GUAC_LOG_DEBUG,
    228            "%s: path=\"%s\", access=0x%x, file_attributes=0x%x, "
    229            "create_disposition=0x%x, create_options=0x%x",
    230            __func__, path, access, file_attributes,
    231            create_disposition, create_options);
    232
    233    /* If no files available, return too many open */
    234    if (fs->open_files >= GUAC_RDP_FS_MAX_FILES) {
    235        guac_client_log(fs->client, GUAC_LOG_DEBUG,
    236                "%s: Too many open files.",
    237                __func__, path);
    238        return GUAC_RDP_FS_ENFILE;
    239    }
    240
    241    /* If path empty, transform to root path */
    242    if (path[0] == '\0')
    243        path = "\\";
    244
    245    /* If path is relative, the file does not exist */
    246    else if (path[0] != '\\' && path[0] != '/') {
    247        guac_client_log(fs->client, GUAC_LOG_DEBUG,
    248                "%s: Access denied - supplied path \"%s\" is relative.",
    249                __func__, path);
    250        return GUAC_RDP_FS_ENOENT;
    251    }
    252
    253    /* Translate access into flags */
    254    if (access & GENERIC_ALL)
    255        flags = O_RDWR;
    256    else if ((access & ( GENERIC_WRITE
    257                       | FILE_WRITE_DATA
    258                       | FILE_APPEND_DATA))
    259          && (access & (GENERIC_READ  | FILE_READ_DATA)))
    260        flags = O_RDWR;
    261    else if (access & ( GENERIC_WRITE
    262                      | FILE_WRITE_DATA
    263                      | FILE_APPEND_DATA))
    264        flags = O_WRONLY;
    265    else
    266        flags = O_RDONLY;
    267
    268    /* Normalize path, return no-such-file if invalid  */
    269    if (guac_rdp_fs_normalize_path(path, normalized_path)) {
    270        guac_client_log(fs->client, GUAC_LOG_DEBUG,
    271                "%s: Normalization of path \"%s\" failed.", __func__, path);
    272        return GUAC_RDP_FS_ENOENT;
    273    }
    274
    275    guac_client_log(fs->client, GUAC_LOG_DEBUG,
    276            "%s: Normalized path \"%s\" to \"%s\".",
    277            __func__, path, normalized_path);
    278
    279    /* Translate normalized path to real path */
    280    __guac_rdp_fs_translate_path(fs, normalized_path, real_path);
    281
    282    guac_client_log(fs->client, GUAC_LOG_DEBUG,
    283            "%s: Translated path \"%s\" to \"%s\".",
    284            __func__, normalized_path, real_path);
    285
    286    switch (create_disposition) {
    287
    288        /* Create if not exist, fail otherwise */
    289        case FILE_CREATE:
    290            flags |= O_CREAT | O_EXCL;
    291            break;
    292
    293        /* Open file if exists and do not overwrite, fail otherwise */
    294        case FILE_OPEN:
    295            /* No flag necessary - default functionality of open */
    296            break;
    297
    298        /* Open if exists, create otherwise */
    299        case FILE_OPEN_IF:
    300            flags |= O_CREAT;
    301            break;
    302
    303        /* Overwrite if exists, fail otherwise */
    304        case FILE_OVERWRITE:
    305            flags |= O_TRUNC;
    306            break;
    307
    308        /* Overwrite if exists, create otherwise */
    309        case FILE_OVERWRITE_IF:
    310            flags |= O_CREAT | O_TRUNC;
    311            break;
    312
    313        /* Supersede (replace) if exists, otherwise create */
    314        case FILE_SUPERSEDE:
    315            unlink(real_path);
    316            flags |= O_CREAT | O_TRUNC;
    317            break;
    318
    319        /* Unrecognised disposition */
    320        default:
    321            return GUAC_RDP_FS_ENOSYS;
    322
    323    }
    324
    325    /* Create directory first, if necessary */
    326    if ((create_options & FILE_DIRECTORY_FILE) && (flags & O_CREAT)) {
    327
    328        /* Create directory */
    329        if (mkdir(real_path, S_IRWXU)) {
    330            if (errno != EEXIST || (flags & O_EXCL)) {
    331                guac_client_log(fs->client, GUAC_LOG_DEBUG,
    332                        "%s: mkdir() failed: %s",
    333                        __func__, strerror(errno));
    334                return guac_rdp_fs_get_errorcode(errno);
    335            }
    336        }
    337
    338        /* Unset O_CREAT and O_EXCL as directory must exist before open() */
    339        flags &= ~(O_CREAT | O_EXCL);
    340
    341    }
    342
    343    guac_client_log(fs->client, GUAC_LOG_DEBUG,
    344            "%s: native open: real_path=\"%s\", flags=0x%x",
    345            __func__, real_path, flags);
    346
    347    /* Open file */
    348    fd = open(real_path, flags, S_IRUSR | S_IWUSR);
    349
    350    /* If file open failed as we're trying to write a dir, retry as read-only */
    351    if (fd == -1 && errno == EISDIR) {
    352        flags &= ~(O_WRONLY | O_RDWR);
    353        flags |= O_RDONLY;
    354        fd = open(real_path, flags, S_IRUSR | S_IWUSR);
    355    }
    356
    357    if (fd == -1) {
    358        guac_client_log(fs->client, GUAC_LOG_DEBUG,
    359                "%s: open() failed: %s", __func__, strerror(errno));
    360        return guac_rdp_fs_get_errorcode(errno);
    361    }
    362
    363    /* Get file ID, init file */
    364    file_id = guac_pool_next_int(fs->file_id_pool);
    365    file = &(fs->files[file_id]);
    366    file->id = file_id;
    367    file->fd  = fd;
    368    file->dir = NULL;
    369    file->dir_pattern[0] = '\0';
    370    file->absolute_path = guac_strdup(normalized_path);
    371    file->real_path = guac_strdup(real_path);
    372    file->bytes_written = 0;
    373
    374    guac_client_log(fs->client, GUAC_LOG_DEBUG,
    375            "%s: Opened \"%s\" as file_id=%i",
    376            __func__, normalized_path, file_id);
    377
    378    /* Attempt to pull file information */
    379    if (fstat(fd, &file_stat) == 0) {
    380
    381        /* Load size and times */
    382        file->size  = file_stat.st_size;
    383        file->ctime = WINDOWS_TIME(file_stat.st_ctime);
    384        file->mtime = WINDOWS_TIME(file_stat.st_mtime);
    385        file->atime = WINDOWS_TIME(file_stat.st_atime);
    386
    387        /* Set type */
    388        if (S_ISDIR(file_stat.st_mode))
    389            file->attributes = FILE_ATTRIBUTE_DIRECTORY;
    390        else
    391            file->attributes = FILE_ATTRIBUTE_NORMAL;
    392
    393    }
    394
    395    /* If information cannot be retrieved, fake it */
    396    else {
    397
    398        /* Init information to 0, lacking any alternative */
    399        file->size  = 0;
    400        file->ctime = 0;
    401        file->mtime = 0;
    402        file->atime = 0;
    403        file->attributes = FILE_ATTRIBUTE_NORMAL;
    404
    405    }
    406
    407    fs->open_files++;
    408
    409    return file_id;
    410
    411}
    412
    413int guac_rdp_fs_read(guac_rdp_fs* fs, int file_id, uint64_t offset,
    414        void* buffer, int length) {
    415
    416    int bytes_read;
    417
    418    guac_rdp_fs_file* file = guac_rdp_fs_get_file(fs, file_id);
    419    if (file == NULL) {
    420        guac_client_log(fs->client, GUAC_LOG_DEBUG,
    421                "%s: Read from bad file_id: %i", __func__, file_id);
    422        return GUAC_RDP_FS_EINVAL;
    423    }
    424
    425    /* Attempt read */
    426    lseek(file->fd, offset, SEEK_SET);
    427    bytes_read = read(file->fd, buffer, length);
    428
    429    /* Translate errno on error */
    430    if (bytes_read < 0)
    431        return guac_rdp_fs_get_errorcode(errno);
    432
    433    return bytes_read;
    434
    435}
    436
    437int guac_rdp_fs_write(guac_rdp_fs* fs, int file_id, uint64_t offset,
    438        void* buffer, int length) {
    439
    440    int bytes_written;
    441
    442    guac_rdp_fs_file* file = guac_rdp_fs_get_file(fs, file_id);
    443    if (file == NULL) {
    444        guac_client_log(fs->client, GUAC_LOG_DEBUG,
    445                "%s: Write to bad file_id: %i", __func__, file_id);
    446        return GUAC_RDP_FS_EINVAL;
    447    }
    448
    449    /* Attempt write */
    450    lseek(file->fd, offset, SEEK_SET);
    451    bytes_written = write(file->fd, buffer, length);
    452
    453    /* Translate errno on error */
    454    if (bytes_written < 0)
    455        return guac_rdp_fs_get_errorcode(errno);
    456
    457    file->bytes_written += bytes_written;
    458    return bytes_written;
    459
    460}
    461
    462int guac_rdp_fs_rename(guac_rdp_fs* fs, int file_id,
    463        const char* new_path) {
    464
    465    char real_path[GUAC_RDP_FS_MAX_PATH];
    466    char normalized_path[GUAC_RDP_FS_MAX_PATH];
    467
    468    guac_rdp_fs_file* file = guac_rdp_fs_get_file(fs, file_id);
    469    if (file == NULL) {
    470        guac_client_log(fs->client, GUAC_LOG_DEBUG,
    471                "%s: Rename of bad file_id: %i", __func__, file_id);
    472        return GUAC_RDP_FS_EINVAL;
    473    }
    474
    475    /* Normalize path, return no-such-file if invalid  */
    476    if (guac_rdp_fs_normalize_path(new_path, normalized_path)) {
    477        guac_client_log(fs->client, GUAC_LOG_DEBUG,
    478                "%s: Normalization of path \"%s\" failed.",
    479                __func__, new_path);
    480        return GUAC_RDP_FS_ENOENT;
    481    }
    482
    483    /* Translate normalized path to real path */
    484    __guac_rdp_fs_translate_path(fs, normalized_path, real_path);
    485
    486    guac_client_log(fs->client, GUAC_LOG_DEBUG,
    487            "%s: Renaming \"%s\" -> \"%s\"",
    488            __func__, file->real_path, real_path);
    489
    490    /* Perform rename */
    491    if (rename(file->real_path, real_path)) {
    492        guac_client_log(fs->client, GUAC_LOG_DEBUG,
    493                "%s: rename() failed: \"%s\" -> \"%s\"",
    494                __func__, file->real_path, real_path);
    495        return guac_rdp_fs_get_errorcode(errno);
    496    }
    497
    498    return 0;
    499
    500}
    501
    502int guac_rdp_fs_delete(guac_rdp_fs* fs, int file_id) {
    503
    504    /* Get file */
    505    guac_rdp_fs_file* file = guac_rdp_fs_get_file(fs, file_id);
    506    if (file == NULL) {
    507        guac_client_log(fs->client, GUAC_LOG_DEBUG,
    508                "%s: Delete of bad file_id: %i", __func__, file_id);
    509        return GUAC_RDP_FS_EINVAL;
    510    }
    511
    512    /* If directory, attempt removal */
    513    if (file->attributes & FILE_ATTRIBUTE_DIRECTORY) {
    514        if (rmdir(file->real_path)) {
    515            guac_client_log(fs->client, GUAC_LOG_DEBUG,
    516                    "%s: rmdir() failed: \"%s\"", __func__, file->real_path);
    517            return guac_rdp_fs_get_errorcode(errno);
    518        }
    519    }
    520
    521    /* Otherwise, attempt deletion */
    522    else if (unlink(file->real_path)) {
    523        guac_client_log(fs->client, GUAC_LOG_DEBUG,
    524                "%s: unlink() failed: \"%s\"", __func__, file->real_path);
    525        return guac_rdp_fs_get_errorcode(errno);
    526    }
    527
    528    return 0;
    529
    530}
    531
    532int guac_rdp_fs_truncate(guac_rdp_fs* fs, int file_id, int length) {
    533
    534    /* Get file */
    535    guac_rdp_fs_file* file = guac_rdp_fs_get_file(fs, file_id);
    536    if (file == NULL) {
    537        guac_client_log(fs->client, GUAC_LOG_DEBUG,
    538                "%s: Delete of bad file_id: %i", __func__, file_id);
    539        return GUAC_RDP_FS_EINVAL;
    540    }
    541
    542    /* Attempt truncate */
    543    if (ftruncate(file->fd, length)) {
    544        guac_client_log(fs->client, GUAC_LOG_DEBUG,
    545                "%s: ftruncate() to %i bytes failed: \"%s\"",
    546                __func__, length, file->real_path);
    547        return guac_rdp_fs_get_errorcode(errno);
    548    }
    549
    550    return 0;
    551
    552}
    553
    554void guac_rdp_fs_close(guac_rdp_fs* fs, int file_id) {
    555
    556    guac_rdp_fs_file* file = guac_rdp_fs_get_file(fs, file_id);
    557    if (file == NULL) {
    558        guac_client_log(fs->client, GUAC_LOG_DEBUG,
    559                "%s: Ignoring close for bad file_id: %i",
    560                __func__, file_id);
    561        return;
    562    }
    563
    564    file = &(fs->files[file_id]);
    565
    566    guac_client_log(fs->client, GUAC_LOG_DEBUG,
    567            "%s: Closed \"%s\" (file_id=%i)",
    568            __func__, file->absolute_path, file_id);
    569
    570    /* Close directory, if open */
    571    if (file->dir != NULL)
    572        closedir(file->dir);
    573
    574    /* Close file */
    575    close(file->fd);
    576
    577    /* Free name */
    578    guac_mem_free(file->absolute_path);
    579    guac_mem_free(file->real_path);
    580
    581    /* Free ID back to pool */
    582    guac_pool_free_int(fs->file_id_pool, file_id);
    583    fs->open_files--;
    584
    585}
    586
    587const char* guac_rdp_fs_read_dir(guac_rdp_fs* fs, int file_id) {
    588
    589    guac_rdp_fs_file* file;
    590
    591    struct dirent* result;
    592
    593    /* Only read if file ID is valid */
    594    if (file_id < 0 || file_id >= GUAC_RDP_FS_MAX_FILES)
    595        return NULL;
    596
    597    file = &(fs->files[file_id]);
    598
    599    /* Open directory if not yet open, stop if error */
    600    if (file->dir == NULL) {
    601        file->dir = fdopendir(file->fd);
    602        if (file->dir == NULL)
    603            return NULL;
    604    }
    605
    606    /* Read next entry, stop if error or no more entries */
    607    if ((result = readdir(file->dir)) == NULL)
    608        return NULL;
    609
    610    /* Return filename */
    611    return result->d_name;
    612
    613}
    614
    615const char* guac_rdp_fs_basename(const char* path) {
    616
    617    for (const char* c = path; *c != '\0'; c++) {
    618
    619        /* Reset beginning of path if a path separator is found */
    620        if (*c == '/' || *c == '\\')
    621            path = c + 1;
    622
    623    }
    624
    625    /* path now points to the first character after the last path separator */
    626    return path;
    627
    628}
    629
    630int guac_rdp_fs_normalize_path(const char* path, char* abs_path) {
    631
    632    int path_depth = 0;
    633    const char* path_components[GUAC_RDP_MAX_PATH_DEPTH];
    634
    635    /* If original path is not absolute, normalization fails */
    636    if (path[0] != '\\' && path[0] != '/')
    637        return 1;
    638
    639    /* Create scratch copy of path excluding leading slash (we will be
    640     * replacing path separators with null terminators and referencing those
    641     * substrings directly as path components) */
    642    char path_scratch[GUAC_RDP_FS_MAX_PATH - 1];
    643    int length = guac_strlcpy(path_scratch, path + 1,
    644            sizeof(path_scratch));
    645
    646    /* Fail if provided path is too long */
    647    if (length >= sizeof(path_scratch))
    648        return 1;
    649
    650    /* Locate all path components within path */
    651    const char* current_path_component = &(path_scratch[0]);
    652    for (int i = 0; i <= length; i++) {
    653
    654        /* If current character is a path separator, parse as component */
    655        char c = path_scratch[i];
    656        if (c == '/' || c == '\\' || c == '\0') {
    657
    658            /* Terminate current component */
    659            path_scratch[i] = '\0';
    660
    661            /* If component refers to parent, just move up in depth */
    662            if (strcmp(current_path_component, "..") == 0) {
    663                if (path_depth > 0)
    664                    path_depth--;
    665            }
    666
    667            /* Otherwise, if component not current directory, add to list */
    668            else if (strcmp(current_path_component, ".") != 0
    669                    && strcmp(current_path_component, "") != 0) {
    670
    671                /* Fail normalization if path is too deep */
    672                if (path_depth >= GUAC_RDP_MAX_PATH_DEPTH)
    673                    return 1;
    674
    675                path_components[path_depth++] = current_path_component;
    676
    677            }
    678
    679            /* Update start of next component */
    680            current_path_component = &(path_scratch[i+1]);
    681
    682        } /* end if separator */
    683
    684        /* We do not currently support named streams */
    685        else if (c == ':')
    686            return 1;
    687
    688    } /* end for each character */
    689
    690    /* Add leading slash for resulting absolute path */
    691    abs_path[0] = '\\';
    692
    693    /* Append normalized components to path, separated by slashes */
    694    guac_strljoin(abs_path + 1, path_components, path_depth,
    695            "\\", GUAC_RDP_FS_MAX_PATH - 1);
    696
    697    return 0;
    698
    699}
    700
    701int guac_rdp_fs_convert_path(const char* parent, const char* rel_path, char* abs_path) {
    702
    703    int length;
    704    char combined_path[GUAC_RDP_FS_MAX_PATH];
    705
    706    /* Copy parent path */
    707    length = guac_strlcpy(combined_path, parent, sizeof(combined_path));
    708
    709    /* Add trailing slash */
    710    length += guac_strlcpy(combined_path + length, "\\",
    711            sizeof(combined_path) - length);
    712
    713    /* Copy remaining path */
    714    length += guac_strlcpy(combined_path + length, rel_path,
    715            sizeof(combined_path) - length);
    716
    717    /* Normalize into provided buffer */
    718    return guac_rdp_fs_normalize_path(combined_path, abs_path);
    719
    720}
    721
    722guac_rdp_fs_file* guac_rdp_fs_get_file(guac_rdp_fs* fs, int file_id) {
    723
    724    /* Validate ID */
    725    if (file_id < 0 || file_id >= GUAC_RDP_FS_MAX_FILES)
    726        return NULL;
    727
    728    /* Return file at given ID */
    729    return &(fs->files[file_id]);
    730
    731}
    732
    733int guac_rdp_fs_matches(const char* filename, const char* pattern) {
    734    return fnmatch(pattern, filename, FNM_NOESCAPE) != 0;
    735}
    736
    737int guac_rdp_fs_get_info(guac_rdp_fs* fs, guac_rdp_fs_info* info) {
    738
    739    /* Read FS information */
    740    struct statvfs fs_stat;
    741    if (statvfs(fs->drive_path, &fs_stat))
    742        return guac_rdp_fs_get_errorcode(errno);
    743
    744    /* Assign to structure */
    745    info->blocks_available = fs_stat.f_bfree;
    746    info->blocks_total = fs_stat.f_blocks;
    747    info->block_size = fs_stat.f_bsize;
    748    return 0;
    749
    750}
    751
    752int guac_rdp_fs_append_filename(char* fullpath, const char* path,
    753        const char* filename) {
    754
    755    int i;
    756
    757    /* Disallow "." as a filename */
    758    if (strcmp(filename, ".") == 0)
    759        return 0;
    760
    761    /* Disallow ".." as a filename */
    762    if (strcmp(filename, "..") == 0)
    763        return 0;
    764
    765    /* Copy path, append trailing slash */
    766    for (i=0; i<GUAC_RDP_FS_MAX_PATH; i++) {
    767
    768        /*
    769         * Append trailing slash only if:
    770         *  1) Trailing slash is not already present
    771         *  2) Path is non-empty
    772         */
    773
    774        char c = path[i];
    775        if (c == '\0') {
    776            if (i > 0 && path[i-1] != '/' && path[i-1] != '\\')
    777                fullpath[i++] = '/';
    778            break;
    779        }
    780
    781        /* Copy character if not end of string */
    782        fullpath[i] = c;
    783
    784    }
    785
    786    /* Append filename */
    787    for (; i<GUAC_RDP_FS_MAX_PATH; i++) {
    788
    789        char c = *(filename++);
    790        if (c == '\0')
    791            break;
    792
    793        /* Filenames may not contain slashes */
    794        if (c == '\\' || c == '/')
    795            return 0;
    796
    797        /* Append each character within filename */
    798        fullpath[i] = c;
    799
    800    }
    801
    802    /* Verify path length is within maximum */
    803    if (i == GUAC_RDP_FS_MAX_PATH)
    804        return 0;
    805
    806    /* Terminate path string */
    807    fullpath[i] = '\0';
    808
    809    /* Append was successful */
    810    return 1;
    811
    812}
    813