AmendHub

Download

jcs

/

subtext

/

folder.c

 

(View History)

jcs   folder: constify fields Latest amendment: 276 on 2022-11-11

1 /*
2 * Copyright (c) 2022 joshua stein <jcs@jcs.org>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #include <stdarg.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21
22 #include "subtext.h"
23 #include "ansi.h"
24 #include "folder.h"
25 #include "sha1.h"
26 #include "user.h"
27 #include "zmodem.h"
28
29 #define FILES_PER_PAGE 10
30
31 #define FILE_VIEW_RETURN_DONE -1
32 #define FILE_VIEW_RETURN_LIST -2
33 #define FILE_VIEW_RETURN_FIND -3
34
35 const struct struct_field folder_fields[] = {
36 { "Folder ID", CONFIG_TYPE_LONG,
37 offsetof(struct folder, id),
38 1, ULONG_MAX },
39 { "Name", CONFIG_TYPE_STRING,
40 offsetof(struct folder, name),
41 1, member_size(struct folder, name) },
42 { "Disk Path", CONFIG_TYPE_STRING,
43 offsetof(struct folder, path),
44 1, member_size(struct folder, path) },
45 { "Description", CONFIG_TYPE_STRING,
46 offsetof(struct folder, description),
47 0, member_size(struct folder, description) },
48 { "Restricted Posting", CONFIG_TYPE_BOOLEAN,
49 offsetof(struct folder, restricted_posting),
50 0, 0 },
51 { "Restricted Viewing", CONFIG_TYPE_BOOLEAN,
52 offsetof(struct folder, restricted_viewing),
53 0, 0 },
54 };
55 const size_t nfolder_fields = nitems(folder_fields);
56
57 const struct bile_object_field folder_object_fields[] = {
58 { offsetof(struct folder, id),
59 member_size(struct folder, id), -1 },
60 { offsetof(struct folder, name),
61 member_size(struct folder, name), -1 },
62 { offsetof(struct folder, description),
63 member_size(struct folder, description), -1 },
64 { offsetof(struct folder, restricted_posting),
65 member_size(struct folder, restricted_posting), -1 },
66 { offsetof(struct folder, restricted_viewing),
67 member_size(struct folder, restricted_viewing), -1 },
68 { offsetof(struct folder, last_upload_at),
69 member_size(struct folder, last_upload_at), -1 },
70 { offsetof(struct folder, file_count),
71 member_size(struct folder, file_count), -1 },
72 { offsetof(struct folder, path),
73 member_size(struct folder, path), -1 },
74 };
75 const size_t nfolder_object_fields = nitems(folder_object_fields);
76
77 const struct bile_object_field folder_file_object_fields[] = {
78 { offsetof(struct folder_file, id),
79 member_size(struct folder_file, id), -1 },
80 { offsetof(struct folder_file, time),
81 member_size(struct folder_file, time), -1 },
82 { offsetof(struct folder_file, uploader_user_id),
83 member_size(struct folder_file, uploader_user_id), -1 },
84 { offsetof(struct folder_file, filename),
85 member_size(struct folder_file, filename), -1 },
86 { offsetof(struct folder_file, description),
87 member_size(struct folder_file, description), -1 },
88 { offsetof(struct folder_file, size),
89 member_size(struct folder_file, size), -1 },
90 { offsetof(struct folder_file, sha1_checksum),
91 member_size(struct folder_file, sha1_checksum), -1 },
92 { offsetof(struct folder_file, notes_size),
93 member_size(struct folder_file, notes_size), -1 },
94 { offsetof(struct folder_file, notes),
95 -1, offsetof(struct folder_file, notes_size) },
96 };
97 const size_t nfolder_file_object_fields = nitems(folder_file_object_fields);
98
99 unsigned long folder_upload(struct session *s, struct folder *folder,
100 char *initial_filename, char *initial_description);
101 void folder_list_files(struct session *s, struct folder *folder,
102 size_t nfile_ids, unsigned long *file_ids, size_t page, size_t pages);
103 void folder_show(struct session *s, struct folder *folder);
104 short folder_file_view(struct session *s, struct folder *folder,
105 unsigned long id, short idx);
106 void folder_delete_file(struct session *s, struct folder *folder,
107 struct folder_file *file);
108 size_t folder_find_file_ids(struct folder *folder, size_t *nfile_ids,
109 unsigned long **file_ids);
110 bool folder_file_save(struct folder *folder, struct folder_file *file,
111 char *temp_path);
112 bool folder_file_valid_filename(struct session *session,
113 struct folder *folder, struct folder_file *file, char **error);
114 void folder_edit_file(struct session *s, struct folder *folder,
115 struct folder_file *file);
116
117 void
118 folder_list(struct session *s)
119 {
120 static const struct session_menu_option opts[] = {
121 { '#', "#0123456789", "View file folder [#]" },
122 { 'l', "Ll", "List folders" },
123 { 'q', "QqXx", "Return to main menu" },
124 { '?', "?", "List menu options" },
125 };
126 short n;
127 char c;
128 bool done, show_list, show_help;
129
130 show_list = true;
131 show_help = false;
132 done = false;
133
134 while (!done && !s->ending) {
135 if (show_list) {
136 session_printf(s, "{{B}}File Folders{{/B}}\r\n");
137 session_printf(s, "%s# Name Description%s\r\n",
138 ansi(s, ANSI_BOLD, ANSI_END), ansi(s, ANSI_RESET, ANSI_END));
139 session_flush(s);
140
141 for (n = 0; n < db->nfolders; n++) {
142 session_printf(s, "%d %- 13.13s %s\r\n",
143 n,
144 db->folders[n].name,
145 db->folders[n].description);
146 }
147 session_flush(s);
148
149 show_list = false;
150 }
151
152 c = session_menu(s, "File Folders", "Files", opts, nitems(opts),
153 show_help);
154 show_help = false;
155
156 handle_opt:
157 switch (c) {
158 case 'l':
159 show_list = true;
160 break;
161 case 0:
162 case 1:
163 case 2:
164 case 3:
165 case 4:
166 case 5:
167 case 6:
168 case 7:
169 case 8:
170 case 9:
171 if (c >= db->nfolders) {
172 session_printf(s, "Invalid folder\r\n");
173 session_flush(s);
174 break;
175 }
176 folder_show(s, &db->folders[c]);
177 break;
178 case '?':
179 show_help = true;
180 break;
181 default:
182 done = true;
183 break;
184 }
185 }
186 }
187
188 void
189 folder_show(struct session *s, struct folder *folder)
190 {
191 static const struct session_menu_option opts[] = {
192 { '#', "#0123456789", "View file [#]" },
193 { '<', "<", "Previous page of files" },
194 { 'l', "Ll", "List files" },
195 { '>', ">", "Next page of files" },
196 { 'u', "Uu", "Upload new file" },
197 { 'q', "QqXx", "Return to main menu" },
198 { '?', "?", "List menu options" },
199 };
200 char prompt[6 + BOARD_NAME_LENGTH + 8];
201 size_t n, page, pages, nfile_ids;
202 unsigned long *file_ids = NULL;
203 short ret;
204 char c;
205 bool done, show_list, show_help, find_files;
206
207 page = 0;
208 show_list = true;
209 find_files = true;
210 show_help = false;
211 done = false;
212
213 snprintf(prompt, sizeof(prompt), "Files:%s", folder->name);
214
215 while (!done && !s->ending) {
216 if (find_files) {
217 folder_find_file_ids(folder, &nfile_ids, &file_ids);
218 /* ceil(nfile_ids / FILES_PER_PAGE) */
219 pages = (nfile_ids + FILES_PER_PAGE - 1) / FILES_PER_PAGE;
220
221 if (page >= pages)
222 page = pages - 1;
223
224 find_files = false;
225 }
226
227 if (show_list) {
228 folder_list_files(s, folder, nfile_ids, file_ids, page + 1,
229 pages);
230 show_list = false;
231 }
232
233 c = session_menu(s, folder->description, prompt, opts,
234 nitems(opts), show_help);
235 show_help = false;
236
237 handle_opt:
238 switch (c) {
239 case 'l':
240 show_list = true;
241 break;
242 case 'u':
243 if (folder_upload(s, folder, NULL, NULL))
244 find_files = true;
245 break;
246 case '>':
247 case '<':
248 if (c == '>' && page == pages - 1) {
249 session_printf(s, "You are at the last page of files\r\n");
250 session_flush(s);
251 break;
252 }
253 if (c == '<' && page == 0) {
254 session_printf(s, "You are already at the first page\r\n");
255 session_flush(s);
256 break;
257 }
258 if (c == '>')
259 page++;
260 else
261 page--;
262 show_list = true;
263 break;
264 case 0:
265 case 1:
266 case 2:
267 case 3:
268 case 4:
269 case 5:
270 case 6:
271 case 7:
272 case 8:
273 case 9:
274 if (c >= nfile_ids) {
275 session_printf(s, "Invalid file\r\n");
276 session_flush(s);
277 break;
278 }
279 c = folder_file_view(s, folder, file_ids[c], c);
280 if (c == FILE_VIEW_RETURN_FIND)
281 find_files = true;
282 break;
283 case '?':
284 show_help = true;
285 break;
286 default:
287 done = true;
288 break;
289 }
290 }
291
292 if (file_ids != NULL)
293 xfree(&file_ids);
294 }
295
296 void
297 folder_list_files(struct session *s, struct folder *folder,
298 size_t nfile_ids, unsigned long *file_ids, unsigned long page,
299 unsigned long pages)
300 {
301 char time[24];
302 size_t n, off, size, idx;
303 struct bile_object *obj;
304 struct folder_file file;
305 short j, k;
306 char *data;
307
308 session_printf(s, "{{B}}%s: %s (Page %ld of %ld){{/B}}\r\n",
309 folder->name, folder->description, page, pages);
310 session_printf(s,
311 "%s# Date File Description%s\r\n",
312 ansi(s, ANSI_BOLD, ANSI_END), ansi(s, ANSI_RESET, ANSI_END));
313 session_flush(s);
314
315 if (nfile_ids == 0) {
316 session_printf(s, "No files here yet.\r\n");
317 session_flush(s);
318 return;
319 }
320
321 off = FILES_PER_PAGE * (page - 1);
322
323 for (n = 0; n < FILES_PER_PAGE; n++) {
324 if (off + n >= nfile_ids)
325 break;
326
327 size = bile_read_alloc(folder->bile, FOLDER_FILE_RTYPE,
328 file_ids[off + n], &data);
329 bile_unmarshall_object(folder->bile, folder_file_object_fields,
330 nfolder_file_object_fields, data, size, &file, sizeof(file),
331 false, "folder_list_files");
332 xfree(&data);
333
334 strftime(time, sizeof(time), "%Y-%m-%d", localtime(&file.time));
335
336 session_printf(s, "%ld %s {{#}}%- 17.17s %- 40s\r\n",
337 n,
338 time,
339 file.filename,
340 file.description);
341 }
342 session_flush(s);
343 }
344
345 unsigned long
346 folder_upload(struct session *s, struct folder *folder,
347 char *initial_filename, char *initial_description)
348 {
349 struct folder_file file = { 0 };
350 FILE *fp;
351 SHA1_CTX sha1;
352 struct stat sb;
353 struct zmodem_session *zs;
354 char *upload_path = NULL, *data = NULL, *tmp = NULL, *errorstr = NULL;
355 size_t size;
356 short c, n, ret, error;
357
358 if (!s->user) {
359 session_printf(s, "Uploading is not available to guests.\r\n"
360 "Please create an account first.\r\n");
361 session_flush(s);
362 return 0;
363 }
364
365 if (initial_filename)
366 strlcpy(file.filename, initial_filename, sizeof(file.filename));
367
368 file.uploader_user_id = s->user->id;
369
370 session_printf(s, "{{B}}Upload New File{{/B}}\r\n");
371 session_printf(s,
372 "To begin your ZMODEM upload, press Enter and choose a file through\r\n");
373 session_printf(s,
374 "your terminal program (only one file can be uploaded at a time).\r\n");
375 session_printf(s,
376 "\r\n"
377 "You'll then be given the option to change the filename and enter a\r\n"
378 "description for the file.\r\n"
379 "\r\n"
380 "To cancel your upload, press ^C a few times.\r\n\r\n");
381 session_flush(s);
382 session_pause_return(s, 0, "when you are ready...");
383
384 upload_path = xmalloc(FILENAME_MAX, "folder_upload path");
385 snprintf(upload_path, FILENAME_MAX, "%s:upload-%08lx%08lx",
386 folder->path, xorshift32(), xorshift32());
387
388 zs = ZCreateReceiver(s, upload_path);
389 zs->DoIACEscape = s->is_telnet;
390
391 ZInit(zs);
392 s->transferring_file = true;
393 session_flush(s);
394 session_logf(s, "[%s] Receiving uploaded file to %s", folder->name,
395 upload_path);
396 while (!s->ending) {
397 if (ZHaveTimedOut(zs)) {
398 ZTimeOutProc(zs);
399 session_logf(s, "[%s] Transfer timed out, canceling",
400 folder->name);
401 break;
402 }
403
404 if (!ZParse(zs))
405 break;
406
407 if (s->obuflen)
408 session_flush(s);
409 }
410
411 /* flush the pipes */
412 s->obuflen = 0;
413 uthread_msleep(1000);
414 session_clear_input(s);
415 s->transferring_file = false;
416
417 if (zs->file) {
418 fclose(zs->file);
419 zs->file = NULL;
420 }
421
422 session_printf(s, "\r\n\r\n");
423
424 if (stat(upload_path, &sb) != 0) {
425 session_logf(s, "[%s] Failed receiving upload, no temp file",
426 folder->name);
427 session_printf(s, "Failed receiving file, aborting.\r\n");
428 session_pause_return(s, CONTROL_C, "to continue...");
429 ZDestroy(zs);
430 zs = NULL;
431 xfree(&upload_path);
432 return 0;
433 }
434
435 if (sb.st_size != zs->file_size) {
436 session_logf(s, "[%s] Received uploaded file of size %ld but "
437 "supposed to be %ld, canceling", folder->name, sb.st_size,
438 zs->file_size);
439 session_printf(s,
440 "Uploaded file expected to be {{B}}%ld{{/B}} byte%s, but "
441 "received\r\n"
442 "{{B}}%ld{{/B}} byte%s. Deleting file and canceling upload.\r\n",
443 zs->file_size, zs->file_size == 1 ? "" : "s",
444 sb.st_size, sb.st_size == 1 ? "" : "s");
445 session_pause_return(s, CONTROL_C, "to continue...");
446 ZDestroy(zs);
447 zs = NULL;
448 goto file_upload_cancel;
449 }
450
451 strlcpy(file.filename, zs->file_name, sizeof(file.filename));
452 file.size = sb.st_size;
453
454 session_printf(s, "Successfully received file {{B}}%s{{/B}} of size "
455 "{{B}}%ld{{/B}} byte%s.\r\n",
456 file.filename, file.size, file.size == 1 ? "" : "s");
457 session_pause_return(s, '\r', "to continue...");
458
459 session_logf(s, "[%s] Received uploaded file %s of size %ld",
460 folder->name, file.filename, file.size);
461
462 /* this will fclose and free zs */
463 ZDestroy(zs);
464 zs = NULL;
465
466 session_printf(s, "Calculating SHA1 checksum of uploaded file...");
467 session_flush(s);
468 data = xmalloc(1024, "folder_upload data");
469 SHA1Init(&sha1);
470 fp = fopen(upload_path, "rb");
471 for (n = 1; fp && !feof(fp); n++) {
472 size = fread(data, 1024, 1, fp);
473 SHA1Update(&sha1, (const u_int8_t *)data, size);
474 if (n % 2 == 0)
475 uthread_yield();
476 }
477 SHA1End(&sha1, (char *)&file.sha1_checksum);
478 if (fp)
479 fclose(fp);
480 xfree(&data);
481
482 session_printf(s, "done.\r\n\r\n");
483 session_flush(s);
484 session_printf(s, "{{B}}Uploaded By:{{/B}} %s\r\n",
485 s->user->username);
486 session_printf(s, "{{B}}Folder:{{/B}} %s\r\n",
487 folder->name);
488 session_printf(s, "{{B}}SHA1 Checksum:{{/B}} %s\r\n",
489 file.sha1_checksum);
490 session_printf(s, "{{B}}File Size:{{/B}} %ld\r\n", file.size);
491 session_flush(s);
492
493 file_upload_annotate:
494 for (;;) {
495 session_printf(s, "{{B}}File Name:{{/B}} ");
496 session_flush(s);
497
498 tmp = session_field_input(s, FOLDER_FILE_FILENAME_LENGTH,
499 FOLDER_FILE_FILENAME_LENGTH - 1, file.filename, false, 0);
500 if (tmp == NULL)
501 goto file_upload_cancel;
502 strlcpy(file.filename, tmp, sizeof(file.filename));
503 xfree(&tmp);
504 session_output(s, "\r\n", 2);
505 session_flush(s);
506
507 rtrim(file.filename, "\r\n\t ");
508
509 if (!folder_file_valid_filename(s, folder, &file, &errorstr)) {
510 session_printf(s, "{{B}}Error:{{/B}} %s\r\n", errorstr);
511 xfree(&errorstr);
512 continue;
513 }
514
515 break;
516 }
517
518 for (;;) {
519 session_printf(s, "{{B}}Short Description:{{/B}} ");
520 session_flush(s);
521
522 tmp = session_field_input(s, FOLDER_FILE_DESCR_LENGTH,
523 FOLDER_FILE_DESCR_LENGTH - 1, file.description, false, 0);
524 if (tmp == NULL)
525 goto file_upload_cancel;
526 strlcpy(file.description, tmp, sizeof(file.description));
527 xfree(&tmp);
528 session_output(s, "\r\n", 2);
529 session_flush(s);
530
531 rtrim(file.description, "\r\n\t ");
532
533 if (file.description[0] == '\0') {
534 session_printf(s, "{{B}}Error:{{/B}} File "
535 "description cannot be blank (^C to cancel)\r\n");
536 session_flush(s);
537 continue;
538 }
539
540 break;
541 }
542
543 for (;;) {
544 session_printf(s,
545 "{{B}}Notes (Optional, ^D when finished):{{/B}}\r\n");
546 session_flush(s);
547
548 tmp = session_field_input(s, 2048, s->terminal_columns - 1,
549 file.notes, true, 0);
550 if (file.notes != NULL)
551 xfree(&file.notes);
552 file.notes = tmp;
553 session_output(s, "\r\n", 2);
554 session_flush(s);
555 if (file.notes == NULL) {
556 file.notes_size = 0;
557 break;
558 }
559
560 rtrim(file.notes, "\r\n\t ");
561
562 if (file.notes[0] == '\0') {
563 xfree(&file.notes);
564 file.notes = NULL;
565 file.notes_size = 0;
566 break;
567 }
568 file.notes_size = strlen(file.notes) + 1;
569 break;
570 }
571
572 for (;;) {
573 session_printf(s, "\r\n{{B}}(S){{/B}}ave file, "
574 "{{B}}(E){{/B}}dit again, or {{B}}(C){{/B}}ancel? ");
575 session_flush(s);
576
577 c = session_input_char(s);
578 if (c == 0 || s->ending)
579 goto file_upload_done;
580
581 switch (c) {
582 case 's':
583 case 'S':
584 case 'y':
585 session_printf(s, "%c\r\n", c);
586 session_flush(s);
587 /* FALLTHROUGH */
588 case '\n':
589 case '\r':
590 /* save */
591 session_printf(s, "Saving file... ");
592 session_flush(s);
593
594 if (folder_file_save(folder, &file, upload_path)) {
595 session_logf(s, "[%s] Saved file %s", folder->name,
596 file.filename);
597 session_printf(s, "done\r\n");
598 session_flush(s);
599 goto file_upload_done;
600 } else {
601 session_printf(s, "failed!\r\n");
602 session_flush(s);
603 goto file_upload_cancel;
604 }
605 break;
606 case 'e':
607 case 'E':
608 session_printf(s, "%c\r\n", c);
609 session_flush(s);
610 goto file_upload_annotate;
611 case 'c':
612 case 'C':
613 session_printf(s, "%c\r\n", c);
614 session_flush(s);
615 /* FALLTHROUGH */
616 case CONTROL_C:
617 goto file_upload_cancel;
618 }
619 }
620
621 file_upload_error:
622 session_printf(s, "Failed saving file!\r\n");
623 session_flush(s);
624
625 file_upload_cancel:
626 session_printf(s, "\r\n");
627 session_flush(s);
628
629 if (upload_path[0] != '\0') {
630 CtoPstr(upload_path);
631 error = FSDelete(upload_path, 0);
632 PtoCstr(upload_path);
633 if (error) {
634 session_logf(s, "[%s] Failed deleting temporary uploaded "
635 "file %s: %d", folder->name, upload_path, error);
636 } else {
637 session_logf(s, "[%s] Canceled upload, deleted temp file %s",
638 folder->name, upload_path);
639 }
640 }
641
642 file_upload_done:
643 if (file.notes)
644 xfree(&file.notes);
645 if (upload_path != NULL)
646 xfree(&upload_path);
647
648 return file.id;
649 }
650
651 short
652 folder_file_view(struct session *s, struct folder *folder,
653 unsigned long id, short idx)
654 {
655 static const struct session_menu_option opts[] = {
656 { '#', "#0123456789", "View file [#]" },
657 { 'd', "Dd", "Download this file" },
658 { 'r', "Rr", "Remove this file" },
659 { 'e', "Ee", "Edit this file" },
660 { 'q', "QqXx", "Return to folder" },
661 { '?', "?", "List these options" },
662 };
663 char time[32];
664 struct folder_file file;
665 struct username_cache *uploader;
666 struct zmodem_session *zs;
667 struct session_menu_option *dopts = NULL;
668 FILE *fp;
669 char *path = NULL;
670 char prompt[6 + BOARD_NAME_LENGTH + 1 + FOLDER_FILE_FILENAME_LENGTH];
671 size_t n, size;
672 char c;
673 char *data;
674 short cc, ret;
675 bool done = false, show_help = false;
676
677 size = bile_read_alloc(folder->bile, FOLDER_FILE_RTYPE, id, &data);
678 if (size == 0)
679 panic("failed fetching message %ld: %d", id,
680 bile_error(folder->bile));
681 bile_unmarshall_object(folder->bile, folder_file_object_fields,
682 nfolder_file_object_fields, data, size, &file, sizeof(file), true,
683 "folder_file_view");
684 xfree(&data);
685
686 dopts = xmalloc(sizeof(opts), "folder_file_view opts");
687 memcpy(dopts, opts, sizeof(opts));
688 if (!(s->user && (s->user->is_sysop ||
689 s->user->id == file.uploader_user_id))) {
690 /* disable deleting and editing */
691 dopts[2].key[0] = '\0';
692 dopts[3].key[0] = '\0';
693 }
694
695 uploader = user_username(file.uploader_user_id);
696
697 strftime(time, sizeof(time), "%Y-%m-%d %H:%M:%S",
698 localtime(&file.time));
699
700 session_printf(s, "{{B}}Folder:{{/B}} %s\r\n", folder->name);
701 session_printf(s, "{{B}}File Name:{{/B}} %s\r\n", file.filename);
702 session_printf(s, "{{B}}Description:{{/B}} %s\r\n", file.description);
703 session_printf(s, "{{B}}File Size:{{/B}} %lu\r\n", file.size);
704 session_printf(s, "{{B}}File SHA1:{{/B}} %s\r\n", file.sha1_checksum);
705 session_printf(s, "{{B}}Uploaded:{{/B}} %s %s\r\n", time,
706 db->config.timezone);
707 session_printf(s, "{{B}}Uploaded By:{{/B}} %s\r\n",
708 uploader ? uploader->username : "(unknown)");
709 session_flush(s);
710
711 if (file.notes_size > 0) {
712 session_printf(s, "{{B}}Notes:{{/B}}\r\n");
713 session_output(s, file.notes, file.notes_size);
714 session_printf(s, "\r\n");
715 }
716
717 session_printf(s,
718 "\r\n[ Not viewing file contents, press 'd' to download. ]\r\n");
719 session_flush(s);
720
721 snprintf(prompt, sizeof(prompt), "Files:%s:%s", folder->name,
722 file.filename);
723
724 while (!done && !s->ending) {
725 c = session_menu(s, file.filename, prompt, dopts, nitems(opts),
726 show_help);
727 show_help = false;
728
729 switch (c) {
730 case 'd':
731 path = xmalloc(FILENAME_MAX, "folder_file_view filename");
732 snprintf(path, FILENAME_MAX, "%s:%s", folder->path,
733 file.filename);
734 fp = fopen(path, "rb");
735 if (!fp) {
736 session_logf(s, "[%s] Failed opening file %s",
737 folder->name, path);
738 session_printf(s,
739 "{{B}}Error:{{/B}} Failed opening file\r\n");
740 xfree(&path);
741 break;
742 }
743
744 zs = ZCreateSender(s, fp, file.filename);
745 zs->DoIACEscape = s->is_telnet;
746
747 ZInit(zs);
748 s->transferring_file = true;
749 session_logf(s, "[%s] Downloading file %s", folder->name,
750 file.filename);
751 for (;;) {
752 if (ZHaveTimedOut(zs)) {
753 ZTimeOutProc(zs);
754 session_logf(s, "[%s] Transfer timed out, canceling",
755 folder->name);
756 break;
757 }
758
759 if (!ZParse(zs))
760 break;
761
762 if (s->obuflen)
763 session_flush(s);
764 }
765
766 /* flush the pipes */
767 s->obuflen = 0;
768 uthread_msleep(1000);
769 session_clear_input(s);
770 s->transferring_file = false;
771
772 if (zs->file) {
773 fclose(zs->file);
774 zs->file = NULL;
775 }
776 ZDestroy(zs);
777 zs = NULL;
778 xfree(&path);
779
780 session_printf(s, "\r\n");
781 session_flush(s);
782 session_pause_return(s, CONTROL_C, "to continue...");
783 break;
784 case 'e':
785 if (!(s->user && (s->user->is_sysop ||
786 s->user->id == file.uploader_user_id))) {
787 session_printf(s, "Invalid option\r\n");
788 session_flush(s);
789 break;
790 }
791 folder_edit_file(s, folder, &file);
792 ret = FILE_VIEW_RETURN_FIND;
793 done = true;
794 break;
795 case 'r':
796 if (!(s->user && (s->user->is_sysop ||
797 s->user->id == file.uploader_user_id))) {
798 session_printf(s, "Invalid option\r\n");
799 session_flush(s);
800 break;
801 }
802
803 session_printf(s, "Are you sure you want to permanently "
804 "delete this file? [y/N] ");
805 session_flush(s);
806
807 cc = session_input_char(s);
808 if (cc == 'y' || c == 'Y') {
809 session_printf(s, "%c\r\n", cc);
810 session_flush(s);
811
812 folder_delete_file(s, folder, &file);
813
814 session_printf(s, "\r\n{{B}}File deleted!{{/B}}\r\n");
815 session_flush(s);
816 ret = FILE_VIEW_RETURN_FIND;
817 done = true;
818 } else {
819 session_printf(s, "\r\Post not deleted.\r\n");
820 session_flush(s);
821 }
822 break;
823 case 0:
824 case 1:
825 case 2:
826 case 3:
827 case 4:
828 case 5:
829 case 6:
830 case 7:
831 case 8:
832 case 9:
833 ret = c;
834 done = true;
835 break;
836 case 'q':
837 done = true;
838 break;
839 case '?':
840 show_help = true;
841 break;
842 }
843 }
844
845 if (file.notes != NULL)
846 xfree(&file.notes);
847
848 xfree(&dopts);
849
850 return ret;
851 }
852
853 size_t
854 folder_find_file_ids(struct folder *folder, size_t *nfile_ids,
855 unsigned long **file_ids)
856 {
857 struct bile_object *o;
858 struct folder_file file;
859 struct file_name_map {
860 unsigned long id;
861 char filename[10]; /* only sorted to 10 character places */
862 } *name_map = NULL, tmp_map;
863 size_t n, size;
864 short i, j;
865 char *data;
866
867 *nfile_ids = bile_count_by_type(folder->bile, FOLDER_FILE_RTYPE);
868 if (*nfile_ids == 0)
869 return 0;
870
871 name_map = xcalloc(*nfile_ids, sizeof(struct file_name_map),
872 "folder_find_file_ids");
873
874 for (n = 0; o = bile_get_nth_of_type(folder->bile, n,
875 FOLDER_FILE_RTYPE); n++) {
876 if (n >= *nfile_ids)
877 break;
878 bile_read_alloc(folder->bile, FOLDER_FILE_RTYPE, o->id, &data);
879 bile_unmarshall_object(folder->bile, folder_file_object_fields,
880 nfolder_file_object_fields, data, o->size, &file, sizeof(file),
881 false, "folder_find_file_ids");
882 xfree(&data);
883 xfree(&o);
884
885 name_map[n].id = file.id;
886 strlcpy(name_map[n].filename, file.filename,
887 sizeof(name_map[n].filename));
888 }
889
890 /* sort by filename */
891 for (i = 0; i < *nfile_ids; i++) {
892 for (j = 0; j < *nfile_ids - i - 1; j++) {
893 if (strcasecmp(name_map[j].filename,
894 name_map[j + 1].filename) > 0) {
895 tmp_map = name_map[j];
896 name_map[j] = name_map[j + 1];
897 name_map[j + 1] = tmp_map;
898 }
899 }
900 }
901
902 *file_ids = xcalloc(sizeof(long), *nfile_ids, "folder_find_file_ids");
903 for (i = 0; i < *nfile_ids; i++)
904 (*file_ids)[i] = name_map[i].id;
905
906 xfree(&name_map);
907
908 done:
909 return *nfile_ids;
910 }
911
912 bool
913 folder_file_save(struct folder *folder, struct folder_file *file,
914 char *temp_path)
915 {
916 char note[MALLOC_NOTE_SIZE];
917 char *new_name = NULL;
918 short ret;
919 char *data;
920 size_t size;
921 ssize_t n, j;
922 size_t insert;
923
924 file->id = bile_next_id(folder->bile, FOLDER_FILE_RTYPE);
925 file->time = Time;
926
927 snprintf(note, sizeof(note), "folder_file_save %ld", file->id);
928 ret = bile_marshall_object(folder->bile, folder_file_object_fields,
929 nfolder_file_object_fields, file, &data, &size, note);
930 if (ret != 0 || size == 0) {
931 warn("failed to marshall new file object");
932 file->id = 0;
933 return false;
934 }
935 if (bile_write(folder->bile, FOLDER_FILE_RTYPE, file->id, data,
936 size) != size) {
937 warn("bile_write of new file failed! %d", bile_error(folder->bile));
938 file->id = 0;
939 xfree(&data);
940 return false;
941 }
942 xfree(&data);
943
944 bile_flush(folder->bile, true);
945
946 new_name = xmalloc(FILENAME_MAX, "folder_file_save");
947 snprintf(new_name, FILENAME_MAX, "%s:%s", folder->path,
948 file->filename);
949 CtoPstr(temp_path);
950 CtoPstr(new_name);
951 ret = Rename(temp_path, 0, new_name);
952 PtoCstr(temp_path);
953 PtoCstr(new_name);
954 if (ret != 0) {
955 warn("FSRename(%s, 0, %s) failed: %d", temp_path, new_name, ret);
956 bile_delete(folder->bile, FOLDER_FILE_RTYPE, file->id);
957 file->id = 0;
958 xfree(&new_name);
959 return false;
960 }
961
962 xfree(&new_name);
963 return true;
964 }
965
966 void
967 folder_delete_file(struct session *s, struct folder *folder,
968 struct folder_file *file)
969 {
970 char *path = NULL;
971 short ret;
972
973 bile_delete(folder->bile, FOLDER_FILE_RTYPE, file->id);
974 bile_flush(folder->bile, true);
975
976 path = xmalloc(FILENAME_MAX, "folder_delete_file");
977 snprintf(path, FILENAME_MAX, "%s:%s", folder->path, file->filename);
978 CtoPstr(path);
979 ret = FSDelete(path, 0);
980 PtoCstr(path);
981
982 if (ret != 0)
983 session_logf(s, "[%s] Failed deleting %s: %d", folder->name,
984 path, ret);
985
986 xfree(&path);
987 if (file->notes != NULL)
988 xfree(&file->notes);
989
990 session_logf(s, "[%s] Deleted file %s (%ld)", folder->name,
991 file->filename, file->id);
992 }
993
994 bool
995 folder_file_valid_filename(struct session *session,
996 struct folder *folder, struct folder_file *file, char **error)
997 {
998 struct bile_object *o;
999 struct folder_file tfile;
1000 size_t len, n;
1001 char *data;
1002 char c;
1003
1004 if (file->filename[0] == '\0') {
1005 *error = xstrdup("filename cannot be empty",
1006 "folder_file_valid_filename");
1007 return false;
1008 }
1009
1010 len = strlen(file->filename);
1011 if (len > FOLDER_FILE_FILENAME_LENGTH) {
1012 *error = xmalloc(61, "folder_file_valid_filename");
1013 snprintf(*error, 60, "filename cannot be more than %d characters",
1014 FOLDER_FILE_FILENAME_LENGTH);
1015 return false;
1016 }
1017
1018 for (n = 0; n < len; n++) {
1019 c = file->filename[n];
1020
1021 if (n == 0 && c == '.') {
1022 *error = xstrdup("filename cannot start with a dot",
1023 "folder_file_valid_filename");
1024 return false;
1025 }
1026
1027 if (n == len - 1 && c == ' ') {
1028 *error = xstrdup("filename cannot end with a space",
1029 "folder_file_valid_filename");
1030 return false;
1031 }
1032
1033 if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') ||
1034 (c >= 'a' && c <= 'z') || c == '_' || c == '-' || c == '+' ||
1035 c == '*' || c == ' ' || c == '.' || c == ',')) {
1036 *error = xmalloc(61, "folder_file_valid_filename");
1037 snprintf(*error, 60, "filename cannot contain '%c' character",
1038 c);
1039 return false;
1040 }
1041 }
1042
1043 for (n = 0; o = bile_get_nth_of_type(folder->bile, n,
1044 FOLDER_FILE_RTYPE); n++) {
1045 if (o->id == file->id) {
1046 xfree(&o);
1047 continue;
1048 }
1049
1050 bile_read_alloc(folder->bile, FOLDER_FILE_RTYPE, o->id, &data);
1051 bile_unmarshall_object(folder->bile, folder_file_object_fields,
1052 nfolder_file_object_fields, data, o->size, &tfile, sizeof(tfile),
1053 false, "folder_file_valid_filename");
1054 xfree(&data);
1055 xfree(&o);
1056
1057 if (strcasecmp(file->filename, tfile.filename) == 0) {
1058 *error = xstrdup("filename is already taken",
1059 "folder_file_valid_filename");
1060 return false;
1061 }
1062 }
1063
1064 return true;
1065 }
1066
1067 void
1068 folder_edit_file(struct session *s, struct folder *folder,
1069 struct folder_file *file)
1070 {
1071 /* TODO */
1072 }