/*****************************************************************************/ /* MimeDec.c MIME decode functions. COPYRIGHT --------- Copyright (C) 2005-2023 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 --------------- 03-MAR-2023 MGD bugfix; MimeDecIsoString() copy intermediate chars 13-JAN-2023 MGD MimeDecParseBody() allow final multipart to be terminated by the multipart boundary (a MIME error for sure but ...) 04-NOV-2016 MGD MimeDecIsoString() ensure it's complete or ignored! 10-JAN-2015 MGD MimeDecParseBody() relax closing boundary 23-SEP-2008 MGD MimeDecBase64() relax (ignore) accepted characters outside of encoding sequence 23-AUG-2007 MGD MimeDecParseBody() report premature end-of-message 07-MAR-2007 MGD MimeDecParseBody() better processing of multi-multiparts 03-JAN-2007 MGD bugfix; MimeDecParseBody() add MimeDecProcessPart() to message/rfc822 processing to cope with content encoding 24-JUN-2006 MGD MimeDecManufac..() functions to support new part processing 17-MAR-2006 MGD MimeDec8bit() disable reporting and always return OK 08-MAR-2006 MGD MimeDecParseBody() return FALSE on detected parse issue MimeDecParseError() to report MIME parse problems 15-FEB-2006 MGD bugfix; MimeDecProcessPart() sanity check on pointer not length (after all you can have empty content) 12-FEB-2006 MGD bugfix; MimeDecParseBody() again 08-FEB-2006 MGD change default transfer encoding from 7bit to 8bit (allows sloppy/non-compliant agents a bit more latitude) 04-FEB-2006 MGD refine MimeDecParseBody() to accomodate "message/rfc822" attachments and any such internal MIME parts 15-APR-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 /* VMS related header files */ #include #include /* application header file */ #include #include "cgilib.h" #include #define FI_LI __FILE__, __LINE__ /* global storage */ #define XX 255 #define CHAR64(c) (MimeDecBase64Table[c&0x7f]) static unsigned char MimeDecBase64Table [] = { XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,62, XX,XX,XX,63, 52,53,54,55, 56,57,58,59, 60,61,XX,XX, XX,XX,XX,XX, XX, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, 15,16,17,18, 19,20,21,22, 23,24,25,XX, XX,XX,XX,XX, XX,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40, 41,42,43,44, 45,46,47,48, 49,50,51,XX, XX,XX,XX,XX }; #define CHAR16(c) (MimeDecHexCharTable[c&0x7f]) char MimeDecHexCharTable [] = { XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,XX,XX, XX,XX,XX,XX, XX,10,11,12, 13,14,15,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,10,11,12, 13,14,15,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, }; /* prototypes */ /* external storage */ extern BOOL Debug, WatchEnabled; extern int VmsVersion; extern char SoftwareId[], SoftwareVn[]; extern VMS_MAIL_USER VmsMailUser; /*****************************************************************************/ /* Parse the entire message body for MIME parts. Create a MIME_DATA structure for each found linked-to from each of the previous MIME parts. Populate the MIME data provided with each of those parts. Do not decode the part as yet - except in the case of "message/rfc822". */ BOOL MimeDecParseBody (MIME_DATA *mdptr) { #define MIME_DATA_STACK_MAX 16 BOOL EndMultiPart; int bylen, by0len, idx, len, mdidx, BoundaryIndex, PartCount, PartLength; char *cptr, *byptr, *by0ptr, *sptr, *MessageRfc822Ptr; char FieldBuffer [4096]; MIME_DATA *MimeDataPtr; MIME_DATA MessageRfc822; MIME_DATA *MimeDataStack [MIME_DATA_STACK_MAX]; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "MimeDecParseBody()\n"); /* only should do it the once */ if (mdptr->MessageParsed) return (TRUE); mdptr->MessageParsed = TRUE; if (!mdptr->BoundaryPtr) { /*****************/ /* discrete MIME */ /*****************/ if (!mdptr->ContentTypeIs) { mdptr->ContentTypeIs = MIME_CONTENT_TEXT_PLAIN; mdptr->ContentTypePtr = "text/plain"; } if (!mdptr->TransferEncodingIs) { /* default to 8 bit */ mdptr->TransferEncodingIs = MIME_TRANSENC_8BIT; mdptr->TransferEncodingPtr = "8bit"; } mdptr->PartCount = 1; sprintf (mdptr->PartName, "%s_1", LangFor("mime_part")); return (TRUE); } /******************/ /* multipart MIME */ /******************/ mdidx = PartCount = 0; /* of course the zeroeth element is the primary, body MIME part */ MimeDataStack[mdidx] = MimeDataPtr = mdptr; byptr = by0ptr = mdptr->BoundaryPtr; bylen = by0len = mdptr->BoundaryLength; cptr = mdptr->PartContentPtr; while (*cptr) { /************************/ /* looking for boundary */ /************************/ if (!memcmp (cptr, byptr, bylen)) { /* matches boundary string */ if (WatchEnabled) WatchThis ("BOUNDARY !AZ", byptr); PartLength = cptr - mdptr->PartContentPtr; if (cptr[bylen] == '\n') { cptr += bylen + 1; EndMultiPart = FALSE; } else if (cptr[bylen] == '\r' && cptr[bylen+1] == '\n') { cptr += bylen + 2; EndMultiPart = FALSE; } else if (cptr[bylen] == '-' && cptr[bylen+1] == '-' && cptr[bylen+2] == '\n') { /* end of this MIME part (could be whole MIME message) */ cptr += bylen + 3; EndMultiPart = TRUE; } else if (cptr[bylen] == '-' && cptr[bylen+1] == '-' && cptr[bylen+2] == '\r' && cptr[bylen+3] == '\n') { /* end of this MIME part (could be whole MIME message) */ cptr += bylen + 4; EndMultiPart = TRUE; } else { /* neither a part or an end boundary, keep looking */ mdptr->PartContentLength = 0; while (*cptr && *cptr != '\n') cptr++; if (*cptr) cptr++; continue; } } else if (!memcmp (cptr, by0ptr, by0len)) { /* matches multipart end boundary string */ if (WatchEnabled) WatchThis ("BOUNDARY0 !AZ", by0ptr); PartLength = cptr - mdptr->PartContentPtr; EndMultiPart = TRUE; } else { /* keep looking */ while (*cptr && *cptr != '\n') cptr++; if (*cptr) cptr++; continue; } if (!mdptr->PartContentLength) { mdptr->PartContentLength = PartLength; if (WatchEnabled) WatchThis ("MIME !8XL !UL mdidx:!UL type:!UL enc:!UL !UL bytes", mdptr, mdptr->PartCount, mdidx, mdptr->ContentTypeIs, mdptr->TransferEncodingIs, mdptr->PartContentLength); } if (WatchEnabled) if (EndMultiPart) WatchThis ("MULTIPART end"); if (EndMultiPart) { if (--mdidx < 0) break; mdptr = MimeDataStack[mdidx]; byptr = mdptr->BoundaryPtr; bylen = mdptr->BoundaryLength; continue; } while (*cptr) { /**************************/ /* start MIME part header */ /**************************/ MimeDataPtr->NextMimePtr = (MIME_DATA*) CgiLibVeeMemCalloc (sizeof(MIME_DATA)); if (!MimeDataPtr->NextMimePtr) ErrorExit (vaxc$errno, FI_LI); mdptr = MimeDataPtr->NextMimePtr; MimeDataPtr = mdptr; if (WatchEnabled) WatchThis ("MIME !8XL", mdptr); while (*cptr) { /**************************/ /* parse MIME part header */ /**************************/ len = InetMailParseField (cptr); if (len == -1) { MimeDecParseError (MimeDataStack[0], cptr, FI_LI); return (FALSE); } /* note the start of this line */ sptr = cptr; /* adjust to the beginning of the next line */ cptr += len; /* get any field value from the current line */ len = InetMailGetField (sptr, len, FieldBuffer, sizeof(FieldBuffer)); /* if the get function could not make sense of the text */ if (len == -1) { MimeDecParseError (MimeDataStack[0], sptr, FI_LI); return (FALSE); } /* get the MIME field value */ InetMailParseMimeHeader (mdptr, sptr, FieldBuffer); /* break if it looks like the end-of-MIME-header empty line */ if (*cptr == '\n') { cptr++; mdptr->PartContentPtr = cptr; break; } if (*(USHORTPTR)cptr == '\r\n') { cptr += 2; mdptr->PartContentPtr = cptr; break; } } if (!*cptr) { /* should have run out of message before closing boundary */ MimeDecParseError (MimeDataStack[0], cptr, FI_LI); return (FALSE); } if (!mdptr->ContentTypeIs) { mdptr->ContentTypeIs = MIME_CONTENT_TEXT_PLAIN; mdptr->ContentTypePtr = "text/plain"; } if (!mdptr->TransferEncodingIs) { /* default to 8 bit */ mdptr->TransferEncodingIs = MIME_TRANSENC_8BIT; mdptr->TransferEncodingPtr = "8bit"; } /* have we changed boundary? (i.e. a next multipart, etc.) */ if (mdptr->BoundaryPtr) { /* yes, nested boundaries */ if (mdidx++ > MIME_DATA_STACK_MAX-1) ErrorExit (SS$_BUGCHECK, FI_LI); byptr = mdptr->BoundaryPtr; bylen = mdptr->BoundaryLength; } /* replace the top-of-stack with this latest part */ MimeDataStack[mdidx] = mdptr; /* ensure that the appropriate boundary is used if/when popped */ mdptr->BoundaryPtr = byptr; mdptr->BoundaryLength = bylen; mdptr->PartCount = ++PartCount; sprintf (mdptr->PartName, "%s_%d", LangFor("mime_part"), mdptr->PartCount); if (strsame (mdptr->ContentTypePtr, "message/rfc822", -1)) { /*****************/ /* russian dolls */ /*****************/ if (WatchEnabled) WatchThis ("MESSAGE/RFC822"); while (*cptr && *cptr != '\n') cptr++; if (*cptr) cptr++; /* note current location in the part ("message/rfc822" part) */ MessageRfc822Ptr = cptr; cptr = mdptr->PartContentPtr; if (Debug) fprintf (stdout, "|%s|", cptr); memset (&MessageRfc822, 0, sizeof(MessageRfc822)); while (*cptr) { len = InetMailParseField (cptr); if (len == -1) { MimeDecParseError (MimeDataStack[0], cptr, FI_LI); break; } /* note the start of this line */ sptr = cptr; /* adjust to the beginning of the next line */ cptr += len; /* get any field value from the current line */ len = InetMailGetField (sptr, len, FieldBuffer, sizeof(FieldBuffer)); /* if the get function could not make sense of the text */ if (len == -1) { MimeDecParseError (MimeDataStack[0], sptr, FI_LI); break; } /* get the MIME field value */ InetMailParseMimeHeader (&MessageRfc822, sptr, FieldBuffer); /* break if it looks like the end-of-MIME-header empty line */ if (*cptr == '\n') { cptr++; break; } if (*(USHORTPTR)cptr == '\r\n') { cptr += 2; break; } } /* reset the pointer into the part */ cptr = MessageRfc822Ptr; /* if this "message/rfc822" has MIME content then parse header */ if (MessageRfc822.BoundaryPtr) continue; } break; } } if (mdidx >= 0) { /* allow something like ... Content-Type: text/plain; charset=ISO-8859-15; format=flowed Content-Transfer-Encoding: 7bit not to be closed (VGG at UMA)! */ if (mdptr->ContentTypeIs != MIME_CONTENT_TEXT_PLAIN && mdptr->ContentTypeIs != MIME_CONTENT_TEXT_HTML) { /* should not have run out of message before closing boundary */ MimeDecParseError (MimeDataStack[0], cptr, FI_LI); return (FALSE); } } return (TRUE); } /*****************************************************************************/ /* Report a MIME parse error providing the line and character at which the error halted MIME parse processing. */ void MimeDecParseError ( MIME_DATA *BodyMimeDataPtr, char *CharPtr, char *SourceFileName, int SourceLineNumber ) { int lcnt, len; char ch; char *cptr, *lptr, *sptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "MimeDecParseError()\n"); lcnt = 1; cptr = CharPtr; for (sptr = BodyMimeDataPtr->PartContentPtr; sptr < cptr; sptr++) { if (*sptr == '\n') lcnt++; else if (*(USHORTPTR)sptr == '\r\n') { lcnt++; sptr++; } } /* 'MIME parse error at line 9 character 9 ("blah"). */ lptr = cptr; if (*lptr == '\n') lptr++; while (*lptr && *lptr != '\n' && lptr-cptr < 96) lptr++; ch = *lptr; *lptr = '\0'; StatusMessage (SourceFileName, SourceLineNumber, 1, LangFor("mime_parse_error"), lcnt, cptr-sptr+1, cptr); *lptr = ch; } /*****************************************************************************/ /* Free all memory associated with parsing the MIME body. */ void MimeDecParseFree (MIME_DATA *mdptr1) { MIME_DATA *mdptr2; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "MimeDecParseFree()\n"); if (!mdptr1) ErrorExit (SS$_BUGCHECK, FI_LI); while (mdptr1) { mdptr2 = mdptr1; mdptr1 = mdptr1->NextMimePtr; CgiLibVeeMemFree (mdptr2); } } /*****************************************************************************/ /* Process the MIME part pointed to in the parameter. If required decode it in-situ appropriate to the encoding type. The part is provided by 'mdptr->PartContentPtr' and 'mdptr->PartContentLength'. */ BOOL MimeDecProcessPart (MIME_DATA *mdptr) { BOOL ok; int len; /*********/ /* begin */ /*********/ if (WatchEnabled) WatchThis ("MimeDecProcessPart() !UL, !8XL !UL bytes", mdptr->TransferEncodingIs, mdptr, mdptr->PartContentLength); if (!mdptr) ErrorExit (SS$_BUGCHECK, FI_LI); /* this can happen if there's been a problem processing the MIME content */ if (!mdptr->PartContentPtr) return (FALSE); /* only should do it the once */ if (mdptr->PartDecoded) return (TRUE); mdptr->PartDecoded = TRUE; /* null-terminate the part */ mdptr->PartContentPtr[mdptr->PartContentLength] = '\0'; switch (mdptr->TransferEncodingIs) { case MIME_TRANSENC_7BIT : ok = MimeDec7bit (mdptr); break; case MIME_TRANSENC_8BIT : ok = MimeDec8bit (mdptr); break; case MIME_TRANSENC_QUOTED : len = MimeDecQuotedPrintable (mdptr->PartContentPtr); if (len == -1) ok = mdptr->PartContentLength = 0; else ok = mdptr->PartContentLength = len; break; case MIME_TRANSENC_BASE64 : len = MimeDecBase64 (mdptr->PartContentPtr); if (len == -1) ok = mdptr->PartContentLength = 0; else ok = mdptr->PartContentLength = len; break; case MIME_TRANSENC_OTHER : ok = TRUE; break; default : ok = MimeDec7bit (mdptr); break; } return (ok); } /*****************************************************************************/ /* Ensure the 7bit encoding is compliant. Reset any eighth bit and return FALSE if found (really informational only), TRUE is fully compliant. MGD - DISABLE 8 bit reporting and correction. Always return OK! */ BOOL MimeDec7bit (MIME_DATA *mdptr) { BOOL ok; char *cptr; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "MimeDec7bit()\n"); ok = TRUE; for (cptr = mdptr->PartContentPtr; *cptr; cptr++) { if (!(*cptr & 0x80)) continue; /** StatusMessage (FI_LI, 1, "bit 8 found set"); *cptr &= 0x7f; **/ ok = FALSE; } if (WatchEnabled) if (ok) WatchThis ("MIME 7bit OK !UL bytes", mdptr->PartContentLength); else WatchThis ("MIME 7bit ERROR !AZ", "bit 8 found set"); return (TRUE); } /*****************************************************************************/ /* Not sure how this should be handled ... so do nothing to it for the present :-) */ BOOL MimeDec8bit (MIME_DATA *mdptr) { /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "MimeDec8bit()\n"); if (WatchEnabled) WatchThis ("MIME 8bit !UL bytes", mdptr->PartContentLength); return (TRUE); } /*****************************************************************************/ /* Generic base64 in-situ decode function. Returns the size of the decoded data or -1 and an empty string to indicate an error. */ int MimeDecBase64 (char *Base64Ptr) { int ccnt, BufferLength, ErrorLine; unsigned long reg; unsigned char val; char *cptr, *sptr, *ErrorPtr = ""; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "MimeDecBase64()\n"); if (!Base64Ptr) ErrorExit (SS$_BUGCHECK, FI_LI); cptr = sptr = Base64Ptr; while (*cptr) { while (*cptr == '\r' || *cptr == '\n' || *cptr & 0x80 || (*cptr && CHAR64(*cptr) == XX)) cptr++; if (!*cptr) break; reg = 0; if (*cptr) { if (*cptr == '=') { ErrorPtr = cptr; ErrorLine = __LINE__; goto MimeDecBase64_fail; } if ((val = CHAR64(*cptr++)) == XX) { ErrorPtr = cptr; ErrorLine = __LINE__; goto MimeDecBase64_fail; } reg |= val << 18; } if (*cptr) { if (*cptr == '=') goto MimeDecBase64_fail; ccnt = 1; if ((val = CHAR64(*cptr++)) == XX) { ErrorPtr = cptr; ErrorLine = __LINE__; goto MimeDecBase64_fail; } reg |= val << 12; } if (*cptr) { if (*cptr == '=') cptr++; else { ccnt++; if ((val = CHAR64(*cptr++)) == XX) { ErrorPtr = cptr; ErrorLine = __LINE__; goto MimeDecBase64_fail; } reg |= val << 6; } } if (*cptr) { if (*cptr == '=') cptr++; else { ccnt++; if ((val = CHAR64(*cptr++)) == XX) { ErrorPtr = cptr; ErrorLine = __LINE__; goto MimeDecBase64_fail; } reg |= val; } } else goto MimeDecBase64_fail; if (ccnt--) *sptr++ = (reg >> 16) & 0xff; if (ccnt--) *sptr++ = (reg >> 8) & 0xff; if (ccnt--) *sptr++ = reg & 0xff; } *sptr = '\0'; if (WatchEnabled) WatchThis ("BASE64 ok !UL bytes", sptr-Base64Ptr); return (sptr - Base64Ptr); MimeDecBase64_fail: if (WatchEnabled) WatchThis ("BASE64 error; !UL |!#AZ", ErrorLine, strlen(ErrorPtr) > 160 ? 160 : strlen(ErrorPtr), ErrorPtr); *Base64Ptr = '\0'; return (-1); } /*****************************************************************************/ /* Generic quoted-printable in-situ decode function. Returns the size of the decoded data or -1 and an empty string to indicate an error. */ int MimeDecQuotedPrintable (char *QuotedPtr) { int len, ErrorLine = 0; unsigned char ch; char *cptr, *sptr, *ErrorPtr = ""; /*********/ /* begin */ /*********/ if (Debug) fprintf (stdout, "MimeDecQuotedPrintable()\n"); if (!QuotedPtr) ErrorExit (SS$_BUGCHECK, FI_LI); len = 0; cptr = sptr = QuotedPtr; while (*cptr) { if (*cptr == '=') { cptr++; if (*cptr == '\n') cptr++; else if (*(USHORTPTR)cptr == '\r\n') cptr += 2; else if (CHAR16(*cptr) != XX) { ch = CHAR16(*cptr) << 4; cptr++; if (CHAR16(*cptr) != XX) { ch |= CHAR16(*cptr); cptr++; *sptr++ = ch; } else { len = -1; ErrorPtr = cptr; ErrorLine = __LINE__; break; } } else { len = -1; ErrorPtr = cptr; ErrorLine = __LINE__; break; } } else *sptr++ = *cptr++; } if (len == -1) sptr = QuotedPtr; else len = sptr - QuotedPtr; *sptr = '\0'; if (WatchEnabled) if (len == -1) WatchThis ("QUOTED-PRINTABLE error; !UL |!#AZ", ErrorLine, strlen(ErrorPtr) > 160 ? 160 : strlen(ErrorPtr), ErrorPtr); else WatchThis ("MIME quoted-printable OK !UL bytes", len); return (len); } /*****************************************************************************/ /* Generic ISO encoded-word decode function. Returns a pointer to a dynamically allocated memory containing the decoded string or the same string that was passed to it if there was an error. */ int MimeDecIsoString (char *IsoString) { int IsoStringLength, DataDone = 0, WatchDone = 0; int c, c1, c2, c3, c4, encoding; char *aptr, *cptr, *sptr, *BufferPtr; char buf[3]; if (Debug) fprintf (stdout, "MimeDecIsoString() |%s|\n", IsoString); if (!IsoString) ErrorExit (SS$_BUGCHECK, FI_LI); for (cptr = IsoString; *cptr; cptr++) if (*cptr == '=' && *(USHORTPTR)cptr == '=?') break; /* if it doesn't need decoding */ if (!*cptr) return (cptr-IsoString); while (*cptr) cptr++; IsoStringLength = cptr - IsoString; sptr = BufferPtr = CgiLibVeeMemCalloc (IsoStringLength+1); if (!sptr) ErrorExit (vaxc$errno, FI_LI); cptr = IsoString; while (*cptr) { if (*cptr != '=' || *(USHORTPTR)cptr != '=?') { *sptr++ = *cptr++; continue; } encoding = 0; if (!WatchDone) { WatchDone = 1; if (WatchEnabled) WatchThis ("ISO-BASE64 !AZ", IsoString); } cptr += 2; /* (VMS) Mail can concatenate the likes of the subject: From: SANS Institute Subject: =?UTF-8?B?U0FOUyBOZXdzQml0ZXMgVm9sLiAxOCBOdW0uIDA4NyA6IFVLIE5I?= =?UTF-8?B?UyBUcnVzdCBTaHV0cyBEb3duIFN5c3RlbXMgQWZ0ZXIgTWFsd2FyZSBJbmZl?= =?UTF-8?B?Y3Rpb247IElvVCBCb3RuZXQ6IEl04oCZcyBNb3JlIFRoYW4gYSBQYXNzd29y?= =?UTF-8?B?ZCBJc3N1ZTsgRE1DQSBFeGVtcHRpb25zIE5vdyBQcm90ZWN0ICJSZXNlYXJj?= =?UTF-8?B?aGVycyI=?= To: Mark.Daniel@wasd.vsm.com.au into a 255 character subject line: =?UTF-8?B?U0FOUyBOZXdzQml0ZXMgVm9sLiAxOCBOdW0uIDA4NyA6IFVLIE5I?= =?UTF-8?B?UyBUcnVzdCBTaHV0cyBEb3duIFN5c3RlbXMgQWZ0ZXIgTWFsd2FyZSBJbmZl?= ... which can break/mangle the encoding. Ensure it's complete or ignored! */ for (aptr = cptr; *aptr; aptr++) if (*aptr == '?' && *(USHORTPTR)aptr == '?=') break; if (!*aptr) break; /* find the encoding type */ while (*cptr && *cptr != '?') cptr++; if (*cptr) cptr++; if (toupper(*cptr) == 'B') encoding = 'B'; if (toupper(*cptr) == 'Q') encoding = 'Q'; if (*cptr) cptr++; if (*cptr == '?') cptr++; if (!encoding) { *sptr++ = *cptr; cptr++; continue; } if (encoding == 'Q') { for (;;) { c1 = *cptr; if (!c1) break; cptr++; if (c1 == '?' && *cptr == '=') { cptr++; break; } if (c1 == '=') { if (*cptr) c1 = *cptr++; else break; if (c1 != '\n') { c1 = CHAR16(c1); if (*cptr) c2 = *cptr++; else break; c2 = CHAR16(c2); c = c1 << 4 | c2; if (c != '\r') *sptr++ = c; } } else if (c1 == '_') *sptr++ = ' '; else *sptr++ = c1; } continue; } if (encoding == 'B') { for (;;) { c1 = *cptr; if (!c1) break; cptr++; if (c1 == '?' && *cptr == '=') { cptr++; break; } if (c1 != '=' && CHAR64(c1) == XX) continue; if (DataDone) continue; do { c2 = *cptr; if (c2) cptr++; else c2 = EOF; } while (c2 != EOF && c2 != '=' && CHAR64(c2) == XX); do { c3 = *cptr; if (c3) cptr++; else c3 = EOF; } while (c3 != EOF && c3 != '=' && CHAR64(c3) == XX); do { c4 = *cptr; if (c4) cptr++; else c4 = EOF; } while (c4 != EOF && c4 != '=' && CHAR64(c4) == XX); if (c2 == EOF || c3 == EOF || c4 == EOF) { if (WatchEnabled) WatchThis ("ISO-BASE64 PREMATURE EOF \"!AZ\"", cptr); return (-1); } if (c1 == '=' || c2 == '=') { DataDone=1; continue; } c1 = CHAR64(c1); c2 = CHAR64(c2); buf[0] = ((c1<<2) | ((c2&0x30)>>4)); *sptr++ = buf[0]; if (c3 == '=') DataDone = 1; else { c3 = CHAR64(c3); buf[1] = (((c2&0x0F) << 4) | ((c3&0x3C) >> 2)); *sptr++ = buf[1]; if (c4 == '=') DataDone = 1; else { c4 = CHAR64(c4); buf[2] = (((c3&0x03) << 6) | c4); *sptr++ = buf[2]; } } } continue; } } *sptr = '\0'; IsoStringLength = sptr - BufferPtr; memcpy (IsoString, BufferPtr, IsoStringLength+1); CgiLibVeeMemFree (BufferPtr); if (WatchDone && WatchEnabled) WatchThis ("ISO-BASE64 !AZ", IsoString); return (IsoStringLength); } /*****************************************************************************/ /* Manufacture a text/plain pseudo MIME part. */ MIME_DATA* MimeDecManufacTextPlain ( char *TextPtr, int TextLength, int PartCount ) { MIME_DATA *mdptr; /*********/ /* begin */ /*********/ if (WatchEnabled) WatchThis ("MIME manufacture text/plain"); mdptr = CgiLibVeeMemCalloc (sizeof(MIME_DATA)); if (!mdptr) ErrorExit (vaxc$errno, FI_LI); mdptr->ManufacturedMime = TRUE; mdptr->TransferEncodingPtr = "8bit"; mdptr->TransferEncodingIs = MIME_TRANSENC_8BIT; mdptr->ContentTypeIs = MIME_CONTENT_TEXT_PLAIN; mdptr->ContentTypePtr = "text/plain"; mdptr->PartCount = PartCount; sprintf (mdptr->PartName, "%s_%d", LangFor("mime_part"), PartCount); mdptr->PartContentPtr = CgiLibVeeMemCalloc (TextLength+1); if (!mdptr->PartContentPtr) ErrorExit (vaxc$errno, FI_LI); memcpy (mdptr->PartContentPtr, TextPtr, TextLength); mdptr->PartContentPtr[mdptr->PartContentLength = TextLength] = '\0'; return (mdptr); } /*****************************************************************************/ /* Manufacture a text/html pseudo MIME part. */ MIME_DATA* MimeDecManufacTextHtml ( char *TextPtr, int TextLength, int PartCount ) { MIME_DATA *mdptr; /*********/ /* begin */ /*********/ if (WatchEnabled) WatchThis ("MIME manufacture text/html"); mdptr = CgiLibVeeMemCalloc (sizeof(MIME_DATA)); if (!mdptr) ErrorExit (vaxc$errno, FI_LI); mdptr->ManufacturedMime = TRUE; mdptr->TransferEncodingPtr = "8bit"; mdptr->TransferEncodingIs = MIME_TRANSENC_8BIT; mdptr->ContentTypeIs = MIME_CONTENT_TEXT_HTML; mdptr->ContentTypePtr = "text/html"; mdptr->PartCount = PartCount; sprintf (mdptr->PartName, "%s_%d", LangFor("mime_part"), PartCount); mdptr->PartContentPtr = CgiLibVeeMemCalloc (TextLength+1); if (!mdptr->PartContentPtr) ErrorExit (vaxc$errno, FI_LI); memcpy (mdptr->PartContentPtr, TextPtr, TextLength); mdptr->PartContentPtr[mdptr->PartContentLength = TextLength] = '\0'; return (mdptr); } /*****************************************************************************/ /* This is a special-case MIME part manufacture. Create binary bag-of-bytes for a VMS/FOREIGN mail message content. */ MIME_DATA* MimeDecManufacVmsForeign ( char *DataPtr, int DataLength ) { MIME_DATA *mdptr; /*********/ /* begin */ /*********/ if (WatchEnabled) WatchThis ("MIME manufacture VMS/FOREIGN"); mdptr = CgiLibVeeMemCalloc (sizeof(MIME_DATA)); if (!mdptr) ErrorExit (vaxc$errno, FI_LI); mdptr->ManufacturedMime = TRUE; mdptr->TransferEncodingPtr = "8bit"; mdptr->TransferEncodingIs = MIME_TRANSENC_8BIT; mdptr->ContentTypeIs = MIME_CONTENT_OCTET_STREAM; mdptr->ContentTypePtr = "application/octet-stream"; mdptr->PartCount = 1; strcpy (mdptr->PartName, "VMS/FOREIGN"); mdptr->NamePtr = mdptr->PartName; mdptr->PartContentPtr = DataPtr; mdptr->PartContentLength = DataLength; return (mdptr); } /*****************************************************************************/