/*****************************************************************************/ /* attach.c Message part/attachment and it's associated file management. ATTACHMENTS ----------- soyMAIL manages attachments as files stored in the VMS Mail directory. The names of these files allow them to be distinguished from VMS Mail and other other soyMAIL files. They are named 'SOYMAIL_yyyymmddhhmmsshhnn.ATT' where 'yyyymmddhhmmsshh' are the numeric time components, and 'nn' a two-digit file count. UPLOAD ------ The message compose page allows files stored on the user's browser system to be uploaded to the system running soyMAIL. These files are listed on the compose page and can be marked (checkboxed) for inclusion as MIME parts in the sent message. SAVE ---- The message read page allows the MIME parts (usually attachments) of a received message to be marked (checkboxed) and saved in the VMS Mail directory for inclusion at the message composition page. An attachment file has an internal structure. The actual attachment content is preceded by three fields with null-terminated strings explicitly providing the soyMAIL version that saved them, the name and MIME content-type of the content. [soyMAIL-version]version\0 [name]the name of the attachment.gif\0 [type]image/gif\0 [content] EXTRACT ------- MIME parts (including attachments) may also be extracted as files to the user home directory. The extracted file name is always prefixed by "SOYMAIL-" to hopefully avoid clashes with any other files in this area. The rest of the name is derived from the part name. For implementation simplicity the generated file name is constrained to ODS-2 compliance. Textual files have a stream-LF format. Any others have a fixed 512 byte format. Extracted message parts where file names have no file type have an extension guessed by examining the MIME content-type; text/plain become .TXT, text/html become .HTML, and message/rfc822 become .LIS (message listings). COPYRIGHT --------- Copyright (C) 2005-2024 Mark G.Daniel This program, comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under the conditions of the GNU GENERAL PUBLIC LICENSE, version 3, or any later version. VERSION HISTORY --------------- 02-SEP-2010 MGD bugfix; AttachComposeList() uninitialised pointer if attachment file did not produce a content-type 13-JUL-2008 MGD AttachComposeList() accomodate [attachment-MIME-types] 22-MAR-2007 MGD AttachComposeList() JavaScript-enable the [attach] button AttachUpload() massage MSIE filenames with full path 10-FEB-2007 MGD Purveyor support removed 21-OCT-2006 MGD AttachComposeList() checkbox to 'delete all attachments' after a successful message send 27-JUN-2006 MGD AttachFileExtract() to extract message parts as user-accessable files into the user home directory 25-MAR-2006 MGD AttachView() small accomodation for Purveyor 15-MAR-2006 MGD AttachFile() ensure any file system path is stripped from the attachment name before saving (MSIE) 05-FEB-2006 MGD support saving message as "message/rfc822" 'attachment' bugfix; AttachComposeList() attachment size calculation 01-FEB-2005 MGD initial */ /*****************************************************************************/ #ifdef SOYMAIL_VMS_V7 #undef _VMS_V6_SOURCE #define _VMS_V6_SOURCE #undef __VMS_VER #define __VMS_VER 70000000 #undef __CRTL_VER #define __CRTL_VER 70000000 #endif #pragma nomember_alignment /* standard C header files */ #include #include #include #include #include #include #include /* VMS related header files */ #include #include #include #include #include /* application header file */ #include "soymail.h" #include "config.h" #include "attach.h" #include "message.h" #define FI_LI "ATTACH", __LINE__ /* global storage */ /* external storage */ extern BOOL Debug, WatchEnabled; extern char SoftwareVn[]; extern CONFIG_DATA SoyMailConfig; extern VMS_MAIL_USER VmsMailUser; /* prototypes */ int sys$fao (__unknown_params); int sys$getuai (__unknown_params); int sys$numtim (__unknown_params); /*****************************************************************************/ /* Assemble the request data related to attachment management. Return true to indicate that there was attachment management undertaken and that the calling function should do no more, false to indicate none and the calling function should display a page. */ BOOL AttachComposeRequest (REQUEST_DATA *rdptr) { int cnt, AttachFileCount; char *cptr, *sptr; char AttachmentName [256], ButtonName [64]; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "AttachComposeRequest()\n"); CGIVARNULL (cptr, "FORM_ATTACH_BTN"); if (!cptr) CGIVARNULL (cptr, "FORM_ATTACH_DELETE_BTN"); if (cptr) { /**********************/ /* attachment buttons */ /**********************/ if (LangSame ("attach_file", cptr)) AttachUpload (rdptr); else if (LangSame ("attach_delete", cptr)) AttachDelete (rdptr, NULL); else ErrorExit (SS$_BUGCHECK, FI_LI); return (FALSE); } else { /**************************/ /* individual attachments */ /**************************/ CGIVARNULL (cptr, "FORM_ATTACH_COUNT"); if (cptr) AttachFileCount = atoi(cptr); else AttachFileCount = 0; for (cnt = 1; cnt <= AttachFileCount+5; cnt++) { sprintf (ButtonName, "FORM_ATTACH_DELETE_%d", cnt); CGIVARNULL (cptr, ButtonName); if (cptr) { sprintf (AttachmentName, "FORM_ATTACH_ID_%d", cnt); CGIVARNULL (sptr, AttachmentName); if (!sptr) ErrorExit (SS$_BUGCHECK, FI_LI); AttachDelete (rdptr, sptr); return (FALSE); } sprintf (ButtonName, "FORM_ATTACH_VIEW_%d", cnt); CGIVARNULL (cptr, ButtonName); if (cptr) { sprintf (AttachmentName, "FORM_ATTACH_ID_%d", cnt); CGIVARNULL (sptr, AttachmentName); if (!sptr) ErrorExit (SS$_BUGCHECK, FI_LI); /* NOTE: if successful this returns from here! */ if (AttachView (rdptr, sptr)) return (TRUE); return (FALSE); } } } return (FALSE); } /*****************************************************************************/ /* Generate a list of attachments available for sending from the message composition page. */ void AttachComposeList (REQUEST_DATA *rdptr) { int cnt, status, AttachFileCount, AttachFileTotal, ButtonWidth, FileSizeBytes; char *cptr, *fnptr, *sptr, *zptr, *CheckedPtr, *ContentTypePtr, *NamePtr; char AttId [64], BlockBuffer [512], CbxName [256]; stat_t FstatBuffer; FILE *fp; VMS_MAIL_USER *muptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "AttachComposeList()\n"); muptr = &VmsMailUser; if (!muptr->VmsMailFullDirectoryLength) ErrorExit (SS$_BUGCHECK, FI_LI); cptr = LangFor("compose_button_width"); ButtonWidth = atoi(cptr); if (ButtonWidth <= 0 || ButtonWidth > 10) ButtonWidth = 5; fprintf (stdout, "\n\

\ \ \n"); /* first up count the number of attachment files */ AttachFileTotal = 0; cptr = FileSpecSearch (1, muptr->VmsMailFullDirectory, "SOYMAIL_*.ATT"); while (cptr) { AttachFileTotal++; cptr = FileSpecSearch (1, NULL, NULL); } /* list each one of those attachment files */ AttachFileCount = 0; fnptr = FileSpecSearch (FALSE, muptr->VmsMailFullDirectory, "SOYMAIL_*.ATT"); while (fnptr) { AttachFileCount++; /* get the digits from the file name */ for (cptr = fnptr; *cptr; cptr++); while (cptr > fnptr && *cptr != ']') cptr--; if (*cptr == ']') cptr++; while (*cptr && !isdigit(*cptr)) cptr++; zptr = (sptr = AttId) + sizeof(AttId); while (isdigit(*cptr) && sptr < zptr) *sptr++ = *cptr++; if (sptr > zptr) ErrorExit (SS$_BUGCHECK, FI_LI); *sptr = '\0'; NamePtr = ContentTypePtr = ""; fp = fopen (fnptr, "r", "ctx=bin"); if (!fp) { status = vaxc$errno; sprintf (BlockBuffer, "%s: %s", fnptr, SysGetMsg(status)); cptr = BlockBuffer; } else { if (fstat (fileno(fp), &FstatBuffer) != 0) FstatBuffer.st_size = -1; FileSizeBytes = FstatBuffer.st_size; memset (BlockBuffer, 0, sizeof(BlockBuffer)); fread (BlockBuffer, sizeof(BlockBuffer), 1, fp); fclose (fp); if (!strncmp (BlockBuffer, "[soyMAIL-version]", 17)) { cptr = BlockBuffer + 17; while (*cptr) cptr++; cptr++; if (!strncmp (cptr, "[name]", 6)) { cptr += 6; NamePtr = cptr; /* adjust for the number of bytes in the internal data */ while (*cptr) cptr++; cptr++; if (!strncmp (cptr, "[type]", 6)) { cptr += 6; ContentTypePtr = cptr; while (*cptr) cptr++; cptr++; if (!strncmp (cptr, "[content]", 9)) { cptr += 9; FileSizeBytes -= cptr - BlockBuffer; } else NamePtr = "***ERROR4***"; } else NamePtr = "***ERROR3***"; } else NamePtr = "***ERROR2***"; } else NamePtr = "***ERROR1***"; } /* a 'delete all' button on the first line of the attachment listing */ if (AttachFileCount == 1) { CGIVARNULL (CheckedPtr, "FORM_ATTACH_CHK_DELETE"); if (CheckedPtr) { CGIVARNULL (CheckedPtr, "FORM_ATTACH_CBX_DELETE"); if (CheckedPtr) CheckedPtr = " checked"; } else CheckedPtr = " checked"; if (!CheckedPtr) CheckedPtr = ""; fprintf (stdout, "\n", AttachFileCount, rdptr->FormAction, rdptr->FormAction[strlen(rdptr->FormAction)-1] == '/' ? "" : "/", HTML_ESCAPE(NamePtr), AttId, AttachFileCount, AttachFileCount, AttId, CheckedPtr, HTML_ESCAPE(NamePtr), FileSizeBytes > 1024 ? FileSizeBytes / 1024: FileSizeBytes, FileSizeBytes > 1024 ? "kB": "bytes"); fnptr = FileSpecSearch (FALSE, NULL, NULL); } /* attachment file upload button */ fprintf (stdout, "\n\
\   \ \ \ ", ButtonWidth, LangFor("attach_delete"), CheckedPtr); } else fprintf (stdout, "
"); /* check if the attachment checkbox is currently checked */ sprintf (CbxName, "FORM_PART_CBX_%s", AttId); CGIVARNULL (CheckedPtr, CbxName); if (CheckedPtr) CheckedPtr = " checked"; if (!CheckedPtr && !strcmp (rdptr->AttachmentName, NamePtr)) CheckedPtr = " checked"; if (!CheckedPtr) CheckedPtr = ""; if (cptr = SoyMailConfig.AttachmentMimeTypes) { /********************/ /* separate window? */ /********************/ while (*cptr) { sptr = ContentTypePtr; if (WatchEnabled) WatchThis ("MIME \"!AZ\" !AZ", sptr, cptr); while (*cptr && !isspace(*cptr) && *sptr && tolower(*cptr) == tolower(*sptr)) { cptr++; sptr++; } if (!*cptr || isspace(*cptr)) { cptr = NULL; break; } while (*cptr && !isspace(*cptr)) cptr++; while (*cptr && isspace(*cptr)) cptr++; } } /* cptr is non-NULL to indicate hit */ if (!cptr) { /******/ /* no */ /******/ /* default or internally handled type */ fprintf (stdout, "\  \ ", AttachFileCount, AttId, AttachFileCount, LangFor("delete"), AttachFileCount, LangFor("view")); } else { /*******/ /* yes */ /*******/ /* not an internally handled type (with JavaScript) */ fprintf (stdout, "\  \ ", AttachFileCount, AttId, AttachFileCount, LangFor("delete"), AttachFileCount, rdptr->FormAction, rdptr->FormAction[strlen(rdptr->FormAction)-1] == '/' ? "" : "/", HTML_ESCAPE(NamePtr), AttId, AttachFileCount, LangFor("view"), AttachFileCount); } fprintf (stdout, "\   \ \  \ %s\   (%d %s)\
\ \ \
\ \ %s\
\
\n\ \n\ \n\n", ButtonWidth, LangFor("attach_file"), LangFor("select_file"), AttachFileCount); } /*****************************************************************************/ /* Upload an attachment content (from the message composition page). */ void AttachUpload (REQUEST_DATA *rdptr) { int DataLength; char *cptr, *sptr, *zptr, *DataPtr, *NamePtr, *TypePtr; VMS_MAIL_USER *muptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "AttachUpload()\n"); muptr = &VmsMailUser; if (!muptr->VmsMailFullDirectoryLength) ErrorExit (SS$_BUGCHECK, FI_LI); DataPtr = CgiLibVar ("FORM_ATTACH_FILE"); if (WatchEnabled) WatchThis ("CGIVAR !AZ=!AZ", "FORM_ATTACH_FILE", DataPtr); if (CgiLibBodyIsMultipartFormData()) DataLength = CgiLibHtmlDeEntify (DataPtr); else DataLength = strlen(DataPtr); CGIVAR (NamePtr, "FORM_ATTACH_FILE_MIME_FILENAME"); if (!(NamePtr && *NamePtr)) { StatusMessage (FI_LI, 1, "%s", LangFor("attach_none_supplied")); return; } /* MSIE (what else?) provides the file name with a full path */ for (cptr = NamePtr; *cptr; cptr++); while (cptr > NamePtr && *cptr != '/' && *cptr != '\\' && *cptr != ']') cptr--; if (*cptr == '/' || *cptr == '\\' || *cptr == ']') NamePtr = cptr + 1; CGIVAR (TypePtr, "FORM_ATTACH_FILE_MIME_CONTENT_TYPE"); if (!(TypePtr && *TypePtr)) TypePtr = "application/octet-stream"; /* if the attachment file was not successfully writen */ if (!AttachFileSave (NamePtr, TypePtr, DataPtr, DataLength)) return; /* save the file name for checkbox checking in the attachment list */ zptr = (sptr = rdptr->AttachmentName) + sizeof(rdptr->AttachmentName)-1; for (cptr = NamePtr; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; StatusMessage (FI_LI, 0, "%s "%s".", LangFor("attach_upload_done"), NamePtr); } /*****************************************************************************/ /* Save one or more attachment content (from the message read page). */ void AttachSave ( REQUEST_DATA *rdptr, BOOL ExtractFile ) { BOOL ok, AttachMessage = FALSE; int cnt, DataLength, FileCount, PartCount; char *cptr, *DataPtr, *NamePtr, *TypePtr; char AttachCbxName [32], PartTypeField [32]; $DESCRIPTOR (ReportDsc, ""); MIME_DATA *mdptr; USER_OPTIONS *uoptr; VMS_MAIL_MSG *vmptr; VMS_MAIL_USER *muptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "AttachSave() %d\n", ExtractFile); muptr = &VmsMailUser; if (!muptr->VmsMailFullDirectoryLength) ErrorExit (SS$_BUGCHECK, FI_LI); uoptr = &rdptr->UserOptions; muptr = &VmsMailUser; vmptr = &muptr->VmsMailMsg; vmptr->MessageId = rdptr->MessageId; CallMailMessageGet (muptr, vmptr); if (VMSnok (muptr->MailVmsStatus)) { StatusMessage (FI_LI, 1, "MAIL: %s.", SysGetMsg(muptr->MailVmsStatus)); return; } CGIVARNULL (cptr, "FORM_PART_CBX_0"); if (cptr) AttachMessage = TRUE; MessageBodyProcess (vmptr); FileCount = 0; if (AttachMessage) { /* the full message */ cptr = vmptr->Subject; if (!*cptr) cptr = LangFor("attach_message"); if (ExtractFile) cptr = AttachFileExtract (cptr, "message/rfc822", vmptr->BodyPtr, vmptr->BodyLength); else cptr = AttachFileSave (cptr, "message/rfc822", vmptr->BodyPtr, vmptr->BodyLength); if (!cptr) return; AttachReportFile (&ReportDsc, cptr); FileCount++; } CGIVAR (cptr, "FORM_PART_COUNT"); if (cptr) { PartCount = atoi(cptr); if (PartCount > 512) ErrorExit (SS$_BUGCHECK, FI_LI); } else PartCount = 0; for (mdptr = vmptr->MimeDataPtr; mdptr; mdptr = mdptr->NextMimePtr) { if (mdptr->ContentTypeIsMultipart) continue; for (cnt = 1; cnt <= PartCount; cnt++) { sprintf (AttachCbxName, "FORM_PART_CBX_%d", cnt); CGIVAR (cptr, AttachCbxName); if (!cptr) continue; if (mdptr->NamePtr) NamePtr = mdptr->NamePtr; else NamePtr = mdptr->PartName; if (!strsame (cptr, NamePtr, -1)) continue; /* use the content-type from the form */ sprintf (PartTypeField, "FORM_PART_TYPE_%d", cnt); CGIVARNULL (TypePtr, PartTypeField); if (!TypePtr || !*TypePtr) TypePtr = mdptr->ContentTypePtr; if (MimeDecProcessPart (mdptr)) { DataPtr = mdptr->PartContentPtr; DataLength = mdptr->PartContentLength; /* if writing the attachment file was not successful */ if (ExtractFile) cptr = AttachFileExtract (NamePtr, TypePtr, DataPtr, DataLength); else cptr = AttachFileSave (NamePtr, TypePtr, DataPtr, DataLength); if (!cptr) return; AttachReportFile (&ReportDsc, cptr); FileCount++; } break; } } if (FileCount) { if (ExtractFile) StatusMessage (FI_LI, 0, "%s %s.", LangFor("attach_extract_done"), HTML_ESCAPE(ReportDsc.dsc$a_pointer)); else StatusMessage (FI_LI, 0, "%s %s.", LangFor("attach_save_done"), HTML_ESCAPE(ReportDsc.dsc$a_pointer)); } else StatusMessage (FI_LI, 1, "%s", LangFor("attach_none_supplied")); } /*****************************************************************************/ /* Creat a null-terminated string described by the supplied descriptor containing a comma-separated list of part or file names. The string is dynamically built over successive calls to this function. */ int AttachReportFile ( struct dsc$descriptor_s *DscPtr, char *NamePtr ) { int len; char sbuf [1024]; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "AttachReportFile() |%s|\n", NamePtr); len = PrintFao (sbuf, sizeof(sbuf), "!AZ\"!AZ\"", DscPtr->dsc$w_length ? ", " : "", NamePtr); if (DscPtr->dsc$w_length) DscPtr->dsc$a_pointer = CgiLibVeeMemRealloc (DscPtr->dsc$a_pointer, DscPtr->dsc$w_length + len + 8); else DscPtr->dsc$a_pointer = CgiLibVeeMemCalloc (DscPtr->dsc$w_length + len + 8); strcpy (DscPtr->dsc$a_pointer + DscPtr->dsc$w_length, sbuf); DscPtr->dsc$w_length += len; return (DscPtr->dsc$w_length); } /*****************************************************************************/ /* Write an attachment to the file system. Return pointer if the write was ok, NULL otherwise. */ char* AttachFileSave ( char *NamePtr, char *TypePtr, char *DataPtr, int DataLength ) { static $DESCRIPTOR (AttIdFaoDsc, "!4ZL!2ZL!2ZL!2ZL!2ZL!2ZL!2ZL!2ZL\0"); static $DESCRIPTOR (FileNameFaoDsc, "SOYMAIL_!AZ.ATT\0"); static int FileCount; static unsigned short NumTime [7]; int retval, status; char *cptr, *fnptr; char AttId [20], FileName [64]; FILE *fp; VMS_MAIL_USER *muptr; $DESCRIPTOR (AttIdDsc, AttId); $DESCRIPTOR (FileNameDsc, FileName); /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "AttachFileSave()\n"); muptr = &VmsMailUser; if (!muptr->VmsMailFullDirectoryLength) ErrorExit (SS$_BUGCHECK, FI_LI); /* ensure any file system path is stripped from the file name (MSIE) */ NamePtr = AttachStripPath (NamePtr); /* only generate the one time-stamp per request */ if (!NumTime[0]) sys$numtim (&NumTime, 0); /* two digits */ if (FileCount++ > 99) ErrorExit (SS$_BUGCHECK, FI_LI); /* create an attachment ID based on the time and counter */ sys$fao (&AttIdFaoDsc, NULL, &AttIdDsc, NumTime[0], NumTime[1], NumTime[2], NumTime[3], NumTime[4], NumTime[5], NumTime[6], FileCount); sys$fao (&FileNameFaoDsc, NULL, &FileNameDsc, AttId); fnptr = CallMailFileIn (muptr, NULL, FileName); if (WatchEnabled) WatchThis ("ATTACHMENT !AZ", fnptr); fp = fopen (fnptr, "w", "ctx=bin", "rfm=udf", "rat=none"); if (!fp) { status = vaxc$errno; StatusMessage (FI_LI, 1, "%s: %s.", NamePtr, SysGetMsg(status)); return (NULL); } retval = write (fileno(fp), "[soyMAIL-version]", 17); /* include the terminating null */ if (retval > 0) retval = write (fileno(fp), SoftwareVn, strlen(SoftwareVn)+1); if (retval > 0) retval = write (fileno(fp), "[name]", 6); /* include the terminating null */ if (retval > 0) retval = write (fileno(fp), NamePtr, strlen(NamePtr)+1); if (retval > 0) retval = write (fileno(fp), "[type]", 6); /* include the terminating null */ if (retval > 0) retval = write (fileno(fp), TypePtr, strlen(TypePtr)+1); if (retval > 0) retval = write (fileno(fp), "[content]", 9); if (retval > 0) retval = write (fileno(fp), DataPtr, DataLength); fclose (fp); if (retval <= 0) { status = vaxc$errno; StatusMessage (FI_LI, 1, "%s: %s.", NamePtr, SysGetMsg(status)); /* remove remnants of failed write */ while (!remove (fnptr)); return (NULL); } /* purge the versions back to a maximum of 1 */ strcat (fnptr, ";-1"); while (!remove (fnptr)); return (NamePtr); } /*****************************************************************************/ /* Extract and write an attachment as a file to the user home directory. Return pointer if the write was ok, NULL otherwise. The extracted file name is always prefixed by "SOYMAIL-" to hopefully avoid clashes with any other files in this area. The rest of the name is derived from the part name. For simplicity the generated file name is constrained to ODS-2 compliance. Textual files have a stream-LF format. Any others have an fixed 512 byte format. */ char* AttachFileExtract ( char *NamePtr, char *TypePtr, char *DataPtr, int DataLength ) { static unsigned long UaiContext = -1; static char FileName [256+8], UaiDefDev [1+31+1], UaiDefDir [1+63+1]; static VMS_ITEM_LIST3 UaiItems [] = { { sizeof(UaiDefDev)-1, UAI$_DEFDEV, &UaiDefDev, 0 }, { sizeof(UaiDefDir)-1, UAI$_DEFDIR, &UaiDefDir, 0 }, { 0,0,0,0 } }; BOOL TypePeriod; int retval, status; char *cptr, *fnptr, *sptr, *zptr; FILE *fp; VMS_MAIL_USER *muptr; $DESCRIPTOR (FileNameDsc, FileName); $DESCRIPTOR (UserNameDsc, ""); /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "AttachFileExtract()\n"); muptr = &VmsMailUser; if (!muptr->VmsMailFullDirectoryLength) ErrorExit (SS$_BUGCHECK, FI_LI); UserNameDsc.dsc$a_pointer = muptr->UserName; UserNameDsc.dsc$w_length = muptr->UserNameLength; status = sys$getuai (0, &UaiContext, &UserNameDsc, &UaiItems, 0, 0, 0); if (Debug) fprintf (stdout, "sys$getuai() %%X%08.08X\n", status); if (VMSnok (status)) { StatusMessage (FI_LI, 1, "GETUAI: %s.", SysGetMsg(status)); return (NULL); } UaiDefDev[UaiDefDev[0]+1] = '\0'; UaiDefDir[UaiDefDir[0]+1] = '\0'; /* ensure any file system path is stripped from the file name (MSIE) */ NamePtr = AttachStripPath (NamePtr); /* create a file name for in the user's home directory */ TypePeriod = FALSE; zptr = (sptr = FileName) + sizeof(FileName)-8; for (cptr = UaiDefDev+1; *cptr && sptr < zptr; *sptr++ = *cptr++); for (cptr = UaiDefDir+1; *cptr && sptr < zptr; *sptr++ = *cptr++); /* keep the name ODS-2 compliant for now */ fnptr = sptr; /* hyphen so it cannot collide with soyMAIL's own files! */ for (cptr = "SOYMAIL-"; *cptr && sptr < zptr; *sptr++ = *cptr++); cptr = NamePtr; while (*cptr && *cptr != '.' && sptr < zptr && sptr - fnptr < 39) { if (isalnum(*cptr)) *sptr++ = toupper(*cptr); else *sptr++ = '_'; cptr++; } if (*cptr == '.') { /* hit a (hopefully) type delimitting period */ TypePeriod = TRUE; if (sptr < zptr && sptr - fnptr < 39) *sptr++ = *cptr++; while (*cptr && sptr < zptr && sptr - fnptr < 39) { if (isalnum(*cptr)) *sptr++ = toupper(*cptr); else *sptr++ = '_'; cptr++; } } if (*cptr) { /* not end of name yet, probe for the file type */ while (*cptr && *cptr != '.') cptr++; if (*cptr) { TypePeriod = TRUE; fnptr = sptr; if (sptr < zptr) *sptr++ = *cptr++; while (*cptr && sptr < zptr && sptr - fnptr < 39) { if (isalnum(*cptr)) *sptr++ = toupper(*cptr); else *sptr++ = '_'; cptr++; } } } *sptr = '\0'; if (!TypePeriod) { /* no file-type, have a guess using the content-type */ if (strsame (TypePtr, "text/plain", -1)) cptr = ".TXT"; else if (strsame (TypePtr, "text/html", -1)) cptr = ".HTML"; else if (strsame (TypePtr, "message/rfc822", -1)) cptr = ".LIS"; else cptr = ""; while (*cptr && sptr < zptr) *sptr++ = *cptr++; *sptr = '\0'; } fnptr = FileName; if (WatchEnabled) WatchThis ("EXTRACT !AZ", fnptr); if (strsame (TypePtr, "text/", 5) || strsame (TypePtr, "message/", 8)) fp = fopen (fnptr, "w", "ctx=bin", "rfm=stmlf"); else fp = fopen (fnptr, "w", "ctx=bin", "rfm=fix", "mrs=512", "rat=none"); if (!fp) { status = vaxc$errno; StatusMessage (FI_LI, 1, "%s: %s.", NamePtr, SysGetMsg(status)); return (NULL); } retval = write (fileno(fp), DataPtr, DataLength); fclose (fp); if (retval <= 0) { status = vaxc$errno; StatusMessage (FI_LI, 1, "%s: %s.", NamePtr, SysGetMsg(status)); /* remove remnants of failed write */ strcat (fnptr, ";0"); remove (fnptr); return (NULL); } /* return at the point the file name begins */ cptr = FileName + UaiDefDev[0] + UaiDefDir[0]; return (cptr); } /*****************************************************************************/ /* Ensure any file system path is stripped from the file name (MSIE). */ char* AttachStripPath (char *NamePtr) { char *cptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "AttachStripPath() %s\n", NamePtr); if (*NamePtr != '/' && *NamePtr != '\\' && memcmp(NamePtr, "./", 2) && memcmp(NamePtr, "../", 3) && !(isalpha(NamePtr[0]) && NamePtr[1] == ':')) return (NamePtr); for (cptr = NamePtr; *cptr && *cptr != '/'; cptr++); if (*cptr) { while (*cptr) cptr++; while (cptr > NamePtr && *cptr != '/') cptr--; if (*cptr == '/') cptr++; NamePtr = cptr; } else { for (cptr = NamePtr; *cptr && *cptr != '\\'; cptr++); if (*cptr) { while (*cptr) cptr++; while (cptr > NamePtr && *cptr != '\\') cptr--; if (*cptr == '\\') cptr++; NamePtr = cptr; } } return (NamePtr); } /*****************************************************************************/ /* Delete attachment files, a single one or all. If 'FileName' is NULL delete all attachment files. */ void AttachDelete ( REQUEST_DATA *rdptr, char *AttId ) { int status; char *cptr, *fnptr, *sptr, *zptr; char FileName [64]; VMS_MAIL_USER *muptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "AttachDelete() %s\n", AttId); muptr = &VmsMailUser; if (!muptr->VmsMailFullDirectoryLength) ErrorExit (SS$_BUGCHECK, FI_LI); if (AttId) { zptr = (sptr = FileName) + sizeof(FileName)-1; for (cptr = "SOYMAIL_"; *cptr && sptr < zptr; *sptr++ = *cptr++); for (cptr = AttId; *cptr && sptr < zptr; *sptr++ = *cptr++); for (cptr = ".ATT"; *cptr && sptr < zptr; *sptr++ = *cptr++); *sptr = '\0'; fnptr = CallMailFileIn (muptr, NULL, FileName); if (WatchEnabled) WatchThis ("ATTACHMENT DELETE !AZ", fnptr); if (remove (fnptr)) { status = vaxc$errno; StatusMessage (FI_LI, 1, "%s: %s.", AttId, SysGetMsg(status)); return; } /* remove all versions */ while (!remove (fnptr)); StatusMessage (FI_LI, 0, "%s.", LangFor("attach_delete_done")); } else { cptr = FileSpecSearch (1, muptr->VmsMailFullDirectory, "SOYMAIL_*.ATT"); while (cptr) { if (WatchEnabled) WatchThis ("ATTACHMENT DELETE !AZ", fnptr); fnptr = CallMailFileIn (muptr, NULL, cptr); if (remove (fnptr)) { status = vaxc$errno; StatusMessage (FI_LI, 1, "%s: %s.", fnptr, SysGetMsg(status)); return; } /* remove all versions */ while (!remove (fnptr)); cptr = FileSpecSearch (1, NULL, NULL); } StatusMessage (FI_LI, 0, "%s.", LangFor("attach_delete_all")); } } /*****************************************************************************/ /* The message composition page contains buttons for viewing attachment content. */ BOOL AttachView ( REQUEST_DATA *rdptr, char *AttId ) { static $DESCRIPTOR (FileNameFaoDsc, "SOYMAIL_!AZ.ATT\0"); int status, ContentLength, DataLength, FileSizeBytes; char *cptr, *fnptr, *czptr, *ContentTypePtr, *DataPtr; char DataBuffer [4096], FileName [64]; stat_t FstatBuffer; FILE *fp; VMS_MAIL_USER *muptr; $DESCRIPTOR (FileNameDsc, FileName); /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "AttachView() %s\n", FileName); muptr = &VmsMailUser; if (!muptr->VmsMailFullDirectoryLength) ErrorExit (SS$_BUGCHECK, FI_LI); sys$fao (&FileNameFaoDsc, NULL, &FileNameDsc, AttId); fnptr = CallMailFileIn (muptr, NULL, FileName); if (WatchEnabled) WatchThis ("ATTACHMENT !AZ", fnptr); fp = fopen (fnptr, "r", "ctx=bin"); if (!fp) { status = vaxc$errno; StatusMessage (FI_LI, 1, "%s: %s.", AttId, SysGetMsg(status)); if (strcmp (rdptr->CgiRequestMethodPtr, "GET")) return (FALSE); CgiLibResponseError (FI_LI, status, AttId); return (TRUE); } else { if (fstat (fileno(fp), &FstatBuffer)) FstatBuffer.st_size = -1; FileSizeBytes = FstatBuffer.st_size; DataLength = read (fileno(fp), DataBuffer, sizeof(DataBuffer)); } czptr = (cptr = DataBuffer) + DataLength; if (strncmp (cptr, "[soyMAIL-version]", 17)) { StatusMessage (FI_LI, 1, "%s: %s.", AttId, SysGetMsg(SS$_BUGCHECK)); if (strcmp (rdptr->CgiRequestMethodPtr, "GET")) return (FALSE); CgiLibResponseError (FI_LI, SS$_BUGCHECK, AttId); return (TRUE); } cptr += 17; while (*cptr && cptr < czptr) cptr++; if (cptr < czptr) cptr++; if (strncmp (cptr, "[name]", 6)) { StatusMessage (FI_LI, 1, "%s: %s.", AttId, SysGetMsg(SS$_BUGCHECK)); if (strcmp (rdptr->CgiRequestMethodPtr, "GET")) return (FALSE); CgiLibResponseError (FI_LI, SS$_BUGCHECK, AttId); return (TRUE); } cptr += 6; while (*cptr && cptr < czptr) cptr++; if (cptr < czptr) cptr++; if (strncmp (cptr, "[type]", 6)) { StatusMessage (FI_LI, 1, "%s: %s.", AttId, SysGetMsg(SS$_BUGCHECK)); if (strcmp (rdptr->CgiRequestMethodPtr, "GET")) return (FALSE); CgiLibResponseError (FI_LI, SS$_BUGCHECK, AttId); return (TRUE); } cptr += 6; /* now should be pointing at the content type */ ContentTypePtr = cptr; /* skip over the type */ while (*cptr && cptr < czptr) cptr++; if (cptr < czptr) cptr++; if (strncmp (cptr, "[content]", 9)) { StatusMessage (FI_LI, 1, "%s: %s.", AttId, SysGetMsg(SS$_BUGCHECK)); if (strcmp (rdptr->CgiRequestMethodPtr, "GET")) return (FALSE); CgiLibResponseError (FI_LI, SS$_BUGCHECK, AttId); return (TRUE); } /* now should be pointing at the content itself (finally) */ cptr += 9; ContentLength = FileSizeBytes - (cptr - DataBuffer); /* what remains in the data buffer */ DataLength -= cptr - DataBuffer; CgiLibResponseHeader (200, ContentTypePtr, "Content-Length: %d\n%s", ContentLength, rdptr->LoginSetCookiePtr); while (DataLength) { fwrite (cptr, DataLength, 1, stdout); DataLength = read (fileno(fp), cptr = DataBuffer, sizeof(DataBuffer)); } fclose (fp); return (TRUE); } /*****************************************************************************/