* For mailing lists, Alpine adds a description of the type of link
[alpine.git] / pico / attach.c
blob1ea700e8456e6aab3e4c53aa5442ab8c3097ea86
1 /*
2 * ========================================================================
3 * Copyright 2006-2008 University of Washington
4 * Copyright 2013-2022 Eduardo Chappa
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * ========================================================================
14 * Program: Routines to support attachments in the Pine composer
17 #include "headers.h"
18 #include "../pith/charconv/filesys.h"
19 #include "../pith/string.h"
21 #include <math.h>
23 #ifdef ATTACHMENTS
26 int ParseAttach(struct hdr_line **, int *, char *,
27 size_t, char *, size_t, char *, size_t, int *);
28 PATMT *NewAttach(char *, long, char *);
29 void ZotAttach(struct pico_atmt *);
30 void sinserts(UCS *, int, UCS *, int);
31 int AttachUpload(char *, size_t, char *, size_t);
32 int AttachCancel(char *);
35 #define HIBIT_WARN "Only ASCII characters allowed in attachment comments"
39 * AskAttach - ask for attachment fields and build resulting structure
40 * return pointer to that struct if OK, NULL otherwise
42 int
43 AskAttach(char *cmnt, size_t cmntlen, LMLIST **lm)
45 int i, status, fbrv, upload = 0;
46 int fb_flags = FB_READ | FB_ATTACH;
47 off_t attsz = 0;
48 size_t len;
49 char bfn[NLINE];
50 char fn[NLINE], sz[32];
51 LMLIST *new;
52 EML eml;
54 i = 2; /* 2 is prompt for file, 1 is prompt for comment */
55 fn[0] = '\0';
56 sz[0] = '\0';
57 cmnt[0] = '\0';
59 while(i){
60 if(i == 2){
61 EXTRAKEYS menu_attach[10];
62 int n;
64 memset(&menu_attach, 0, 10*sizeof(EXTRAKEYS));
65 menu_attach[n = 0].name = "^T";
66 menu_attach[n].label = N_("To Files");
67 menu_attach[n].key = (CTRL|'T');
69 if(gmode & MDCMPLT){
70 menu_attach[++n].name = "TAB";
71 menu_attach[n].label = N_("Complete");
72 menu_attach[n].key = (CTRL|'I');
75 #if !defined(DOS) && !defined(MAC)
76 if(Pmaster && Pmaster->upload){
78 * The Plan: ^R prompts for uploaded file's name which
79 * is passed to the defined upload command when the user
80 * hits Return to confirm the name.
81 * NOTE: this is different than upload into message
82 * text in which case the uploaded name isn't useful so
83 * a temp file is ok (be sure to fix the file mode).
85 menu_attach[++n].name = "^Y";
86 menu_attach[n].key = (CTRL|'Y');
87 /* TRANSLATORS: Read File is a prompt for the name of
88 a file to be read into the composer. */
89 menu_attach[n].label = upload ? N_("Read File") : N_("RcvUpload");
91 #endif
93 menu_attach[++n].name = NULL;
94 KS_OSDATASET(&menu_attach[0], KS_NONE);
95 status = mlreply_utf8(upload ? _("Name to give uploaded attachment: ")
96 /* TRANSLATORS: User is being asked for the name
97 of the file they want to attach to a message. */
98 : _("File to attach: "),
99 fn, sizeof(fn), QNORML, menu_attach);
101 else
102 /* TRANSLATORS: This is a prompt for a comment about the file
103 they have attached. */
104 status = mlreply_utf8(_("Attachment comment: "), cmnt, cmntlen, QNODQT, NULL);
106 switch(status){
107 case HELPCH:
108 if(Pmaster){
109 VARS_TO_SAVE *saved_state;
111 saved_state = save_pico_state();
112 (*Pmaster->helper)(Pmaster->attach_help, _("Attach Help"), 1);
113 if(saved_state){
114 restore_pico_state(saved_state);
115 free_pico_state(saved_state);
118 pico_refresh(FALSE, 1);
119 update();
120 continue;
122 else{
123 eml.s = (i == 2) ? "file" : "comment";
124 emlwrite("No Attachment %s help yet!", &eml);
125 sleep(3);
128 break;
130 case (CTRL|'I') :
131 if(i == 2){
132 char *fname, *p;
133 int dirlen;
135 bfn[0] = '\0';
136 if(*fn && (p = strrchr(fn, C_FILESEP))){
137 fname = p + 1;
138 dirlen = p - fn;
139 if(p == fn){
140 strncpy(bfn, S_FILESEP, sizeof(bfn));
141 bfn[sizeof(bfn)-1] = '\0';
143 #ifdef DOS
144 else if(fn[0] == C_FILESEP
145 || (isalpha((unsigned char)fn[0])
146 && fn[1] == ':')){
147 if(fn[1] == ':' && p == fn+2)
148 dirlen = fname - fn;
150 if(dirlen < sizeof(bfn)){
151 strncpy(bfn, fn, dirlen);
152 bfn[dirlen] = '\0';
155 #else
156 else if (fn[0] == C_FILESEP || fn[0] == '~'){
157 if(dirlen < sizeof(bfn)){
158 strncpy(bfn, fn, dirlen);
159 bfn[dirlen] = '\0';
162 #endif
163 else
164 snprintf(bfn, sizeof(bfn), "%s%c%.*s",
165 (gmode & MDCURDIR)
166 ? "."
167 : ((gmode & MDTREE) || opertree[0])
168 ? opertree : gethomedir(NULL),
169 C_FILESEP, (int) (p - fn), fn);
171 else{
172 fname = fn;
173 strncpy(bfn, (gmode & MDCURDIR)
174 ? "."
175 : ((gmode & MDTREE) || opertree[0])
176 ? opertree : gethomedir(NULL), sizeof(bfn));
177 bfn[sizeof(bfn)-1] = '\0';
180 if(!pico_fncomplete(bfn, fname, sizeof(fn)-(fname-fn)))
181 (*term.t_beep)();
183 else
184 (*term.t_beep)();
186 break;
188 case (CTRL|'T'):
189 if(i != 2){
190 (*term.t_beep)();
191 break;
194 *bfn = '\0';
195 if(*fn == '\0' || !isdir(fn, NULL, NULL)){
196 strncpy(fn, (gmode & MDCURDIR)
197 ? (browse_dir[0] ? browse_dir : ".")
198 : ((gmode & MDTREE) || opertree[0])
199 ? opertree
200 : (browse_dir[0] ? browse_dir
201 : gethomedir(NULL)), sizeof(fn));
202 fn[sizeof(fn)-1] = '\0';
205 if((fbrv = FileBrowse(fn, sizeof(fn), bfn, sizeof(bfn), sz, sizeof(sz),
206 upload ? fb_flags
207 : fb_flags|FB_LMODEPOS,
208 upload ? NULL
209 : lm)) == 1){
210 if (upload && (strlen(fn)+strlen(S_FILESEP)+strlen(bfn)) < sizeof(fn)){
211 size_t len1, len2;
213 len1 = strlen(bfn);
214 len2 = strlen(fn);
215 if((new=(LMLIST *)malloc(sizeof(*new))) == NULL
216 || (new->fname=malloc((len1+1) * sizeof(char))) == NULL
217 || (new->dir=malloc((len2+1) * sizeof(char))) == NULL){
218 emlwwrite(_("Can't malloc space for filename"), NULL);
219 return(-1);
222 strncpy(new->fname, bfn, len1);
223 new->fname[len1] = '\0';
224 strncpy(new->dir, fn, len2);
225 new->dir[len2] = '\0';
226 strncpy(new->size, sz, sizeof(new->size)-1);
227 new->size[sizeof(new->size)-1] = '\0';
228 new->next = NULL;
229 *lm = new;
231 strncat(fn, S_FILESEP, sizeof(fn)-strlen(fn)-1);
232 fn[sizeof(fn)-1] = '\0';
233 strncat(fn, bfn, sizeof(fn)-strlen(fn)-1);
234 fn[sizeof(fn)-1] = '\0';
235 if(!AttachUpload(fn, sizeof(fn), sz, sizeof(sz))){
236 i = 2; /* keep prompting for file */
237 sleep(3); /* problem, show error! */
239 else{
240 i--; /* go prompt for comment */
243 else if(!upload){
244 if(lm && *lm && !(*lm)->next) /* get comment */
245 i--;
246 else{ /* no comments if multiple files */
247 update();
248 return(1);
251 else{ /* trouble */
252 *fn = '\0';
253 AttachCancel(fn);
254 pico_refresh(FALSE,1);
255 update();
256 emlwwrite(_("File name too BIG, cannot select!"), NULL);
257 sleep(3);
260 else if (!fbrv)
261 *fn = '\0';
262 else{
263 *fn = '\0';
264 AttachCancel(fn);
265 pico_refresh(FALSE, 1);
266 update();
267 emlwwrite(_("File name too big, cannot select!"), NULL);
268 sleep(3);
271 /* fall thru to clean up the screen */
273 case (CTRL|'L'):
274 pico_refresh(FALSE, 1);
275 update();
276 continue;
278 #if !defined(DOS) && !defined(MAC)
279 case (CTRL|'Y'): /* upload? */
280 if(i == 2)
281 upload ^= 1; /* flip mode */
282 else
283 (*term.t_beep)();
285 break;
286 #endif
288 case ABORT:
289 return(AttachCancel((upload && i == 1) ? fn : NULL));
291 case TRUE: /* some comment */
292 case FALSE: /* No comment */
293 if(i-- == 2){
294 if(upload){
295 fixpath(fn, sizeof(fn)); /* names relative to ~ */
296 status = AttachUpload(fn, sizeof(fn), sz, sizeof(sz));
297 pico_refresh(FALSE, 1);
298 update();
299 if(!status){
300 i = 2; /* keep prompting for file */
301 sleep(3); /* problem, show error! */
304 else {
305 if(*fn == '\"' && fn[strlen(fn)-1] == '\"'){
306 int j;
308 for(j = 0; (fn[j] = fn[j+1]); j++)
311 fn[j-1] = '\0';
314 if(fn[0]){
315 if((gmode & MDTREE)
316 && !compresspath(opertree, fn, sizeof(fn))){
317 eml.s = (gmode&MDSCUR) ? _("home directory") : opertree;
318 emlwrite(
319 /* TRANSLATORS: the %s is replaced with the name of a directory */
320 _("Restricted mode allows attachments from %s only: too many ..'s"),
321 &eml);
322 return(0);
324 else{
325 fixpath(fn, sizeof(fn)); /* names relative to ~ */
326 if((gmode&MDTREE) && !in_oper_tree(fn)){
327 eml.s = (gmode&MDSCUR) ? _("home directory") : opertree;
328 emlwwrite(
329 _("Restricted mode allows attachments from %s only"), &eml);
330 return(0);
334 if((status = fexist(fn, "r", &attsz)) != FIOSUC){
335 fioperr(status, fn); /* file DOESN'T exist! */
336 return(0);
339 len = strlen(fn);
340 if((new=(LMLIST *)malloc(sizeof(*new))) == NULL
341 || (new->fname=malloc((len+1)*sizeof(char))) == NULL){
342 emlwwrite(_("Can't malloc space for filename"), NULL);
343 return(-1);
346 new->dir = NULL;
347 strncpy(new->fname, fn, len);
348 new->fname[len] = '\0';
349 strncpy(new->size, prettysz(attsz), sizeof(new->size));
350 new->size[sizeof(new->size)-1] = '\0';
351 new->next = NULL;
352 *lm = new;
354 else
355 return(AttachCancel((upload && i == 1) ? fn : NULL));
358 else{
359 mlerase();
360 return(1); /* mission accomplished! */
363 break;
364 default:
365 break;
369 return(0);
374 * AttachUpload - Use call back to run the external upload command.
377 AttachUpload(char *fn, size_t fnlen, char *sz, size_t szlen)
379 long l;
381 if(gmode&MDSCUR){
382 emlwwrite(_("Restricted mode disallows uploaded command"), NULL);
383 return(0);
386 if(Pmaster && Pmaster->upload && (*Pmaster->upload)(fn, fnlen, &l)){
387 strncpy(sz, prettysz((off_t)l), szlen);
388 sz[szlen-1] = '\0';
389 return(1);
392 return(0);
397 * AttachCancel -
400 AttachCancel(char *fn)
402 emlwrite(_("Attach cancelled"), NULL);
403 if(fn && fn[0])
404 our_unlink(fn); /* blast uploaded file */
406 return(0);
410 extern struct headerentry *headents;
413 * SyncAttach - given a pointer to a linked list of attachment structures,
414 * return with that structure sync'd with what's displayed.
415 * delete any attachments in list of structs that's not on
416 * the display, and add any that aren't in list but on display.
419 SyncAttach(void)
421 int offset = 0, /* the offset to begin */
422 rv = 0,
423 ki = 0, /* number of known attmnts */
424 bi = 0, /* build array index */
425 nbld = 0, /* size of build array */
426 na, /* old number of attachmnt */
427 status, i, n = 0;
428 size_t j;
429 char file[NLINE], /* buffers to hold it all */
430 size[32],
431 comment[1024];
432 struct hdr_line *lp; /* current line in header */
433 struct headerentry *entry;
434 PATMT *tp, **knwn = NULL, **bld = NULL;
435 EML eml;
437 if(Pmaster == NULL)
438 return(-1);
440 for(entry = headents; entry->name != NULL; entry++) {
441 if(entry->is_attach)
442 break;
445 for(tp = Pmaster->attachments; tp; tp = tp->next)
446 ki++; /* Count known attachments */
448 if(ki){
449 if((knwn = (PATMT **)malloc((ki+1) * (sizeof(PATMT *)))) == NULL){
450 eml.s = comatose(ki + 1);
451 emlwwrite(_("Can't allocate space for %s known attachment array entries"),
452 &eml);
453 rv = -1;
454 goto exit_early;
456 for(i=0, tp = Pmaster->attachments; i < ki; i++, tp = tp->next){
457 knwn[i] = tp; /* fill table of */
458 /* known attachments */
464 * As a quick hack to avoid too many reallocs, check to see if
465 * there are more header lines than known attachments.
467 for(lp = entry->hd_text; lp ; lp = lp->next)
468 nbld++; /* count header lines */
470 nbld = nbld > ki ? nbld : ki + 1;
472 if((bld = (PATMT **)malloc(nbld * (sizeof(PATMT *)))) == NULL){
473 eml.s = comatose(nbld);
474 emlwwrite(_("Can't allocate space for %s build array entries"), &eml);
475 rv = -1;
476 goto exit_early;
479 lp = entry->hd_text;
480 while(lp != NULL){
481 char fn[NLINE];
482 na = ++n;
484 if(bi == nbld){ /* need to grow build array? */
485 if((bld = (PATMT **)realloc(bld, ++nbld * sizeof(PATMT *))) == NULL){
486 eml.s = comatose(nbld);
487 emlwwrite(_("Can't resize build array to %s entries"), &eml);
488 rv = -1;
489 goto exit_early;
493 if((status = ParseAttach(&lp, &offset, file, sizeof(file), size, sizeof(size),
494 comment, sizeof(comment), &na)) != 0)
495 rv = (rv < 0) ? rv : status ; /* remember worst case */
497 if(*file == '\0'){
498 if(n != na && na > 0 && na <= ki && (knwn[na-1]->flags & A_FLIT)){
499 bld[bi++] = knwn[na-1];
500 knwn[na-1] = NULL;
502 continue;
505 if((gmode&MDTREE)
506 && file[0] != '['
507 && (!in_oper_tree(file)
508 || !compresspath(file, fn, sizeof(fn))))
509 /* no attachments outside ~ in secure mode! */
510 continue;
512 tp = NULL;
513 for(i = 0; i < ki; i++){ /* already know about it? */
515 * this is kind of gruesome. what we want to do is keep track
516 * of literal attachment entries because they may not be
517 * actual files we can access or that the user can readily
518 * access.
520 if(knwn[i]
521 && ((!(knwn[i]->flags&A_FLIT)
522 && !strcmp(file, knwn[i]->filename))
523 || ((knwn[i]->flags&A_FLIT) && i+1 == na))){
524 tp = knwn[i];
525 knwn[i] = NULL; /* forget we know about it */
527 if(status == -1) /* ignore garbage! */
528 break;
530 if((tp->flags&A_FLIT) && strcmp(file, tp->filename)){
531 rv = 1;
532 if((j=strlen(file)) > strlen(tp->filename)){
533 if((tp->filename = (char *)realloc(tp->filename,
534 sizeof(char)*(j+1))) == NULL){
535 emlwwrite(_("Can't realloc filename space"),NULL);
536 rv = -1;
537 goto exit_early;
541 strncpy(tp->filename, file, j);
542 tp->filename[j] = '\0';
544 if(tp->id) fs_give((void **) &tp->id);
546 else if(tp->size && strcmp(tp->size, size)){
547 rv = 1;
548 if((j=strlen(size)) > strlen(tp->size)){
549 if((tp->size=(char *)realloc(tp->size,
550 sizeof(char)*(j+1))) == NULL){
551 emlwwrite(_("Can't realloc space for size"), NULL);
552 rv = -1;
553 goto exit_early;
557 strncpy(tp->size, size, j);
558 tp->size[j] = '\0';
561 if(strcmp(tp->description, comment)){ /* new comment */
562 rv = 1;
563 if((j=strlen(comment)) > strlen(tp->description)){
564 if((tp->description=(char *)realloc(tp->description,
565 sizeof(char)*(j+1))) == NULL){
566 emlwwrite(_("Can't realloc description"), NULL);
567 rv = -1;
568 goto exit_early;
572 strncpy(tp->description, comment, j);
573 tp->description[j] = '\0';
575 break;
579 if(tp){
580 bld[bi++] = tp;
582 else{
583 if(file[0] != '['){
584 if((tp = NewAttach(file, atol(size), comment)) == NULL){
585 rv = -1;
586 goto exit_early;
588 bld[bi++] = tp;
590 else break;
593 if(status < 0)
594 tp->flags |= A_ERR; /* turn ON error bit */
595 else
596 tp->flags &= ~(A_ERR); /* turn OFF error bit */
599 if(bi){
600 for(i=0; i < bi-1; i++) /* link together newly built list */
601 bld[i]->next = bld[i+1];
603 bld[i]->next = NULL; /* tie it off */
604 Pmaster->attachments = bld[0];
606 else
607 Pmaster->attachments = NULL;
609 exit_early:
610 if(knwn){
611 for(i = 0; i < ki; i++){ /* kill old/unused references */
612 if(knwn[i]){
613 ZotAttach(knwn[i]);
614 free((char *) knwn[i]);
617 free((void *)knwn);
620 if(bld)
621 free((void *)bld);
623 return(rv);
628 * ParseAttach - given a header line and an offset into it, return with
629 * the three given fields filled in. Size of fn and cmnt
630 * buffers should be passed in fnlen and cmntlen.
631 * Always updates header fields that have changed or are
632 * fixed. An error advances offset to next attachment.
634 * returns: 1 if a field changed
635 * 0 nothing changed
636 * -1 on error
639 ParseAttach(struct hdr_line **lp, /* current header line */
640 int *off, /* offset into that line */
641 char *fn, /* return file name field */
642 size_t fnlen, /* fn buffer size */
643 char *sz,
644 size_t szlen,
645 char *cmnt, /* places to return fields */
646 size_t cmntlen,
647 int *no) /* attachment number */
649 int j = 0, status, bod, eod = -1,
650 rv = 0, /* return value */
651 orig_offset,
652 lbln = 0, /* label'd attachment */
653 hibit = 0,
654 quoted = 0,
655 add_quotes = 0,
656 escaped = 0;
657 off_t attsz; /* attachment length */
658 EML eml;
659 UCS c, c_lookahead;
660 UCS tmp[1024], *p, *u, quotechar[2];
661 char ctmp[1024];
662 char *utf8 = NULL;
663 char *lblsz = NULL, /* label'd attchmnt's size */
664 number[8];
665 struct hdr_line *lprev = NULL;
666 enum { /* parse levels */
667 LWS, /* leading white space */
668 NUMB, /* attachment number */
669 WSN, /* white space after number */
670 TAG, /* attachments tag (fname) */
671 WST, /* white space after tag */
672 ASIZE, /* attachments size */
673 SWS, /* white space after size */
674 CMMNT, /* attachment comment */
675 TG} level; /* trailing garbage */
677 *fn = *sz = *cmnt = '\0'; /* initialize return strings */
678 p = tmp;
679 orig_offset = bod = *off;
680 quotechar[0] = '\"';
681 quotechar[1] = '\0';
683 level = LWS; /* start at beginning */
684 while(*lp != NULL){
686 if((c=(*lp)->text[*off]) == '\0'){ /* end of display line */
687 if(level == LWS && bod != *off){
688 (*lp)->text[bod] = '\0';
689 rv = 1;
691 lprev = *lp;
692 if((*lp = (*lp)->next) != NULL)
693 c = (*lp)->text[*off = bod = 0]; /* reset offset */
696 if(c != '\0'){
697 c_lookahead = (*lp)->text[*off + 1];
698 if(c_lookahead == '\0'){ /* end of display line */
699 if((*lp)->next != NULL)
700 c_lookahead = (*lp)->next->text[0];
704 switch(level){
705 case LWS: /* skip leading white space */
706 if(c <= 0xff && (isspace((unsigned char)c) || c == ',')){
707 c = ' ';
708 break;
711 if(c == '\0'){
712 if(bod > 0 && *off >= bod && lprev){
713 lprev->text[bod - 1] = '\0';
714 rv = 1;
717 else if(*off > bod && *lp){ /* wipe out whitespace */
718 memcpy(&(*lp)->text[bod], &(*lp)->text[*off],
719 ucs4_strlen(&(*lp)->text[*off]) + 1);
720 *off = bod; /* reset pointer */
721 rv = 1;
724 if(c == '\0')
725 break;
727 if(c > 0xff || !isdigit((unsigned char)c)){ /* add a number */
728 snprintf(number, sizeof(number), "%d. ", *no);
729 *no = 0; /* no previous number! */
730 u = utf8_to_ucs4_cpystr(number);
731 if(u){
732 sinserts((*lp == NULL) ? &lprev->text[*off]
733 : &(*lp)->text[*off],
734 0, u, j=ucs4_strlen(u));
736 fs_give((void **) &u);
739 *off += j - 1;
740 rv = 1;
741 level = TAG; /* interpret the name */
742 break;
744 level = NUMB;
745 case NUMB: /* attachment number */
746 if(c == '\0' || c == ','){ /* got to end, no number yet */
747 *p = '\0';
748 snprintf(number, sizeof(number), "%d. ", *no);
749 *no = 0; /* no previous number! */
750 if(c == '\0')
751 *lp = lprev; /* go back and look at prev */
753 c = (*lp)->text[*off = orig_offset]; /* reset offset */
754 u = utf8_to_ucs4_cpystr(number);
755 if(u){
756 sinserts((*lp == NULL) ? &lprev->text[*off]
757 : &(*lp)->text[*off],
758 0, u, j=ucs4_strlen(u));
760 fs_give((void **) &u);
762 *off += j - 1;
763 rv = 1;
764 p = tmp;
765 level = WSN; /* what's next... */
766 break;
768 else if(c == '.' && c_lookahead <= 0xff && isspace((unsigned char)c_lookahead)){
769 /* finished grabbing number */
770 /* if not space is not number */
772 * replace number if it's not right
774 *p = '\0';
775 snprintf(number, sizeof(number), "%d", *no); /* record the current... */
776 utf8 = ucs4_to_utf8_cpystr(tmp);
777 *no = atoi(utf8); /* and the old place in list */
778 if(strcmp(number, utf8)){
779 if(p-tmp > *off){ /* where to begin replacement */
780 UCS uu[1];
782 uu[0] = '\0';
783 j = (p-tmp) - *off;
784 sinserts((*lp)->text, *off, uu, 0);
785 u = utf8_to_ucs4_cpystr(number);
786 if(u){
787 sinserts(&lprev->text[ucs4_strlen(lprev->text)-j], j,
788 u, ucs4_strlen(u));
790 fs_give((void **) &u);
793 *off = 0;
795 else{
796 j = (*off) - (p-tmp);
797 u = utf8_to_ucs4_cpystr(number);
798 if(u){
799 sinserts((*lp == NULL) ? &lprev->text[j]
800 : &(*lp)->text[j],
801 p-tmp, u, ucs4_strlen(u));
803 fs_give((void **) &u);
806 *off += strlen(number) - (p-tmp);
808 rv = 1;
811 if(utf8)
812 fs_give((void **) &utf8);
814 p = tmp;
815 level = WSN; /* what's next... */
817 else if(c < '0' || c > '9'){ /* Must be part of tag */
818 snprintf(number, sizeof(number), "%d. ", *no);
819 u = utf8_to_ucs4_cpystr(number);
820 if(u){
821 sinserts((*lp == NULL) ? &lprev->text[*off - (p - tmp)]
822 : &(*lp)->text[*off - (p - tmp)],
823 0, u, j=ucs4_strlen(u));
825 fs_give((void **) &u);
828 *off += j;
829 level = TAG; /* interpret the name */
830 goto process_tag; /* in case already past end of tag */
832 else
833 *p++ = c;
835 break;
837 case WSN: /* blast whitespace */
838 if(c <= 0xff && (isspace((unsigned char)c) || c == '\0')){
839 break;
841 else if(c == '['){ /* labeled attachment */
842 lbln++;
844 else if(c == ',' || c == ' '){
845 /* TRANSLATORS: Attchmnt is an abbreviation for Attachment and
846 the %s is replaced with the character that is not
847 allowed in the name. */
848 eml.s = (c == ',') ? "," : "space";
849 emlwwrite(_("Attchmnt: '%s' not allowed in file name"), &eml);
850 rv = -1;
851 level = TG; /* eat rest of garbage */
852 break;
854 level = TAG;
856 case TAG: /* get and check filename */
857 /* or labeled attachment */
858 process_tag: /* enclosed in [] */
859 if(c == '\0'
860 || (lbln && c == ']')
861 || (quoted && p != tmp && c == '\"')
862 || (!(lbln || quoted)
863 && (c <= 0xff && ((isspace((unsigned char) c) && c_lookahead == (UCS) '(') || strchr(",(\"", c))))){
864 if(p == tmp){
865 if(c == '\"')
866 quoted++;
868 else{
869 *p = '\0'; /* got something */
871 utf8 = ucs4_to_utf8_cpystr(tmp);
872 if(utf8){
873 if(strlen(utf8) > fnlen)
874 emlwrite("File name too big!",NULL);
876 strncpy(fn, utf8, fnlen); /* store file name */
877 fn[fnlen-1] = '\0';
878 fs_give((void **) &utf8);
881 if(!lbln){ /* normal file attachment */
882 if((gmode & MDTREE)
883 && !compresspath(opertree, fn, fnlen)){
884 eml.s = (gmode&MDSCUR) ? _("home directory") : opertree;
885 emlwrite(
886 _("Attachments allowed only from %s: too many ..'s"),
887 &eml);
888 rv = -1;
889 level = TG;
890 break;
892 else{
893 fixpath(fn, fnlen);
894 if((status=fexist(fn, "r", &attsz)) != FIOSUC){
895 fioperr(status, fn);
896 rv = -1;
897 level = TG; /* munch rest of garbage */
898 break;
901 if((gmode & MDTREE) && !in_oper_tree(fn)){
902 eml.s = (gmode&MDSCUR) ? _("home directory") : opertree;
903 emlwwrite(_("Attachments allowed only from %s"), &eml);
904 rv = -1;
905 level = TG;
906 break;
910 utf8 = ucs4_to_utf8_cpystr(tmp);
912 if(utf8 && strcmp(fn, utf8)){ /* fn changed: display it */
913 if(*off >= p - tmp){ /* room for it? */
914 u = utf8_to_ucs4_cpystr(fn);
915 if(u){
917 * This whole parsing of the attachment line
918 * thing is ad hoc and susceptible to problems,
919 * and this particular part is no exception.
920 * Quote the filename if it contains spaces.
922 if(add_quotes){
923 sinserts((*lp == NULL) ? &lprev->text[*off - (p-tmp)]
924 : &(*lp)->text[*off - (p-tmp)],
925 0, quotechar, 1);
926 (*off)++;
929 sinserts((*lp == NULL) ? &lprev->text[*off - (p-tmp)]
930 : &(*lp)->text[*off - (p-tmp)],
931 p-tmp, u, j=ucs4_strlen(u));
933 *off += j - (p - tmp); /* advance offset */
935 if(add_quotes){
936 sinserts((*lp == NULL) ? &lprev->text[*off]
937 : &(*lp)->text[*off],
938 0, quotechar, 1);
939 (*off)++;
940 add_quotes = 0;
943 fs_give((void **) &u);
946 rv = 1;
948 else{
949 emlwwrite(_("Attchmnt: Problem displaying real file path"), NULL);
953 if(utf8)
954 fs_give((void **) &utf8);
956 else{ /* labelled attachment! */
958 * should explain about labelled attachments:
959 * these are attachments that came into the composer
960 * with meaningless file names (up to caller of
961 * composer to decide), for example, attachments
962 * being forwarded from another message. here, we
963 * just make sure the size stays what was passed
964 * to us. The user is SOL if they change the label
965 * since, as it is now, after changed, it will
966 * just get dropped from the list of what gets
967 * passed back to the caller.
969 PATMT *tp;
971 if(c != ']'){ /* legit label? */
972 eml.s = fn;
973 emlwwrite(_("Attchmnt: Expected ']' after \"%s\""),
974 &eml);
975 rv = -1;
976 level = TG;
977 break;
980 strncat(fn, "]", fnlen-strlen(fn)-1);
981 fn[fnlen-1] = '\0';
984 * This is kind of cheating since otherwise
985 * ParseAttach doesn't know about the attachment
986 * struct. OK if filename's not found as it will
987 * get taken care of later...
989 tp = Pmaster->attachments; /* caller check Pmaster! */
990 j = 0;
991 while(tp != NULL){
992 if(++j == *no){
993 lblsz = tp->size;
994 break;
997 tp = tp->next;
1000 if(tp == NULL){
1001 eml.s = fn;
1002 emlwwrite(_("Attchmnt: Unknown reference: %s"), &eml);
1003 lblsz = "XXX";
1007 if(add_quotes){
1008 sinserts((*lp == NULL) ? &lprev->text[*off - (p-tmp)]
1009 : &(*lp)->text[*off - (p-tmp)],
1010 0, quotechar, 1);
1011 (*off)++;
1012 sinserts((*lp == NULL) ? &lprev->text[*off]
1013 : &(*lp)->text[*off],
1014 0, quotechar, 1);
1015 (*off)++;
1016 add_quotes = 0;
1019 p = tmp; /* reset p in tmp */
1020 level = WST;
1023 if(!lbln && c == '(') /* no space 'tween file, size*/
1024 level = ASIZE;
1025 else if(c == '\0'
1026 || (!(lbln || quoted) && (c == ',' || c == '\"'))){
1027 strncpy(sz, (lblsz) ? lblsz : prettysz(attsz), szlen);
1028 sz[szlen-1] = '\0';
1030 snprintf(ctmp, sizeof(ctmp), " (%s) %s", sz, (c == '\"') ? "" : "\"\"");
1031 u = utf8_to_ucs4_cpystr(ctmp);
1032 if(u){
1033 ucs4_strncpy(tmp, u, sizeof(tmp)/sizeof(tmp[0]));
1034 tmp[sizeof(tmp)/sizeof(tmp[0]) - 1] = '\0';
1035 fs_give((void **) &u);
1038 sinserts((*lp == NULL) ? &lprev->text[*off]
1039 : &(*lp)->text[*off],
1040 0, tmp, j=ucs4_strlen(tmp));
1041 *off += j;
1042 rv = 1;
1043 level = (c == '\"') ? CMMNT : TG;/* cmnt or eat trash */
1046 else if(!(lbln || quoted)
1047 && (c == ',' || /** c == ' ' || **/ c == '[' || c == ']')){
1048 eml.s = c == ',' ? ","
1049 : c == ' ' ? "space"
1050 : c == '[' ? "[" : "]";
1051 emlwwrite(_("Attchmnt: '%s' not allowed in file name"), &eml);
1052 rv = -1; /* bad char in file name */
1053 level = TG; /* gobble garbage */
1055 else if(!(lbln || quoted) && (c <= 0xff && isspace((unsigned char) c))){
1056 add_quotes++;
1057 *p++ = c; /* add char to name */
1059 else
1060 *p++ = c; /* add char to name */
1062 break;
1064 case WST: /* skip white space */
1065 if(c > 0xff || !isspace((unsigned char)c)){
1067 * whole attachment, comment or done!
1069 if(c == ',' || c == '\0' || c == '\"'){
1070 strncpy(sz, (lblsz) ? lblsz : prettysz(attsz), szlen);
1071 sz[szlen-1] = '\0';
1073 snprintf(ctmp, sizeof(ctmp), " (%s) %s", sz, (c == '\"') ? "" : "\"\"");
1074 u = utf8_to_ucs4_cpystr(ctmp);
1075 if(u){
1076 ucs4_strncpy(tmp, u, sizeof(tmp)/sizeof(tmp[0]));
1077 tmp[sizeof(tmp)/sizeof(tmp[0]) - 1] = '\0';
1078 fs_give((void **) &u);
1081 sinserts((*lp == NULL) ? &lprev->text[*off]
1082 : &(*lp)->text[*off],
1083 0, tmp, j=ucs4_strlen(tmp));
1084 *off += j;
1085 rv = 1;
1086 level = (c == '\"') ? CMMNT : TG;
1087 lbln = 0; /* reset flag */
1089 else if(c == '('){ /* get the size */
1090 level = ASIZE;
1092 else{
1093 eml.s = fn;
1094 emlwwrite(_("Attchmnt: Expected '(' or '\"' after %s"), &eml);
1095 rv = -1; /* bag it all */
1096 level = TG;
1099 break;
1101 case ASIZE: /* check size */
1102 if(c == ')'){ /* finished grabbing size */
1103 *p = '\0';
1105 * replace sizes if they don't match!
1107 utf8 = ucs4_to_utf8_cpystr(tmp);
1108 if(utf8){
1109 strncpy(sz, utf8, szlen);
1110 sz[szlen-1] = '\0';
1111 fs_give((void **) &utf8);
1114 if(strcmp(sz, (lblsz) ? lblsz : prettysz(attsz))){
1115 strncpy(sz, (lblsz) ? lblsz : prettysz(attsz), szlen);
1116 sz[szlen-1] = '\0';
1117 if(p-tmp > *off){ /* where to begin replacement */
1118 UCS uu[1];
1120 uu[0] = '\0';
1121 j = (p-tmp) - *off;
1122 sinserts((*lp)->text, *off, uu, 0);
1123 u = utf8_to_ucs4_cpystr(sz);
1124 if(u){
1125 sinserts(&lprev->text[ucs4_strlen(lprev->text)-j], j,
1126 u, ucs4_strlen(u));
1127 fs_give((void **) &u);
1130 *off = 0;
1132 else{
1133 j = (*off) - (p-tmp);
1134 u = utf8_to_ucs4_cpystr(sz);
1135 if(u){
1136 sinserts((*lp == NULL) ? &lprev->text[j]
1137 : &(*lp)->text[j],
1138 p-tmp , u, ucs4_strlen(u));
1139 *off += ucs4_strlen(u) - (p-tmp);
1140 fs_give((void **) &u);
1143 rv = 1;
1146 p = tmp;
1147 level = SWS; /* what's next... */
1149 else if(c == '\0' || c == ','){
1150 *p = '\0';
1151 utf8 = ucs4_to_utf8_cpystr(tmp);
1152 eml.s = utf8;
1153 emlwwrite(_("Attchmnt: Size field missing ')': \"%s\""), &eml);
1154 if(utf8)
1155 fs_give((void **) &utf8);
1157 rv = -1;
1158 level = TG;
1160 else
1161 *p++ = c;
1163 break;
1165 case SWS: /* skip white space */
1166 if(c > 0xff || !isspace((unsigned char)c)){
1167 if(c == ','){ /* no description */
1168 level = TG; /* munch rest of garbage */
1169 lbln = 0; /* reset flag */
1171 else if(c != '\"' && c != '\0'){
1172 emlwwrite(_("Attchmnt: Malformed comment, quotes required"), NULL);
1173 rv = -1;
1174 level = TG;
1176 else
1177 level = CMMNT;
1179 break;
1181 case CMMNT: /* slurp up comment */
1182 if((c == '\"' && !escaped) || c == '\0'){
1183 *p = '\0'; /* cap it off */
1184 p = tmp; /* reset p */
1185 utf8 = ucs4_to_utf8_cpystr(tmp);
1186 if(utf8){
1187 if(strlen(utf8) > cmntlen)
1188 emlwrite("Comment too long!",NULL);
1190 strncpy(cmnt,utf8,cmntlen-1); /* copy the comment */
1191 cmnt[cmntlen-1] = '\0';
1192 fs_give((void **) &utf8);
1193 if(c == '\0'){
1194 emlwwrite(_("Attchmnt: Closing quote required at end of comment"), NULL);
1195 rv = -1;
1199 level = TG; /* prepare for next one */
1200 lbln = 0; /* reset flag */
1202 else if(c == '\\' && !escaped){ /* something escaped? */
1203 escaped = 1;
1205 else{
1206 if(escaped){
1207 if(c != '\"') /* we only quote escapes */
1208 *p++ = '\\';
1210 escaped = 0;
1213 if(((*p++ = c) & 0x80) && (gmode & MDHBTIGN) && !hibit++)
1214 emlwrite(HIBIT_WARN, NULL);
1217 break;
1219 case TG: /* get comma or final EOL */
1220 if(eod < 0)
1221 eod = *off;
1222 if(c > 0xff || !isspace((unsigned char)c)){
1223 switch(c){
1224 case '\0':
1225 if(eod != *off)
1226 lprev->text[*off = eod] = '\0';
1227 break;
1228 case ',':
1229 if(eod != *off){
1230 memcpy(&(*lp)->text[eod], &(*lp)->text[*off],
1231 ucs4_strlen(&(*lp)->text[*off]) + 1);
1232 *off = eod;
1233 rv = 1;
1235 break;
1236 default:
1237 if(rv != -1)
1238 emlwwrite(_("Attchmnt: Comma must separate attachments"), NULL);
1239 rv = -1;
1242 break;
1244 default: /* something's very wrong */
1245 emlwwrite(_("Attchmnt: Weirdness in ParseAttach"), NULL);
1246 return(-1); /* just give up */
1249 if(c == '\0') /* we're done */
1250 break;
1252 (*off)++;
1255 * not in comment or label name? done.
1257 if(c == ',' && (level != TAG && level != CMMNT && !lbln))
1258 break; /* put offset past ',' */
1261 return(rv);
1266 * NewAttach - given a filename (assumed to accessible) and comment, creat
1268 PATMT *
1269 NewAttach(char *f, long l, char *c)
1271 PATMT *tp;
1272 size_t len;
1274 if((tp=(PATMT *)malloc(sizeof(PATMT))) == NULL){
1275 emlwrite("No memory to add attachment", NULL);
1276 return(NULL);
1278 else
1279 memset(tp, 0, sizeof(PATMT));
1281 /* file and size malloc */
1282 len = strlen(f);
1283 if((tp->filename = (char *) malloc((len+1) * sizeof(char))) == NULL){
1284 emlwrite("Can't malloc name for attachment", NULL);
1285 free((char *) tp);
1286 return(NULL);
1289 strncpy(tp->filename, f, len);
1290 tp->filename[len] = '\0';
1292 if(l > -1){
1293 len = strlen(prettysz((off_t) l));
1294 tp->size = (char *) malloc((len+1) * sizeof(char));
1295 if(tp->size == NULL){
1296 emlwrite("Can't malloc size for attachment", NULL);
1297 free((char *) tp->filename);
1298 free((char *) tp);
1299 return(NULL);
1301 else{
1302 strncpy(tp->size, prettysz((off_t) l), len);
1303 tp->size[len] = '\0';
1307 /* description malloc */
1308 len = strlen(c);
1309 if((tp->description = (char *) malloc((len+1) * sizeof(char))) == NULL){
1310 emlwrite("Can't malloc description for attachment", NULL);
1311 free((char *) tp->size);
1312 free((char *) tp->filename);
1313 free((char *) tp);
1314 return(NULL);
1317 strncpy(tp->description, c, len);
1318 tp->description[len] = '\0';
1320 /* callback to show user the mime type that will be used for attachment */
1321 if(Pmaster->mimetype && (*Pmaster->mimetype)(f) > 0){
1322 int rv ;
1324 clearcursor();
1325 mlerase();
1326 rv = (*Pmaster->showmsg)('x');
1327 ttresize();
1328 picosigs();
1329 if(rv) /* Did showmsg corrupt the screen? */
1330 PaintBody(0); /* Yes, repaint it */
1332 mpresf = 1;
1335 return(tp);
1340 * AttachError - Sniff list of attachments, returning TRUE if there's
1341 * any sign of trouble...
1344 AttachError(void)
1346 PATMT *ap;
1348 if(!Pmaster)
1349 return(0);
1351 ap = Pmaster->attachments;
1352 while(ap){
1353 if((ap->flags) & A_ERR)
1354 return(1);
1356 ap = ap->next;
1359 return(FALSE);
1363 char *
1364 QuoteAttach(char *fn, size_t fnlen)
1366 char *p;
1368 if(*fn && strpbrk(fn, " \t,(\"")){ /* Quote it? */
1369 p = &fn[strlen(fn)];
1370 if(p+2-fn < fnlen){
1371 *(p+2) = '\0';
1372 *(p+1) = '\"';
1375 *p = *(p-1);
1376 while(--p != fn);
1377 *p = '\"';
1381 return(fn);
1385 void
1386 ZotAttach(PATMT *p)
1388 if(!p)
1389 return;
1391 if(p->description)
1392 free((char *)p->description);
1394 if(p->filename){
1395 if(p->flags & A_TMP)
1396 our_unlink(p->filename);
1398 free((char *)p->filename);
1401 if(p->size)
1402 free((char *)p->size);
1404 if(p->id)
1405 free((char *)p->id);
1407 p->next = NULL;
1409 #endif /* ATTACHMENTS */
1413 * intag - return TRUE if i is in a column that makes up an
1414 * attachment line number
1417 intag(UCS *s, int i)
1419 UCS *p = s;
1420 int n = 0;
1422 while(*p != '\0' && (p-s) < 5){ /* is there a tag? it */
1423 if(n && *p == '.') /* can't be more than 4 */
1424 return(i <= p-s); /* chars long! */
1426 if(*p < '0' || *p > '9')
1427 break;
1428 else
1429 n = (n * 10) + (*p - '0');
1431 p++;
1434 return(FALSE);
1439 * prettysz - return pointer to string containing nice size description
1441 char *
1442 prettysz(off_t l)
1444 static char b[32];
1445 long sz, left, right;
1447 sz = (long) l;
1448 b[0] = '\0';
1450 if(sz < 1000L){
1451 snprintf(b, sizeof(b), "%ld B", sz); /* xxx B */
1453 else if(sz < 9950L){
1454 left = (sz + 50L) / 1000L;
1455 right = ((sz + 50L) - left * 1000L) / 100L;
1456 snprintf(b, sizeof(b), "%ld.%ld KB", left, right); /* x.x KB */
1458 else if(sz < 999500L){
1459 snprintf(b, sizeof(b), "%ld KB", (sz + 500L) / 1000L); /* xxx KB */
1461 else if(sz < 9950000L){
1462 left = (sz + 50000L) / 1000000L;
1463 right = ((sz + 50000L) - left * 1000000L) / 100000L;
1464 snprintf(b, sizeof(b), "%ld.%ld MB", left, right); /* x.x MB */
1466 else{
1467 snprintf(b, sizeof(b), "%ld MB", (sz + 500000L) / 1000000L); /* xxx MB */
1470 return(b);
1475 * sinserts - s insert into another string
1477 void
1478 sinserts(UCS *ds, /* dest string */
1479 int dl, /* where to begin insert */
1480 UCS *ss, /* source string */
1481 int sl) /* length of ss */
1483 UCS *dp, *edp; /* pointers into dest. */
1484 size_t j; /* jump difference */
1486 if(sl >= dl){ /* source bigger than dest. */
1487 dp = ds + dl; /* shift dest. to make room */
1488 if((edp = ucs4_strchr(dp, '\0')) != NULL){
1489 j = sl - dl;
1491 for( ;edp >= dp; edp--)
1492 edp[j] = *edp;
1494 while(sl--)
1495 *ds++ = *ss++;
1497 else
1498 emlwwrite(_("No end of line???"), NULL); /* can this happen? */
1500 else{ /* dest is longer, shrink it */
1501 j = dl - sl; /* difference in lengths */
1503 while(sl--) /* copy u onto ds */
1504 *ds++ = *ss++;
1506 if(ucs4_strlen(ds) > j){ /* shuffle the rest left */
1508 *ds = ds[j];
1509 while(*ds++ != '\0');
1511 else
1512 *ds = '\0';