* new version 2.19.9999
[alpine.git] / pico / attach.c
blobff29efa7b583887578444be8bb8b4c2c9bc7a06a
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-2015 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[4];
66 int n;
68 menu_attach[n = 0].name = "^T";
69 menu_attach[n].label = N_("To Files");
70 menu_attach[n].key = (CTRL|'T');
72 if(gmode & MDCMPLT){
73 menu_attach[++n].name = "TAB";
74 menu_attach[n].label = N_("Complete");
75 menu_attach[n].key = (CTRL|'I');
78 #if !defined(DOS) && !defined(MAC)
79 if(Pmaster && Pmaster->upload){
81 * The Plan: ^R prompts for uploaded file's name which
82 * is passed to the defined upload command when the user
83 * hits Return to confirm the name.
84 * NOTE: this is different than upload into message
85 * text in which case the uploaded name isn't useful so
86 * a temp file is ok (be sure to fix the file mode).
88 menu_attach[++n].name = "^Y";
89 menu_attach[n].key = (CTRL|'Y');
90 /* TRANSLATORS: Read File is a prompt for the name of
91 a file to be read into the composer. */
92 menu_attach[n].label = upload ? N_("Read File") : N_("RcvUpload");
94 #endif
96 menu_attach[++n].name = NULL;
97 KS_OSDATASET(&menu_attach[0], KS_NONE);
98 status = mlreply_utf8(upload ? _("Name to give uploaded attachment: ")
99 /* TRANSLATORS: User is being asked for the name
100 of the file they want to attach to a message. */
101 : _("File to attach: "),
102 fn, sizeof(fn), QNORML, menu_attach);
104 else
105 /* TRANSLATORS: This is a prompt for a comment about the file
106 they have attached. */
107 status = mlreply_utf8(_("Attachment comment: "), cmnt, cmntlen, QNODQT, NULL);
109 switch(status){
110 case HELPCH:
111 if(Pmaster){
112 VARS_TO_SAVE *saved_state;
114 saved_state = save_pico_state();
115 (*Pmaster->helper)(Pmaster->attach_help, _("Attach Help"), 1);
116 if(saved_state){
117 restore_pico_state(saved_state);
118 free_pico_state(saved_state);
121 pico_refresh(FALSE, 1);
122 update();
123 continue;
125 else{
126 eml.s = (i == 2) ? "file" : "comment";
127 emlwrite("No Attachment %s help yet!", &eml);
128 sleep(3);
131 break;
133 case (CTRL|'I') :
134 if(i == 2){
135 char *fname, *p;
136 int dirlen;
138 bfn[0] = '\0';
139 if(*fn && (p = strrchr(fn, C_FILESEP))){
140 fname = p + 1;
141 dirlen = p - fn;
142 if(p == fn){
143 strncpy(bfn, S_FILESEP, sizeof(bfn));
144 bfn[sizeof(bfn)-1] = '\0';
146 #ifdef DOS
147 else if(fn[0] == C_FILESEP
148 || (isalpha((unsigned char)fn[0])
149 && fn[1] == ':')){
150 if(fn[1] == ':' && p == fn+2)
151 dirlen = fname - fn;
153 if(dirlen < sizeof(bfn)){
154 strncpy(bfn, fn, dirlen);
155 bfn[dirlen] = '\0';
158 #else
159 else if (fn[0] == C_FILESEP || fn[0] == '~'){
160 if(dirlen < sizeof(bfn)){
161 strncpy(bfn, fn, dirlen);
162 bfn[dirlen] = '\0';
165 #endif
166 else
167 snprintf(bfn, sizeof(bfn), "%s%c%.*s",
168 (gmode & MDCURDIR)
169 ? "."
170 : ((gmode & MDTREE) || opertree[0])
171 ? opertree : gethomedir(NULL),
172 C_FILESEP, p - fn, fn);
174 else{
175 fname = fn;
176 strncpy(bfn, (gmode & MDCURDIR)
177 ? "."
178 : ((gmode & MDTREE) || opertree[0])
179 ? opertree : gethomedir(NULL), sizeof(bfn));
180 bfn[sizeof(bfn)-1] = '\0';
183 if(!pico_fncomplete(bfn, fname, sizeof(fn)-(fname-fn)))
184 (*term.t_beep)();
186 else
187 (*term.t_beep)();
189 break;
191 case (CTRL|'T'):
192 if(i != 2){
193 (*term.t_beep)();
194 break;
197 *bfn = '\0';
198 if(*fn == '\0' || !isdir(fn, NULL, NULL)){
199 strncpy(fn, (gmode & MDCURDIR)
200 ? (browse_dir[0] ? browse_dir : ".")
201 : ((gmode & MDTREE) || opertree[0])
202 ? opertree
203 : (browse_dir[0] ? browse_dir
204 : gethomedir(NULL)), sizeof(fn));
205 fn[sizeof(fn)-1] = '\0';
208 if((fbrv = FileBrowse(fn, sizeof(fn), bfn, sizeof(bfn), sz, sizeof(sz),
209 upload ? fb_flags
210 : fb_flags|FB_LMODEPOS,
211 upload ? NULL
212 : lm)) == 1){
213 if (upload && (strlen(fn)+strlen(S_FILESEP)+strlen(bfn)) < sizeof(fn)){
214 size_t len1, len2;
216 len1 = strlen(bfn);
217 len2 = strlen(fn);
218 if((new=(LMLIST *)malloc(sizeof(*new))) == NULL
219 || (new->fname=malloc((len1+1) * sizeof(char))) == NULL
220 || (new->dir=malloc((len2+1) * sizeof(char))) == NULL){
221 emlwrite("\007Can't malloc space for filename", NULL);
222 return(-1);
225 strncpy(new->fname, bfn, len1);
226 new->fname[len1] = '\0';
227 strncpy(new->dir, fn, len2);
228 new->dir[len2] = '\0';
229 strncpy(new->size, sz, sizeof(new->size)-1);
230 new->size[sizeof(new->size)-1] = '\0';
231 new->next = NULL;
232 *lm = new;
234 strncat(fn, S_FILESEP, sizeof(fn)-strlen(fn)-1);
235 fn[sizeof(fn)-1] = '\0';
236 strncat(fn, bfn, sizeof(fn)-strlen(fn)-1);
237 fn[sizeof(fn)-1] = '\0';
238 if(!AttachUpload(fn, sizeof(fn), sz, sizeof(sz))){
239 i = 2; /* keep prompting for file */
240 sleep(3); /* problem, show error! */
242 else{
243 i--; /* go prompt for comment */
246 else if(!upload){
247 if(lm && *lm && !(*lm)->next) /* get comment */
248 i--;
249 else{ /* no comments if multiple files */
250 update();
251 return(1);
254 else{ /* trouble */
255 *fn = '\0';
256 AttachCancel(fn);
257 pico_refresh(FALSE,1);
258 update();
259 emlwrite("\007File name too BIG, cannot select!", NULL);
260 sleep(3);
263 else if (!fbrv)
264 *fn = '\0';
265 else{
266 *fn = '\0';
267 AttachCancel(fn);
268 pico_refresh(FALSE, 1);
269 update();
270 emlwrite("\007File name too big, cannot select!", NULL);
271 sleep(3);
274 /* fall thru to clean up the screen */
276 case (CTRL|'L'):
277 pico_refresh(FALSE, 1);
278 update();
279 continue;
281 #if !defined(DOS) && !defined(MAC)
282 case (CTRL|'Y'): /* upload? */
283 if(i == 2)
284 upload ^= 1; /* flip mode */
285 else
286 (*term.t_beep)();
288 break;
289 #endif
291 case ABORT:
292 return(AttachCancel((upload && i == 1) ? fn : NULL));
294 case TRUE: /* some comment */
295 case FALSE: /* No comment */
296 if(i-- == 2){
297 if(upload){
298 fixpath(fn, sizeof(fn)); /* names relative to ~ */
299 status = AttachUpload(fn, sizeof(fn), sz, sizeof(sz));
300 pico_refresh(FALSE, 1);
301 update();
302 if(!status){
303 i = 2; /* keep prompting for file */
304 sleep(3); /* problem, show error! */
307 else {
308 if(*fn == '\"' && fn[strlen(fn)-1] == '\"'){
309 int j;
311 for(j = 0; (fn[j] = fn[j+1]); j++)
314 fn[j-1] = '\0';
317 if(fn[0]){
318 if((gmode & MDTREE)
319 && !compresspath(opertree, fn, sizeof(fn))){
320 eml.s = (gmode&MDSCUR) ? _("home directory") : opertree;
321 emlwrite(
322 /* TRANSLATORS: the %s is replaced with the name of a directory */
323 _("Restricted mode allows attachments from %s only: too many ..'s"),
324 &eml);
325 return(0);
327 else{
328 fixpath(fn, sizeof(fn)); /* names relative to ~ */
329 if((gmode&MDTREE) && !in_oper_tree(fn)){
330 eml.s = (gmode&MDSCUR) ? _("home directory") : opertree;
331 emlwrite(
332 _("\007Restricted mode allows attachments from %s only"), &eml);
333 return(0);
337 if((status = fexist(fn, "r", &attsz)) != FIOSUC){
338 fioperr(status, fn); /* file DOESN'T exist! */
339 return(0);
342 len = strlen(fn);
343 if((new=(LMLIST *)malloc(sizeof(*new))) == NULL
344 || (new->fname=malloc((len+1)*sizeof(char))) == NULL){
345 emlwrite("\007Can't malloc space for filename", NULL);
346 return(-1);
349 new->dir = NULL;
350 strncpy(new->fname, fn, len);
351 new->fname[len] = '\0';
352 strncpy(new->size, prettysz(attsz), sizeof(new->size));
353 new->size[sizeof(new->size)-1] = '\0';
354 new->next = NULL;
355 *lm = new;
357 else
358 return(AttachCancel((upload && i == 1) ? fn : NULL));
361 else{
362 mlerase();
363 return(1); /* mission accomplished! */
366 break;
367 default:
368 break;
372 return(0);
377 * AttachUpload - Use call back to run the external upload command.
380 AttachUpload(char *fn, size_t fnlen, char *sz, size_t szlen)
382 long l;
384 if(gmode&MDSCUR){
385 emlwrite("\007Restricted mode disallows uploaded command", NULL);
386 return(0);
389 if(Pmaster && Pmaster->upload && (*Pmaster->upload)(fn, fnlen, &l)){
390 strncpy(sz, prettysz((off_t)l), szlen);
391 sz[szlen-1] = '\0';
392 return(1);
395 return(0);
400 * AttachCancel -
403 AttachCancel(char *fn)
405 emlwrite(_("Attach cancelled"), NULL);
406 if(fn && fn[0])
407 our_unlink(fn); /* blast uploaded file */
409 return(0);
413 extern struct headerentry *headents;
416 * SyncAttach - given a pointer to a linked list of attachment structures,
417 * return with that structure sync'd with what's displayed.
418 * delete any attachments in list of structs that's not on
419 * the display, and add any that aren't in list but on display.
422 SyncAttach(void)
424 int offset = 0, /* the offset to begin */
425 rv = 0,
426 ki = 0, /* number of known attmnts */
427 bi = 0, /* build array index */
428 nbld = 0, /* size of build array */
429 na, /* old number of attachmnt */
430 status, i, n = 0;
431 size_t j;
432 char file[NLINE], /* buffers to hold it all */
433 size[32],
434 comment[1024];
435 struct hdr_line *lp; /* current line in header */
436 struct headerentry *entry;
437 PATMT *tp, **knwn = NULL, **bld;
438 EML eml;
440 if(Pmaster == NULL)
441 return(-1);
443 for(entry = headents; entry->name != NULL; entry++) {
444 if(entry->is_attach)
445 break;
448 for(tp = Pmaster->attachments; tp; tp = tp->next)
449 ki++; /* Count known attachments */
451 if(ki){
452 if((knwn = (PATMT **)malloc((ki+1) * (sizeof(PATMT *)))) == NULL){
453 eml.s = comatose(ki + 1);
454 emlwrite("\007Can't allocate space for %s known attachment array entries",
455 &eml);
456 rv = -1;
457 goto exit_early;
459 for(i=0, tp = Pmaster->attachments; i < ki; i++, tp = tp->next){
460 knwn[i] = tp; /* fill table of */
461 /* known attachments */
467 * As a quick hack to avoid too many reallocs, check to see if
468 * there are more header lines than known attachments.
470 for(lp = entry->hd_text; lp ; lp = lp->next)
471 nbld++; /* count header lines */
473 nbld = nbld > ki ? nbld : ki + 1;
475 if((bld = (PATMT **)malloc(nbld * (sizeof(PATMT *)))) == NULL){
476 eml.s = comatose(nbld);
477 emlwrite("\007Can't allocate space for %s build array entries", &eml);
478 rv = -1;
479 goto exit_early;
482 lp = entry->hd_text;
483 while(lp != NULL){
484 char fn[NLINE];
485 na = ++n;
487 if(bi == nbld){ /* need to grow build array? */
488 if((bld = (PATMT **)realloc(bld, ++nbld * sizeof(PATMT *))) == NULL){
489 eml.s = comatose(nbld);
490 emlwrite("\007Can't resize build array to %s entries ", &eml);
491 rv = -1;
492 goto exit_early;
496 if((status = ParseAttach(&lp, &offset, file, sizeof(file), size, sizeof(size),
497 comment, sizeof(comment), &na)) != 0)
498 rv = (rv < 0) ? rv : status ; /* remember worst case */
500 if(*file == '\0'){
501 if(n != na && na > 0 && na <= ki && (knwn[na-1]->flags & A_FLIT)){
502 bld[bi++] = knwn[na-1];
503 knwn[na-1] = NULL;
505 continue;
508 if((gmode&MDTREE)
509 && file[0] != '['
510 && (!in_oper_tree(file)
511 || !compresspath(file, fn, sizeof(fn))))
512 /* no attachments outside ~ in secure mode! */
513 continue;
515 tp = NULL;
516 for(i = 0; i < ki; i++){ /* already know about it? */
518 * this is kind of gruesome. what we want to do is keep track
519 * of literal attachment entries because they may not be
520 * actual files we can access or that the user can readily
521 * access.
523 if(knwn[i]
524 && ((!(knwn[i]->flags&A_FLIT)
525 && !strcmp(file, knwn[i]->filename))
526 || ((knwn[i]->flags&A_FLIT) && i+1 == na))){
527 tp = knwn[i];
528 knwn[i] = NULL; /* forget we know about it */
530 if(status == -1) /* ignore garbage! */
531 break;
533 if((tp->flags&A_FLIT) && strcmp(file, tp->filename)){
534 rv = 1;
535 if((j=strlen(file)) > strlen(tp->filename)){
536 if((tp->filename = (char *)realloc(tp->filename,
537 sizeof(char)*(j+1))) == NULL){
538 emlwrite("\007Can't realloc filename space",NULL);
539 rv = -1;
540 goto exit_early;
544 strncpy(tp->filename, file, j);
545 tp->filename[j] = '\0';
547 else if(tp->size && strcmp(tp->size, size)){
548 rv = 1;
549 if((j=strlen(size)) > strlen(tp->size)){
550 if((tp->size=(char *)realloc(tp->size,
551 sizeof(char)*(j+1))) == NULL){
552 emlwrite("\007Can't realloc space for size", NULL);
553 rv = -1;
554 goto exit_early;
558 strncpy(tp->size, size, j);
559 tp->size[j] = '\0';
562 if(strcmp(tp->description, comment)){ /* new comment */
563 rv = 1;
564 if((j=strlen(comment)) > strlen(tp->description)){
565 if((tp->description=(char *)realloc(tp->description,
566 sizeof(char)*(j+1))) == NULL){
567 emlwrite("\007Can't realloc description", NULL);
568 rv = -1;
569 goto exit_early;
573 strncpy(tp->description, comment, j);
574 tp->description[j] = '\0';
576 break;
580 if(tp){
581 bld[bi++] = tp;
583 else{
584 if(file[0] != '['){
585 if((tp = NewAttach(file, atol(size), comment)) == NULL){
586 rv = -1;
587 goto exit_early;
589 bld[bi++] = tp;
591 else break;
594 if(status < 0)
595 tp->flags |= A_ERR; /* turn ON error bit */
596 else
597 tp->flags &= ~(A_ERR); /* turn OFF error bit */
600 if(bi){
601 for(i=0; i < bi-1; i++) /* link together newly built list */
602 bld[i]->next = bld[i+1];
604 bld[i]->next = NULL; /* tie it off */
605 Pmaster->attachments = bld[0];
607 else
608 Pmaster->attachments = NULL;
610 exit_early:
611 if(knwn){
612 for(i = 0; i < ki; i++){ /* kill old/unused references */
613 if(knwn[i]){
614 ZotAttach(knwn[i]);
615 free((char *) knwn[i]);
618 free((void *)knwn);
621 if(bld)
622 free((void *)bld);
624 return(rv);
629 * ParseAttach - given a header line and an offset into it, return with
630 * the three given fields filled in. Size of fn and cmnt
631 * buffers should be passed in fnlen and cmntlen.
632 * Always updates header fields that have changed or are
633 * fixed. An error advances offset to next attachment.
635 * returns: 1 if a field changed
636 * 0 nothing changed
637 * -1 on error
640 ParseAttach(struct hdr_line **lp, /* current header line */
641 int *off, /* offset into that line */
642 char *fn, /* return file name field */
643 size_t fnlen, /* fn buffer size */
644 char *sz,
645 size_t szlen,
646 char *cmnt, /* places to return fields */
647 size_t cmntlen,
648 int *no) /* attachment number */
650 int j, status, bod, eod = -1,
651 rv = 0, /* return value */
652 orig_offset,
653 lbln = 0, /* label'd attachment */
654 hibit = 0,
655 quoted = 0,
656 add_quotes = 0,
657 escaped = 0;
658 off_t attsz; /* attachment length */
659 EML eml;
660 UCS c, c_lookahead;
661 UCS tmp[1024], *p, *u, quotechar[2];
662 char ctmp[1024];
663 char *utf8 = NULL;
664 char *lblsz = NULL, /* label'd attchmnt's size */
665 number[8];
666 struct hdr_line *lprev = NULL;
667 enum { /* parse levels */
668 LWS, /* leading white space */
669 NUMB, /* attachment number */
670 WSN, /* white space after number */
671 TAG, /* attachments tag (fname) */
672 WST, /* white space after tag */
673 ASIZE, /* attachments size */
674 SWS, /* white space after size */
675 CMMNT, /* attachment comment */
676 TG} level; /* trailing garbage */
678 *fn = *sz = *cmnt = '\0'; /* initialize return strings */
679 p = tmp;
680 orig_offset = bod = *off;
681 quotechar[0] = '\"';
682 quotechar[1] = '\0';
684 level = LWS; /* start at beginning */
685 while(*lp != NULL){
687 if((c=(*lp)->text[*off]) == '\0'){ /* end of display line */
688 if(level == LWS && bod != *off){
689 (*lp)->text[bod] = '\0';
690 rv = 1;
692 lprev = *lp;
693 if((*lp = (*lp)->next) != NULL)
694 c = (*lp)->text[*off = bod = 0]; /* reset offset */
697 if(c != '\0'){
698 c_lookahead = (*lp)->text[*off + 1];
699 if(c_lookahead == '\0'){ /* end of display line */
700 if((*lp)->next != NULL)
701 c_lookahead = (*lp)->next->text[0];
705 switch(level){
706 case LWS: /* skip leading white space */
707 if(c <= 0xff && (isspace((unsigned char)c) || c == ',')){
708 c = ' ';
709 break;
712 if(c == '\0'){
713 if(bod > 0 && *off >= bod && lprev){
714 lprev->text[bod - 1] = '\0';
715 rv = 1;
718 else if(*off > bod && *lp){ /* wipe out whitespace */
719 memcpy(&(*lp)->text[bod], &(*lp)->text[*off],
720 ucs4_strlen(&(*lp)->text[*off]) + 1);
721 *off = bod; /* reset pointer */
722 rv = 1;
725 if(c == '\0')
726 break;
728 if(c > 0xff || !isdigit((unsigned char)c)){ /* add a number */
729 snprintf(number, sizeof(number), "%d. ", *no);
730 *no = 0; /* no previous number! */
731 u = utf8_to_ucs4_cpystr(number);
732 if(u){
733 sinserts((*lp == NULL) ? &lprev->text[*off]
734 : &(*lp)->text[*off],
735 0, u, j=ucs4_strlen(u));
737 fs_give((void **) &u);
740 *off += j - 1;
741 rv = 1;
742 level = TAG; /* interpret the name */
743 break;
745 level = NUMB;
746 case NUMB: /* attachment number */
747 if(c == '\0' || c == ','){ /* got to end, no number yet */
748 *p = '\0';
749 snprintf(number, sizeof(number), "%d. ", *no);
750 *no = 0; /* no previous number! */
751 if(c == '\0')
752 *lp = lprev; /* go back and look at prev */
754 c = (*lp)->text[*off = orig_offset]; /* reset offset */
755 u = utf8_to_ucs4_cpystr(number);
756 if(u){
757 sinserts((*lp == NULL) ? &lprev->text[*off]
758 : &(*lp)->text[*off],
759 0, u, j=ucs4_strlen(u));
761 fs_give((void **) &u);
763 *off += j - 1;
764 rv = 1;
765 p = tmp;
766 level = WSN; /* what's next... */
767 break;
769 else if(c == '.' && c_lookahead <= 0xff && isspace((unsigned char)c_lookahead)){
770 /* finished grabbing number */
771 /* if not space is not number */
773 * replace number if it's not right
775 *p = '\0';
776 snprintf(number, sizeof(number), "%d", *no); /* record the current... */
777 utf8 = ucs4_to_utf8_cpystr(tmp);
778 *no = atoi(utf8); /* and the old place in list */
779 if(strcmp(number, utf8)){
780 if(p-tmp > *off){ /* where to begin replacemnt */
781 UCS uu[1];
783 uu[0] = '\0';
784 j = (p-tmp) - *off;
785 sinserts((*lp)->text, *off, uu, 0);
786 u = utf8_to_ucs4_cpystr(number);
787 if(u){
788 sinserts(&lprev->text[ucs4_strlen(lprev->text)-j], j,
789 u, ucs4_strlen(u));
791 fs_give((void **) &u);
794 *off = 0;
796 else{
797 j = (*off) - (p-tmp);
798 u = utf8_to_ucs4_cpystr(number);
799 if(u){
800 sinserts((*lp == NULL) ? &lprev->text[j]
801 : &(*lp)->text[j],
802 p-tmp, u, ucs4_strlen(u));
804 fs_give((void **) &u);
807 *off += strlen(number) - (p-tmp);
809 rv = 1;
812 if(utf8)
813 fs_give((void **) &utf8);
815 p = tmp;
816 level = WSN; /* what's next... */
818 else if(c < '0' || c > '9'){ /* Must be part of tag */
819 snprintf(number, sizeof(number), "%d. ", *no);
820 u = utf8_to_ucs4_cpystr(number);
821 if(u){
822 sinserts((*lp == NULL) ? &lprev->text[*off - (p - tmp)]
823 : &(*lp)->text[*off - (p - tmp)],
824 0, u, j=ucs4_strlen(u));
826 fs_give((void **) &u);
829 *off += j;
830 level = TAG; /* interpret the name */
831 goto process_tag; /* in case already past end of tag */
833 else
834 *p++ = c;
836 break;
838 case WSN: /* blast whitespace */
839 if(c <= 0xff && (isspace((unsigned char)c) || c == '\0')){
840 break;
842 else if(c == '['){ /* labeled attachment */
843 lbln++;
845 else if(c == ',' || c == ' '){
846 /* TRANSLATORS: Attchmnt is an abbreviation for Attachment and
847 the %s is replaced with the character that is not
848 allowed in the name. */
849 eml.s = (c == ',') ? "," : "space";
850 emlwrite(_("\007Attchmnt: '%s' not allowed in file name"), &eml);
851 rv = -1;
852 level = TG; /* eat rest of garbage */
853 break;
855 level = TAG;
857 case TAG: /* get and check filename */
858 /* or labeled attachment */
859 process_tag: /* enclosed in [] */
860 if(c == '\0'
861 || (lbln && c == ']')
862 || (quoted && p != tmp && c == '\"')
863 || (!(lbln || quoted)
864 && (c <= 0xff && ((isspace((unsigned char) c) && c_lookahead == (UCS) '(') || strchr(",(\"", c))))){
865 if(p == tmp){
866 if(c == '\"')
867 quoted++;
869 else{
870 *p = '\0'; /* got something */
872 utf8 = ucs4_to_utf8_cpystr(tmp);
873 if(utf8){
874 if(strlen(utf8) > fnlen)
875 emlwrite("File name too big!",NULL);
877 strncpy(fn, utf8, fnlen); /* store file name */
878 fn[fnlen-1] = '\0';
879 fs_give((void **) &utf8);
882 if(!lbln){ /* normal file attachment */
883 if((gmode & MDTREE)
884 && !compresspath(opertree, fn, fnlen)){
885 eml.s = (gmode&MDSCUR) ? _("home directory") : opertree;
886 emlwrite(
887 _("Attachments allowed only from %s: too many ..'s"),
888 &eml);
889 rv = -1;
890 level = TG;
891 break;
893 else{
894 fixpath(fn, fnlen);
895 if((status=fexist(fn, "r", &attsz)) != FIOSUC){
896 fioperr(status, fn);
897 rv = -1;
898 level = TG; /* munch rest of garbage */
899 break;
902 if((gmode & MDTREE) && !in_oper_tree(fn)){
903 eml.s = (gmode&MDSCUR) ? _("home directory") : opertree;
904 emlwrite(_("\007Attachments allowed only from %s"), &eml);
905 rv = -1;
906 level = TG;
907 break;
911 utf8 = ucs4_to_utf8_cpystr(tmp);
913 if(utf8 && strcmp(fn, utf8)){ /* fn changed: display it */
914 if(*off >= p - tmp){ /* room for it? */
915 u = utf8_to_ucs4_cpystr(fn);
916 if(u){
918 * This whole parsing of the attachment line
919 * thing is ad hoc and susceptible to problems,
920 * and this particular part is no exception.
921 * Quote the filename if it contains spaces.
923 if(add_quotes){
924 sinserts((*lp == NULL) ? &lprev->text[*off - (p-tmp)]
925 : &(*lp)->text[*off - (p-tmp)],
926 0, quotechar, 1);
927 (*off)++;
930 sinserts((*lp == NULL) ? &lprev->text[*off - (p-tmp)]
931 : &(*lp)->text[*off - (p-tmp)],
932 p-tmp, u, j=ucs4_strlen(u));
934 *off += j - (p - tmp); /* advance offset */
936 if(add_quotes){
937 sinserts((*lp == NULL) ? &lprev->text[*off]
938 : &(*lp)->text[*off],
939 0, quotechar, 1);
940 (*off)++;
941 add_quotes = 0;
944 fs_give((void **) &u);
947 rv = 1;
949 else{
950 emlwrite("\007Attchmnt: Problem displaying real file path", NULL);
954 if(utf8)
955 fs_give((void **) &utf8);
957 else{ /* labelled attachment! */
959 * should explain about labelled attachments:
960 * these are attachments that came into the composer
961 * with meaningless file names (up to caller of
962 * composer to decide), for example, attachments
963 * being forwarded from another message. here, we
964 * just make sure the size stays what was passed
965 * to us. The user is SOL if they change the label
966 * since, as it is now, after changed, it will
967 * just get dropped from the list of what gets
968 * passed back to the caller.
970 PATMT *tp;
972 if(c != ']'){ /* legit label? */
973 eml.s = fn;
974 emlwrite(_("\007Attchmnt: Expected ']' after \"%s\""),
975 &eml);
976 rv = -1;
977 level = TG;
978 break;
981 strncat(fn, "]", fnlen-strlen(fn)-1);
982 fn[fnlen-1] = '\0';
985 * This is kind of cheating since otherwise
986 * ParseAttach doesn't know about the attachment
987 * struct. OK if filename's not found as it will
988 * get taken care of later...
990 tp = Pmaster->attachments; /* caller check Pmaster! */
991 j = 0;
992 while(tp != NULL){
993 if(++j == *no){
994 lblsz = tp->size;
995 break;
998 tp = tp->next;
1001 if(tp == NULL){
1002 eml.s = fn;
1003 emlwrite("\007Attchmnt: Unknown reference: %s", &eml);
1004 lblsz = "XXX";
1008 if(add_quotes){
1009 sinserts((*lp == NULL) ? &lprev->text[*off - (p-tmp)]
1010 : &(*lp)->text[*off - (p-tmp)],
1011 0, quotechar, 1);
1012 (*off)++;
1013 sinserts((*lp == NULL) ? &lprev->text[*off]
1014 : &(*lp)->text[*off],
1015 0, quotechar, 1);
1016 (*off)++;
1017 add_quotes = 0;
1020 p = tmp; /* reset p in tmp */
1021 level = WST;
1024 if(!lbln && c == '(') /* no space 'tween file, size*/
1025 level = ASIZE;
1026 else if(c == '\0'
1027 || (!(lbln || quoted) && (c == ',' || c == '\"'))){
1028 strncpy(sz, (lblsz) ? lblsz : prettysz(attsz), szlen);
1029 sz[szlen-1] = '\0';
1031 snprintf(ctmp, sizeof(ctmp), " (%s) %s", sz, (c == '\"') ? "" : "\"\"");
1032 u = utf8_to_ucs4_cpystr(ctmp);
1033 if(u){
1034 ucs4_strncpy(tmp, u, sizeof(tmp)/sizeof(tmp[0]));
1035 tmp[sizeof(tmp)/sizeof(tmp[0]) - 1] = '\0';
1036 fs_give((void **) &u);
1039 sinserts((*lp == NULL) ? &lprev->text[*off]
1040 : &(*lp)->text[*off],
1041 0, tmp, j=ucs4_strlen(tmp));
1042 *off += j;
1043 rv = 1;
1044 level = (c == '\"') ? CMMNT : TG;/* cmnt or eat trash */
1047 else if(!(lbln || quoted)
1048 && (c == ',' || /** c == ' ' || **/ c == '[' || c == ']')){
1049 eml.s = c == ',' ? ","
1050 : c == ' ' ? "space"
1051 : c == '[' ? "[" : "]";
1052 emlwrite(_("\007Attchmnt: '%s' not allowed in file name"), &eml);
1053 rv = -1; /* bad char in file name */
1054 level = TG; /* gobble garbage */
1056 else if(!(lbln || quoted) && (c <= 0xff && isspace((unsigned char) c))){
1057 add_quotes++;
1058 *p++ = c; /* add char to name */
1060 else
1061 *p++ = c; /* add char to name */
1063 break;
1065 case WST: /* skip white space */
1066 if(c > 0xff || !isspace((unsigned char)c)){
1068 * whole attachment, comment or done!
1070 if(c == ',' || c == '\0' || c == '\"'){
1071 strncpy(sz, (lblsz) ? lblsz : prettysz(attsz), sizeof(sz));
1072 sz[sizeof(sz)-1] = '\0';
1074 snprintf(ctmp, sizeof(ctmp), " (%s) %s", sz, (c == '\"') ? "" : "\"\"");
1075 u = utf8_to_ucs4_cpystr(ctmp);
1076 if(u){
1077 ucs4_strncpy(tmp, u, sizeof(tmp)/sizeof(tmp[0]));
1078 tmp[sizeof(tmp)/sizeof(tmp[0]) - 1] = '\0';
1079 fs_give((void **) &u);
1082 sinserts((*lp == NULL) ? &lprev->text[*off]
1083 : &(*lp)->text[*off],
1084 0, tmp, j=ucs4_strlen(tmp));
1085 *off += j;
1086 rv = 1;
1087 level = (c == '\"') ? CMMNT : TG;
1088 lbln = 0; /* reset flag */
1090 else if(c == '('){ /* get the size */
1091 level = ASIZE;
1093 else{
1094 eml.s = fn;
1095 emlwrite(_("\007Attchmnt: Expected '(' or '\"' after %s"), &eml);
1096 rv = -1; /* bag it all */
1097 level = TG;
1100 break;
1102 case ASIZE: /* check size */
1103 if(c == ')'){ /* finished grabbing size */
1104 *p = '\0';
1106 * replace sizes if they don't match!
1108 utf8 = ucs4_to_utf8_cpystr(tmp);
1109 if(utf8){
1110 strncpy(sz, utf8, szlen);
1111 sz[szlen-1] = '\0';
1112 fs_give((void **) &utf8);
1115 if(strcmp(sz, (lblsz) ? lblsz : prettysz(attsz))){
1116 strncpy(sz, (lblsz) ? lblsz : prettysz(attsz), szlen);
1117 sz[szlen-1] = '\0';
1118 if(p-tmp > *off){ /* where to begin replacemnt */
1119 UCS uu[1];
1121 uu[0] = '\0';
1122 j = (p-tmp) - *off;
1123 sinserts((*lp)->text, *off, uu, 0);
1124 u = utf8_to_ucs4_cpystr(sz);
1125 if(u){
1126 sinserts(&lprev->text[ucs4_strlen(lprev->text)-j], j,
1127 u, ucs4_strlen(u));
1128 fs_give((void **) &u);
1131 *off = 0;
1133 else{
1134 j = (*off) - (p-tmp);
1135 u = utf8_to_ucs4_cpystr(sz);
1136 if(u){
1137 sinserts((*lp == NULL) ? &lprev->text[j]
1138 : &(*lp)->text[j],
1139 p-tmp , u, ucs4_strlen(u));
1140 *off += ucs4_strlen(u) - (p-tmp);
1141 fs_give((void **) &u);
1144 rv = 1;
1147 p = tmp;
1148 level = SWS; /* what's next... */
1150 else if(c == '\0' || c == ','){
1151 *p = '\0';
1152 utf8 = ucs4_to_utf8_cpystr(tmp);
1153 eml.s = utf8;
1154 emlwrite(_("\007Attchmnt: Size field missing ')': \"%s\""), &eml);
1155 if(utf8)
1156 fs_give((void **) &utf8);
1158 rv = -1;
1159 level = TG;
1161 else
1162 *p++ = c;
1164 break;
1166 case SWS: /* skip white space */
1167 if(c > 0xff || !isspace((unsigned char)c)){
1168 if(c == ','){ /* no description */
1169 level = TG; /* munch rest of garbage */
1170 lbln = 0; /* reset flag */
1172 else if(c != '\"' && c != '\0'){
1173 emlwrite(_("\007Attchmnt: Malformed comment, quotes required"), NULL);
1174 rv = -1;
1175 level = TG;
1177 else
1178 level = CMMNT;
1180 break;
1182 case CMMNT: /* slurp up comment */
1183 if((c == '\"' && !escaped) || c == '\0'){
1184 *p = '\0'; /* cap it off */
1185 p = tmp; /* reset p */
1186 utf8 = ucs4_to_utf8_cpystr(tmp);
1187 if(utf8){
1188 if(strlen(utf8) > cmntlen)
1189 emlwrite("Comment too long!",NULL);
1191 strncpy(cmnt,utf8,cmntlen-1); /* copy the comment */
1192 cmnt[cmntlen-1] = '\0';
1193 fs_give((void **) &utf8);
1194 if(c == '\0'){
1195 emlwrite(_("\007Attchmnt: Closing quote required at end of comment"), NULL);
1196 rv = -1;
1200 level = TG; /* prepare for next one */
1201 lbln = 0; /* reset flag */
1203 else if(c == '\\' && !escaped){ /* something escaped? */
1204 escaped = 1;
1206 else{
1207 if(escaped){
1208 if(c != '\"') /* we only quote escapes */
1209 *p++ = '\\';
1211 escaped = 0;
1214 if(((*p++ = c) & 0x80) && (gmode & MDHBTIGN) && !hibit++)
1215 emlwrite(HIBIT_WARN, NULL);
1218 break;
1220 case TG: /* get comma or final EOL */
1221 if(eod < 0)
1222 eod = *off;
1223 if(c > 0xff || !isspace((unsigned char)c)){
1224 switch(c){
1225 case '\0':
1226 if(eod != *off)
1227 lprev->text[*off = eod] = '\0';
1228 break;
1229 case ',':
1230 if(eod != *off){
1231 memcpy(&(*lp)->text[eod], &(*lp)->text[*off],
1232 ucs4_strlen(&(*lp)->text[*off]) + 1);
1233 *off = eod;
1234 rv = 1;
1236 break;
1237 default:
1238 if(rv != -1)
1239 emlwrite(_("\007Attchmnt: Comma must separate attachments"), NULL);
1240 rv = -1;
1243 break;
1245 default: /* something's very wrong */
1246 emlwrite("\007Attchmnt: Weirdness in ParseAttach", NULL);
1247 return(-1); /* just give up */
1250 if(c == '\0') /* we're done */
1251 break;
1253 (*off)++;
1256 * not in comment or label name? done.
1258 if(c == ',' && (level != TAG && level != CMMNT && !lbln))
1259 break; /* put offset past ',' */
1262 return(rv);
1267 * NewAttach - given a filename (assumed to accessible) and comment, creat
1269 PATMT *
1270 NewAttach(char *f, long l, char *c)
1272 PATMT *tp;
1273 size_t len;
1275 if((tp=(PATMT *)malloc(sizeof(PATMT))) == NULL){
1276 emlwrite("No memory to add attachment", NULL);
1277 return(NULL);
1279 else
1280 memset(tp, 0, sizeof(PATMT));
1282 /* file and size malloc */
1283 len = strlen(f);
1284 if((tp->filename = (char *) malloc((len+1) * sizeof(char))) == NULL){
1285 emlwrite("Can't malloc name for attachment", NULL);
1286 free((char *) tp);
1287 return(NULL);
1290 strncpy(tp->filename, f, len);
1291 tp->filename[len] = '\0';
1293 if(l > -1){
1294 len = strlen(prettysz((off_t) l));
1295 tp->size = (char *) malloc((len+1) * sizeof(char));
1296 if(tp->size == NULL){
1297 emlwrite("Can't malloc size for attachment", NULL);
1298 free((char *) tp->filename);
1299 free((char *) tp);
1300 return(NULL);
1302 else{
1303 strncpy(tp->size, prettysz((off_t) l), len);
1304 tp->size[len] = '\0';
1308 /* description malloc */
1309 len = strlen(c);
1310 if((tp->description = (char *) malloc((len+1) * sizeof(char))) == NULL){
1311 emlwrite("Can't malloc description for attachment", NULL);
1312 free((char *) tp->size);
1313 free((char *) tp->filename);
1314 free((char *) tp);
1315 return(NULL);
1318 strncpy(tp->description, c, len);
1319 tp->description[len] = '\0';
1321 /* callback to show user the mime type that will be used for attachment */
1322 if(Pmaster->mimetype && (*Pmaster->mimetype)(f) > 0){
1323 int rv ;
1325 clearcursor();
1326 mlerase();
1327 rv = (*Pmaster->showmsg)('x');
1328 ttresize();
1329 picosigs();
1330 if(rv) /* Did showmsg corrupt the screen? */
1331 PaintBody(0); /* Yes, repaint it */
1333 mpresf = 1;
1336 return(tp);
1341 * AttachError - Sniff list of attachments, returning TRUE if there's
1342 * any sign of trouble...
1345 AttachError(void)
1347 PATMT *ap;
1349 if(!Pmaster)
1350 return(0);
1352 ap = Pmaster->attachments;
1353 while(ap){
1354 if((ap->flags) & A_ERR)
1355 return(1);
1357 ap = ap->next;
1360 return(FALSE);
1364 char *
1365 QuoteAttach(char *fn, size_t fnlen)
1367 char *p;
1369 if(*fn && strpbrk(fn, " \t,(\"")){ /* Quote it? */
1370 p = &fn[strlen(fn)];
1371 if(p+2-fn < fnlen){
1372 *(p+2) = '\0';
1373 *(p+1) = '\"';
1376 *p = *(p-1);
1377 while(--p != fn);
1378 *p = '\"';
1382 return(fn);
1386 void
1387 ZotAttach(PATMT *p)
1389 if(!p)
1390 return;
1392 if(p->description)
1393 free((char *)p->description);
1395 if(p->filename){
1396 if(p->flags & A_TMP)
1397 our_unlink(p->filename);
1399 free((char *)p->filename);
1402 if(p->size)
1403 free((char *)p->size);
1405 if(p->id)
1406 free((char *)p->id);
1408 p->next = NULL;
1410 #endif /* ATTACHMENTS */
1414 * intag - return TRUE if i is in a column that makes up an
1415 * attachment line number
1418 intag(UCS *s, int i)
1420 UCS *p = s;
1421 int n = 0;
1423 while(*p != '\0' && (p-s) < 5){ /* is there a tag? it */
1424 if(n && *p == '.') /* can't be more than 4 */
1425 return(i <= p-s); /* chars long! */
1427 if(*p < '0' || *p > '9')
1428 break;
1429 else
1430 n = (n * 10) + (*p - '0');
1432 p++;
1435 return(FALSE);
1440 * prettysz - return pointer to string containing nice size description
1442 char *
1443 prettysz(off_t l)
1445 static char b[32];
1446 long sz, left, right;
1448 sz = (long) l;
1449 b[0] = '\0';
1451 if(sz < 1000L){
1452 snprintf(b, sizeof(b), "%ld B", sz); /* xxx B */
1454 else if(sz < 9950L){
1455 left = (sz + 50L) / 1000L;
1456 right = ((sz + 50L) - left * 1000L) / 100L;
1457 snprintf(b, sizeof(b), "%ld.%ld KB", left, right); /* x.x KB */
1459 else if(sz < 999500L){
1460 snprintf(b, sizeof(b), "%ld KB", (sz + 500L) / 1000L); /* xxx KB */
1462 else if(sz < 9950000L){
1463 left = (sz + 50000L) / 1000000L;
1464 right = ((sz + 50000L) - left * 1000000L) / 100000L;
1465 snprintf(b, sizeof(b), "%ld.%ld MB", left, right); /* x.x MB */
1467 else{
1468 snprintf(b, sizeof(b), "%ld MB", (sz + 500000L) / 1000000L); /* xxx MB */
1471 return(b);
1476 * sinserts - s insert into another string
1478 void
1479 sinserts(UCS *ds, /* dest string */
1480 int dl, /* where to begin insert */
1481 UCS *ss, /* source string */
1482 int sl) /* length of ss */
1484 UCS *dp, *edp; /* pointers into dest. */
1485 size_t j; /* jump difference */
1487 if(sl >= dl){ /* source bigger than dest. */
1488 dp = ds + dl; /* shift dest. to make room */
1489 if((edp = ucs4_strchr(dp, '\0')) != NULL){
1490 j = sl - dl;
1492 for( ;edp >= dp; edp--)
1493 edp[j] = *edp;
1495 while(sl--)
1496 *ds++ = *ss++;
1498 else
1499 emlwrite("\007No end of line???", NULL); /* can this happen? */
1501 else{ /* dest is longer, shrink it */
1502 j = dl - sl; /* difference in lengths */
1504 while(sl--) /* copy u onto ds */
1505 *ds++ = *ss++;
1507 if(ucs4_strlen(ds) > j){ /* shuffle the rest left */
1509 *ds = ds[j];
1510 while(*ds++ != '\0');
1512 else
1513 *ds = '\0';