/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Netscape Public * License Version 1.1 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.mozilla.org/NPL/ * * Software distributed under the License is distributed on an "AS * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or * implied. See the License for the specific language governing * rights and limitations under the License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All * Rights Reserved. * * Contributor(s): */ /* mimeunty.c --- definition of the MimeUntypedText class (see mimei.h) Created: Jamie Zawinski , 15-May-96. */ #include "rosetta.h" #include "mimeunty.h" #define MIME_SUPERCLASS mimeContainerClass MimeDefClass(MimeUntypedText, MimeUntypedTextClass, mimeUntypedTextClass, &MIME_SUPERCLASS); static int MimeUntypedText_initialize (MimeObject *); static void MimeUntypedText_finalize (MimeObject *); static int MimeUntypedText_parse_begin (MimeObject *); static int MimeUntypedText_parse_line (char *, int32, MimeObject *); static int MimeUntypedText_open_subpart (MimeObject *obj, MimeUntypedTextSubpartType ttype, const char *type, const char *enc, const char *name, const char *desc); static int MimeUntypedText_close_subpart (MimeObject *obj); static XP_Bool MimeUntypedText_uu_begin_line_p(const char *line, int32 length, MimeDisplayOptions *opt, char **type_ret, char **name_ret); static XP_Bool MimeUntypedText_uu_end_line_p(const char *line, int32 length); static XP_Bool MimeUntypedText_binhex_begin_line_p(const char *line, int32 length, MimeDisplayOptions *opt); static XP_Bool MimeUntypedText_binhex_end_line_p(const char *line, int32 length); static int MimeUntypedTextClassInitialize(MimeUntypedTextClass *class) { MimeObjectClass *oclass = (MimeObjectClass *) class; XP_ASSERT(!oclass->class_initialized); oclass->initialize = MimeUntypedText_initialize; oclass->finalize = MimeUntypedText_finalize; oclass->parse_begin = MimeUntypedText_parse_begin; oclass->parse_line = MimeUntypedText_parse_line; return 0; } static int MimeUntypedText_initialize (MimeObject *object) { return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(object); } static void MimeUntypedText_finalize (MimeObject *object) { MimeUntypedText *uty = (MimeUntypedText *) object; if (uty->open_hdrs) { /* Oops, those shouldn't still be here... */ MimeHeaders_free(uty->open_hdrs); uty->open_hdrs = 0; } /* What about the open_subpart? We're gonna have to assume that it is also on the MimeContainer->children list, and will get cleaned up by that class. */ ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(object); } static int MimeUntypedText_parse_begin (MimeObject *obj) { return ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj); } static int MimeUntypedText_parse_line (char *line, int32 length, MimeObject *obj) { MimeUntypedText *uty = (MimeUntypedText *) obj; int status = 0; char *name = 0, *type = 0; XP_Bool begin_line_p = FALSE; XP_ASSERT(line && *line); if (!line || !*line) return -1; /* If we're supposed to write this object, but aren't supposed to convert it to HTML, simply pass it through unaltered. */ if (obj->output_p && obj->options && !obj->options->write_html_p && obj->options->output_fn) return MimeObject_write(obj, line, length, TRUE); /* Open a new sub-part if this line demands it. */ if (line[0] == 'b' && MimeUntypedText_uu_begin_line_p(line, length, obj->options, &type, &name)) { /* Close the old part and open a new one. */ status = MimeUntypedText_open_subpart (obj, MimeUntypedTextSubpartTypeUUE, type, ENCODING_UUENCODE, name, NULL); FREEIF(name); FREEIF(type); if (status < 0) return status; begin_line_p = TRUE; } else if (line[0] == '(' && line[1] == 'T' && MimeUntypedText_binhex_begin_line_p(line, length, obj->options)) { /* Close the old part and open a new one. */ status = MimeUntypedText_open_subpart (obj, MimeUntypedTextSubpartTypeBinhex, APPLICATION_BINHEX, NULL, NULL, NULL); if (status < 0) return status; begin_line_p = TRUE; } /* Open a text/plain sub-part if there is no sub-part open. */ if (!uty->open_subpart) { XP_ASSERT(!begin_line_p); status = MimeUntypedText_open_subpart (obj, MimeUntypedTextSubpartTypeText, TEXT_PLAIN, NULL, NULL, NULL); XP_ASSERT(uty->open_subpart); if (!uty->open_subpart) return -1; if (status < 0) return status; } /* Hand this line to the currently-open sub-part. */ status = uty->open_subpart->class->parse_buffer(line, length, uty->open_subpart); if (status < 0) return status; /* Close this sub-part if this line demands it. */ if (begin_line_p) ; else if (line[0] == 'e' && uty->type == MimeUntypedTextSubpartTypeUUE && MimeUntypedText_uu_end_line_p(line, length)) { status = MimeUntypedText_close_subpart (obj); if (status < 0) return status; XP_ASSERT(!uty->open_subpart); } else if (uty->type == MimeUntypedTextSubpartTypeBinhex && MimeUntypedText_binhex_end_line_p(line, length)) { status = MimeUntypedText_close_subpart (obj); if (status < 0) return status; XP_ASSERT(!uty->open_subpart); } return 0; } static int MimeUntypedText_close_subpart (MimeObject *obj) { MimeUntypedText *uty = (MimeUntypedText *) obj; int status; if (uty->open_subpart) { status = uty->open_subpart->class->parse_eof(uty->open_subpart, FALSE); uty->open_subpart = 0; XP_ASSERT(uty->open_hdrs); if (uty->open_hdrs) { MimeHeaders_free(uty->open_hdrs); uty->open_hdrs = 0; } uty->type = MimeUntypedTextSubpartTypeText; if (status < 0) return status; /* Never put out a separator between sub-parts of UntypedText. (This bypasses the rule that text/plain subparts always have separators before and after them.) */ if (obj->options && obj->options->state) obj->options->state->separator_suppressed_p = TRUE; } XP_ASSERT(!uty->open_hdrs); return 0; } static int MimeUntypedText_open_subpart (MimeObject *obj, MimeUntypedTextSubpartType ttype, const char *type, const char *enc, const char *name, const char *desc) { MimeUntypedText *uty = (MimeUntypedText *) obj; int status = 0; char *h = 0; if (!type || !*type || !strcasecomp(type, UNKNOWN_CONTENT_TYPE)) type = APPLICATION_OCTET_STREAM; if (enc && !*enc) enc = 0; if (desc && !*desc) desc = 0; if (name && !*name) name = 0; if (uty->open_subpart) { status = MimeUntypedText_close_subpart (obj); if (status < 0) return status; } XP_ASSERT(!uty->open_subpart); XP_ASSERT(!uty->open_hdrs); /* To make one of these implicitly-typed sub-objects, we make up a fake header block, containing only the minimum number of MIME headers needed. We could do most of this (Type and Encoding) by making a null header block, and simply setting obj->content_type and obj->encoding; but making a fake header block is better for two reasons: first, it means that something will actually be displayed when in `Show All Headers' mode; and second, it's the only way to communicate the filename parameter, aside from adding a new slot to MimeObject (which is something to be avoided when possible.) */ uty->open_hdrs = MimeHeaders_new(); if (!uty->open_hdrs) return MK_OUT_OF_MEMORY; h = (char *) XP_ALLOC(XP_STRLEN(type) + (enc ? XP_STRLEN(enc) : 0) + (desc ? XP_STRLEN(desc) : 0) + (name ? XP_STRLEN(name) : 0) + 100); if (!h) return MK_OUT_OF_MEMORY; XP_STRCPY(h, HEADER_CONTENT_TYPE ": "); XP_STRCAT(h, type); XP_STRCAT(h, LINEBREAK); status = MimeHeaders_parse_line(h, XP_STRLEN(h), uty->open_hdrs); if (status < 0) goto FAIL; if (enc) { XP_STRCPY(h, HEADER_CONTENT_TRANSFER_ENCODING ": "); XP_STRCAT(h, enc); XP_STRCAT(h, LINEBREAK); status = MimeHeaders_parse_line(h, XP_STRLEN(h), uty->open_hdrs); if (status < 0) goto FAIL; } if (desc) { XP_STRCPY(h, HEADER_CONTENT_DESCRIPTION ": "); XP_STRCAT(h, desc); XP_STRCAT(h, LINEBREAK); status = MimeHeaders_parse_line(h, XP_STRLEN(h), uty->open_hdrs); if (status < 0) goto FAIL; } if (name) { XP_STRCPY(h, HEADER_CONTENT_DISPOSITION ": inline; filename=\""); XP_STRCAT(h, name); XP_STRCAT(h, "\"" LINEBREAK); status = MimeHeaders_parse_line(h, XP_STRLEN(h), uty->open_hdrs); if (status < 0) goto FAIL; } /* push out a blank line. */ XP_STRCPY(h, LINEBREAK); status = MimeHeaders_parse_line(h, XP_STRLEN(h), uty->open_hdrs); if (status < 0) goto FAIL; /* Create a child... */ { XP_Bool horrid_kludge = (obj->options && obj->options->state && obj->options->state->first_part_written_p); if (horrid_kludge) obj->options->state->first_part_written_p = FALSE; uty->open_subpart = mime_create(type, uty->open_hdrs, obj->options); if (horrid_kludge) obj->options->state->first_part_written_p = TRUE; if (!uty->open_subpart) { status = MK_OUT_OF_MEMORY; goto FAIL; } } /* Add it to the list... */ status = ((MimeContainerClass *) obj->class)->add_child(obj, uty->open_subpart); if (status < 0) { mime_free(uty->open_subpart); uty->open_subpart = 0; goto FAIL; } /* And start its parser going. */ status = uty->open_subpart->class->parse_begin(uty->open_subpart); if (status < 0) { /* MimeContainer->finalize will take care of shutting it down now. */ uty->open_subpart = 0; goto FAIL; } uty->type = ttype; FAIL: FREEIF(h); if (status < 0 && uty->open_hdrs) { MimeHeaders_free(uty->open_hdrs); uty->open_hdrs = 0; } return status; } static XP_Bool MimeUntypedText_uu_begin_line_p(const char *line, int32 length, MimeDisplayOptions *opt, char **type_ret, char **name_ret) { const char *s; char *name = 0; char *type = 0; if (type_ret) *type_ret = 0; if (name_ret) *name_ret = 0; if (XP_STRNCMP (line, "begin ", 6)) return FALSE; /* ...then three or four octal digits. */ s = line + 6; if (*s < '0' || *s > '7') return FALSE; s++; if (*s < '0' || *s > '7') return FALSE; s++; if (*s < '0' || *s > '7') return FALSE; s++; if (*s == ' ') s++; else { if (*s < '0' || *s > '7') return FALSE; s++; if (*s != ' ') return FALSE; } while (XP_IS_SPACE(*s)) s++; name = (char *) XP_ALLOC(((line+length)-s) + 1); if (!name) return FALSE; /* grr... */ XP_MEMCPY(name, s, (line+length)-s); name[(line+length)-s] = 0; /* take off newline. */ if (name[XP_STRLEN(name)-1] == LF) name[XP_STRLEN(name)-1] = 0; if (name[XP_STRLEN(name)-1] == CR) name[XP_STRLEN(name)-1] = 0; /* Now try and figure out a type. */ if (opt && opt->file_type_fn) type = opt->file_type_fn(name, opt->stream_closure); else type = 0; if (name_ret) *name_ret = name; else FREEIF(name); if (type_ret) *type_ret = type; else FREEIF(type); return TRUE; } static XP_Bool MimeUntypedText_uu_end_line_p(const char *line, int32 length) { #if 0 /* A strictly conforming uuencode end line. */ return (line[0] == 'e' && line[1] == 'n' && line[2] == 'd' && (line[3] == 0 || XP_IS_SPACE(line[3]))); #else /* ...but, why don't we accept any line that begins with the three letters "END" in any case: I've seen lots of partial messages that look like BEGIN----- Cut Here----- begin 644 foo.gif Mxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Mxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Mxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx END------- Cut Here----- so let's be lenient here. (This is only for the untyped-text-plain case -- the uudecode parser itself is strict.) */ return (line[0] == ' ' || line[0] == '\t' || ((line[0] == 'e' || line[0] == 'E') && (line[1] == 'n' || line[1] == 'N') && (line[2] == 'd' || line[2] == 'D'))); #endif } #define BINHEX_MAGIC "(This file must be converted with BinHex 4.0)" #define BINHEX_MAGIC_LEN 45 static XP_Bool MimeUntypedText_binhex_begin_line_p(const char *line, int32 length, MimeDisplayOptions *opt) { if (length <= BINHEX_MAGIC_LEN) return FALSE; while(length > 0 && XP_IS_SPACE(line[length-1])) length--; if (length != BINHEX_MAGIC_LEN) return FALSE; if (!XP_STRNCMP(line, BINHEX_MAGIC, BINHEX_MAGIC_LEN)) return TRUE; else return FALSE; } static XP_Bool MimeUntypedText_binhex_end_line_p(const char *line, int32 length) { if (length > 0 && line[length-1] == LF) length--; if (length > 0 && line[length-1] == CR) length--; if (length != 0 && length != 64) return TRUE; else return FALSE; }