AmendHub

Download:

jcs

/

subtext

/

amendments

/

194

folder: Finally integrate with ZMODEM for downloads and uploads


jcs made amendment 194 about 1 year ago
--- folder.c Thu Jun 30 15:38:24 2022 +++ folder.c Tue Jul 12 11:32:19 2022 @@ -22,7 +22,9 @@ #include "subtext.h" #include "ansi.h" #include "folder.h" +#include "sha1.h" #include "user.h" +#include "zmodem.h" #define FILES_PER_PAGE 10 @@ -36,10 +38,13 @@ struct struct_field folder_fields[] = { 1, ULONG_MAX }, { "Name", CONFIG_TYPE_STRING, offsetof(struct folder, name), - 1, member_size(struct board, name) }, + 1, member_size(struct folder, name) }, + { "Disk Path", CONFIG_TYPE_STRING, + offsetof(struct folder, path), + 1, member_size(struct folder, path) }, { "Description", CONFIG_TYPE_STRING, offsetof(struct folder, description), - 0, member_size(struct board, description) }, + 0, member_size(struct folder, description) }, { "Restricted Posting", CONFIG_TYPE_BOOLEAN, offsetof(struct folder, restricted_posting), 0, 0 }, @@ -64,6 +69,8 @@ struct bile_object_field folder_object_fields[] = { member_size(struct folder, last_upload_at), -1 }, { offsetof(struct folder, file_count), member_size(struct folder, file_count), -1 }, + { offsetof(struct folder, path), + member_size(struct folder, path), -1 }, }; size_t nfolder_object_fields = nitems(folder_object_fields); @@ -82,20 +89,17 @@ struct bile_object_field folder_file_object_fields[] = member_size(struct folder_file, size), -1 }, { offsetof(struct folder_file, sha1_checksum), member_size(struct folder_file, sha1_checksum), -1 }, + { offsetof(struct folder_file, notes_size), + member_size(struct folder_file, notes_size), -1 }, { offsetof(struct folder_file, notes), -1, offsetof(struct folder_file, notes_size) }, }; size_t nfolder_file_object_fields = nitems(folder_file_object_fields); -enum { - PROTO_XMODEM = 1, - PROTO_ZMODEM -}; - unsigned long folder_upload(struct session *s, struct folder *folder, char *initial_filename, char *initial_description); void folder_list_files(struct session *s, struct folder *folder, - size_t nfile_Ids, unsigned long *file_ids, size_t page, size_t pages); + size_t nfile_ids, unsigned long *file_ids, size_t page, size_t pages); void folder_show(struct session *s, struct folder *folder); short folder_file_view(struct session *s, struct folder *folder, unsigned long id, short idx); @@ -103,12 +107,17 @@ void folder_delete_file(struct session *s, struct fold struct folder_file *file); size_t folder_find_file_ids(struct folder *folder, size_t *nfile_ids, unsigned long **file_ids); -short folder_file_save(struct folder *folder, struct folder_file *file); +bool folder_file_save(struct folder *folder, struct folder_file *file, + char *temp_path); +bool folder_file_valid_filename(struct session *session, + struct folder *folder, struct folder_file *file, char **error); +void folder_edit_file(struct session *s, struct folder *folder, + struct folder_file *file); void folder_list(struct session *s) { - static struct session_menu_option opts[] = { + static const struct session_menu_option opts[] = { { '#', "#0123456789", "View file folder [#]" }, { 'l', "Ll", "List folders" }, { 'q', "QqXx", "Return to main menu" }, @@ -179,7 +188,7 @@ handle_opt: void folder_show(struct session *s, struct folder *folder) { - static struct session_menu_option opts[] = { + static const struct session_menu_option opts[] = { { '#', "#0123456789", "View file [#]" }, { '<', "<", "Previous page of files" }, { 'l', "Ll", "List files" }, @@ -188,32 +197,40 @@ folder_show(struct session *s, struct folder *folder) { 'q', "QqXx", "Return to main menu" }, { '?', "?", "List menu options" }, }; + char prompt[6 + BOARD_NAME_LENGTH + 8]; size_t n, page, pages, nfile_ids; unsigned long *file_ids = NULL; short ret; char c; - bool done, show_list, show_help; + bool done, show_list, show_help, find_files; page = 0; show_list = true; + find_files = true; show_help = false; done = false; - folder_find_file_ids(folder, &nfile_ids, &file_ids); - /* ceil(nfile_ids / FILES_PER_PAGE) */ - pages = (nfile_ids + FILES_PER_PAGE - 1) / FILES_PER_PAGE; + snprintf(prompt, sizeof(prompt), "Files:%s", folder->name); - if (page >= pages) - page = pages - 1; - while (!done && !s->ending) { + if (find_files) { + folder_find_file_ids(folder, &nfile_ids, &file_ids); + /* ceil(nfile_ids / FILES_PER_PAGE) */ + pages = (nfile_ids + FILES_PER_PAGE - 1) / FILES_PER_PAGE; + + if (page >= pages) + page = pages - 1; + + find_files = false; + } + if (show_list) { folder_list_files(s, folder, nfile_ids, file_ids, page + 1, pages); show_list = false; } - c = session_menu(s, folder->description, folder->name, opts, + c = session_menu(s, folder->description, prompt, opts, nitems(opts), show_help); show_help = false; @@ -223,7 +240,8 @@ handle_opt: show_list = true; break; case 'u': - folder_upload(s, folder, NULL, NULL); + if (folder_upload(s, folder, NULL, NULL)) + find_files = true; break; case '>': case '<': @@ -259,7 +277,9 @@ handle_opt: break; } c = folder_file_view(s, folder, file_ids[c], c); - goto handle_opt; + if (c == FILE_VIEW_RETURN_FIND) + find_files = true; + break; case '?': show_help = true; break; @@ -279,7 +299,6 @@ folder_list_files(struct session *s, struct folder *fo { char time[24]; size_t n, off, size, idx; - struct username_cache *user; struct bile_object *obj; struct folder_file file; short j, k; @@ -287,7 +306,8 @@ folder_list_files(struct session *s, struct folder *fo session_printf(s, "{{B}}%s: %s (Page %ld of %ld){{/B}}\r\n", folder->name, folder->description, page, pages); - session_printf(s, "%s# Date From File Description%s\r\n", + session_printf(s, + "%s# Date File Description%s\r\n", ansi(s, ANSI_BOLD, ANSI_END), ansi(s, ANSI_RESET, ANSI_END)); session_flush(s); @@ -309,14 +329,13 @@ folder_list_files(struct session *s, struct folder *fo nfolder_file_object_fields, data, size, &file, false); free(data); - user = user_find_username(file.uploader_user_id); - strftime(time, sizeof(time), "%b %d", localtime(&file.time)); + strftime(time, sizeof(time), "%Y-%m-%d", localtime(&file.time)); - session_printf(s, "%ld %s {{#}}%- 10s %- 40s\r\n", + session_printf(s, "%ld %s {{#}}%- 17s %- 40s\r\n", n, - user ? user->username : "(unknown)", + time, file.filename, - ""); + file.description); } session_flush(s); } @@ -325,10 +344,17 @@ unsigned long folder_upload(struct session *s, struct folder *folder, char *initial_filename, char *initial_description) { + char upload_path[256] = { 0 }; + char time[32]; struct folder_file file = { 0 }; - char *data = NULL, *tmp = NULL; + FILE *fp; + SHA1_CTX sha1; + struct stat sb; + unsigned long now; + struct zmodem_session *zs; + char *data = NULL, *tmp = NULL, *errorstr = NULL; size_t size; - short c, ret, proto = -1, error; + short c, ret, error; if (!s->user) { session_printf(s, "Uploading is not available to guests.\r\n" @@ -344,71 +370,133 @@ folder_upload(struct session *s, struct folder *folder session_printf(s, "{{B}}Upload New File{{/B}}\r\n"); session_printf(s, - "Your file will be received and temporarily stored. You'll\r\n"); + "To begin your ZMODEM upload, press Enter and choose a file through\r\n"); session_printf(s, - "then be given the option to change the filename and enter a\r\n"); + "your terminal program (only one file can be uploaded at a time).\r\n"); session_printf(s, - "description for the file.\r\n\r\n"); - session_printf(s, - "Upload file via (X)MODEM or (Z)MODEM? [x/Z] "); + "\r\n" + "You'll then be given the option to change the filename and enter a\r\n" + "description for the file.\r\n" + "\r\n" + "To cancel your upload, press ^C a few times.\r\n\r\n"); session_flush(s); + session_pause_return(s, 0, "when you are ready..."); + + now = Time; + strftime(time, sizeof(time), "%Y%m%d%H%M%S", localtime(&now)); + snprintf(upload_path, sizeof(upload_path), + "%s:upload-%s-%ld", folder->path, time, s->log.id); - while (proto < 0) { - c = session_input_char(s); - if (c == 0 || s->ending) - return 0; - - switch (c) { - case 'x': - case 'X': - session_printf(s, "%c\r\n", c); - session_flush(s); - proto = PROTO_XMODEM; + zs = ZCreateReceiver(s, upload_path); + zs->DoIACEscape = s->is_telnet; + + ZInit(zs); + s->transferring_file = true; + session_log(s, "[%s] Uploading file to %s", folder->name, upload_path); + for (;;) { + if (ZHaveTimedOut(zs)) { + ZTimeOutProc(zs); + session_log(s, "[%s] Transfer timed out, canceling", + folder->name); break; - case '\r': - case '\n': - c = 'z'; - case 'z': - case 'Z': - session_printf(s, "%c\r\n", c); - session_flush(s); - proto = PROTO_ZMODEM; - break; - case CONTROL_C: - return 0; } + + if (!ZParse(zs)) + break; + + if (s->obuflen) + session_flush(s); } + + /* flush the pipes */ + s->obuflen = 0; + uthread_msleep(1000); + session_clear_input(s); + s->transferring_file = false; + + if (zs->file) { + fclose(zs->file); + zs->file = NULL; + } - session_printf(s, - "\r\n" - "To begin your %s upload, press Enter and choose a file through\r\n", - (proto == PROTO_XMODEM ? "XMODEM" : "ZMODEM")); - session_printf(s, - "your terminal program (only one file can be uploaded at a time).\r\n"); - session_printf(s, - "\r\n" - "To cancel your upload, press ^C a few times.\r\n\r\n"); - session_flush(s); - session_pause_return(s, false, "when you are ready..."); + session_printf(s, "\r\n\r\n"); - /* TODO: do xmodem or zmodem */ - - if (file.tmp_filename[0] == '\0') { - session_printf(s, "No file received, canceling.\r\n"); - session_flush(s); + if (stat(upload_path, &sb) != 0) { + session_printf(s, "Failed receiving file, aborting.\r\n"); + session_pause_return(s, '\r', "to continue..."); + ZDestroy(zs); + zs = NULL; + session_log(s, "[%s] Failed receiving upload, no temp file", + folder->name); return 0; } - session_printf(s, "{{B}}From:{{/B}} %s\r\n", s->user->username); - session_printf(s, "{{B}}To:{{/B}} %s\r\n", folder->name); + if (sb.st_size != zs->file_size) { + session_log(s, "[%s] Received uploaded file of size %ld but " + "supposed to be %ld, canceling", folder->name, sb.st_size, + zs->file_size); + + session_printf(s, + "Uploaded file expected to be {{B}}%ld{{/B}} byte%s, but " + "received\r\n" + "{{B}}%ld{{/B}} byte%s. Deleting file and canceling upload.\r\n", + zs->file_size, zs->file_size == 1 ? "" : "s", + sb.st_size, sb.st_size == 1 ? "" : "s"); + session_pause_return(s, '\r', "to continue..."); + + ZDestroy(zs); + zs = NULL; + goto file_upload_cancel; + } + session_printf(s, "Successfully received file {{B}}%s{{/B}} of size " + "{{B}}%ld{{/B}} byte%s.\r\n", + zs->file_name, zs->file_size, zs->file_size == 1 ? "" : "s"); + session_pause_return(s, '\r', "to continue..."); + + strlcpy(file.filename, zs->file_name, sizeof(file.filename)); + file.size = sb.st_size; + + session_log(s, "[%s] Received uploaded file %s of size %ld", + folder->name, file.filename, file.size); + + /* this will fclose and free zs */ + ZDestroy(zs); + zs = NULL; + + session_printf(s, "Calculating SHA1 checksum of uploaded file..."); + session_flush(s); + SHA1Init(&sha1); + data = xmalloc(1024); + fp = fopen(upload_path, "rb"); + while (fp && !feof(fp)) { + size = fread(data, 1024, 1, fp); + SHA1Update(&sha1, (const u_int8_t *)data, size); + uthread_yield(); + } + if (fp) + fclose(fp); + free(data); + + SHA1End(&sha1, (char *)&file.sha1_checksum); + session_printf(s, "done.\r\n\r\n"); + session_flush(s); + session_printf(s, "{{B}}Uploaded By:{{/B}} %s\r\n", + s->user->username); + session_printf(s, "{{B}}Folder:{{/B}} %s\r\n", + folder->name); + session_printf(s, "{{B}}SHA1 Checksum:{{/B}} %s\r\n", + file.sha1_checksum); + session_printf(s, "{{B}}File Size:{{/B}} %ld\r\n", file.size); + session_flush(s); + file_upload_annotate: for (;;) { - session_printf(s, "{{B}}Name:{{/B}} "); + session_printf(s, "{{B}}File Name:{{/B}} "); session_flush(s); tmp = session_field_input(s, FOLDER_FILE_FILENAME_LENGTH, - FOLDER_FILE_FILENAME_LENGTH - 1, file.filename, true, 0); + FOLDER_FILE_FILENAME_LENGTH - 1, file.filename, false, 0); if (tmp == NULL) goto file_upload_cancel; strlcpy(file.filename, tmp, sizeof(file.filename)); @@ -421,12 +509,40 @@ file_upload_annotate: session_flush(s); continue; } - /* TODO: verify filename */ + + if (!folder_file_valid_filename(s, folder, &file, &errorstr)) { + session_printf(s, "{{B}}Error:{{/B}} %s\r\n", errorstr); + free(errorstr); + continue; + } + break; } + + for (;;) { + session_printf(s, "{{B}}Short Description:{{/B}} "); + session_flush(s); + tmp = session_field_input(s, FOLDER_FILE_DESCR_LENGTH, + FOLDER_FILE_DESCR_LENGTH - 1, file.description, false, 0); + if (tmp == NULL) + goto file_upload_cancel; + strlcpy(file.description, tmp, sizeof(file.description)); + free(tmp); + session_output(s, "\r\n", 2); + session_flush(s); + if (file.description[0] == '\0') { + session_output_template(s, "{{B}}Error:{{/B}} File " + "description cannot be blank (^C to cancel)\r\n"); + session_flush(s); + continue; + } + + break; + } + for (;;) { - session_printf(s, "{{B}}Notes:{{/B}} (Optional)\r\n"); + session_printf(s, "{{B}}Notes:{{/B}} (Optional, ^D when finished.)\r\n"); session_flush(s); tmp = session_field_input(s, FOLDER_FILE_DESCR_LENGTH, @@ -440,6 +556,12 @@ file_upload_annotate: file.notes_size = 0; break; } + if (file.notes[0] == '\0') { + free(file.notes); + file.notes = NULL; + file.notes_size = 0; + break; + } file.notes_size = strlen(file.notes) + 1; break; } @@ -462,20 +584,22 @@ file_upload_annotate: /* FALLTHROUGH */ case '\n': case '\r': - /* send */ + /* save */ session_printf(s, "Saving file... "); session_flush(s); - if (folder_file_save(folder, &file) == 0) { - session_log(s, "Saved file %s to %s", file.filename, - folder->name); + if (folder_file_save(folder, &file, upload_path)) { + session_log(s, "[%s] Saved file %s", folder->name, + file.filename); session_printf(s, "done\r\n"); - } else + session_flush(s); + goto file_upload_done; + } else { session_printf(s, "failed!\r\n"); - - session_flush(s); - - goto file_upload_done; + session_flush(s); + goto file_upload_cancel; + } + break; case 'e': case 'E': session_printf(s, "%c\r\n", c); @@ -487,7 +611,7 @@ file_upload_annotate: session_flush(s); /* FALLTHROUGH */ case CONTROL_C: - goto file_upload_done; + goto file_upload_cancel; } } @@ -496,16 +620,22 @@ file_upload_error: session_flush(s); file_upload_cancel: - if (file.tmp_filename[0] != '\0') { - CtoPstr(&file.tmp_filename); - error = FSDelete(&file.tmp_filename, folder->folder_path_vrefnum); + session_printf(s, "\r\n"); + session_flush(s); + + if (upload_path[0] != '\0') { + CtoPstr(upload_path); + error = FSDelete(upload_path, 0); + PtoCstr(upload_path); if (error) { - PtoCstr(&file.tmp_filename); - session_log(s, "Failed deleting temporary uploaded file %s: %d", - file.tmp_filename); + session_log(s, "[%s] Failed deleting temporary uploaded " + "file %s: %d", folder->name, upload_path, error); + } else { + session_log(s, "[%s] Canceled upload, deleted temp file %s", + folder->name, upload_path); } } - + file_upload_done: if (file.notes) free(file.notes); @@ -517,18 +647,22 @@ short folder_file_view(struct session *s, struct folder *folder, unsigned long id, short idx) { - static struct session_menu_option opts[] = { + static const struct session_menu_option opts[] = { { '#', "#0123456789", "View file [#]" }, { 'd', "Dd", "Download this file" }, - { 'r', "Rr", "Remove this file (if permitted)" }, - { 'q', "QqXx", "Return to threads" }, + { 'r', "Rr", "Remove this file" }, + { 'e', "Ee", "Edit this file" }, + { 'q', "QqXx", "Return to folder" }, { '?', "?", "List these options" }, }; + char path[256]; char time[32]; struct folder_file file; struct username_cache *uploader; - unsigned long new_id; - char prompt[BOARD_NAME_LENGTH + 8]; + struct zmodem_session *zs; + struct session_menu_option *dopts = NULL; + FILE *fp; + char prompt[6 + BOARD_NAME_LENGTH + 8]; size_t n, size; char c; char *data; @@ -543,36 +677,112 @@ folder_file_view(struct session *s, struct folder *fol nfolder_file_object_fields, data, size, &file, true); free(data); + dopts = xmalloc(sizeof(opts)); + memcpy(dopts, opts, sizeof(opts)); + if (!(s->user && (s->user->is_sysop || + s->user->id == file.uploader_user_id))) { + /* disable deleting and editing */ + dopts[2].key[0] = '\0'; + dopts[3].key[0] = '\0'; + } + uploader = user_find_username(file.uploader_user_id); strftime(time, sizeof(time), "%Y-%m-%d %H:%M:%S", localtime(&file.time)); + session_printf(s, "{{B}}Folder:{{/B}} %s\r\n", folder->name); session_printf(s, "{{B}}File Name:{{/B}} %s\r\n", file.filename); session_printf(s, "{{B}}Description:{{/B}} %s\r\n", file.description); - session_printf(s, "{{B}}Folder:{{/B}} %s\r\n", folder->name); - session_printf(s, "{{B}}Date:{{/B}} %s %s\r\n", time, - db->config.timezone); session_printf(s, "{{B}}File Size:{{/B}} %lu\r\n", file.size); session_printf(s, "{{B}}File SHA1:{{/B}} %s\r\n", file.sha1_checksum); + session_printf(s, "{{B}}Uploaded:{{/B}} %s %s\r\n", time, + db->config.timezone); session_printf(s, "{{B}}Uploaded By:{{/B}} %s\r\n", uploader ? uploader->username : "(unknown)"); session_flush(s); if (file.notes_size > 0) { - session_printf(s, "\r\n"); + session_printf(s, "{{B}}Notes:{{/B}}\r\n"); session_output(s, file.notes, file.notes_size); session_printf(s, "\r\n"); } - snprintf(prompt, sizeof(prompt), "%s:%ld", folder->name, file.id); + session_printf(s, + "\r\n[ Not viewing file contents, press 'd' to download. ]\r\n"); + session_flush(s); + snprintf(prompt, sizeof(prompt), "Files:%s:%ld", folder->name, + file.id); + while (!done && !s->ending) { - c = session_menu(s, file.filename, prompt, opts, nitems(opts), + c = session_menu(s, file.filename, prompt, dopts, nitems(opts), show_help); show_help = false; switch (c) { + case 'd': + snprintf(path, sizeof(path), "%s:%s", folder->path, + file.filename); + fp = fopen(path, "rb"); + if (!fp) { + session_log(s, "[%s] Failed opening file %s", + folder->name, path); + session_printf(s, + "{{B}}Error:{{/B}} Failed opening file\r\n"); + break; + } + + zs = ZCreateSender(s, fp, file.filename); + zs->DoIACEscape = s->is_telnet; + + ZInit(zs); + s->transferring_file = true; + session_log(s, "[%s] Downloading file %s", folder->name, + file.filename); + for (;;) { + if (ZHaveTimedOut(zs)) { + ZTimeOutProc(zs); + session_log(s, "[%s] Transfer timed out, canceling", + folder->name); + break; + } + + if (!ZParse(zs)) + break; + + if (s->obuflen) + session_flush(s); + } + + /* flush the pipes */ + s->obuflen = 0; + uthread_msleep(1000); + session_clear_input(s); + s->transferring_file = false; + + if (zs->file) { + fclose(zs->file); + zs->file = NULL; + } + ZDestroy(zs); + zs = NULL; + + session_printf(s, "\r\n"); + session_flush(s); + session_pause_return(s, CONTROL_C, "to continue..."); + break; + case 'e': + if (!(s->user && (s->user->is_sysop || + s->user->id == file.uploader_user_id))) { + session_printf(s, "Invalid option\r\n"); + session_flush(s); + break; + } + folder_edit_file(s, folder, &file); + ret = FILE_VIEW_RETURN_FIND; + done = true; + break; case 'r': if (!(s->user && (s->user->is_sysop || s->user->id == file.uploader_user_id))) { @@ -592,9 +802,6 @@ folder_file_view(struct session *s, struct folder *fol folder_delete_file(s, folder, &file); - session_log(s, "Deleted file %ld (%s)", file.id, - file.filename); - session_printf(s, "\r\n{{B}}File deleted!{{/B}}\r\n"); session_flush(s); ret = FILE_VIEW_RETURN_FIND; @@ -649,7 +856,7 @@ folder_find_file_ids(struct folder *folder, size_t *nf return 0; name_map = xcalloc(*nfile_ids, sizeof(struct file_name_map)); - + for (n = 0; o = bile_get_nth_of_type(folder->bile, n, FOLDER_FILE_RTYPE); n++) { if (n >= *nfile_ids) @@ -685,9 +892,11 @@ done: return *nfile_ids; } -short -folder_file_save(struct folder *folder, struct folder_file *file) +bool +folder_file_save(struct folder *folder, struct folder_file *file, + char *temp_path) { + char new_name[256]; short ret; char *data; size_t size; @@ -702,119 +911,133 @@ folder_file_save(struct folder *folder, struct folder_ if (ret != 0 || size == 0) { warn("failed to marshall new file object"); file->id = 0; - goto done; + return false; } if (bile_write(folder->bile, FOLDER_FILE_RTYPE, file->id, data, size) != size) { warn("bile_write of new file failed! %d", bile_error(folder->bile)); file->id = 0; free(data); - goto done; + return false; } free(data); bile_flush(folder->bile, true); - -done: - return file->id; + + snprintf(new_name, sizeof(new_name), "%s:%s", folder->path, + file->filename); + CtoPstr(temp_path); + CtoPstr(new_name); + ret = Rename(temp_path, 0, new_name); + PtoCstr(temp_path); + PtoCstr(new_name); + if (ret != 0) { + warn("FSRename(%s, 0, %s) failed: %d", temp_path, new_name, ret); + bile_delete(folder->bile, FOLDER_FILE_RTYPE, file->id); + file->id = 0; + return false; + } + + return true; } void folder_delete_file(struct session *s, struct folder *folder, struct folder_file *file) { -#if 0 - size_t size, n, nn; - char *data; - char del[50]; + char path[256]; short ret; - bool childs = false; - unsigned long *new_post_ids; - unsigned long *new_parent_post_ids; + + bile_delete(folder->bile, FOLDER_FILE_RTYPE, file->id); + bile_flush(folder->bile, true); + + snprintf(path, sizeof(path), "%s:%s", folder->path, file->filename); + CtoPstr(path); + ret = FSDelete(path, 0); + PtoCstr(path); + + if (ret != 0) + session_log(s, "[%s] Failed deleting %s: %d", folder->name, + path, ret); + + if (file->notes != NULL) + free(file->notes); - if (thread->nposts == 1) { - bile_delete(board->bile, BOARD_THREAD_RTYPE, thread->thread_id); - bile_delete(board->bile, BOARD_POST_RTYPE, post->id); - return; + session_log(s, "[%s] Deleted file %s (%ld)", folder->name, + file->filename, file->id); +} + +bool +folder_file_valid_filename(struct session *session, + struct folder *folder, struct folder_file *file, char **error) +{ + struct bile_object *o; + struct folder_file tfile; + size_t len, n; + char *data; + char c; + + if (file->filename[0] == '\0') { + *error = xstrdup("filename cannot be empty"); + return false; } - for (n = 0; n < thread->nposts; n++) { - if (thread->parent_post_ids[n] == post->id) { - childs = true; - break; - } + len = strlen(file->filename); + if (len > FOLDER_FILE_FILENAME_LENGTH) { + *error = xmalloc(61); + snprintf(*error, 60, "filename cannot be more than %d characters", + FOLDER_FILE_FILENAME_LENGTH); + return false; } - if (!childs) { - /* just zap this off the end of the tree */ - new_post_ids = xcalloc(thread->nposts - 1, sizeof(unsigned long)); - new_parent_post_ids = xcalloc(thread->nposts - 1, - sizeof(unsigned long)); - - for (n = 0, nn = 0; n < thread->nposts; n++) { - if (thread->post_ids[n] == post->id) - continue; - - new_post_ids[nn] = thread->post_ids[n]; - nn++; - } + for (n = 0; n < len; n++) { + c = file->filename[n]; - for (n = 0, nn = 0; n < thread->nposts; n++) { - if (thread->post_ids[n] == post->id) - continue; - - new_parent_post_ids[nn] = thread->parent_post_ids[n]; - nn++; + if (n == 0 && c == '.') { + *error = xstrdup("filename cannot start with a dot"); + return false; } - thread->nposts--; - - free(thread->post_ids); - thread->post_ids = new_post_ids; - free(thread->parent_post_ids); - thread->parent_post_ids = new_parent_post_ids; - - ret = bile_marshall_object(board->bile, board_thread_object_fields, - nboard_thread_object_fields, thread, &data, &size); - if (ret != 0 || size == 0) { - warn("failed to marshall thread object during post delete"); - return; + if (n == len - 1 && c == ' ') { + *error = xstrdup("filename cannot end with a space"); + return false; } - if (bile_write(board->bile, BOARD_THREAD_RTYPE, thread->thread_id, - data, size) != size) { - warn("bile_write of updated thread after post delete failed! " - "%d", bile_error(board->bile)); - free(data); - return; - } - free(data); - bile_delete(board->bile, BOARD_POST_RTYPE, post->id); - - bile_flush(board->bile, true); - return; + if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || c == '_' || c == '-' || c == '+' || + c == '*' || c == ' ' || c == '.' || c == ',')) { + *error = xmalloc(61); + snprintf(*error, 60, "filename cannot contain '%c' character", + c); + return false; + } } - /* all we can do is change the post to say deleted */ - if (post->body != NULL) - free(post->body); - snprintf(del, sizeof(del), "[ Post deleted by %s ]", - s->user ? s->user->username : "unknown"); - post->body = xstrdup(del); - post->body_size = strlen(post->body) + 1; + for (n = 0; o = bile_get_nth_of_type(folder->bile, n, + FOLDER_FILE_RTYPE); n++) { + if (o->id == file->id) { + free(o); + continue; + } + + bile_read_alloc(folder->bile, FOLDER_FILE_RTYPE, o->id, &data); + bile_unmarshall_object(folder->bile, folder_file_object_fields, + nfolder_file_object_fields, data, o->size, &tfile, false); + free(data); + free(o); - ret = bile_marshall_object(board->bile, board_post_object_fields, - nboard_post_object_fields, post, &data, &size); - if (ret != 0 || size == 0) { - warn("failed to marshall updated post object"); - return; + if (strcasecmp(file->filename, tfile.filename) == 0) { + *error = xstrdup("filename is already taken"); + return false; + } } - if (bile_write(board->bile, BOARD_POST_RTYPE, post->id, data, - size) != size) { - warn("bile_write of updated post failed! %d", - bile_error(board->bile)); - free(data); - return; - } -#endif -} + + return true; +} + +void +folder_edit_file(struct session *s, struct folder *folder, + struct folder_file *file) +{ + /* TODO */ +} --- folder.h Thu Jun 30 13:38:47 2022 +++ folder.h Mon Jul 11 15:09:01 2022 @@ -31,8 +31,8 @@ struct folder { bool restricted_viewing; unsigned long last_upload_at; unsigned long file_count; + char path[256]; - short folder_path_vrefnum; struct bile *bile; }; @@ -53,8 +53,6 @@ struct folder_file { char sha1_checksum[SHA1_DIGEST_STRING_LENGTH + 1]; /* 42 */ size_t notes_size; char *notes; - - char tmp_filename[FOLDER_FILE_FILENAME_LENGTH]; }; extern struct bile_object_field folder_file_object_fields[]; extern size_t nfolder_file_object_fields;