/* mime.c */ #include "ml.h" #ifdef __STDC__ Mime_Handler *new_mime_handler(void) #else Mime_Handler *new_mime_handler() #endif { Mime_Handler *mime_handler = (Mime_Handler *) fs_get(sizeof(Mime_Handler)); mime_handler->type = TYPEOTHER; mime_handler->subtype = NULL; mime_handler->view_builtin = FALSE; mime_handler->builtin = NULL; mime_handler->view = NULL; mime_handler->compose = NULL; mime_handler->next = NULL; return(mime_handler); } Mime_Type *mime_types = NULL; Filename_Map *external_filename_map = NULL; Filename_Map builtin_filename_map[] = { { ".text", TYPETEXT, "plain", NULL }, { ".txt", TYPETEXT, "plain", NULL }, { ".ps", TYPEAPPLICATION, "postscript", NULL }, { ".c", TYPETEXT, "plain", NULL }, { ".html", TYPETEXT, "html", NULL }, { ".gif", TYPEIMAGE, "gif", NULL }, { ".tiff", TYPEIMAGE, "tiff", NULL }, { ".jpg", TYPEIMAGE, "jpeg", NULL }, { ".mpg", TYPEVIDEO, "mpeg", NULL }, { ".qt", TYPEVIDEO, "quicktime", NULL }, { ".au", TYPEAUDIO, "basic", NULL }, { ".tar", TYPEAPPLICATION, "octet-stream", NULL }, }; Mime_Type builtin_mime_types[] = { { TYPETEXT, "plain", NULL }, { TYPETEXT, "html", NULL }, { TYPETEXT, "tab-separated-values", NULL }, { TYPEIMAGE, "jpeg", NULL }, { TYPEIMAGE, "gif", NULL }, { TYPEIMAGE, "ief", NULL }, { TYPEIMAGE, "tiff", NULL }, { TYPEIMAGE, "x-xwindowdump", NULL }, { TYPEIMAGE, "x-portable-anymap", NULL }, { TYPEIMAGE, "x-portable-bitmap", NULL }, { TYPEIMAGE, "x-portable-graymap", NULL }, { TYPEIMAGE, "x-portable-pixmap", NULL }, { TYPEIMAGE, "x-x11-dump", NULL }, { TYPEIMAGE, "x-xbitmap", NULL }, { TYPEAUDIO, "basic", NULL }, { TYPEAUDIO, "x-sun-audio", NULL }, { TYPEVIDEO, "mpeg", NULL }, { TYPEVIDEO, "quicktime", NULL }, { TYPEMULTIPART, "mixed", NULL }, { TYPEMULTIPART, "alternative", NULL }, { TYPEMULTIPART, "digest", NULL }, { TYPEMULTIPART, "parallel", NULL }, { TYPEMULTIPART, "appledouble", NULL }, { TYPEMESSAGE, "rfc822", NULL }, { TYPEMESSAGE, "partial", NULL }, { TYPEMESSAGE, "external-body", NULL }, { TYPEMESSAGE, "news", NULL }, { TYPEAPPLICATION, "octet-stream", NULL }, { TYPEAPPLICATION, "postscript", NULL }, { TYPEAPPLICATION, "slate", NULL }, { TYPEAPPLICATION, "wita", NULL }, { TYPEAPPLICATION, "rtf", NULL }, { TYPEAPPLICATION, "applefile", NULL }, { TYPEAPPLICATION, "mac-binhex40", NULL }, { TYPEAPPLICATION, "news-message-id", NULL }, { TYPEAPPLICATION, "news-transmission", NULL }, { TYPEAPPLICATION, "wordperfect5.1", NULL }, { TYPEAPPLICATION, "pgp", NULL }, { TYPEAPPLICATION, "pdf", NULL }, { TYPEAPPLICATION, "zip", NULL }, { TYPEAPPLICATION, "macwriteii", NULL }, { TYPEAPPLICATION, "msword", NULL }, { TYPEAPPLICATION, "remote-printing", NULL }, { TYPEAPPLICATION, "x-tex", NULL }, { TYPEAPPLICATION, "x-texinfo", NULL }, { TYPEAPPLICATION, "x-latex", NULL }, { TYPEAPPLICATION, "x-troff", NULL }, }; Menu attach_menu[] = { { NULL, "attachtype_accept", NUL_TERM, attachtype_accept, NULL, 0, NULL, NULL, BTN_ON }, { NULL, "attachtype_cancel", NUL_TERM, attachtype_cancel, NULL, 0, NULL, NULL, BTN_ON }, { NULL, "attachtype_HELP", NUL_TERM, attachtype_help, NULL, 0, NULL, NULL, BTN_ON }, }; Menu mimecompose_menu[] = { { NULL, "mimecompose_accept", NUL_TERM, mimecompose_accept, NULL, 0, NULL, NULL, BTN_ON }, { NULL, "mimecompose_cancel", NUL_TERM, mimecompose_cancel, NULL, 0, NULL, NULL, BTN_ON }, { NULL, "mimecompose_HELP", NUL_TERM, mimecompose_help, NULL, 0, NULL, NULL, BTN_ON }, }; #ifdef __STDC__ Boolean create_mime_attach_window(Widget w, BODY *body, Boolean is_multi) #else Boolean create_mime_attach_window(w, body, is_multi) Widget w; BODY *body; Boolean is_multi; #endif { Arg args[ARGLISTSIZE]; int n = 0; char *ptr; char *prelim_string = NULL; Boolean valid = FALSE; Widget form, menubar; XtTranslations translations; char buffer[FILEBUFFLEN]; Mime_Attach *mime_attach = (Mime_Attach *) fs_get(sizeof(Mime_Attach)); push_cursor(PIRATE_CURSOR); mime_attach->done = 0; if(preferences.autoPlace == TRUE) { XtSetArg(args[n], XtNx, -1000); n ++; XtSetArg(args[n], XtNy, -1000); n ++; } XtSetArg(args[n], XmNdeleteResponse, XmDO_NOTHING); n++; mime_attach->shell = XtCreatePopupShell("attachtype_shell", topLevelShellWidgetClass, w, args, n); n = 0; AddDestroyCallback(mime_attach->shell); setup_editres(mime_attach->shell); if(pirate_icon != (Pixmap) None) XtVaSetValues(mime_attach->shell, XmNiconPixmap,pirate_icon, NULL); form = XmCreateForm(mime_attach->shell, "attachtype_form", args, n ); n = 0; XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n ++; XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n ++; XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n ++; menubar = XmCreateMenuBar(form,"attachtype_menubar", args, n); n = 0; XtManageChild(menubar); create_buttons(NULL, menubar, attach_menu, XtNumber(attach_menu), BTN_ON, (XtPointer) mime_attach, ROOTMENULEVEL); mime_attach->desc_text = create_text_field(form, menubar, "attachtype_description", body->description,0, NULL, NULL); if(body->description) fs_give((void **) &body->description); sprintf(buffer,"%s/%s",type_to_name(body->type), (body->subtype) ? body->subtype : EMPTYSTR ); mime_attach->type_text = create_text_field(form, mime_attach->desc_text, "attachtype_type", buffer, 0, (XtPointer) attachtype_accept, (XtPointer) mime_attach ); XtSetArg(args[n], XmNscrollBarDisplayPolicy, XmSTATIC); n ++; XtSetArg(args[n], XmNlistSizePolicy,XmVARIABLE); n ++; XtSetArg(args[n], XmNvisibleItemCount, 10 ); n ++; XtSetArg(args[n], XmNselectionPolicy,XmSINGLE_SELECT); n ++; mime_attach->type_list = XmCreateScrolledList(form,"attachtype_list",args,n); n = 0; XtAddCallback(mime_attach->type_list, XmNsingleSelectionCallback, (XtCallbackProc) attachtype_select, mime_attach); XtAddCallback(mime_attach->type_list, XmNdefaultActionCallback, (XtCallbackProc) attachtype_accept, mime_attach); translations = XtParseTranslationTable(GLOBAL_modal_list_translations); XtOverrideTranslations(mime_attach->type_list,translations); XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n ++; XtSetArg(args[n], XmNtopWidget, mime_attach->type_text); n ++; XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n ++; XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n ++; XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n ++; XtSetValues(XtParent(mime_attach->type_list), args, n); n = 0; (void) stuff_typelist(mime_attach->type_list,is_multi); XtManageChild(mime_attach->type_list); XtManageChild(form); XtManageChild(mime_attach->shell); XtPopup(mime_attach->shell, XtGrabExclusive); position_popup_widget(mime_attach->shell, TRUE); modal_main_loop(&mime_attach->done); if(body->subtype) fs_give((void **) &body->subtype); body->description = XmTextGetString(mime_attach->desc_text); prelim_string = XmTextGetString(mime_attach->type_text); if(*prelim_string != NUL_TERM) { body->type = name_to_type(prelim_string); ptr = strchr(prelim_string,TYPE_SEPARATOR_CHAR); if(ptr) { body->subtype = cpystr(ptr + 1); valid = TRUE; } } fs_give((void **) &prelim_string); XtPopdown(mime_attach->shell); XtDestroyWidget(mime_attach->shell); fs_give((void **) &mime_attach); pop_cursor(); return(valid); } #ifdef __STDC__ void stuff_typelist(Widget w, Boolean is_multi) #else void stuff_typelist(w,is_multi) Widget w; Boolean is_multi; #endif { char buff[FILEBUFFLEN]; Mime_Type *mime_type; int i; XmString xstr; if(mime_types != NULL) { for(mime_type = mime_types; mime_type; mime_type = mime_type->next) { if(is_multi) { if(mime_type->type != TYPEMULTIPART) continue; } else { if(mime_type->type == TYPEMULTIPART) continue; } sprintf(buff,"%s/%s",type_to_name(mime_type->type),mime_type->subtype); xstr = XmStringCreateSimple(buff); XmListAddItemUnselected(w,xstr,0); XmStringFree(xstr); } } else { for(i = 0; i < XtNumber(builtin_mime_types); i ++) { if(is_multi) { if(builtin_mime_types[i].type != TYPEMULTIPART) continue; } else { if(builtin_mime_types[i].type == TYPEMULTIPART) continue; } sprintf(buff,"%s/%s",type_to_name(builtin_mime_types[i].type), builtin_mime_types[i].subtype); xstr = XmStringCreateSimple(buff); XmListAddItemUnselected(w,xstr,0); XmStringFree(xstr); } } return; } #ifdef __STDC__ void attachtype_select(Widget w, Mime_Attach *mime_attach, XmListCallbackStruct *xp) #else void attachtype_select(w,mime_attach,xp) Widget w; Mime_Attach *mime_attach; XmListCallbackStruct *xp; #endif { char *str; XmStringGetLtoR(xp->item,XmSTRING_DEFAULT_CHARSET,&str); XmTextSetString(mime_attach->type_text,str); fs_give((void **) &str); return; } #ifdef __STDC__ void attachtype_accept(Widget w,Mime_Attach *mime_attach, XtPointer xp) #else void attachtype_accept(w,mime_attach,xp) Widget w; Mime_Attach *mime_attach; XtPointer xp; #endif { mime_attach->done = MODAL_LOOP_DONE; return; } #ifdef __STDC__ void attachtype_cancel(Widget w,Mime_Attach *mime_attach, XtPointer xp) #else void attachtype_cancel(w,mime_attach,xp) Widget w; Mime_Attach *mime_attach; XtPointer xp; #endif { XmTextSetString(mime_attach->type_text,""); mime_attach->done = MODAL_LOOP_DONE; return; } #ifdef __STDC__ void attachtype_help(Widget w,Mime_Attach *mime_attach, XtPointer xp) #else void attachtype_help(w,mime_attach,xp) Widget w; Mime_Attach *mime_attach; XtPointer xp; #endif { help(mime_attach->shell, MIMEATTACHHELPFILE); return; } /* * Try and match a MIME type/subtype pair to the filename suffix, * and set the BODY info accordingly. We supply a very limited suffix * map which can be over-ridden (not enhanced) via an external map file. * Returns TRUE if we matched with something. The format of the external * file "$(LIBDIR)/mime.map" is * .extensiontype/subtype * * Example: * * # This is a comment * .html text/html * .ps application/postscript * * The parser which gets this info is reasonably forgiving; and should * allow any variations of whitespace and comment lines beginning with * a '#' character. The extension _must_ start with a period. Matches * are case-insensitive. We return the first match found. Since our linked * list is built in LIFO order, the last matching entry in the file will * always be the first found. * */ #ifdef __STDC__ Boolean get_type_from_suffix(BODY *body, char *filename) #else Boolean get_type_from_suffix(body, filename) BODY *body; char *filename; #endif { Boolean found = FALSE; Filename_Map *current; char *ptr; int i; if((ptr = strrchr(filename,'.')) != NULL) { if(external_filename_map == NULL) { for(i = 0; i < XtNumber(builtin_filename_map); i ++) { if((strcasecmp(builtin_filename_map[i].suffix,ptr)) == STRMATCH ){ found = TRUE; body->type = builtin_filename_map[i].type; if(body->subtype) fs_give((void **) &body->subtype); body->subtype = cpystr(builtin_filename_map[i].subtype); break; } } } else { for(current = external_filename_map; current; current = current->next) { if((strcasecmp(current->suffix,ptr)) == STRMATCH ) { found = TRUE; body->type = current->type; if(body->subtype) fs_give((void **) &body->subtype); body->subtype = cpystr(current->subtype); break; } } } } return(found); } #ifdef __STDC__ void set_default_attach_type(BODY *body, char *filename) #else void set_default_attach_type(body,filename) BODY *body; char *filename; #endif { char *ptr; if(filename) { ptr = strrchr(filename,PATH_SEPARATOR_CHAR); if(ptr) { body->description = cpystr(ptr + 1); body->parameter = mail_newbody_parameter () ; body->parameter->attribute = cpystr("name"); body->parameter->value = cpystr(ptr + 1); body->parameter->next = NULL; } } body->type = TYPETEXT; body->subtype = cpystr(PLAINSUBTYPE); return; } /* * Takes a body, whose type info and description has already been filled in, * and sets the contents appropriately to what's in the Binary_Buffer. */ #ifdef __STDC__ void add_attachment_contents_to_body(BODY *body,Binary_Buffer *binary_buffer) #else void add_attachment_contents_to_body(body,binary_buffer) BODY *body; Binary_Buffer *binary_buffer; #endif { unsigned long i; Boolean encode_it = FALSE; unsigned line_len = 0; unsigned long newlen; if(body == NULL) return; /* Allow for no contents only if adding a new multipart leaf */ if((body->type != TYPEMULTIPART) && ((binary_buffer == NULL) || (binary_buffer->data == NULL))) return; switch(body->type) { /* * They say text, but we want to make sure it will go through * the mail unmolested. */ case TYPETEXT: for(i = 0; binary_buffer->data[i]; i ++) { if(binary_buffer->data[i] >= 0x80 ) encode_it = TRUE; if(binary_buffer->data[i] == LFCHAR) line_len = 0; if(++line_len > 128) encode_it = TRUE; } if(i != binary_buffer->length) encode_it = TRUE; if(encode_it == TRUE) { body->contents.text = (unsigned char *) rfc822_8bit(binary_buffer->data, i, &newlen); body->size.bytes = newlen; body->encoding = ENCQUOTEDPRINTABLE; } else { body->contents.text = (unsigned char *) lftocrlf((char *) binary_buffer->data); body->size.bytes = strlen((char *) body->contents.text); body->encoding = ENC7BIT; } break; case TYPEMULTIPART: /* do nothing */ break; case TYPEMESSAGE: /* * Hack to set the body parameters for external types. See notes * below on the parse_external() function */ if((body->subtype) && (strcasecmp(body->subtype,"external-body") == STRMATCH)) parse_external(body,binary_buffer); body->contents.msg.text = lftocrlf((char *) binary_buffer->data); body->size.bytes = strlen(body->contents.msg.text); body->encoding = ENC7BIT; break; case TYPEAPPLICATION: case TYPEAUDIO: case TYPEIMAGE: case TYPEVIDEO: case TYPEOTHER: body->contents.text = (unsigned char *) rfc822_binary(binary_buffer->data, binary_buffer->length, &body->size.bytes); body->encoding = ENCBASE64; break; default: break; } return; } /* ACCKKKKK!!!!!!! * For message/external-body attachments, we need to somehow get * the access parameters into the body->parameter list. Our compose program * (I'm basing this on Metamail's "extcompose") needs to have a standard * mail header line with all of the access stuff in it. We're assuming * it starts at the beginning of the buffer. We're also going to destroy * it before we're done parsing it. * There's a lot that could go wrong here, but no checking is done. * If the compose program does the right thing, it'll work. If not, too bad. * Here's an example of what we might expect as input: * ------ Content-Type: message/external-body; access-type=mail-server; server=mailserver@podunk.edu Content-Type: text/plain send file.1 ------ * * Note that we only want the first "Content-Type:". The second one * refers to the following text. It might also refer to the disposition * of the external part itself. Can you say "kludge"? Thought so. * A form for creating these things under our control would be easier. * */ #ifdef __STDC__ void parse_external(BODY *body,Binary_Buffer *binary_buffer) #else void parse_external(body,binary_buffer) BODY *body; Binary_Buffer *binary_buffer; #endif { char *ptr = (char *) binary_buffer->data; char *p; char *tmp; /* * We assume the input text is in mail header format. Tie off the * input text at the end of the first header field; which might have * continuation lines. When we've done that, set ptr to the character * following this termination. We'll use it later. * */ while(*ptr) { for( ; (*ptr) && *ptr != LFCHAR; ptr ++) ; if((*ptr) && ((*(ptr+1) == SPACECHAR) || (*(ptr+1) == TABCHAR))) { *ptr = SPACECHAR; ptr ++; continue; } else { if(ptr) { *ptr = NUL_TERM; ptr ++; /* point just beyond the end. */ } break; } } /* * Our pointer is stashed away, and we've got an isolated header line. * Tie off the header token name, and save the position following *it*. * */ if((p = strchr((char *) binary_buffer->data,':')) != NULL) { *p = NUL_TERM; p ++; } /* * Set up everything to call the c-client parser. Damn, this is ugly. * But -- it saves having to write a complete mail-header parsing engine. * */ /* point back to the beginning. */ tmp = (char *) binary_buffer->data; /* The c-client wants the header to be capitalized. */ ucase(tmp); /* Don't blame me. This is a copy of what the c-client does. */ if((tmp[0] == 'C') && (tmp[1] == 'O') && (tmp[2] == 'N') && (tmp[3] == 'T') && (tmp[4] == 'E') && (tmp[5] == 'N') && (tmp[6] == 'T') && (tmp[7] == '-')) { /* Lie to the c-client */ body->type = 0; fs_give((void **) &body->subtype); /* parse the sucker. */ rfc822_parse_content_header(body,tmp+8,p); /* We now have the parameters filled in. Wasn't that fun? */ body->type = TYPEMESSAGE; /* reset this guy */ } /* Now make a fresh copy of any remaining body text. */ if(ptr) tmp = cpystr(ptr); fs_give((void **) &binary_buffer->data); /* free the original */ binary_buffer->data = (unsigned char *) tmp; /* return the new pointer */ return; } /* * This and the following function convert to/from the text representation * of MIME (major) type and the c-client's integer representation. * It's a major hassle in this representation, because anywhere where * type and subtype must exist together, we've got to do at least one * conversion. And if we want to check a particular handler, we must do a * numeric compare on type and string compare on subtype. Of course, there * are good reasons for doing things this way, I'm just griping to whoever * decides to actually read these comments; because they will appreciate * and most likely need to know these subtleties. * */ #ifdef __STDC__ int name_to_type(char *str) #else int name_to_type(str) char *str; #endif { if(!str) return(TYPEOTHER); if((strncasecmp(str,TEXT_TYPE_STR,strlen(TEXT_TYPE_STR))) == STRMATCH) return(TYPETEXT); if((strncasecmp(str,MULTI_TYPE_STR,strlen(MULTI_TYPE_STR))) == STRMATCH) return(TYPEMULTIPART); if((strncasecmp(str,MESSAGE_TYPE_STR,strlen(MESSAGE_TYPE_STR))) == STRMATCH) return(TYPEMESSAGE); if((strncasecmp(str,APPL_TYPE_STR,strlen(APPL_TYPE_STR))) == STRMATCH) return(TYPEAPPLICATION); if((strncasecmp(str,AUDIO_TYPE_STR,strlen(AUDIO_TYPE_STR))) == STRMATCH) return(TYPEAUDIO); if((strncasecmp(str,IMAGE_TYPE_STR,strlen(IMAGE_TYPE_STR))) == STRMATCH) return(TYPEIMAGE); if((strncasecmp(str,VIDEO_TYPE_STR,strlen(VIDEO_TYPE_STR))) == STRMATCH) return(TYPEVIDEO); return(TYPEOTHER); } #ifdef __STDC__ char *type_to_name(int type) #else char *type_to_name(type) int type; #endif { switch(type) { case TYPETEXT: return(TEXT_TYPE_STR); break; case TYPEMULTIPART: return(MULTI_TYPE_STR); break; case TYPEMESSAGE: return(MESSAGE_TYPE_STR); break; case TYPEAPPLICATION: return(APPL_TYPE_STR); break; case TYPEAUDIO: return(AUDIO_TYPE_STR); break; case TYPEIMAGE: return(IMAGE_TYPE_STR); break; case TYPEVIDEO: return(VIDEO_TYPE_STR); break; default: return(OTHER_TYPE_STR); break; } /* NOTREACHED */ } /* * Loads the file "$(MIMEDIR)/mime.types" into memory. * File format is "type/subtype". Example: * * text/plain * application/postscript * * Note that while this makes the list site-configurable (a good thing), * people who want to muck with this file need to know what's acceptible * (a bad thing). Don't arbitrarily add types into here which haven't been * registered with the IANA. If you do, the subtype should be prefixed with * "x-", such as "application/x-myfile". The master list supplied with this * program contains two entries which were not registered as of this writing, * but have enough common use that the "x-" rule was intentionally violated. * "text/html" and "application/pgp". Perfect examples of why this file exists, * and why one should follow the rules. It just screws up everybody when a * sender and receiver can't agree on the correct typename. * */ #ifdef __STDC__ void load_mime_types(void) #else void load_mime_types() #endif { FILE *fp; Mime_Type *mime_type = NULL; char filename[MAXPATHLEN]; char buffer[FILEBUFFLEN]; char *ptr = NULL; sprintf(filename,"%s/%s",preferences.mime_directory,MIMETYPESFILE); if((fp = fopen(filename,"r")) == NULL) return; while((fgets(buffer,sizeof(buffer),fp)) != NULL) { remove_trailing_white(buffer); if((*buffer == NUL_TERM) || (*buffer == COMMENT_CHAR)) continue; if((ptr = strchr(buffer,TYPE_SEPARATOR_CHAR)) == NULL) continue; mime_type = (Mime_Type *) fs_get(sizeof(Mime_Type)); mime_type->type = name_to_type(buffer); mime_type->subtype = cpystr(ptr + 1); mime_type->next = mime_types; mime_types = mime_type; } fclose(fp); return; } /* * Load external filename_extension-to-mimetype list. It is not an error * if this file isn't there. We'll use our limited hard-wired map in that * case. */ #ifdef __STDC__ void load_mime_extension_map(void) #else void load_mime_extension_map() #endif { FILE *fp; Filename_Map *filename_map = NULL; char filename[MAXPATHLEN]; char buffer[FILEBUFFLEN]; char *ptr = NULL; char *suffix = NULL; char *typename = NULL; sprintf(filename,"%s/%s",preferences.mime_directory,MIMEMAPFILE); if((fp = fopen(filename,"r")) == NULL) return; while((fgets(buffer,sizeof(buffer),fp)) != NULL) { remove_trailing_white(buffer); if((*buffer == NUL_TERM) || (*buffer == COMMENT_CHAR)) continue; suffix = first_nonwhite(buffer); for(typename = suffix; ((*typename) && (! isspace(*typename)));typename++); while(isspace(*typename)) { *typename = NUL_TERM; typename ++; } if((*typename == NUL_TERM) || ((ptr = strchr(typename,TYPE_SEPARATOR_CHAR)) == NULL)) continue; filename_map = (Filename_Map *) fs_get(sizeof(Filename_Map)); filename_map->suffix = cpystr(suffix); filename_map->type = name_to_type(typename); filename_map->subtype = cpystr(ptr + 1); filename_map->next = external_filename_map; external_filename_map = filename_map; } fclose(fp); return; } #ifdef __STDC__ Mime_Handler *get_handler(int type, char *subtype) #else Mime_Handler *get_handler(type,subtype) int type; char *subtype; #endif { Mime_Handler *mime_handler; if((subtype == NULL) || (*subtype == NUL_TERM)) return(NULL); for(mime_handler = session->mime_handlers; mime_handler; mime_handler = mime_handler->next) if((mime_handler->type == type) && (mime_handler->subtype) && ((strcasecmp(mime_handler->subtype,subtype)) == STRMATCH)) return(mime_handler); return(NULL); } /* * Read in the mime handler config file. $(MIMEDIR)/mime.types * This is "sort of" like metamail, but tailored for use in this program, * so the metamail mailcap file won't really work. It has too many * hacks. So does this. Format is: type/subtype; view_program $COMPOSE=compose_program * There is no white space between the type designator and the semi-colon. * The view program is specified first. Note we don't have things like * "needsterminal" or "copiousouput", so if the program requires a terminal, * it needs to be started with "xterm -e {whatever}", and copiousoutput * requires setting the scroll bars and a large window buffer on the xterm. * Everything up to $COMPOSE= is taken as the viewer. Leading and trailing * whitespace are trashed. Everything following the $COMPOSE= tag is the * compose program (up to the linefeed). No continuation lines. One big line. * Note also that the compose pipe currently doesn't allow input from stdin, * only to a specific temp file designated as "%s". We control stderr, * redirecting to another temporary file and logging the results. If you have * a program with multiple pipes, and they all spit out stderr stuff, this * will only catch the last one. You might consider wrapping the multiple * pipes into a single shell command so that the user doesn't get terminal * messages, only program log messages. And it's OK to have a viewer and * no composer, or vice versa. There is also no "catch-all" handler, like * "image/{*}". (Curly-braces used so I don't trip up "lint"). If the * subtypes don't match, it won't get handled. We will pick * off our internally handled types first, so it's pointless to set a handler * for them. */ #ifdef __STDC__ void load_mime_handlers(void) #else void load_mime_handlers() #endif { FILE *fp; Mime_Handler *mime_handler = NULL; char filename[MAXPATHLEN]; char buffer[FILEBUFFLEN]; char *ptr; char *viewptr = NULL; char *composeptr = NULL; sprintf(filename,"%s/%s",preferences.mime_directory,MIMEHANDLERFILE); if((fp = fopen(filename,"r")) == NULL) return; while((fgets(buffer,sizeof(buffer),fp)) != NULL) { remove_trailing_white(buffer); if((*buffer == NUL_TERM) || (*buffer == COMMENT_CHAR)) continue; if((viewptr = strchr(buffer,SEMICOLONCHAR)) == NULL) continue; *viewptr = NUL_TERM; viewptr ++; viewptr = first_nonwhite(viewptr); if(*viewptr == NUL_TERM) continue; if((ptr = strchr(buffer,TYPE_SEPARATOR_CHAR)) == NULL) continue; mime_handler = new_mime_handler(); mime_handler->type = name_to_type(buffer); mime_handler->subtype = cpystr(ptr + 1); composeptr = ML_Strstr(viewptr,COMPOSETAG); if(composeptr) { *composeptr = NUL_TERM; composeptr += strlen(COMPOSETAG); composeptr = first_nonwhite(composeptr); if(*composeptr) mime_handler->compose = cpystr(composeptr); } remove_trailing_white(viewptr); if(*viewptr != NUL_TERM) mime_handler->view = cpystr(viewptr); mime_handler->next = session->mime_handlers; session->mime_handlers = mime_handler; } fclose(fp); return; } #ifdef __STDC__ int show_mime(Widget w, BODY *body, Boolean saveonly) #else int show_mime(w,body, saveonly) Widget w; BODY *body; Boolean saveonly; #endif { Mime_Handler *mime_handler; char *command = NULL; char *contents = NULL; char *decode = NULL; char *view = NULL; char *internal_viewer = MLGetLocalized(XtNstrInternalViewer, StrInternalViewer); unsigned long bodylength; unsigned long newlength; int errors = 0; Boolean internal = FALSE; Boolean append_it = FALSE; if(body == NULL) return(SYSCALL_FAILURE); switch(body->type) { case TYPEMULTIPART: return(SYSCALL_SUCCESS); break; case TYPEMESSAGE: contents = cpystr((char *) body->contents.msg.text); break; case TYPETEXT: case TYPEIMAGE: case TYPEAUDIO: case TYPEAPPLICATION: case TYPEOTHER: default: contents = cpystr((char *) body->contents.text); break; } mime_handler = get_handler(body->type, body->subtype); if(((is_extmail(body)) == TRUE) || ((is_ftp(body)) == TRUE)) internal = TRUE; if((mime_handler != NULL) && (internal == FALSE)) view = mime_handler->view; if(view == NULL) view = cpystr(internal_viewer); push_cursor(WATCH_CURSOR); bodylength = body->size.bytes; switch(body->encoding) { case ENCBASE64: decode = (char *) rfc822_base64((unsigned char *) contents, bodylength,&newlength); break; case ENCQUOTEDPRINTABLE: decode = (char *) rfc822_qprint((unsigned char *) contents, bodylength,&newlength); break; case ENCOTHER: case ENC7BIT: case ENC8BIT: case ENCBINARY: default: break; } if(contents) { stripcr(contents); bodylength = strlen(contents); } else bodylength = 0L; if((body->type == TYPETEXT) && ((strcmp(view,internal_viewer)) == STRMATCH) && (saveonly == FALSE)) { if(((strcasecmp(body->subtype,"plain")) == STRMATCH) && ((decode != NULL) || (contents != NULL))) display_text(w, NULL, NULL, (decode) ? decode : contents, DOCTYPE_PLAIN); if(decode) fs_give((void **) &decode); if(contents) fs_give((void **) &contents); pop_cursor(); return(SYSCALL_SUCCESS); } if(internal == TRUE && (saveonly == FALSE)) { if((is_extmail(body)) == TRUE) get_external(body,contents); if((is_ftp(body)) == TRUE) fetch(body); } else { if((preferences.mime_ask == TRUE) && (saveonly == FALSE)) command = input_string(w,MLGetLocalized(XtNmsgInputCommand, MsgInputCommand), view, MIMEASKHELPFILE); else command = cpystr((view) ? view : EMPTYSTR ); if((command != NULL) && (*command == NUL_TERM)) fs_give((void **) &command); if((command != NULL) && (saveonly == FALSE)) { if((strcmp(command,internal_viewer)) == STRMATCH) { if((decode != NULL) && (strlen(decode) == newlength)) display_text(w, NULL, NULL, decode, DOCTYPE_PLAIN); else if((contents != NULL) && (strlen(contents) == bodylength)) display_text(w, NULL, NULL, contents, DOCTYPE_PLAIN); } else { if((write_to_pipe(command, NULL, (decode) ? decode : contents, (decode) ? newlength : bodylength)) != SYSCALL_SUCCESS) errors ++; } } else { /* No viewer was found, selected, or (saveonly == TRUE) */ command = file_select(w,NULL, NULL, NULL, TRUE, &append_it); if(command) { errors += save_to_file(command,(decode) ? decode : contents, (decode) ? newlength : bodylength, append_it); if(errors) mm_log(MLGetLocalized(XtNmsgSaveFail,MsgSaveFail), WARN); else mm_log(MLGetLocalized(XtNmsgSaveSuccess,MsgSaveSuccess), NIL); } else errors ++; } } if(command) fs_give((void **) &command); if(decode) fs_give((void **) &decode); if(contents) fs_give((void **) &contents); pop_cursor(); return((errors) ? SYSCALL_FAILURE : SYSCALL_SUCCESS); } #ifdef __STDC__ int save_to_file(char *filename,char *str,unsigned long length, Boolean append) #else int save_to_file(filename,str,length,append) char *filename; char *str; unsigned long length; Boolean append; #endif { FILE *fp; int errors = 0; if(filename == NULL) return(1); if((fp = fopen(filename,(append == TRUE) ? "a" : "w")) == NULL) return(1); (void) chmod(filename,S_IRWXU); if(fwrite(str,length,1,fp) != 1) errors ++; if(fclose(fp)) errors ++; return(errors); } #ifdef __STDC__ Binary_Buffer *create_mime_compose_window(Widget w, BODY *body) #else Binary_Buffer *create_mime_compose_window(w, body) Widget w; BODY *body; #endif { Mime_Handler *mime_handler = NULL; Binary_Buffer *binary_buffer = NULL; char *command = NULL; Arg args[ARGLISTSIZE]; int n = 0; char *ptr; Boolean valid = FALSE; Widget form, menubar; XtTranslations translations; Mime_Attach *mime_attach = (Mime_Attach *) fs_get(sizeof(Mime_Attach)); push_cursor(PIRATE_CURSOR); mime_attach->done = 0; mime_attach->compose = NULL; if(preferences.autoPlace == TRUE) { XtSetArg(args[n], XtNx, -1000); n ++; XtSetArg(args[n], XtNy, -1000); n ++; } XtSetArg (args[n], XmNdeleteResponse, XmDO_NOTHING); n++; mime_attach->shell = XtCreatePopupShell("mimecompose_shell", topLevelShellWidgetClass, w, args, n); n = 0; AddDestroyCallback (mime_attach->shell); setup_editres(mime_attach->shell); if(pirate_icon != (Pixmap) None) XtVaSetValues(mime_attach->shell, XmNiconPixmap,pirate_icon, NULL); form = XmCreateForm(mime_attach->shell, "mimecompose_form", args, n ); n = 0; XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n ++; XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n ++; XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n ++; menubar = XmCreateMenuBar(form,"mimecompose_menubar", args, n); n = 0; XtManageChild(menubar); create_buttons(NULL, menubar, mimecompose_menu, XtNumber(mimecompose_menu), BTN_ON, (XtPointer) mime_attach, ROOTMENULEVEL); mime_attach->desc_text = create_text_field(form, menubar, "mimecompose_description", body->description,0, NULL, NULL); if(body->description) fs_give((void **) &body->description); XtSetArg(args[n], XmNscrollBarDisplayPolicy, XmSTATIC); n ++; XtSetArg(args[n], XmNlistSizePolicy,XmCONSTANT); n ++; XtSetArg(args[n], XmNselectionPolicy,XmSINGLE_SELECT); n ++; mime_attach->type_list = XmCreateScrolledList(form,"mimecompose_list",args,n); n = 0; XtAddCallback(mime_attach->type_list, XmNsingleSelectionCallback, (XtCallbackProc) mimecompose_select, mime_attach); XtAddCallback(mime_attach->type_list, XmNdefaultActionCallback, (XtCallbackProc) mimecompose_accept, mime_attach); translations = XtParseTranslationTable(GLOBAL_modal_list_translations); XtOverrideTranslations(mime_attach->type_list,translations); XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n ++; XtSetArg(args[n], XmNtopWidget, mime_attach->desc_text); n ++; XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n ++; XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n ++; XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n ++; XtSetValues(XtParent(mime_attach->type_list), args, n); n = 0; (void) stuff_composelist(mime_attach->type_list); XtManageChild(mime_attach->type_list); XtManageChild(form); XtManageChild(mime_attach->shell); XtPopup(mime_attach->shell, XtGrabExclusive); position_popup_widget(mime_attach->shell, TRUE); modal_main_loop(&mime_attach->done); XDefineCursor(display, XtWindow(mime_attach->shell), clock_cursor); body->description = XmTextGetString(mime_attach->desc_text); if((mime_attach->compose != NULL) && (*mime_attach->compose != NUL_TERM)) { body->type = name_to_type(mime_attach->compose); ptr = strchr(mime_attach->compose,TYPE_SEPARATOR_CHAR); if(ptr) { body->subtype = cpystr(ptr + 1); valid = TRUE; } } if(mime_attach->compose) fs_give((void **) &mime_attach->compose); if(valid) { for(mime_handler = session->mime_handlers; mime_handler; mime_handler = mime_handler->next) { if((mime_handler->type == body->type) && ((strcasecmp(mime_handler->subtype,body->subtype)) == STRMATCH)) command = mime_handler->compose; } } if(command && *command) binary_buffer = read_from_pipe(command); XUndefineCursor(display, XtWindow(mime_attach->shell)); XtPopdown(mime_attach->shell); XtDestroyWidget(mime_attach->shell); fs_give((void **) &mime_attach); pop_cursor(); return(binary_buffer); } #ifdef __STDC__ void stuff_composelist(Widget w) #else void stuff_composelist(w) Widget w; #endif { Mime_Handler *mime_handler; char buff[FILEBUFFLEN]; XmString xstr; for(mime_handler = session->mime_handlers; mime_handler; mime_handler = mime_handler->next) { if(mime_handler->compose && *mime_handler->compose) { sprintf(buff,"%s/%s",type_to_name(mime_handler->type), mime_handler->subtype); xstr = XmStringCreateSimple(buff); XmListAddItemUnselected(w,xstr,0); XmStringFree(xstr); } } return; } #ifdef __STDC__ void mimecompose_select(Widget w,Mime_Attach *mime_attach, XmListCallbackStruct *xp) #else void mimecompose_select(w,mime_attach,xp) Widget w; Mime_Attach *mime_attach; XmListCallbackStruct *xp; #endif { char *str; XmStringGetLtoR(xp->item,XmSTRING_DEFAULT_CHARSET,&str); mime_attach->compose = str; mime_attach->done = MODAL_LOOP_DONE; return; } #ifdef __STDC__ void mimecompose_accept(Widget w, Mime_Attach *mime_attach, XtPointer xp) #else void mimecompose_accept(w,mime_attach,xp) Widget w; Mime_Attach *mime_attach; XtPointer xp; #endif { /* NOTREACHED */ } #ifdef __STDC__ void mimecompose_cancel(Widget w, Mime_Attach *mime_attach, XtPointer xp) #else void mimecompose_cancel(w,mime_attach,xp) Widget w; Mime_Attach *mime_attach; XtPointer xp; #endif { fs_give((void **) &mime_attach->compose); mime_attach->compose = NULL; mime_attach->done = MODAL_LOOP_DONE; return; } #ifdef __STDC__ void mimecompose_help(Widget w, Mime_Attach *mime_attach, XtPointer xp) #else void mimecompose_help(w,mime_attach,xp) Widget w; Mime_Attach *mime_attach; XtPointer xp; #endif { help(mime_attach->shell,MIMECOMPOSEHELPFILE); return; } #ifdef __STDC__ char *find_param(BODY *body, char *s) #else char *find_param(body,s) BODY * body; char *s; #endif { struct mail_body_parameter * params; for (params = body->parameter; params != NULL; params = params->next) { if (!strcasecmp(s, params->attribute)) { return(params->value); } } return(NULL); } #ifdef __STDC__ Boolean is_extmail(BODY *body) #else Boolean is_extmail(body) BODY * body; #endif { char * tmp = NULL; /* don't free */ if((body->type == TYPEMESSAGE) && (strcasecmp(body->subtype,"external-body") == 0) && ((tmp = find_param(body,"access-type")) != NULL) && ((strcasecmp(tmp,"mail-server")) == 0)) return(TRUE); else return(FALSE); } #ifdef __STDC__ Boolean is_ftp(BODY *body) #else Boolean is_ftp(body) BODY * body; #endif { char * tmp = NULL; /* don't free */ if((body->type == TYPEMESSAGE) && (strcasecmp(body->subtype,"external-body") == 0) && ((tmp = find_param(body,"access-type")) != NULL) && (((strcasecmp(tmp,"anon-ftp")) == 0) || ((strcasecmp(tmp,"ftp")) == 0))) return(TRUE); else return(FALSE); } #ifdef __STDC__ void get_external(BODY *body, char *text) #else void get_external(body, text) BODY *body; char *text; #endif { char **mailhosts = NULL; BODY *newbody; ADDRESS *address = NULL; SMTPSTREAM *stream; char * tmp = NULL; /* don't free */ char *from = NULL; char *host = NULL; char log[FILEBUFFLEN]; ENVELOPE *envelope = mail_newenvelope(); mailhosts = (char **) fs_get( 2 * sizeof(char *)); mailhosts[0] = cpystr(preferences.smtp_server); mailhosts[1] = NULL; from = cpystr(preferences.reply_address); host = cpystr(preferences.default_domain); if((stream = smtp_open(mailhosts,0L)) != NIL) { newbody = mail_newbody(); newbody->contents.text = (text != NULL) ? (unsigned char *) cpystr(text) : (unsigned char *) cpystr(EMPTYSTR); newbody->type = TYPETEXT; newbody->encoding = ENC7BIT; tmp = find_param(body,"server"); if(tmp) { rfc822_parse_adrlist(&address, tmp, EMPTYSTR); envelope->to = address; envelope->from = text_to_address(from,host); envelope->reply_to = copy_address(envelope->from); envelope->return_path = mail_newaddr(); envelope->return_path->mailbox = cpystr(envelope->from->mailbox); envelope->return_path->host = cpystr(envelope->from->host); envelope->sender = mail_newaddr(); envelope->sender->mailbox = cpystr(local_auth.username); envelope->sender->host = cpystr(local_auth.hostname); envelope->subject = cpystr(EMPTYSTR); envelope->message_id = (char *) fs_get(MESSAGE_ID_LENGTH); sprintf(envelope->message_id,"<%s-%s.%d.%d.%s@%s>", PROGRAM, MLVERSION, time(NULL), rand() % 10000, local_auth.username, local_auth.hostname); envelope->date = (char *) fs_get(MESSAGE_ID_LENGTH); rfc822_date(envelope->date); if (smtp_mail (stream,"MAIL",envelope,newbody)) { sprintf(log,MLGetLocalized(XtNmsgFetchMail,MsgFetchMail),tmp); mm_log(log, NIL); smtp_close(stream); } else mm_log(MLGetLocalized(XtNmsgFetchMailFail,MsgFetchMailFail),WARN); mail_free_body(&newbody); mail_free_envelope(&envelope); } } if(tmp) fs_give((void **) &tmp); if(host) fs_give((void **) &host); if(from) fs_give((void **) &from); fs_give((void **) &mailhosts[0]); fs_give((void **) &mailhosts); } #ifdef __STDC__ int do_ftp(char *site, char *user, char *pass, char *directory, char *mode,char *name, char *filename) #else int do_ftp(site,user,pass,directory,mode,name,filename) char *site; char *user; char *pass; char *directory; char *mode; char *name; char *filename; #endif { FILE *p; if(!name || !site || *name == '\0' || *user == '\0' || *pass == '\0') return(EOF); if((p = popen("ftp -n","w")) == NULL) return(EOF); if((fprintf(p,"open %s\n",site)) == EOF) return(EOF); if((fprintf(p,"user %s %s\n", user,pass)) == EOF) return(EOF); if(mode) if((fprintf(p,"type %s\n",mode)) == EOF) return(EOF); if(directory) if((fprintf(p,"cd %s\n",directory)) == EOF) return(EOF); if((fprintf(p,"get %s %s\n",name,filename)) == EOF) return(EOF); if((fprintf(p,"quit\n")) == EOF) return(EOF); if(pclose(p)) return(EOF); return(0); } #ifdef __STDC__ void fetch(BODY *body) #else void fetch(body) BODY *body; #endif { char buffer[FILEBUFFLEN]; char *tmp = NULL; char *site = NULL; char *directory = NULL; char *mode = NULL; char *name = NULL; Remote_Auth *remote_auth = NULL; struct stat sb; char user[FILEBUFFLEN]; char pass[FILEBUFFLEN]; char *filename = NULL; char *pw = NULL; Boolean anon = FALSE; Boolean failed = FALSE; Boolean append_it; if(body == NULL) return; if((tmp = find_param(body,"access-type")) == NULL) return; if((strcasecmp(tmp,"anon-ftp")) == 0) anon = TRUE; else { if((strcasecmp(tmp,"ftp")) == 0) anon = FALSE; else return; } mm_log(MLGetLocalized(XtNmsgFetchFTP,MsgFetchFTP), NIL); tmp = find_param(body,"name"); if(tmp) name = cpystr(tmp); filename = file_select(session->shell, NULL, NULL, name, TRUE, &append_it); if(filename == NULL) return; tmp = find_param(body,"site"); if(tmp) site = cpystr(tmp); tmp = find_param(body,"directory"); if(tmp) directory = cpystr(tmp); tmp = find_param(body,"mode"); if(tmp) mode = cpystr(tmp); if(anon) { strcpy(user,"anonymous"); sprintf(pass,"%s@%s",local_auth.username,local_auth.hostname); } else { remote_auth = login(session->shell, NULL, "FTP", "FTP", local_auth.username, NULL); if((remote_auth == NULL) || (remote_auth->username == NULL) || (remote_auth->password == NULL)) { free_remote_auth(remote_auth); failed = TRUE; } else { strcpy(user,remote_auth->username); pw = scramble(remote_auth->password); strcpy(pass,pw); wipeout(pw); } } if(!failed) if(do_ftp(site,user,pass,directory,mode,name,filename)) failed = TRUE; if(failed) mm_log(MLGetLocalized(XtNmsgFTPFail,MsgFTPFail), WARN); else { if(stat(filename,&sb)) mm_log(MLGetLocalized(XtNmsgFTPFail,MsgFTPFail), WARN); else { sprintf(buffer,MLGetLocalized(XtNmsgFTPSaved,MsgFTPSaved), sb.st_size,filename); mm_log(buffer,NIL); } } if(site) fs_give((void **) &site); if(directory) fs_give((void **) &directory); if(mode) fs_give((void **) &mode); if(name) fs_give((void **) &name); if(remote_auth) free_remote_auth(remote_auth); if(pw) fs_give((void **) &pw); free(filename); return; }