#if !defined(lint) && !defined(DOS) static char rcsid[] = "$Id: attach.c 1082 2008-06-12 18:39:50Z hubert@u.washington.edu $"; #endif /* * ======================================================================== * Copyright 2006-2008 University of Washington * Copyright 2013-2021 Eduardo Chappa * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * ======================================================================== * * Program: Routines to support attachments in the Pine composer */ #include "headers.h" #include "../pith/charconv/filesys.h" #include "../pith/string.h" #include #ifdef ATTACHMENTS int ParseAttach(struct hdr_line **, int *, char *, size_t, char *, size_t, char *, size_t, int *); PATMT *NewAttach(char *, long, char *); void ZotAttach(struct pico_atmt *); void sinserts(UCS *, int, UCS *, int); int AttachUpload(char *, size_t, char *, size_t); int AttachCancel(char *); #define HIBIT_WARN "Only ASCII characters allowed in attachment comments" /* * AskAttach - ask for attachment fields and build resulting structure * return pointer to that struct if OK, NULL otherwise */ int AskAttach(char *cmnt, size_t cmntlen, LMLIST **lm) { int i, status, fbrv, upload = 0; int fb_flags = FB_READ | FB_ATTACH; off_t attsz = 0; size_t len; char bfn[NLINE]; char fn[NLINE], sz[32]; LMLIST *new; EML eml; i = 2; /* 2 is prompt for file, 1 is prompt for comment */ fn[0] = '\0'; sz[0] = '\0'; cmnt[0] = '\0'; while(i){ if(i == 2){ EXTRAKEYS menu_attach[10]; int n; memset(&menu_attach, 0, 10*sizeof(EXTRAKEYS)); menu_attach[n = 0].name = "^T"; menu_attach[n].label = N_("To Files"); menu_attach[n].key = (CTRL|'T'); if(gmode & MDCMPLT){ menu_attach[++n].name = "TAB"; menu_attach[n].label = N_("Complete"); menu_attach[n].key = (CTRL|'I'); } #if !defined(DOS) && !defined(MAC) if(Pmaster && Pmaster->upload){ /* * The Plan: ^R prompts for uploaded file's name which * is passed to the defined upload command when the user * hits Return to confirm the name. * NOTE: this is different than upload into message * text in which case the uploaded name isn't useful so * a temp file is ok (be sure to fix the file mode). */ menu_attach[++n].name = "^Y"; menu_attach[n].key = (CTRL|'Y'); /* TRANSLATORS: Read File is a prompt for the name of a file to be read into the composer. */ menu_attach[n].label = upload ? N_("Read File") : N_("RcvUpload"); } #endif menu_attach[++n].name = NULL; KS_OSDATASET(&menu_attach[0], KS_NONE); status = mlreply_utf8(upload ? _("Name to give uploaded attachment: ") /* TRANSLATORS: User is being asked for the name of the file they want to attach to a message. */ : _("File to attach: "), fn, sizeof(fn), QNORML, menu_attach); } else /* TRANSLATORS: This is a prompt for a comment about the file they have attached. */ status = mlreply_utf8(_("Attachment comment: "), cmnt, cmntlen, QNODQT, NULL); switch(status){ case HELPCH: if(Pmaster){ VARS_TO_SAVE *saved_state; saved_state = save_pico_state(); (*Pmaster->helper)(Pmaster->attach_help, _("Attach Help"), 1); if(saved_state){ restore_pico_state(saved_state); free_pico_state(saved_state); } pico_refresh(FALSE, 1); update(); continue; } else{ eml.s = (i == 2) ? "file" : "comment"; emlwrite("No Attachment %s help yet!", &eml); sleep(3); } break; case (CTRL|'I') : if(i == 2){ char *fname, *p; int dirlen; bfn[0] = '\0'; if(*fn && (p = strrchr(fn, C_FILESEP))){ fname = p + 1; dirlen = p - fn; if(p == fn){ strncpy(bfn, S_FILESEP, sizeof(bfn)); bfn[sizeof(bfn)-1] = '\0'; } #ifdef DOS else if(fn[0] == C_FILESEP || (isalpha((unsigned char)fn[0]) && fn[1] == ':')){ if(fn[1] == ':' && p == fn+2) dirlen = fname - fn; if(dirlen < sizeof(bfn)){ strncpy(bfn, fn, dirlen); bfn[dirlen] = '\0'; } } #else else if (fn[0] == C_FILESEP || fn[0] == '~'){ if(dirlen < sizeof(bfn)){ strncpy(bfn, fn, dirlen); bfn[dirlen] = '\0'; } } #endif else snprintf(bfn, sizeof(bfn), "%s%c%.*s", (gmode & MDCURDIR) ? "." : ((gmode & MDTREE) || opertree[0]) ? opertree : gethomedir(NULL), C_FILESEP, (int) (p - fn), fn); } else{ fname = fn; strncpy(bfn, (gmode & MDCURDIR) ? "." : ((gmode & MDTREE) || opertree[0]) ? opertree : gethomedir(NULL), sizeof(bfn)); bfn[sizeof(bfn)-1] = '\0'; } if(!pico_fncomplete(bfn, fname, sizeof(fn)-(fname-fn))) (*term.t_beep)(); } else (*term.t_beep)(); break; case (CTRL|'T'): if(i != 2){ (*term.t_beep)(); break; } *bfn = '\0'; if(*fn == '\0' || !isdir(fn, NULL, NULL)){ strncpy(fn, (gmode & MDCURDIR) ? (browse_dir[0] ? browse_dir : ".") : ((gmode & MDTREE) || opertree[0]) ? opertree : (browse_dir[0] ? browse_dir : gethomedir(NULL)), sizeof(fn)); fn[sizeof(fn)-1] = '\0'; } if((fbrv = FileBrowse(fn, sizeof(fn), bfn, sizeof(bfn), sz, sizeof(sz), upload ? fb_flags : fb_flags|FB_LMODEPOS, upload ? NULL : lm)) == 1){ if (upload && (strlen(fn)+strlen(S_FILESEP)+strlen(bfn)) < sizeof(fn)){ size_t len1, len2; len1 = strlen(bfn); len2 = strlen(fn); if((new=(LMLIST *)malloc(sizeof(*new))) == NULL || (new->fname=malloc((len1+1) * sizeof(char))) == NULL || (new->dir=malloc((len2+1) * sizeof(char))) == NULL){ emlwwrite(_("Can't malloc space for filename"), NULL); return(-1); } strncpy(new->fname, bfn, len1); new->fname[len1] = '\0'; strncpy(new->dir, fn, len2); new->dir[len2] = '\0'; strncpy(new->size, sz, sizeof(new->size)-1); new->size[sizeof(new->size)-1] = '\0'; new->next = NULL; *lm = new; strncat(fn, S_FILESEP, sizeof(fn)-strlen(fn)-1); fn[sizeof(fn)-1] = '\0'; strncat(fn, bfn, sizeof(fn)-strlen(fn)-1); fn[sizeof(fn)-1] = '\0'; if(!AttachUpload(fn, sizeof(fn), sz, sizeof(sz))){ i = 2; /* keep prompting for file */ sleep(3); /* problem, show error! */ } else{ i--; /* go prompt for comment */ } } else if(!upload){ if(lm && *lm && !(*lm)->next) /* get comment */ i--; else{ /* no comments if multiple files */ update(); return(1); } } else{ /* trouble */ *fn = '\0'; AttachCancel(fn); pico_refresh(FALSE,1); update(); emlwwrite(_("File name too BIG, cannot select!"), NULL); sleep(3); } } else if (!fbrv) *fn = '\0'; else{ *fn = '\0'; AttachCancel(fn); pico_refresh(FALSE, 1); update(); emlwwrite(_("File name too big, cannot select!"), NULL); sleep(3); } /* fall thru to clean up the screen */ case (CTRL|'L'): pico_refresh(FALSE, 1); update(); continue; #if !defined(DOS) && !defined(MAC) case (CTRL|'Y'): /* upload? */ if(i == 2) upload ^= 1; /* flip mode */ else (*term.t_beep)(); break; #endif case ABORT: return(AttachCancel((upload && i == 1) ? fn : NULL)); case TRUE: /* some comment */ case FALSE: /* No comment */ if(i-- == 2){ if(upload){ fixpath(fn, sizeof(fn)); /* names relative to ~ */ status = AttachUpload(fn, sizeof(fn), sz, sizeof(sz)); pico_refresh(FALSE, 1); update(); if(!status){ i = 2; /* keep prompting for file */ sleep(3); /* problem, show error! */ } } else { if(*fn == '\"' && fn[strlen(fn)-1] == '\"'){ int j; for(j = 0; (fn[j] = fn[j+1]); j++) ; fn[j-1] = '\0'; } if(fn[0]){ if((gmode & MDTREE) && !compresspath(opertree, fn, sizeof(fn))){ eml.s = (gmode&MDSCUR) ? _("home directory") : opertree; emlwrite( /* TRANSLATORS: the %s is replaced with the name of a directory */ _("Restricted mode allows attachments from %s only: too many ..'s"), &eml); return(0); } else{ fixpath(fn, sizeof(fn)); /* names relative to ~ */ if((gmode&MDTREE) && !in_oper_tree(fn)){ eml.s = (gmode&MDSCUR) ? _("home directory") : opertree; emlwwrite( _("Restricted mode allows attachments from %s only"), &eml); return(0); } } if((status = fexist(fn, "r", &attsz)) != FIOSUC){ fioperr(status, fn); /* file DOESN'T exist! */ return(0); } len = strlen(fn); if((new=(LMLIST *)malloc(sizeof(*new))) == NULL || (new->fname=malloc((len+1)*sizeof(char))) == NULL){ emlwwrite(_("Can't malloc space for filename"), NULL); return(-1); } new->dir = NULL; strncpy(new->fname, fn, len); new->fname[len] = '\0'; strncpy(new->size, prettysz(attsz), sizeof(new->size)); new->size[sizeof(new->size)-1] = '\0'; new->next = NULL; *lm = new; } else return(AttachCancel((upload && i == 1) ? fn : NULL)); } } else{ mlerase(); return(1); /* mission accomplished! */ } break; default: break; } } return(0); } /* * AttachUpload - Use call back to run the external upload command. */ int AttachUpload(char *fn, size_t fnlen, char *sz, size_t szlen) { long l; if(gmode&MDSCUR){ emlwwrite(_("Restricted mode disallows uploaded command"), NULL); return(0); } if(Pmaster && Pmaster->upload && (*Pmaster->upload)(fn, fnlen, &l)){ strncpy(sz, prettysz((off_t)l), szlen); sz[szlen-1] = '\0'; return(1); } return(0); } /* * AttachCancel - */ int AttachCancel(char *fn) { emlwrite(_("Attach cancelled"), NULL); if(fn && fn[0]) our_unlink(fn); /* blast uploaded file */ return(0); } extern struct headerentry *headents; /* * SyncAttach - given a pointer to a linked list of attachment structures, * return with that structure sync'd with what's displayed. * delete any attachments in list of structs that's not on * the display, and add any that aren't in list but on display. */ int SyncAttach(void) { int offset = 0, /* the offset to begin */ rv = 0, ki = 0, /* number of known attmnts */ bi = 0, /* build array index */ nbld = 0, /* size of build array */ na, /* old number of attachmnt */ status, i, n = 0; size_t j; char file[NLINE], /* buffers to hold it all */ size[32], comment[1024]; struct hdr_line *lp; /* current line in header */ struct headerentry *entry; PATMT *tp, **knwn = NULL, **bld; EML eml; if(Pmaster == NULL) return(-1); for(entry = headents; entry->name != NULL; entry++) { if(entry->is_attach) break; } for(tp = Pmaster->attachments; tp; tp = tp->next) ki++; /* Count known attachments */ if(ki){ if((knwn = (PATMT **)malloc((ki+1) * (sizeof(PATMT *)))) == NULL){ eml.s = comatose(ki + 1); emlwwrite(_("Can't allocate space for %s known attachment array entries"), &eml); rv = -1; goto exit_early; } for(i=0, tp = Pmaster->attachments; i < ki; i++, tp = tp->next){ knwn[i] = tp; /* fill table of */ /* known attachments */ } } /* * As a quick hack to avoid too many reallocs, check to see if * there are more header lines than known attachments. */ for(lp = entry->hd_text; lp ; lp = lp->next) nbld++; /* count header lines */ nbld = nbld > ki ? nbld : ki + 1; if((bld = (PATMT **)malloc(nbld * (sizeof(PATMT *)))) == NULL){ eml.s = comatose(nbld); emlwwrite(_("Can't allocate space for %s build array entries"), &eml); rv = -1; goto exit_early; } lp = entry->hd_text; while(lp != NULL){ char fn[NLINE]; na = ++n; if(bi == nbld){ /* need to grow build array? */ if((bld = (PATMT **)realloc(bld, ++nbld * sizeof(PATMT *))) == NULL){ eml.s = comatose(nbld); emlwwrite(_("Can't resize build array to %s entries"), &eml); rv = -1; goto exit_early; } } if((status = ParseAttach(&lp, &offset, file, sizeof(file), size, sizeof(size), comment, sizeof(comment), &na)) != 0) rv = (rv < 0) ? rv : status ; /* remember worst case */ if(*file == '\0'){ if(n != na && na > 0 && na <= ki && (knwn[na-1]->flags & A_FLIT)){ bld[bi++] = knwn[na-1]; knwn[na-1] = NULL; } continue; } if((gmode&MDTREE) && file[0] != '[' && (!in_oper_tree(file) || !compresspath(file, fn, sizeof(fn)))) /* no attachments outside ~ in secure mode! */ continue; tp = NULL; for(i = 0; i < ki; i++){ /* already know about it? */ /* * this is kind of gruesome. what we want to do is keep track * of literal attachment entries because they may not be * actual files we can access or that the user can readily * access. */ if(knwn[i] && ((!(knwn[i]->flags&A_FLIT) && !strcmp(file, knwn[i]->filename)) || ((knwn[i]->flags&A_FLIT) && i+1 == na))){ tp = knwn[i]; knwn[i] = NULL; /* forget we know about it */ if(status == -1) /* ignore garbage! */ break; if((tp->flags&A_FLIT) && strcmp(file, tp->filename)){ rv = 1; if((j=strlen(file)) > strlen(tp->filename)){ if((tp->filename = (char *)realloc(tp->filename, sizeof(char)*(j+1))) == NULL){ emlwwrite(_("Can't realloc filename space"),NULL); rv = -1; goto exit_early; } } strncpy(tp->filename, file, j); tp->filename[j] = '\0'; if(tp->id) fs_give((void **) &tp->id); } else if(tp->size && strcmp(tp->size, size)){ rv = 1; if((j=strlen(size)) > strlen(tp->size)){ if((tp->size=(char *)realloc(tp->size, sizeof(char)*(j+1))) == NULL){ emlwwrite(_("Can't realloc space for size"), NULL); rv = -1; goto exit_early; } } strncpy(tp->size, size, j); tp->size[j] = '\0'; } if(strcmp(tp->description, comment)){ /* new comment */ rv = 1; if((j=strlen(comment)) > strlen(tp->description)){ if((tp->description=(char *)realloc(tp->description, sizeof(char)*(j+1))) == NULL){ emlwwrite(_("Can't realloc description"), NULL); rv = -1; goto exit_early; } } strncpy(tp->description, comment, j); tp->description[j] = '\0'; } break; } } if(tp){ bld[bi++] = tp; } else{ if(file[0] != '['){ if((tp = NewAttach(file, atol(size), comment)) == NULL){ rv = -1; goto exit_early; } bld[bi++] = tp; } else break; } if(status < 0) tp->flags |= A_ERR; /* turn ON error bit */ else tp->flags &= ~(A_ERR); /* turn OFF error bit */ } if(bi){ for(i=0; i < bi-1; i++) /* link together newly built list */ bld[i]->next = bld[i+1]; bld[i]->next = NULL; /* tie it off */ Pmaster->attachments = bld[0]; } else Pmaster->attachments = NULL; exit_early: if(knwn){ for(i = 0; i < ki; i++){ /* kill old/unused references */ if(knwn[i]){ ZotAttach(knwn[i]); free((char *) knwn[i]); } } free((void *)knwn); } if(bld) free((void *)bld); return(rv); } /* * ParseAttach - given a header line and an offset into it, return with * the three given fields filled in. Size of fn and cmnt * buffers should be passed in fnlen and cmntlen. * Always updates header fields that have changed or are * fixed. An error advances offset to next attachment. * * returns: 1 if a field changed * 0 nothing changed * -1 on error */ int ParseAttach(struct hdr_line **lp, /* current header line */ int *off, /* offset into that line */ char *fn, /* return file name field */ size_t fnlen, /* fn buffer size */ char *sz, size_t szlen, char *cmnt, /* places to return fields */ size_t cmntlen, int *no) /* attachment number */ { int j, status, bod, eod = -1, rv = 0, /* return value */ orig_offset, lbln = 0, /* label'd attachment */ hibit = 0, quoted = 0, add_quotes = 0, escaped = 0; off_t attsz; /* attachment length */ EML eml; UCS c, c_lookahead; UCS tmp[1024], *p, *u, quotechar[2]; char ctmp[1024]; char *utf8 = NULL; char *lblsz = NULL, /* label'd attchmnt's size */ number[8]; struct hdr_line *lprev = NULL; enum { /* parse levels */ LWS, /* leading white space */ NUMB, /* attachment number */ WSN, /* white space after number */ TAG, /* attachments tag (fname) */ WST, /* white space after tag */ ASIZE, /* attachments size */ SWS, /* white space after size */ CMMNT, /* attachment comment */ TG} level; /* trailing garbage */ *fn = *sz = *cmnt = '\0'; /* initialize return strings */ p = tmp; orig_offset = bod = *off; quotechar[0] = '\"'; quotechar[1] = '\0'; level = LWS; /* start at beginning */ while(*lp != NULL){ if((c=(*lp)->text[*off]) == '\0'){ /* end of display line */ if(level == LWS && bod != *off){ (*lp)->text[bod] = '\0'; rv = 1; } lprev = *lp; if((*lp = (*lp)->next) != NULL) c = (*lp)->text[*off = bod = 0]; /* reset offset */ } if(c != '\0'){ c_lookahead = (*lp)->text[*off + 1]; if(c_lookahead == '\0'){ /* end of display line */ if((*lp)->next != NULL) c_lookahead = (*lp)->next->text[0]; } } switch(level){ case LWS: /* skip leading white space */ if(c <= 0xff && (isspace((unsigned char)c) || c == ',')){ c = ' '; break; } if(c == '\0'){ if(bod > 0 && *off >= bod && lprev){ lprev->text[bod - 1] = '\0'; rv = 1; } } else if(*off > bod && *lp){ /* wipe out whitespace */ memcpy(&(*lp)->text[bod], &(*lp)->text[*off], ucs4_strlen(&(*lp)->text[*off]) + 1); *off = bod; /* reset pointer */ rv = 1; } if(c == '\0') break; if(c > 0xff || !isdigit((unsigned char)c)){ /* add a number */ snprintf(number, sizeof(number), "%d. ", *no); *no = 0; /* no previous number! */ u = utf8_to_ucs4_cpystr(number); if(u){ sinserts((*lp == NULL) ? &lprev->text[*off] : &(*lp)->text[*off], 0, u, j=ucs4_strlen(u)); fs_give((void **) &u); } *off += j - 1; rv = 1; level = TAG; /* interpret the name */ break; } level = NUMB; case NUMB: /* attachment number */ if(c == '\0' || c == ','){ /* got to end, no number yet */ *p = '\0'; snprintf(number, sizeof(number), "%d. ", *no); *no = 0; /* no previous number! */ if(c == '\0') *lp = lprev; /* go back and look at prev */ c = (*lp)->text[*off = orig_offset]; /* reset offset */ u = utf8_to_ucs4_cpystr(number); if(u){ sinserts((*lp == NULL) ? &lprev->text[*off] : &(*lp)->text[*off], 0, u, j=ucs4_strlen(u)); fs_give((void **) &u); } *off += j - 1; rv = 1; p = tmp; level = WSN; /* what's next... */ break; } else if(c == '.' && c_lookahead <= 0xff && isspace((unsigned char)c_lookahead)){ /* finished grabbing number */ /* if not space is not number */ /* * replace number if it's not right */ *p = '\0'; snprintf(number, sizeof(number), "%d", *no); /* record the current... */ utf8 = ucs4_to_utf8_cpystr(tmp); *no = atoi(utf8); /* and the old place in list */ if(strcmp(number, utf8)){ if(p-tmp > *off){ /* where to begin replacement */ UCS uu[1]; uu[0] = '\0'; j = (p-tmp) - *off; sinserts((*lp)->text, *off, uu, 0); u = utf8_to_ucs4_cpystr(number); if(u){ sinserts(&lprev->text[ucs4_strlen(lprev->text)-j], j, u, ucs4_strlen(u)); fs_give((void **) &u); } *off = 0; } else{ j = (*off) - (p-tmp); u = utf8_to_ucs4_cpystr(number); if(u){ sinserts((*lp == NULL) ? &lprev->text[j] : &(*lp)->text[j], p-tmp, u, ucs4_strlen(u)); fs_give((void **) &u); } *off += strlen(number) - (p-tmp); } rv = 1; } if(utf8) fs_give((void **) &utf8); p = tmp; level = WSN; /* what's next... */ } else if(c < '0' || c > '9'){ /* Must be part of tag */ snprintf(number, sizeof(number), "%d. ", *no); u = utf8_to_ucs4_cpystr(number); if(u){ sinserts((*lp == NULL) ? &lprev->text[*off - (p - tmp)] : &(*lp)->text[*off - (p - tmp)], 0, u, j=ucs4_strlen(u)); fs_give((void **) &u); } *off += j; level = TAG; /* interpret the name */ goto process_tag; /* in case already past end of tag */ } else *p++ = c; break; case WSN: /* blast whitespace */ if(c <= 0xff && (isspace((unsigned char)c) || c == '\0')){ break; } else if(c == '['){ /* labeled attachment */ lbln++; } else if(c == ',' || c == ' '){ /* TRANSLATORS: Attchmnt is an abbreviation for Attachment and the %s is replaced with the character that is not allowed in the name. */ eml.s = (c == ',') ? "," : "space"; emlwwrite(_("Attchmnt: '%s' not allowed in file name"), &eml); rv = -1; level = TG; /* eat rest of garbage */ break; } level = TAG; case TAG: /* get and check filename */ /* or labeled attachment */ process_tag: /* enclosed in [] */ if(c == '\0' || (lbln && c == ']') || (quoted && p != tmp && c == '\"') || (!(lbln || quoted) && (c <= 0xff && ((isspace((unsigned char) c) && c_lookahead == (UCS) '(') || strchr(",(\"", c))))){ if(p == tmp){ if(c == '\"') quoted++; } else{ *p = '\0'; /* got something */ utf8 = ucs4_to_utf8_cpystr(tmp); if(utf8){ if(strlen(utf8) > fnlen) emlwrite("File name too big!",NULL); strncpy(fn, utf8, fnlen); /* store file name */ fn[fnlen-1] = '\0'; fs_give((void **) &utf8); } if(!lbln){ /* normal file attachment */ if((gmode & MDTREE) && !compresspath(opertree, fn, fnlen)){ eml.s = (gmode&MDSCUR) ? _("home directory") : opertree; emlwrite( _("Attachments allowed only from %s: too many ..'s"), &eml); rv = -1; level = TG; break; } else{ fixpath(fn, fnlen); if((status=fexist(fn, "r", &attsz)) != FIOSUC){ fioperr(status, fn); rv = -1; level = TG; /* munch rest of garbage */ break; } if((gmode & MDTREE) && !in_oper_tree(fn)){ eml.s = (gmode&MDSCUR) ? _("home directory") : opertree; emlwwrite(_("Attachments allowed only from %s"), &eml); rv = -1; level = TG; break; } } utf8 = ucs4_to_utf8_cpystr(tmp); if(utf8 && strcmp(fn, utf8)){ /* fn changed: display it */ if(*off >= p - tmp){ /* room for it? */ u = utf8_to_ucs4_cpystr(fn); if(u){ /* * This whole parsing of the attachment line * thing is ad hoc and susceptible to problems, * and this particular part is no exception. * Quote the filename if it contains spaces. */ if(add_quotes){ sinserts((*lp == NULL) ? &lprev->text[*off - (p-tmp)] : &(*lp)->text[*off - (p-tmp)], 0, quotechar, 1); (*off)++; } sinserts((*lp == NULL) ? &lprev->text[*off - (p-tmp)] : &(*lp)->text[*off - (p-tmp)], p-tmp, u, j=ucs4_strlen(u)); *off += j - (p - tmp); /* advance offset */ if(add_quotes){ sinserts((*lp == NULL) ? &lprev->text[*off] : &(*lp)->text[*off], 0, quotechar, 1); (*off)++; add_quotes = 0; } fs_give((void **) &u); } rv = 1; } else{ emlwwrite(_("Attchmnt: Problem displaying real file path"), NULL); } } if(utf8) fs_give((void **) &utf8); } else{ /* labelled attachment! */ /* * should explain about labelled attachments: * these are attachments that came into the composer * with meaningless file names (up to caller of * composer to decide), for example, attachments * being forwarded from another message. here, we * just make sure the size stays what was passed * to us. The user is SOL if they change the label * since, as it is now, after changed, it will * just get dropped from the list of what gets * passed back to the caller. */ PATMT *tp; if(c != ']'){ /* legit label? */ eml.s = fn; emlwwrite(_("Attchmnt: Expected ']' after \"%s\""), &eml); rv = -1; level = TG; break; } strncat(fn, "]", fnlen-strlen(fn)-1); fn[fnlen-1] = '\0'; /* * This is kind of cheating since otherwise * ParseAttach doesn't know about the attachment * struct. OK if filename's not found as it will * get taken care of later... */ tp = Pmaster->attachments; /* caller check Pmaster! */ j = 0; while(tp != NULL){ if(++j == *no){ lblsz = tp->size; break; } tp = tp->next; } if(tp == NULL){ eml.s = fn; emlwwrite(_("Attchmnt: Unknown reference: %s"), &eml); lblsz = "XXX"; } } if(add_quotes){ sinserts((*lp == NULL) ? &lprev->text[*off - (p-tmp)] : &(*lp)->text[*off - (p-tmp)], 0, quotechar, 1); (*off)++; sinserts((*lp == NULL) ? &lprev->text[*off] : &(*lp)->text[*off], 0, quotechar, 1); (*off)++; add_quotes = 0; } p = tmp; /* reset p in tmp */ level = WST; } if(!lbln && c == '(') /* no space 'tween file, size*/ level = ASIZE; else if(c == '\0' || (!(lbln || quoted) && (c == ',' || c == '\"'))){ strncpy(sz, (lblsz) ? lblsz : prettysz(attsz), szlen); sz[szlen-1] = '\0'; snprintf(ctmp, sizeof(ctmp), " (%s) %s", sz, (c == '\"') ? "" : "\"\""); u = utf8_to_ucs4_cpystr(ctmp); if(u){ ucs4_strncpy(tmp, u, sizeof(tmp)/sizeof(tmp[0])); tmp[sizeof(tmp)/sizeof(tmp[0]) - 1] = '\0'; fs_give((void **) &u); } sinserts((*lp == NULL) ? &lprev->text[*off] : &(*lp)->text[*off], 0, tmp, j=ucs4_strlen(tmp)); *off += j; rv = 1; level = (c == '\"') ? CMMNT : TG;/* cmnt or eat trash */ } } else if(!(lbln || quoted) && (c == ',' || /** c == ' ' || **/ c == '[' || c == ']')){ eml.s = c == ',' ? "," : c == ' ' ? "space" : c == '[' ? "[" : "]"; emlwwrite(_("Attchmnt: '%s' not allowed in file name"), &eml); rv = -1; /* bad char in file name */ level = TG; /* gobble garbage */ } else if(!(lbln || quoted) && (c <= 0xff && isspace((unsigned char) c))){ add_quotes++; *p++ = c; /* add char to name */ } else *p++ = c; /* add char to name */ break; case WST: /* skip white space */ if(c > 0xff || !isspace((unsigned char)c)){ /* * whole attachment, comment or done! */ if(c == ',' || c == '\0' || c == '\"'){ strncpy(sz, (lblsz) ? lblsz : prettysz(attsz), szlen); sz[szlen-1] = '\0'; snprintf(ctmp, sizeof(ctmp), " (%s) %s", sz, (c == '\"') ? "" : "\"\""); u = utf8_to_ucs4_cpystr(ctmp); if(u){ ucs4_strncpy(tmp, u, sizeof(tmp)/sizeof(tmp[0])); tmp[sizeof(tmp)/sizeof(tmp[0]) - 1] = '\0'; fs_give((void **) &u); } sinserts((*lp == NULL) ? &lprev->text[*off] : &(*lp)->text[*off], 0, tmp, j=ucs4_strlen(tmp)); *off += j; rv = 1; level = (c == '\"') ? CMMNT : TG; lbln = 0; /* reset flag */ } else if(c == '('){ /* get the size */ level = ASIZE; } else{ eml.s = fn; emlwwrite(_("Attchmnt: Expected '(' or '\"' after %s"), &eml); rv = -1; /* bag it all */ level = TG; } } break; case ASIZE: /* check size */ if(c == ')'){ /* finished grabbing size */ *p = '\0'; /* * replace sizes if they don't match! */ utf8 = ucs4_to_utf8_cpystr(tmp); if(utf8){ strncpy(sz, utf8, szlen); sz[szlen-1] = '\0'; fs_give((void **) &utf8); } if(strcmp(sz, (lblsz) ? lblsz : prettysz(attsz))){ strncpy(sz, (lblsz) ? lblsz : prettysz(attsz), szlen); sz[szlen-1] = '\0'; if(p-tmp > *off){ /* where to begin replacement */ UCS uu[1]; uu[0] = '\0'; j = (p-tmp) - *off; sinserts((*lp)->text, *off, uu, 0); u = utf8_to_ucs4_cpystr(sz); if(u){ sinserts(&lprev->text[ucs4_strlen(lprev->text)-j], j, u, ucs4_strlen(u)); fs_give((void **) &u); } *off = 0; } else{ j = (*off) - (p-tmp); u = utf8_to_ucs4_cpystr(sz); if(u){ sinserts((*lp == NULL) ? &lprev->text[j] : &(*lp)->text[j], p-tmp , u, ucs4_strlen(u)); *off += ucs4_strlen(u) - (p-tmp); fs_give((void **) &u); } } rv = 1; } p = tmp; level = SWS; /* what's next... */ } else if(c == '\0' || c == ','){ *p = '\0'; utf8 = ucs4_to_utf8_cpystr(tmp); eml.s = utf8; emlwwrite(_("Attchmnt: Size field missing ')': \"%s\""), &eml); if(utf8) fs_give((void **) &utf8); rv = -1; level = TG; } else *p++ = c; break; case SWS: /* skip white space */ if(c > 0xff || !isspace((unsigned char)c)){ if(c == ','){ /* no description */ level = TG; /* munch rest of garbage */ lbln = 0; /* reset flag */ } else if(c != '\"' && c != '\0'){ emlwwrite(_("Attchmnt: Malformed comment, quotes required"), NULL); rv = -1; level = TG; } else level = CMMNT; } break; case CMMNT: /* slurp up comment */ if((c == '\"' && !escaped) || c == '\0'){ *p = '\0'; /* cap it off */ p = tmp; /* reset p */ utf8 = ucs4_to_utf8_cpystr(tmp); if(utf8){ if(strlen(utf8) > cmntlen) emlwrite("Comment too long!",NULL); strncpy(cmnt,utf8,cmntlen-1); /* copy the comment */ cmnt[cmntlen-1] = '\0'; fs_give((void **) &utf8); if(c == '\0'){ emlwwrite(_("Attchmnt: Closing quote required at end of comment"), NULL); rv = -1; } } level = TG; /* prepare for next one */ lbln = 0; /* reset flag */ } else if(c == '\\' && !escaped){ /* something escaped? */ escaped = 1; } else{ if(escaped){ if(c != '\"') /* we only quote escapes */ *p++ = '\\'; escaped = 0; } if(((*p++ = c) & 0x80) && (gmode & MDHBTIGN) && !hibit++) emlwrite(HIBIT_WARN, NULL); } break; case TG: /* get comma or final EOL */ if(eod < 0) eod = *off; if(c > 0xff || !isspace((unsigned char)c)){ switch(c){ case '\0': if(eod != *off) lprev->text[*off = eod] = '\0'; break; case ',': if(eod != *off){ memcpy(&(*lp)->text[eod], &(*lp)->text[*off], ucs4_strlen(&(*lp)->text[*off]) + 1); *off = eod; rv = 1; } break; default: if(rv != -1) emlwwrite(_("Attchmnt: Comma must separate attachments"), NULL); rv = -1; } } break; default: /* something's very wrong */ emlwwrite(_("Attchmnt: Weirdness in ParseAttach"), NULL); return(-1); /* just give up */ } if(c == '\0') /* we're done */ break; (*off)++; /* * not in comment or label name? done. */ if(c == ',' && (level != TAG && level != CMMNT && !lbln)) break; /* put offset past ',' */ } return(rv); } /* * NewAttach - given a filename (assumed to accessible) and comment, creat */ PATMT * NewAttach(char *f, long l, char *c) { PATMT *tp; size_t len; if((tp=(PATMT *)malloc(sizeof(PATMT))) == NULL){ emlwrite("No memory to add attachment", NULL); return(NULL); } else memset(tp, 0, sizeof(PATMT)); /* file and size malloc */ len = strlen(f); if((tp->filename = (char *) malloc((len+1) * sizeof(char))) == NULL){ emlwrite("Can't malloc name for attachment", NULL); free((char *) tp); return(NULL); } strncpy(tp->filename, f, len); tp->filename[len] = '\0'; if(l > -1){ len = strlen(prettysz((off_t) l)); tp->size = (char *) malloc((len+1) * sizeof(char)); if(tp->size == NULL){ emlwrite("Can't malloc size for attachment", NULL); free((char *) tp->filename); free((char *) tp); return(NULL); } else{ strncpy(tp->size, prettysz((off_t) l), len); tp->size[len] = '\0'; } } /* description malloc */ len = strlen(c); if((tp->description = (char *) malloc((len+1) * sizeof(char))) == NULL){ emlwrite("Can't malloc description for attachment", NULL); free((char *) tp->size); free((char *) tp->filename); free((char *) tp); return(NULL); } strncpy(tp->description, c, len); tp->description[len] = '\0'; /* callback to show user the mime type that will be used for attachment */ if(Pmaster->mimetype && (*Pmaster->mimetype)(f) > 0){ int rv ; clearcursor(); mlerase(); rv = (*Pmaster->showmsg)('x'); ttresize(); picosigs(); if(rv) /* Did showmsg corrupt the screen? */ PaintBody(0); /* Yes, repaint it */ mpresf = 1; } return(tp); } /* * AttachError - Sniff list of attachments, returning TRUE if there's * any sign of trouble... */ int AttachError(void) { PATMT *ap; if(!Pmaster) return(0); ap = Pmaster->attachments; while(ap){ if((ap->flags) & A_ERR) return(1); ap = ap->next; } return(FALSE); } char * QuoteAttach(char *fn, size_t fnlen) { char *p; if(*fn && strpbrk(fn, " \t,(\"")){ /* Quote it? */ p = &fn[strlen(fn)]; if(p+2-fn < fnlen){ *(p+2) = '\0'; *(p+1) = '\"'; do *p = *(p-1); while(--p != fn); *p = '\"'; } } return(fn); } void ZotAttach(PATMT *p) { if(!p) return; if(p->description) free((char *)p->description); if(p->filename){ if(p->flags & A_TMP) our_unlink(p->filename); free((char *)p->filename); } if(p->size) free((char *)p->size); if(p->id) free((char *)p->id); p->next = NULL; } #endif /* ATTACHMENTS */ /* * intag - return TRUE if i is in a column that makes up an * attachment line number */ int intag(UCS *s, int i) { UCS *p = s; int n = 0; while(*p != '\0' && (p-s) < 5){ /* is there a tag? it */ if(n && *p == '.') /* can't be more than 4 */ return(i <= p-s); /* chars long! */ if(*p < '0' || *p > '9') break; else n = (n * 10) + (*p - '0'); p++; } return(FALSE); } /* * prettysz - return pointer to string containing nice size description */ char * prettysz(off_t l) { static char b[32]; long sz, left, right; sz = (long) l; b[0] = '\0'; if(sz < 1000L){ snprintf(b, sizeof(b), "%ld B", sz); /* xxx B */ } else if(sz < 9950L){ left = (sz + 50L) / 1000L; right = ((sz + 50L) - left * 1000L) / 100L; snprintf(b, sizeof(b), "%ld.%ld KB", left, right); /* x.x KB */ } else if(sz < 999500L){ snprintf(b, sizeof(b), "%ld KB", (sz + 500L) / 1000L); /* xxx KB */ } else if(sz < 9950000L){ left = (sz + 50000L) / 1000000L; right = ((sz + 50000L) - left * 1000000L) / 100000L; snprintf(b, sizeof(b), "%ld.%ld MB", left, right); /* x.x MB */ } else{ snprintf(b, sizeof(b), "%ld MB", (sz + 500000L) / 1000000L); /* xxx MB */ } return(b); } /* * sinserts - s insert into another string */ void sinserts(UCS *ds, /* dest string */ int dl, /* where to begin insert */ UCS *ss, /* source string */ int sl) /* length of ss */ { UCS *dp, *edp; /* pointers into dest. */ size_t j; /* jump difference */ if(sl >= dl){ /* source bigger than dest. */ dp = ds + dl; /* shift dest. to make room */ if((edp = ucs4_strchr(dp, '\0')) != NULL){ j = sl - dl; for( ;edp >= dp; edp--) edp[j] = *edp; while(sl--) *ds++ = *ss++; } else emlwwrite(_("No end of line???"), NULL); /* can this happen? */ } else{ /* dest is longer, shrink it */ j = dl - sl; /* difference in lengths */ while(sl--) /* copy u onto ds */ *ds++ = *ss++; if(ucs4_strlen(ds) > j){ /* shuffle the rest left */ do *ds = ds[j]; while(*ds++ != '\0'); } else *ds = '\0'; } }