* When a filename is attached and its name is encoded, the save attachment
[alpine.git] / pico / attach.c
blob63ac7e05883b20d7883472a9cfc5972331114fbe
1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: attach.c 1082 2008-06-12 18:39:50Z hubert@u.washington.edu $";
3 #endif
5 /*
6 * ========================================================================
7 * Copyright 2006-2008 University of Washington
8 * Copyright 2013-2016 Eduardo Chappa
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
18 * Program: Routines to support attachments in the Pine composer
21 #include "headers.h"
22 #include "../pith/charconv/filesys.h"
23 #include "../pith/string.h"
25 #include <math.h>
27 #ifdef ATTACHMENTS
30 int ParseAttach(struct hdr_line **, int *, char *,
31 size_t, char *, size_t, char *, size_t, int *);
32 PATMT *NewAttach(char *, long, char *);
33 void ZotAttach(struct pico_atmt *);
34 void sinserts(UCS *, int, UCS *, int);
35 int AttachUpload(char *, size_t, char *, size_t);
36 int AttachCancel(char *);
39 #define HIBIT_WARN "Only ASCII characters allowed in attachment comments"
43 * AskAttach - ask for attachment fields and build resulting structure
44 * return pointer to that struct if OK, NULL otherwise
46 int
47 AskAttach(char *cmnt, size_t cmntlen, LMLIST **lm)
49 int i, status, fbrv, upload = 0;
50 int fb_flags = FB_READ | FB_ATTACH;
51 off_t attsz = 0;
52 size_t len;
53 char bfn[NLINE];
54 char fn[NLINE], sz[32];
55 LMLIST *new;
56 EML eml;
58 i = 2; /* 2 is prompt for file, 1 is prompt for comment */
59 fn[0] = '\0';
60 sz[0] = '\0';
61 cmnt[0] = '\0';
63 while(i){
64 if(i == 2){
65 EXTRAKEYS menu_attach[10];
66 int n;
68 memset(&menu_attach, 0, 10*sizeof(EXTRAKEYS));
69 menu_attach[n = 0].name = "^T";
70 menu_attach[n].label = N_("To Files");
71 menu_attach[n].key = (CTRL|'T');
73 if(gmode & MDCMPLT){
74 menu_attach[++n].name = "TAB";
75 menu_attach[n].label = N_("Complete");
76 menu_attach[n].key = (CTRL|'I');
79 #if !defined(DOS) && !defined(MAC)
80 if(Pmaster && Pmaster->upload){
82 * The Plan: ^R prompts for uploaded file's name which
83 * is passed to the defined upload command when the user
84 * hits Return to confirm the name.
85 * NOTE: this is different than upload into message
86 * text in which case the uploaded name isn't useful so
87 * a temp file is ok (be sure to fix the file mode).
89 menu_attach[++n].name = "^Y";
90 menu_attach[n].key = (CTRL|'Y');
91 /* TRANSLATORS: Read File is a prompt for the name of
92 a file to be read into the composer. */
93 menu_attach[n].label = upload ? N_("Read File") : N_("RcvUpload");
95 #endif
97 menu_attach[++n].name = NULL;
98 KS_OSDATASET(&menu_attach[0], KS_NONE);
99 status = mlreply_utf8(upload ? _("Name to give uploaded attachment: ")
100 /* TRANSLATORS: User is being asked for the name
101 of the file they want to attach to a message. */
102 : _("File to attach: "),
103 fn, sizeof(fn), QNORML, menu_attach);
105 else
106 /* TRANSLATORS: This is a prompt for a comment about the file
107 they have attached. */
108 status = mlreply_utf8(_("Attachment comment: "), cmnt, cmntlen, QNODQT, NULL);
110 switch(status){
111 case HELPCH:
112 if(Pmaster){
113 VARS_TO_SAVE *saved_state;
115 saved_state = save_pico_state();
116 (*Pmaster->helper)(Pmaster->attach_help, _("Attach Help"), 1);
117 if(saved_state){
118 restore_pico_state(saved_state);
119 free_pico_state(saved_state);
122 pico_refresh(FALSE, 1);
123 update();
124 continue;
126 else{
127 eml.s = (i == 2) ? "file" : "comment";
128 emlwrite("No Attachment %s help yet!", &eml);
129 sleep(3);
132 break;
134 case (CTRL|'I') :
135 if(i == 2){
136 char *fname, *p;
137 int dirlen;
139 bfn[0] = '\0';
140 if(*fn && (p = strrchr(fn, C_FILESEP))){
141 fname = p + 1;
142 dirlen = p - fn;
143 if(p == fn){
144 strncpy(bfn, S_FILESEP, sizeof(bfn));
145 bfn[sizeof(bfn)-1] = '\0';
147 #ifdef DOS
148 else if(fn[0] == C_FILESEP
149 || (isalpha((unsigned char)fn[0])
150 && fn[1] == ':')){
151 if(fn[1] == ':' && p == fn+2)
152 dirlen = fname - fn;
154 if(dirlen < sizeof(bfn)){
155 strncpy(bfn, fn, dirlen);
156 bfn[dirlen] = '\0';
159 #else
160 else if (fn[0] == C_FILESEP || fn[0] == '~'){
161 if(dirlen < sizeof(bfn)){
162 strncpy(bfn, fn, dirlen);
163 bfn[dirlen] = '\0';
166 #endif
167 else
168 snprintf(bfn, sizeof(bfn), "%s%c%.*s",
169 (gmode & MDCURDIR)
170 ? "."
171 : ((gmode & MDTREE) || opertree[0])
172 ? opertree : gethomedir(NULL),
173 C_FILESEP, (int) (p - fn), fn);
175 else{
176 fname = fn;
177 strncpy(bfn, (gmode & MDCURDIR)
178 ? "."
179 : ((gmode & MDTREE) || opertree[0])
180 ? opertree : gethomedir(NULL), sizeof(bfn));
181 bfn[sizeof(bfn)-1] = '\0';
184 if(!pico_fncomplete(bfn, fname, sizeof(fn)-(fname-fn)))
185 (*term.t_beep)();
187 else
188 (*term.t_beep)();
190 break;
192 case (CTRL|'T'):
193 if(i != 2){
194 (*term.t_beep)();
195 break;
198 *bfn = '\0';
199 if(*fn == '\0' || !isdir(fn, NULL, NULL)){
200 strncpy(fn, (gmode & MDCURDIR)
201 ? (browse_dir[0] ? browse_dir : ".")
202 : ((gmode & MDTREE) || opertree[0])
203 ? opertree
204 : (browse_dir[0] ? browse_dir
205 : gethomedir(NULL)), sizeof(fn));
206 fn[sizeof(fn)-1] = '\0';
209 if((fbrv = FileBrowse(fn, sizeof(fn), bfn, sizeof(bfn), sz, sizeof(sz),
210 upload ? fb_flags
211 : fb_flags|FB_LMODEPOS,
212 upload ? NULL
213 : lm)) == 1){
214 if (upload && (strlen(fn)+strlen(S_FILESEP)+strlen(bfn)) < sizeof(fn)){
215 size_t len1, len2;
217 len1 = strlen(bfn);
218 len2 = strlen(fn);
219 if((new=(LMLIST *)malloc(sizeof(*new))) == NULL
220 || (new->fname=malloc((len1+1) * sizeof(char))) == NULL
221 || (new->dir=malloc((len2+1) * sizeof(char))) == NULL){
222 emlwrite("\007Can't malloc space for filename", NULL);
223 return(-1);
226 strncpy(new->fname, bfn, len1);
227 new->fname[len1] = '\0';
228 strncpy(new->dir, fn, len2);
229 new->dir[len2] = '\0';
230 strncpy(new->size, sz, sizeof(new->size)-1);
231 new->size[sizeof(new->size)-1] = '\0';
232 new->next = NULL;
233 *lm = new;
235 strncat(fn, S_FILESEP, sizeof(fn)-strlen(fn)-1);
236 fn[sizeof(fn)-1] = '\0';
237 strncat(fn, bfn, sizeof(fn)-strlen(fn)-1);
238 fn[sizeof(fn)-1] = '\0';
239 if(!AttachUpload(fn, sizeof(fn), sz, sizeof(sz))){
240 i = 2; /* keep prompting for file */
241 sleep(3); /* problem, show error! */
243 else{
244 i--; /* go prompt for comment */
247 else if(!upload){
248 if(lm && *lm && !(*lm)->next) /* get comment */
249 i--;
250 else{ /* no comments if multiple files */
251 update();
252 return(1);
255 else{ /* trouble */
256 *fn = '\0';
257 AttachCancel(fn);
258 pico_refresh(FALSE,1);
259 update();
260 emlwrite("\007File name too BIG, cannot select!", NULL);
261 sleep(3);
264 else if (!fbrv)
265 *fn = '\0';
266 else{
267 *fn = '\0';
268 AttachCancel(fn);
269 pico_refresh(FALSE, 1);
270 update();
271 emlwrite("\007File name too big, cannot select!", NULL);
272 sleep(3);
275 /* fall thru to clean up the screen */
277 case (CTRL|'L'):
278 pico_refresh(FALSE, 1);
279 update();
280 continue;
282 #if !defined(DOS) && !defined(MAC)
283 case (CTRL|'Y'): /* upload? */
284 if(i == 2)
285 upload ^= 1; /* flip mode */
286 else
287 (*term.t_beep)();
289 break;
290 #endif
292 case ABORT:
293 return(AttachCancel((upload && i == 1) ? fn : NULL));
295 case TRUE: /* some comment */
296 case FALSE: /* No comment */
297 if(i-- == 2){
298 if(upload){
299 fixpath(fn, sizeof(fn)); /* names relative to ~ */
300 status = AttachUpload(fn, sizeof(fn), sz, sizeof(sz));
301 pico_refresh(FALSE, 1);
302 update();
303 if(!status){
304 i = 2; /* keep prompting for file */
305 sleep(3); /* problem, show error! */
308 else {
309 if(*fn == '\"' && fn[strlen(fn)-1] == '\"'){
310 int j;
312 for(j = 0; (fn[j] = fn[j+1]); j++)
315 fn[j-1] = '\0';
318 if(fn[0]){
319 if((gmode & MDTREE)
320 && !compresspath(opertree, fn, sizeof(fn))){
321 eml.s = (gmode&MDSCUR) ? _("home directory") : opertree;
322 emlwrite(
323 /* TRANSLATORS: the %s is replaced with the name of a directory */
324 _("Restricted mode allows attachments from %s only: too many ..'s"),
325 &eml);
326 return(0);
328 else{
329 fixpath(fn, sizeof(fn)); /* names relative to ~ */
330 if((gmode&MDTREE) && !in_oper_tree(fn)){
331 eml.s = (gmode&MDSCUR) ? _("home directory") : opertree;
332 emlwrite(
333 _("\007Restricted mode allows attachments from %s only"), &eml);
334 return(0);
338 if((status = fexist(fn, "r", &attsz)) != FIOSUC){
339 fioperr(status, fn); /* file DOESN'T exist! */
340 return(0);
343 len = strlen(fn);
344 if((new=(LMLIST *)malloc(sizeof(*new))) == NULL
345 || (new->fname=malloc((len+1)*sizeof(char))) == NULL){
346 emlwrite("\007Can't malloc space for filename", NULL);
347 return(-1);
350 new->dir = NULL;
351 strncpy(new->fname, fn, len);
352 new->fname[len] = '\0';
353 strncpy(new->size, prettysz(attsz), sizeof(new->size));
354 new->size[sizeof(new->size)-1] = '\0';
355 new->next = NULL;
356 *lm = new;
358 else
359 return(AttachCancel((upload && i == 1) ? fn : NULL));
362 else{
363 mlerase();
364 return(1); /* mission accomplished! */
367 break;
368 default:
369 break;
373 return(0);
378 * AttachUpload - Use call back to run the external upload command.
381 AttachUpload(char *fn, size_t fnlen, char *sz, size_t szlen)
383 long l;
385 if(gmode&MDSCUR){
386 emlwrite("\007Restricted mode disallows uploaded command", NULL);
387 return(0);
390 if(Pmaster && Pmaster->upload && (*Pmaster->upload)(fn, fnlen, &l)){
391 strncpy(sz, prettysz((off_t)l), szlen);
392 sz[szlen-1] = '\0';
393 return(1);
396 return(0);
401 * AttachCancel -
404 AttachCancel(char *fn)
406 emlwrite(_("Attach cancelled"), NULL);
407 if(fn && fn[0])
408 our_unlink(fn); /* blast uploaded file */
410 return(0);
414 extern struct headerentry *headents;
417 * SyncAttach - given a pointer to a linked list of attachment structures,
418 * return with that structure sync'd with what's displayed.
419 * delete any attachments in list of structs that's not on
420 * the display, and add any that aren't in list but on display.
423 SyncAttach(void)
425 int offset = 0, /* the offset to begin */
426 rv = 0,
427 ki = 0, /* number of known attmnts */
428 bi = 0, /* build array index */
429 nbld = 0, /* size of build array */
430 na, /* old number of attachmnt */
431 status, i, n = 0;
432 size_t j;
433 char file[NLINE], /* buffers to hold it all */
434 size[32],
435 comment[1024];
436 struct hdr_line *lp; /* current line in header */
437 struct headerentry *entry;
438 PATMT *tp, **knwn = NULL, **bld;
439 EML eml;
441 if(Pmaster == NULL)
442 return(-1);
444 for(entry = headents; entry->name != NULL; entry++) {
445 if(entry->is_attach)
446 break;
449 for(tp = Pmaster->attachments; tp; tp = tp->next)
450 ki++; /* Count known attachments */
452 if(ki){
453 if((knwn = (PATMT **)malloc((ki+1) * (sizeof(PATMT *)))) == NULL){
454 eml.s = comatose(ki + 1);
455 emlwrite("\007Can't allocate space for %s known attachment array entries",
456 &eml);
457 rv = -1;
458 goto exit_early;
460 for(i=0, tp = Pmaster->attachments; i < ki; i++, tp = tp->next){
461 knwn[i] = tp; /* fill table of */
462 /* known attachments */
468 * As a quick hack to avoid too many reallocs, check to see if
469 * there are more header lines than known attachments.
471 for(lp = entry->hd_text; lp ; lp = lp->next)
472 nbld++; /* count header lines */
474 nbld = nbld > ki ? nbld : ki + 1;
476 if((bld = (PATMT **)malloc(nbld * (sizeof(PATMT *)))) == NULL){
477 eml.s = comatose(nbld);
478 emlwrite("\007Can't allocate space for %s build array entries", &eml);
479 rv = -1;
480 goto exit_early;
483 lp = entry->hd_text;
484 while(lp != NULL){
485 char fn[NLINE];
486 na = ++n;
488 if(bi == nbld){ /* need to grow build array? */
489 if((bld = (PATMT **)realloc(bld, ++nbld * sizeof(PATMT *))) == NULL){
490 eml.s = comatose(nbld);
491 emlwrite("\007Can't resize build array to %s entries ", &eml);
492 rv = -1;
493 goto exit_early;
497 if((status = ParseAttach(&lp, &offset, file, sizeof(file), size, sizeof(size),
498 comment, sizeof(comment), &na)) != 0)
499 rv = (rv < 0) ? rv : status ; /* remember worst case */
501 if(*file == '\0'){
502 if(n != na && na > 0 && na <= ki && (knwn[na-1]->flags & A_FLIT)){
503 bld[bi++] = knwn[na-1];
504 knwn[na-1] = NULL;
506 continue;
509 if((gmode&MDTREE)
510 && file[0] != '['
511 && (!in_oper_tree(file)
512 || !compresspath(file, fn, sizeof(fn))))
513 /* no attachments outside ~ in secure mode! */
514 continue;
516 tp = NULL;
517 for(i = 0; i < ki; i++){ /* already know about it? */
519 * this is kind of gruesome. what we want to do is keep track
520 * of literal attachment entries because they may not be
521 * actual files we can access or that the user can readily
522 * access.
524 if(knwn[i]
525 && ((!(knwn[i]->flags&A_FLIT)
526 && !strcmp(file, knwn[i]->filename))
527 || ((knwn[i]->flags&A_FLIT) && i+1 == na))){
528 tp = knwn[i];
529 knwn[i] = NULL; /* forget we know about it */
531 if(status == -1) /* ignore garbage! */
532 break;
534 if((tp->flags&A_FLIT) && strcmp(file, tp->filename)){
535 rv = 1;
536 if((j=strlen(file)) > strlen(tp->filename)){
537 if((tp->filename = (char *)realloc(tp->filename,
538 sizeof(char)*(j+1))) == NULL){
539 emlwrite("\007Can't realloc filename space",NULL);
540 rv = -1;
541 goto exit_early;
545 strncpy(tp->filename, file, j);
546 tp->filename[j] = '\0';
548 else if(tp->size && strcmp(tp->size, size)){
549 rv = 1;
550 if((j=strlen(size)) > strlen(tp->size)){
551 if((tp->size=(char *)realloc(tp->size,
552 sizeof(char)*(j+1))) == NULL){
553 emlwrite("\007Can't realloc space for size", NULL);
554 rv = -1;
555 goto exit_early;
559 strncpy(tp->size, size, j);
560 tp->size[j] = '\0';
563 if(strcmp(tp->description, comment)){ /* new comment */
564 rv = 1;
565 if((j=strlen(comment)) > strlen(tp->description)){
566 if((tp->description=(char *)realloc(tp->description,
567 sizeof(char)*(j+1))) == NULL){
568 emlwrite("\007Can't realloc description", NULL);
569 rv = -1;
570 goto exit_early;
574 strncpy(tp->description, comment, j);
575 tp->description[j] = '\0';
577 break;
581 if(tp){
582 bld[bi++] = tp;
584 else{
585 if(file[0] != '['){
586 if((tp = NewAttach(file, atol(size), comment)) == NULL){
587 rv = -1;
588 goto exit_early;
590 bld[bi++] = tp;
592 else break;
595 if(status < 0)
596 tp->flags |= A_ERR; /* turn ON error bit */
597 else
598 tp->flags &= ~(A_ERR); /* turn OFF error bit */
601 if(bi){
602 for(i=0; i < bi-1; i++) /* link together newly built list */
603 bld[i]->next = bld[i+1];
605 bld[i]->next = NULL; /* tie it off */
606 Pmaster->attachments = bld[0];
608 else
609 Pmaster->attachments = NULL;
611 exit_early:
612 if(knwn){
613 for(i = 0; i < ki; i++){ /* kill old/unused references */
614 if(knwn[i]){
615 ZotAttach(knwn[i]);
616 free((char *) knwn[i]);
619 free((void *)knwn);
622 if(bld)
623 free((void *)bld);
625 return(rv);
630 * ParseAttach - given a header line and an offset into it, return with
631 * the three given fields filled in. Size of fn and cmnt
632 * buffers should be passed in fnlen and cmntlen.
633 * Always updates header fields that have changed or are
634 * fixed. An error advances offset to next attachment.
636 * returns: 1 if a field changed
637 * 0 nothing changed
638 * -1 on error
641 ParseAttach(struct hdr_line **lp, /* current header line */
642 int *off, /* offset into that line */
643 char *fn, /* return file name field */
644 size_t fnlen, /* fn buffer size */
645 char *sz,
646 size_t szlen,
647 char *cmnt, /* places to return fields */
648 size_t cmntlen,
649 int *no) /* attachment number */
651 int j, status, bod, eod = -1,
652 rv = 0, /* return value */
653 orig_offset,
654 lbln = 0, /* label'd attachment */
655 hibit = 0,
656 quoted = 0,
657 add_quotes = 0,
658 escaped = 0;
659 off_t attsz; /* attachment length */
660 EML eml;
661 UCS c, c_lookahead;
662 UCS tmp[1024], *p, *u, quotechar[2];
663 char ctmp[1024];
664 char *utf8 = NULL;
665 char *lblsz = NULL, /* label'd attchmnt's size */
666 number[8];
667 struct hdr_line *lprev = NULL;
668 enum { /* parse levels */
669 LWS, /* leading white space */
670 NUMB, /* attachment number */
671 WSN, /* white space after number */
672 TAG, /* attachments tag (fname) */
673 WST, /* white space after tag */
674 ASIZE, /* attachments size */
675 SWS, /* white space after size */
676 CMMNT, /* attachment comment */
677 TG} level; /* trailing garbage */
679 *fn = *sz = *cmnt = '\0'; /* initialize return strings */
680 p = tmp;
681 orig_offset = bod = *off;
682 quotechar[0] = '\"';
683 quotechar[1] = '\0';
685 level = LWS; /* start at beginning */
686 while(*lp != NULL){
688 if((c=(*lp)->text[*off]) == '\0'){ /* end of display line */
689 if(level == LWS && bod != *off){
690 (*lp)->text[bod] = '\0';
691 rv = 1;
693 lprev = *lp;
694 if((*lp = (*lp)->next) != NULL)
695 c = (*lp)->text[*off = bod = 0]; /* reset offset */
698 if(c != '\0'){
699 c_lookahead = (*lp)->text[*off + 1];
700 if(c_lookahead == '\0'){ /* end of display line */
701 if((*lp)->next != NULL)
702 c_lookahead = (*lp)->next->text[0];
706 switch(level){
707 case LWS: /* skip leading white space */
708 if(c <= 0xff && (isspace((unsigned char)c) || c == ',')){
709 c = ' ';
710 break;
713 if(c == '\0'){
714 if(bod > 0 && *off >= bod && lprev){
715 lprev->text[bod - 1] = '\0';
716 rv = 1;
719 else if(*off > bod && *lp){ /* wipe out whitespace */
720 memcpy(&(*lp)->text[bod], &(*lp)->text[*off],
721 ucs4_strlen(&(*lp)->text[*off]) + 1);
722 *off = bod; /* reset pointer */
723 rv = 1;
726 if(c == '\0')
727 break;
729 if(c > 0xff || !isdigit((unsigned char)c)){ /* add a number */
730 snprintf(number, sizeof(number), "%d. ", *no);
731 *no = 0; /* no previous number! */
732 u = utf8_to_ucs4_cpystr(number);
733 if(u){
734 sinserts((*lp == NULL) ? &lprev->text[*off]
735 : &(*lp)->text[*off],
736 0, u, j=ucs4_strlen(u));
738 fs_give((void **) &u);
741 *off += j - 1;
742 rv = 1;
743 level = TAG; /* interpret the name */
744 break;
746 level = NUMB;
747 case NUMB: /* attachment number */
748 if(c == '\0' || c == ','){ /* got to end, no number yet */
749 *p = '\0';
750 snprintf(number, sizeof(number), "%d. ", *no);
751 *no = 0; /* no previous number! */
752 if(c == '\0')
753 *lp = lprev; /* go back and look at prev */
755 c = (*lp)->text[*off = orig_offset]; /* reset offset */
756 u = utf8_to_ucs4_cpystr(number);
757 if(u){
758 sinserts((*lp == NULL) ? &lprev->text[*off]
759 : &(*lp)->text[*off],
760 0, u, j=ucs4_strlen(u));
762 fs_give((void **) &u);
764 *off += j - 1;
765 rv = 1;
766 p = tmp;
767 level = WSN; /* what's next... */
768 break;
770 else if(c == '.' && c_lookahead <= 0xff && isspace((unsigned char)c_lookahead)){
771 /* finished grabbing number */
772 /* if not space is not number */
774 * replace number if it's not right
776 *p = '\0';
777 snprintf(number, sizeof(number), "%d", *no); /* record the current... */
778 utf8 = ucs4_to_utf8_cpystr(tmp);
779 *no = atoi(utf8); /* and the old place in list */
780 if(strcmp(number, utf8)){
781 if(p-tmp > *off){ /* where to begin replacemnt */
782 UCS uu[1];
784 uu[0] = '\0';
785 j = (p-tmp) - *off;
786 sinserts((*lp)->text, *off, uu, 0);
787 u = utf8_to_ucs4_cpystr(number);
788 if(u){
789 sinserts(&lprev->text[ucs4_strlen(lprev->text)-j], j,
790 u, ucs4_strlen(u));
792 fs_give((void **) &u);
795 *off = 0;
797 else{
798 j = (*off) - (p-tmp);
799 u = utf8_to_ucs4_cpystr(number);
800 if(u){
801 sinserts((*lp == NULL) ? &lprev->text[j]
802 : &(*lp)->text[j],
803 p-tmp, u, ucs4_strlen(u));
805 fs_give((void **) &u);
808 *off += strlen(number) - (p-tmp);
810 rv = 1;
813 if(utf8)
814 fs_give((void **) &utf8);
816 p = tmp;
817 level = WSN; /* what's next... */
819 else if(c < '0' || c > '9'){ /* Must be part of tag */
820 snprintf(number, sizeof(number), "%d. ", *no);
821 u = utf8_to_ucs4_cpystr(number);
822 if(u){
823 sinserts((*lp == NULL) ? &lprev->text[*off - (p - tmp)]
824 : &(*lp)->text[*off - (p - tmp)],
825 0, u, j=ucs4_strlen(u));
827 fs_give((void **) &u);
830 *off += j;
831 level = TAG; /* interpret the name */
832 goto process_tag; /* in case already past end of tag */
834 else
835 *p++ = c;
837 break;
839 case WSN: /* blast whitespace */
840 if(c <= 0xff && (isspace((unsigned char)c) || c == '\0')){
841 break;
843 else if(c == '['){ /* labeled attachment */
844 lbln++;
846 else if(c == ',' || c == ' '){
847 /* TRANSLATORS: Attchmnt is an abbreviation for Attachment and
848 the %s is replaced with the character that is not
849 allowed in the name. */
850 eml.s = (c == ',') ? "," : "space";
851 emlwrite(_("\007Attchmnt: '%s' not allowed in file name"), &eml);
852 rv = -1;
853 level = TG; /* eat rest of garbage */
854 break;
856 level = TAG;
858 case TAG: /* get and check filename */
859 /* or labeled attachment */
860 process_tag: /* enclosed in [] */
861 if(c == '\0'
862 || (lbln && c == ']')
863 || (quoted && p != tmp && c == '\"')
864 || (!(lbln || quoted)
865 && (c <= 0xff && ((isspace((unsigned char) c) && c_lookahead == (UCS) '(') || strchr(",(\"", c))))){
866 if(p == tmp){
867 if(c == '\"')
868 quoted++;
870 else{
871 *p = '\0'; /* got something */
873 utf8 = ucs4_to_utf8_cpystr(tmp);
874 if(utf8){
875 if(strlen(utf8) > fnlen)
876 emlwrite("File name too big!",NULL);
878 strncpy(fn, utf8, fnlen); /* store file name */
879 fn[fnlen-1] = '\0';
880 fs_give((void **) &utf8);
883 if(!lbln){ /* normal file attachment */
884 if((gmode & MDTREE)
885 && !compresspath(opertree, fn, fnlen)){
886 eml.s = (gmode&MDSCUR) ? _("home directory") : opertree;
887 emlwrite(
888 _("Attachments allowed only from %s: too many ..'s"),
889 &eml);
890 rv = -1;
891 level = TG;
892 break;
894 else{
895 fixpath(fn, fnlen);
896 if((status=fexist(fn, "r", &attsz)) != FIOSUC){
897 fioperr(status, fn);
898 rv = -1;
899 level = TG; /* munch rest of garbage */
900 break;
903 if((gmode & MDTREE) && !in_oper_tree(fn)){
904 eml.s = (gmode&MDSCUR) ? _("home directory") : opertree;
905 emlwrite(_("\007Attachments allowed only from %s"), &eml);
906 rv = -1;
907 level = TG;
908 break;
912 utf8 = ucs4_to_utf8_cpystr(tmp);
914 if(utf8 && strcmp(fn, utf8)){ /* fn changed: display it */
915 if(*off >= p - tmp){ /* room for it? */
916 u = utf8_to_ucs4_cpystr(fn);
917 if(u){
919 * This whole parsing of the attachment line
920 * thing is ad hoc and susceptible to problems,
921 * and this particular part is no exception.
922 * Quote the filename if it contains spaces.
924 if(add_quotes){
925 sinserts((*lp == NULL) ? &lprev->text[*off - (p-tmp)]
926 : &(*lp)->text[*off - (p-tmp)],
927 0, quotechar, 1);
928 (*off)++;
931 sinserts((*lp == NULL) ? &lprev->text[*off - (p-tmp)]
932 : &(*lp)->text[*off - (p-tmp)],
933 p-tmp, u, j=ucs4_strlen(u));
935 *off += j - (p - tmp); /* advance offset */
937 if(add_quotes){
938 sinserts((*lp == NULL) ? &lprev->text[*off]
939 : &(*lp)->text[*off],
940 0, quotechar, 1);
941 (*off)++;
942 add_quotes = 0;
945 fs_give((void **) &u);
948 rv = 1;
950 else{
951 emlwrite("\007Attchmnt: Problem displaying real file path", NULL);
955 if(utf8)
956 fs_give((void **) &utf8);
958 else{ /* labelled attachment! */
960 * should explain about labelled attachments:
961 * these are attachments that came into the composer
962 * with meaningless file names (up to caller of
963 * composer to decide), for example, attachments
964 * being forwarded from another message. here, we
965 * just make sure the size stays what was passed
966 * to us. The user is SOL if they change the label
967 * since, as it is now, after changed, it will
968 * just get dropped from the list of what gets
969 * passed back to the caller.
971 PATMT *tp;
973 if(c != ']'){ /* legit label? */
974 eml.s = fn;
975 emlwrite(_("\007Attchmnt: Expected ']' after \"%s\""),
976 &eml);
977 rv = -1;
978 level = TG;
979 break;
982 strncat(fn, "]", fnlen-strlen(fn)-1);
983 fn[fnlen-1] = '\0';
986 * This is kind of cheating since otherwise
987 * ParseAttach doesn't know about the attachment
988 * struct. OK if filename's not found as it will
989 * get taken care of later...
991 tp = Pmaster->attachments; /* caller check Pmaster! */
992 j = 0;
993 while(tp != NULL){
994 if(++j == *no){
995 lblsz = tp->size;
996 break;
999 tp = tp->next;
1002 if(tp == NULL){
1003 eml.s = fn;
1004 emlwrite("\007Attchmnt: Unknown reference: %s", &eml);
1005 lblsz = "XXX";
1009 if(add_quotes){
1010 sinserts((*lp == NULL) ? &lprev->text[*off - (p-tmp)]
1011 : &(*lp)->text[*off - (p-tmp)],
1012 0, quotechar, 1);
1013 (*off)++;
1014 sinserts((*lp == NULL) ? &lprev->text[*off]
1015 : &(*lp)->text[*off],
1016 0, quotechar, 1);
1017 (*off)++;
1018 add_quotes = 0;
1021 p = tmp; /* reset p in tmp */
1022 level = WST;
1025 if(!lbln && c == '(') /* no space 'tween file, size*/
1026 level = ASIZE;
1027 else if(c == '\0'
1028 || (!(lbln || quoted) && (c == ',' || c == '\"'))){
1029 strncpy(sz, (lblsz) ? lblsz : prettysz(attsz), szlen);
1030 sz[szlen-1] = '\0';
1032 snprintf(ctmp, sizeof(ctmp), " (%s) %s", sz, (c == '\"') ? "" : "\"\"");
1033 u = utf8_to_ucs4_cpystr(ctmp);
1034 if(u){
1035 ucs4_strncpy(tmp, u, sizeof(tmp)/sizeof(tmp[0]));
1036 tmp[sizeof(tmp)/sizeof(tmp[0]) - 1] = '\0';
1037 fs_give((void **) &u);
1040 sinserts((*lp == NULL) ? &lprev->text[*off]
1041 : &(*lp)->text[*off],
1042 0, tmp, j=ucs4_strlen(tmp));
1043 *off += j;
1044 rv = 1;
1045 level = (c == '\"') ? CMMNT : TG;/* cmnt or eat trash */
1048 else if(!(lbln || quoted)
1049 && (c == ',' || /** c == ' ' || **/ c == '[' || c == ']')){
1050 eml.s = c == ',' ? ","
1051 : c == ' ' ? "space"
1052 : c == '[' ? "[" : "]";
1053 emlwrite(_("\007Attchmnt: '%s' not allowed in file name"), &eml);
1054 rv = -1; /* bad char in file name */
1055 level = TG; /* gobble garbage */
1057 else if(!(lbln || quoted) && (c <= 0xff && isspace((unsigned char) c))){
1058 add_quotes++;
1059 *p++ = c; /* add char to name */
1061 else
1062 *p++ = c; /* add char to name */
1064 break;
1066 case WST: /* skip white space */
1067 if(c > 0xff || !isspace((unsigned char)c)){
1069 * whole attachment, comment or done!
1071 if(c == ',' || c == '\0' || c == '\"'){
1072 strncpy(sz, (lblsz) ? lblsz : prettysz(attsz), szlen);
1073 sz[szlen-1] = '\0';
1075 snprintf(ctmp, sizeof(ctmp), " (%s) %s", sz, (c == '\"') ? "" : "\"\"");
1076 u = utf8_to_ucs4_cpystr(ctmp);
1077 if(u){
1078 ucs4_strncpy(tmp, u, sizeof(tmp)/sizeof(tmp[0]));
1079 tmp[sizeof(tmp)/sizeof(tmp[0]) - 1] = '\0';
1080 fs_give((void **) &u);
1083 sinserts((*lp == NULL) ? &lprev->text[*off]
1084 : &(*lp)->text[*off],
1085 0, tmp, j=ucs4_strlen(tmp));
1086 *off += j;
1087 rv = 1;
1088 level = (c == '\"') ? CMMNT : TG;
1089 lbln = 0; /* reset flag */
1091 else if(c == '('){ /* get the size */
1092 level = ASIZE;
1094 else{
1095 eml.s = fn;
1096 emlwrite(_("\007Attchmnt: Expected '(' or '\"' after %s"), &eml);
1097 rv = -1; /* bag it all */
1098 level = TG;
1101 break;
1103 case ASIZE: /* check size */
1104 if(c == ')'){ /* finished grabbing size */
1105 *p = '\0';
1107 * replace sizes if they don't match!
1109 utf8 = ucs4_to_utf8_cpystr(tmp);
1110 if(utf8){
1111 strncpy(sz, utf8, szlen);
1112 sz[szlen-1] = '\0';
1113 fs_give((void **) &utf8);
1116 if(strcmp(sz, (lblsz) ? lblsz : prettysz(attsz))){
1117 strncpy(sz, (lblsz) ? lblsz : prettysz(attsz), szlen);
1118 sz[szlen-1] = '\0';
1119 if(p-tmp > *off){ /* where to begin replacemnt */
1120 UCS uu[1];
1122 uu[0] = '\0';
1123 j = (p-tmp) - *off;
1124 sinserts((*lp)->text, *off, uu, 0);
1125 u = utf8_to_ucs4_cpystr(sz);
1126 if(u){
1127 sinserts(&lprev->text[ucs4_strlen(lprev->text)-j], j,
1128 u, ucs4_strlen(u));
1129 fs_give((void **) &u);
1132 *off = 0;
1134 else{
1135 j = (*off) - (p-tmp);
1136 u = utf8_to_ucs4_cpystr(sz);
1137 if(u){
1138 sinserts((*lp == NULL) ? &lprev->text[j]
1139 : &(*lp)->text[j],
1140 p-tmp , u, ucs4_strlen(u));
1141 *off += ucs4_strlen(u) - (p-tmp);
1142 fs_give((void **) &u);
1145 rv = 1;
1148 p = tmp;
1149 level = SWS; /* what's next... */
1151 else if(c == '\0' || c == ','){
1152 *p = '\0';
1153 utf8 = ucs4_to_utf8_cpystr(tmp);
1154 eml.s = utf8;
1155 emlwrite(_("\007Attchmnt: Size field missing ')': \"%s\""), &eml);
1156 if(utf8)
1157 fs_give((void **) &utf8);
1159 rv = -1;
1160 level = TG;
1162 else
1163 *p++ = c;
1165 break;
1167 case SWS: /* skip white space */
1168 if(c > 0xff || !isspace((unsigned char)c)){
1169 if(c == ','){ /* no description */
1170 level = TG; /* munch rest of garbage */
1171 lbln = 0; /* reset flag */
1173 else if(c != '\"' && c != '\0'){
1174 emlwrite(_("\007Attchmnt: Malformed comment, quotes required"), NULL);
1175 rv = -1;
1176 level = TG;
1178 else
1179 level = CMMNT;
1181 break;
1183 case CMMNT: /* slurp up comment */
1184 if((c == '\"' && !escaped) || c == '\0'){
1185 *p = '\0'; /* cap it off */
1186 p = tmp; /* reset p */
1187 utf8 = ucs4_to_utf8_cpystr(tmp);
1188 if(utf8){
1189 if(strlen(utf8) > cmntlen)
1190 emlwrite("Comment too long!",NULL);
1192 strncpy(cmnt,utf8,cmntlen-1); /* copy the comment */
1193 cmnt[cmntlen-1] = '\0';
1194 fs_give((void **) &utf8);
1195 if(c == '\0'){
1196 emlwrite(_("\007Attchmnt: Closing quote required at end of comment"), NULL);
1197 rv = -1;
1201 level = TG; /* prepare for next one */
1202 lbln = 0; /* reset flag */
1204 else if(c == '\\' && !escaped){ /* something escaped? */
1205 escaped = 1;
1207 else{
1208 if(escaped){
1209 if(c != '\"') /* we only quote escapes */
1210 *p++ = '\\';
1212 escaped = 0;
1215 if(((*p++ = c) & 0x80) && (gmode & MDHBTIGN) && !hibit++)
1216 emlwrite(HIBIT_WARN, NULL);
1219 break;
1221 case TG: /* get comma or final EOL */
1222 if(eod < 0)
1223 eod = *off;
1224 if(c > 0xff || !isspace((unsigned char)c)){
1225 switch(c){
1226 case '\0':
1227 if(eod != *off)
1228 lprev->text[*off = eod] = '\0';
1229 break;
1230 case ',':
1231 if(eod != *off){
1232 memcpy(&(*lp)->text[eod], &(*lp)->text[*off],
1233 ucs4_strlen(&(*lp)->text[*off]) + 1);
1234 *off = eod;
1235 rv = 1;
1237 break;
1238 default:
1239 if(rv != -1)
1240 emlwrite(_("\007Attchmnt: Comma must separate attachments"), NULL);
1241 rv = -1;
1244 break;
1246 default: /* something's very wrong */
1247 emlwrite("\007Attchmnt: Weirdness in ParseAttach", NULL);
1248 return(-1); /* just give up */
1251 if(c == '\0') /* we're done */
1252 break;
1254 (*off)++;
1257 * not in comment or label name? done.
1259 if(c == ',' && (level != TAG && level != CMMNT && !lbln))
1260 break; /* put offset past ',' */
1263 return(rv);
1268 * NewAttach - given a filename (assumed to accessible) and comment, creat
1270 PATMT *
1271 NewAttach(char *f, long l, char *c)
1273 PATMT *tp;
1274 size_t len;
1276 if((tp=(PATMT *)malloc(sizeof(PATMT))) == NULL){
1277 emlwrite("No memory to add attachment", NULL);
1278 return(NULL);
1280 else
1281 memset(tp, 0, sizeof(PATMT));
1283 /* file and size malloc */
1284 len = strlen(f);
1285 if((tp->filename = (char *) malloc((len+1) * sizeof(char))) == NULL){
1286 emlwrite("Can't malloc name for attachment", NULL);
1287 free((char *) tp);
1288 return(NULL);
1291 strncpy(tp->filename, f, len);
1292 tp->filename[len] = '\0';
1294 if(l > -1){
1295 len = strlen(prettysz((off_t) l));
1296 tp->size = (char *) malloc((len+1) * sizeof(char));
1297 if(tp->size == NULL){
1298 emlwrite("Can't malloc size for attachment", NULL);
1299 free((char *) tp->filename);
1300 free((char *) tp);
1301 return(NULL);
1303 else{
1304 strncpy(tp->size, prettysz((off_t) l), len);
1305 tp->size[len] = '\0';
1309 /* description malloc */
1310 len = strlen(c);
1311 if((tp->description = (char *) malloc((len+1) * sizeof(char))) == NULL){
1312 emlwrite("Can't malloc description for attachment", NULL);
1313 free((char *) tp->size);
1314 free((char *) tp->filename);
1315 free((char *) tp);
1316 return(NULL);
1319 strncpy(tp->description, c, len);
1320 tp->description[len] = '\0';
1322 /* callback to show user the mime type that will be used for attachment */
1323 if(Pmaster->mimetype && (*Pmaster->mimetype)(f) > 0){
1324 int rv ;
1326 clearcursor();
1327 mlerase();
1328 rv = (*Pmaster->showmsg)('x');
1329 ttresize();
1330 picosigs();
1331 if(rv) /* Did showmsg corrupt the screen? */
1332 PaintBody(0); /* Yes, repaint it */
1334 mpresf = 1;
1337 return(tp);
1342 * AttachError - Sniff list of attachments, returning TRUE if there's
1343 * any sign of trouble...
1346 AttachError(void)
1348 PATMT *ap;
1350 if(!Pmaster)
1351 return(0);
1353 ap = Pmaster->attachments;
1354 while(ap){
1355 if((ap->flags) & A_ERR)
1356 return(1);
1358 ap = ap->next;
1361 return(FALSE);
1365 char *
1366 QuoteAttach(char *fn, size_t fnlen)
1368 char *p;
1370 if(*fn && strpbrk(fn, " \t,(\"")){ /* Quote it? */
1371 p = &fn[strlen(fn)];
1372 if(p+2-fn < fnlen){
1373 *(p+2) = '\0';
1374 *(p+1) = '\"';
1377 *p = *(p-1);
1378 while(--p != fn);
1379 *p = '\"';
1383 return(fn);
1387 void
1388 ZotAttach(PATMT *p)
1390 if(!p)
1391 return;
1393 if(p->description)
1394 free((char *)p->description);
1396 if(p->filename){
1397 if(p->flags & A_TMP)
1398 our_unlink(p->filename);
1400 free((char *)p->filename);
1403 if(p->size)
1404 free((char *)p->size);
1406 if(p->id)
1407 free((char *)p->id);
1409 p->next = NULL;
1411 #endif /* ATTACHMENTS */
1415 * intag - return TRUE if i is in a column that makes up an
1416 * attachment line number
1419 intag(UCS *s, int i)
1421 UCS *p = s;
1422 int n = 0;
1424 while(*p != '\0' && (p-s) < 5){ /* is there a tag? it */
1425 if(n && *p == '.') /* can't be more than 4 */
1426 return(i <= p-s); /* chars long! */
1428 if(*p < '0' || *p > '9')
1429 break;
1430 else
1431 n = (n * 10) + (*p - '0');
1433 p++;
1436 return(FALSE);
1441 * prettysz - return pointer to string containing nice size description
1443 char *
1444 prettysz(off_t l)
1446 static char b[32];
1447 long sz, left, right;
1449 sz = (long) l;
1450 b[0] = '\0';
1452 if(sz < 1000L){
1453 snprintf(b, sizeof(b), "%ld B", sz); /* xxx B */
1455 else if(sz < 9950L){
1456 left = (sz + 50L) / 1000L;
1457 right = ((sz + 50L) - left * 1000L) / 100L;
1458 snprintf(b, sizeof(b), "%ld.%ld KB", left, right); /* x.x KB */
1460 else if(sz < 999500L){
1461 snprintf(b, sizeof(b), "%ld KB", (sz + 500L) / 1000L); /* xxx KB */
1463 else if(sz < 9950000L){
1464 left = (sz + 50000L) / 1000000L;
1465 right = ((sz + 50000L) - left * 1000000L) / 100000L;
1466 snprintf(b, sizeof(b), "%ld.%ld MB", left, right); /* x.x MB */
1468 else{
1469 snprintf(b, sizeof(b), "%ld MB", (sz + 500000L) / 1000000L); /* xxx MB */
1472 return(b);
1477 * sinserts - s insert into another string
1479 void
1480 sinserts(UCS *ds, /* dest string */
1481 int dl, /* where to begin insert */
1482 UCS *ss, /* source string */
1483 int sl) /* length of ss */
1485 UCS *dp, *edp; /* pointers into dest. */
1486 size_t j; /* jump difference */
1488 if(sl >= dl){ /* source bigger than dest. */
1489 dp = ds + dl; /* shift dest. to make room */
1490 if((edp = ucs4_strchr(dp, '\0')) != NULL){
1491 j = sl - dl;
1493 for( ;edp >= dp; edp--)
1494 edp[j] = *edp;
1496 while(sl--)
1497 *ds++ = *ss++;
1499 else
1500 emlwrite("\007No end of line???", NULL); /* can this happen? */
1502 else{ /* dest is longer, shrink it */
1503 j = dl - sl; /* difference in lengths */
1505 while(sl--) /* copy u onto ds */
1506 *ds++ = *ss++;
1508 if(ucs4_strlen(ds) > j){ /* shuffle the rest left */
1510 *ds = ds[j];
1511 while(*ds++ != '\0');
1513 else
1514 *ds = '\0';