From 18cb5c616fb2b3cc44fa9efc1cf5fbcc97e5d64b Mon Sep 17 00:00:00 2001 From: Dylan G <1565516+Helloman892@users.noreply.github.com> Date: Thu, 17 Jun 2021 01:43:50 +0100 Subject: [PATCH] Correctly handle cases where files already exist on the filesystem. Also: patched a bug wherein the filename filter was acting up, transforming `file.zip` to `file-zip.zip`, for example. --- include/fs.h | 1 + source/camera.c | 9 +--- source/fs.c | 114 ++++++++++++++++++++++++++++++++++++++++++++++++ source/remote.c | 89 ++++++++++++------------------------- 4 files changed, 145 insertions(+), 68 deletions(-) diff --git a/include/fs.h b/include/fs.h index 72a9370..3078c2a 100644 --- a/include/fs.h +++ b/include/fs.h @@ -44,5 +44,6 @@ u32 compress_lz_file_fast(FS_Path path, FS_Archive archive, char *in_buf, u32 si Result buf_to_file(u32 size, FS_Path path, FS_Archive archive, char *buf); void remake_file(FS_Path path, FS_Archive archive, u32 size); +void save_zip_to_sd(char * filename, u32 size, char * buf, EntryMode mode); #endif diff --git a/source/camera.c b/source/camera.c index 7c8de42..d2bd871 100644 --- a/source/camera.c +++ b/source/camera.c @@ -391,14 +391,7 @@ bool init_qr(void) if(mode != MODE_AMOUNT) { - char path_to_file[0x107] = {0}; - sprintf(path_to_file, "%s%s", main_paths[mode], filename); - char * extension = strrchr(path_to_file, '.'); - if (extension == NULL || strcmp(extension, ".zip")) - strcat(path_to_file, ".zip"); - - remake_file(fsMakePath(PATH_ASCII, path_to_file), ArchiveSD, zip_size); - buf_to_file(zip_size, fsMakePath(PATH_ASCII, path_to_file), ArchiveSD, zip_buf); + save_zip_to_sd(filename, zip_size, zip_buf, mode); success = true; } else diff --git a/source/fs.c b/source/fs.c index 4ad6b1b..abc302c 100644 --- a/source/fs.c +++ b/source/fs.c @@ -27,6 +27,7 @@ #include #include "fs.h" +#include "draw.h" #include "unicode.h" #include @@ -351,3 +352,116 @@ void remake_file(FS_Path path, FS_Archive archive, u32 size) buf_to_file(size, path, archive, buf); free(buf); } + +#define ILLEGAL_CHARS "><\"?;:/\\+,.|[=]" +static SwkbdCallbackResult fat32filter(void *user, const char **ppMessage, const char *text, size_t textlen) +{ + (void)textlen; + (void)user; + *ppMessage = "Input must not contain:\n" ILLEGAL_CHARS; + if(strpbrk(text, ILLEGAL_CHARS)) + { + DEBUG("illegal filename: %s\n", text); + return SWKBD_CALLBACK_CONTINUE; + } + + return SWKBD_CALLBACK_OK; +} + +// assumes the input buffer is a ZIP. if it isn't, why are you calling this? +void save_zip_to_sd(char * filename, u32 size, char * buf, EntryMode mode) +{ + static char path_to_file[32761]; // FAT32 paths can be quite long. + const int max_chars = 250; + char new_filename[max_chars + 5]; // .zip + \0 +renamed: + sprintf(path_to_file, "%s%s", main_paths[mode], filename); + + // filter out characters illegal in FAT32 filenames + char * curr_filename = strrchr(path_to_file, '/') + 1; + char * illegal_char = curr_filename; + while ((illegal_char = strpbrk(illegal_char, ILLEGAL_CHARS))) + { + DEBUG("Illegal char found in filename: %c\n", *illegal_char); + if (*illegal_char == '.') + { + // skip initial . (this is allowed) + if (illegal_char == curr_filename) + continue; + // skip extension delimiter + if (strpbrk(illegal_char + 1, ".") == NULL) + { + illegal_char++; + continue; + } + } + *illegal_char = '-'; + } + + // ensure the extension is .zip + char * extension = strrchr(path_to_file, '.'); + if (extension == NULL || strcmp(extension, ".zip")) + strcat(path_to_file, ".zip"); + + DEBUG("path: %s\n", path_to_file); + FS_Path path = fsMakePath(PATH_ASCII, path_to_file); + + // check if file already exists, and if it does, prompt the user + // to overwrite or change name (or exit) + Result res = FSUSER_CreateFile(ArchiveSD, path, 0, size); + if (R_FAILED(res)) + { + if (res == (long)0xC82044BE) + { + DEBUG("File already exists\n"); + + SwkbdState swkbd; + + swkbdInit(&swkbd, SWKBD_TYPE_NORMAL, 3, max_chars / 2); + swkbdSetHintText(&swkbd, "Choose a new filename or tap Overwrite"); + swkbdSetFeatures(&swkbd, SWKBD_PREDICTIVE_INPUT | SWKBD_DARKEN_TOP_SCREEN); + + swkbdSetButton(&swkbd, SWKBD_BUTTON_LEFT, "Cancel", false); + swkbdSetButton(&swkbd, SWKBD_BUTTON_MIDDLE, "Overwrite", false); + swkbdSetButton(&swkbd, SWKBD_BUTTON_RIGHT, "Rename", true); + swkbdSetValidation(&swkbd, SWKBD_NOTEMPTY_NOTBLANK, SWKBD_FILTER_CALLBACK, -1); + swkbdSetFilterCallback(&swkbd, &fat32filter, NULL); + + SwkbdButton button = swkbdInputText(&swkbd, new_filename, max_chars); + + switch (button) + { + case SWKBD_BUTTON_RIGHT: + DEBUG("Renaming to %s\n", new_filename); + strcat(new_filename, ".zip"); + filename = new_filename; + goto renamed; + case SWKBD_BUTTON_MIDDLE: + // we good + DEBUG("Overwriting %s\n", filename); + break; + case SWKBD_BUTTON_LEFT: + // do nothing + DEBUG("File rename cancelled\n"); + return; + case SWKBD_BUTTON_NONE: + DEBUG("SWKBD broke wtf??? :- %x\n", swkbdGetResult(&swkbd)); + return throw_error("???\nTry a USB keyboard", ERROR_LEVEL_WARNING); + } + } + else if (res == (long)0xC86044D2) + { + DEBUG("SD card is full\n"); + return throw_error("SD card is full.\nDelete some themes to make space.", ERROR_LEVEL_WARNING); + } + else + { + DEBUG("error: %lx\n", res); + return throw_error("FS Error:\nGet a new SD card.", ERROR_LEVEL_ERROR); + } + } + + DEBUG("Saving to SD: %s\n", path_to_file); + remake_file(path, ArchiveSD, size); + buf_to_file(size, path, ArchiveSD, buf); +} diff --git a/source/remote.c b/source/remote.c index cac7cd2..3950acb 100644 --- a/source/remote.c +++ b/source/remote.c @@ -24,6 +24,8 @@ * reasonable ways as different from the original version. */ +#include + #include "remote.h" #include "loading.h" #include "fs.h" @@ -358,17 +360,8 @@ static void download_remote_entry(Entry_s * entry, EntryMode mode) } free(download_url); - char path_to_file[0x107] = { 0 }; - sprintf(path_to_file, "%s%s", main_paths[mode], filename); + save_zip_to_sd(filename, zip_size, zip_buf, mode); free(filename); - - char * extension = strrchr(path_to_file, '.'); - if (extension == NULL || strcmp(extension, ".zip")) - strcat(path_to_file, ".zip"); - - DEBUG("Saving to SD: %s\n", path_to_file); - remake_file(fsMakePath(PATH_ASCII, path_to_file), ArchiveSD, zip_size); - buf_to_file(zip_size, fsMakePath(PATH_ASCII, path_to_file), ArchiveSD, zip_buf); free(zip_buf); } @@ -759,7 +752,6 @@ typedef enum ParseResult SUCCESS, // 200/203 (203 indicates a successful request with a transformation applied by a proxy) REDIRECT, // 301/302/307/308 HTTPC_ERROR, - ABORTED, SERVER_IS_MISBEHAVING, SEE_OTHER = 303, // Theme Plaza returns these HTTP_UNAUTHORIZED = 401, @@ -856,7 +848,7 @@ static ParseResult parse_header(struct header * out, httpcContext * context, con out->result_code = httpcGetResponseHeader(context, "Content-Disposition", content_buf, 1024); if (R_FAILED(out->result_code)) { - if (out->result_code == 0xD8A0A028) + if (out->result_code == (long)0xD8A0A028L) present = 0; else { @@ -867,55 +859,37 @@ static ParseResult parse_header(struct header * out, httpcContext * context, con // content_buf: Content-Disposition: attachment; ... filename=;? ... - if (!present) + if (present) { - const int max_chars = 250; - // needs to be heap allocated only because the call site is expected to free it - *out->filename = malloc(max_chars + 5); // + .zip and the null term - - SwkbdState swkbd; - - swkbdInit(&swkbd, SWKBD_TYPE_NORMAL, 2, max_chars / 2); - swkbdSetHintText(&swkbd, "Choose a filename"); - swkbdSetFeatures(&swkbd, SWKBD_PREDICTIVE_INPUT | SWKBD_DARKEN_TOP_SCREEN); - - swkbdSetButton(&swkbd, SWKBD_BUTTON_LEFT, "Cancel", false); - swkbdSetButton(&swkbd, SWKBD_BUTTON_RIGHT, "Download", true); - swkbdSetValidation(&swkbd, SWKBD_NOTEMPTY_NOTBLANK, SWKBD_FILTER_CALLBACK, -1); - swkbdSetFilterCallback(&swkbd, &fat32filter, NULL); - - SwkbdButton button = swkbdInputText(&swkbd, *out->filename, max_chars); - - if (button != SWKBD_BUTTON_CONFIRM) + char * filename = strstr(content_buf, "filename="); // filename=;? ... + // in the extreme fringe case that filename is missing: + if (filename != NULL) { - out->result_code = swkbdGetResult(&swkbd); - return ABORTED; + filename = strpbrk(filename, "=") + 1; // ;? + char * end = strpbrk(filename, ";"); + if (end) + *end = '\0'; // + + // safe to assume the filename is quoted + // (if it isn't, then we already have a null-terminated string ) + if (filename[0] == '"') + { + filename[strlen(filename) - 1] = '\0'; + filename++; + } + + *out->filename = malloc(strlen(filename) + 1); + strcpy(*out->filename, filename); + } + else + { + *out->filename = NULL; } - - strcat(*out->filename, ".zip"); - return SUCCESS; } - - char * filename = strstr(content_buf, "filename="); // filename=;? ... - filename = strpbrk(filename, "=") + 1; // ;? - char * end = strpbrk(filename, ";"); - if (end) - *end = '\0'; // - - if (filename[0] == '"') - // safe to assume the filename is quoted + else { - filename[strlen(filename) - 1] = '\0'; - filename++; + *out->filename = NULL; } - - char * illegal_char; - // filter out characters illegal in FAT32 filenames - while ((illegal_char = strpbrk(filename, "><\"?;:/\\+,.|[=]"))) - *illegal_char = '-'; - - *out->filename = malloc(strlen(filename) + 1); - strcpy(*out->filename, filename); } return SUCCESS; } @@ -967,11 +941,6 @@ redirect: // goto here if we need to redirect { case SUCCESS: break; - case ABORTED: - ret = httpcCloseContext(&context); - if(R_FAILED(ret)) - return ret; - return MAKERESULT(RL_SUCCESS, RS_CANCELED, RM_APPLICATION, RD_CANCEL_REQUESTED); case HTTPC_ERROR: DEBUG("httpc error %lx\n", _header.result_code); snprintf(err_buf, ERROR_BUFFER_SIZE, "Error in HTTPC sysmodule - 0x%08lx.\nIf you are seeing this, please contact an\nAnemone developer on the Theme Plaza Discord.", _header.result_code);