Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mimeunty.h"
#include "prmem.h"
#include "plstr.h"
#include "prlog.h"
#include "nsMimeTypes.h"
#include "msgCore.h"
#include "nsMimeStringResources.h"
#include <ctype.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(const char*, int32_t, 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 bool MimeUntypedText_uu_begin_line_p(const char* line, int32_t length,
MimeDisplayOptions* opt,
char** type_ret, char** name_ret);
static bool MimeUntypedText_uu_end_line_p(const char* line, int32_t length);
static bool MimeUntypedText_yenc_begin_line_p(const char* line, int32_t length,
MimeDisplayOptions* opt,
char** type_ret, char** name_ret);
static bool MimeUntypedText_yenc_end_line_p(const char* line, int32_t length);
static bool MimeUntypedText_binhex_begin_line_p(const char* line,
int32_t length,
MimeDisplayOptions* opt);
static bool MimeUntypedText_binhex_end_line_p(const char* line, int32_t length);
static int MimeUntypedTextClassInitialize(MimeObjectClass* oclass) {
PR_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 going to 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(const char* line, int32_t length,
MimeObject* obj) {
MimeUntypedText* uty = (MimeUntypedText*)obj;
int status = 0;
char *name = 0, *type = 0;
bool begin_line_p = false;
NS_ASSERTION(line && *line, "empty line in mime untyped parse_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);
PR_FREEIF(name);
PR_FREEIF(type);
if (status < 0) return status;
begin_line_p = true;
}
else if (line[0] == '=' && MimeUntypedText_yenc_begin_line_p(
line, length, obj->options, &type, &name)) {
/* Close the old part and open a new one. */
status = MimeUntypedText_open_subpart(obj, MimeUntypedTextSubpartTypeYEnc,
type, ENCODING_YENCODE, name, NULL);
PR_FREEIF(name);
PR_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) {
// rhp: If we get here and we are being fed a line ending, we should
// just eat it and continue and if we really get more data, we'll open
// up the subpart then.
//
if (line[0] == '\r') return 0;
if (line[0] == '\n') return 0;
PR_ASSERT(!begin_line_p);
status = MimeUntypedText_open_subpart(obj, MimeUntypedTextSubpartTypeText,
TEXT_PLAIN, NULL, NULL, NULL);
PR_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->clazz->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;
NS_ASSERTION(!uty->open_subpart, "no open subpart");
} else if (line[0] == '=' && uty->type == MimeUntypedTextSubpartTypeYEnc &&
MimeUntypedText_yenc_end_line_p(line, length)) {
status = MimeUntypedText_close_subpart(obj);
if (status < 0) return status;
NS_ASSERTION(!uty->open_subpart, "no open subpart");
} else if (uty->type == MimeUntypedTextSubpartTypeBinhex &&
MimeUntypedText_binhex_end_line_p(line, length)) {
status = MimeUntypedText_close_subpart(obj);
if (status < 0) return status;
NS_ASSERTION(!uty->open_subpart, "no 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->clazz->parse_eof(uty->open_subpart, false);
uty->open_subpart = 0;
PR_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;
}
PR_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 || !PL_strcasecmp(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;
}
NS_ASSERTION(!uty->open_subpart, "no open subpart");
NS_ASSERTION(!uty->open_hdrs, "no open headers");
/* 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 MIME_OUT_OF_MEMORY;
uint32_t hlen = strlen(type) + (enc ? strlen(enc) : 0) +
(desc ? strlen(desc) : 0) + (name ? strlen(name) : 0) + 100;
h = (char*)PR_MALLOC(hlen);
if (!h) return MIME_OUT_OF_MEMORY;
PL_strncpyz(h, HEADER_CONTENT_TYPE ": ", hlen);
PL_strcatn(h, hlen, type);
PL_strcatn(h, hlen, MSG_LINEBREAK);
status = MimeHeaders_parse_line(h, strlen(h), uty->open_hdrs);
if (status < 0) goto FAIL;
if (enc) {
PL_strncpyz(h, HEADER_CONTENT_TRANSFER_ENCODING ": ", hlen);
PL_strcatn(h, hlen, enc);
PL_strcatn(h, hlen, MSG_LINEBREAK);
status = MimeHeaders_parse_line(h, strlen(h), uty->open_hdrs);
if (status < 0) goto FAIL;
}
if (desc) {
PL_strncpyz(h, HEADER_CONTENT_DESCRIPTION ": ", hlen);
PL_strcatn(h, hlen, desc);
PL_strcatn(h, hlen, MSG_LINEBREAK);
status = MimeHeaders_parse_line(h, strlen(h), uty->open_hdrs);
if (status < 0) goto FAIL;
}
if (name) {
PL_strncpyz(h, HEADER_CONTENT_DISPOSITION ": inline; filename=\"", hlen);
PL_strcatn(h, hlen, name);
PL_strcatn(h, hlen, "\"" MSG_LINEBREAK);
status = MimeHeaders_parse_line(h, strlen(h), uty->open_hdrs);
if (status < 0) goto FAIL;
}
/* push out a blank line. */
PL_strncpyz(h, MSG_LINEBREAK, hlen);
status = MimeHeaders_parse_line(h, strlen(h), uty->open_hdrs);
if (status < 0) goto FAIL;
/* Create a child... */
{
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 = MIME_OUT_OF_MEMORY;
goto FAIL;
}
}
/* Add it to the list... */
status = ((MimeContainerClass*)obj->clazz)->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->clazz->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:
PR_FREEIF(h);
if (status < 0 && uty->open_hdrs) {
MimeHeaders_free(uty->open_hdrs);
uty->open_hdrs = 0;
}
return status;
}
static bool MimeUntypedText_uu_begin_line_p(const char* line, int32_t 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 (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 (IS_SPACE(*s)) s++;
int name_len = (line + length) - s;
name = (char*)PR_MALLOC(name_len + 1);
if (!name) return false; /* grr... */
memcpy(name, s, name_len);
name[name_len] = 0;
if (name_len) {
/* take off newline. */
if (name_len && name[name_len - 1] == '\n') {
name[name_len] = 0;
name_len--;
}
if (name_len && name[name_len - 1] == '\r') {
name[name_len] = 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
PR_FREEIF(name);
if (type_ret)
*type_ret = type;
else
PR_FREEIF(type);
return true;
}
static bool MimeUntypedText_uu_end_line_p(const char* line, int32_t length) {
#if 0
/* A strictly conforming uuencode end line. */
return (line[0] == 'e' &&
line[1] == 'n' &&
line[2] == 'd' &&
(line[3] == 0 || 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
}
static bool MimeUntypedText_yenc_begin_line_p(const char* line, int32_t length,
MimeDisplayOptions* opt,
char** type_ret,
char** name_ret) {
const char* s;
const char* endofline = line + length;
char* name = 0;
char* type = 0;
if (type_ret) *type_ret = 0;
if (name_ret) *name_ret = 0;
/* we don't support yenc V2 neither multipart yencode,
therefore the second parameter should always be "line="*/
if (length < 13 || strncmp(line, "=ybegin line=", 13)) return false;
/* ...then couple digits. */
for (s = line + 13; s < endofline; s++)
if (*s < '0' || *s > '9') break;
/* ...next, look for <space>size= */
if ((endofline - s) < 6 || strncmp(s, " size=", 6)) return false;
/* ...then couple digits. */
for (s += 6; s < endofline; s++)
if (*s < '0' || *s > '9') break;
/* ...next, look for <space>name= */
if ((endofline - s) < 6 || strncmp(s, " name=", 6)) return false;
/* anything left is the file name */
s += 6;
int name_len = endofline - s;
name = (char*)PR_MALLOC(name_len + 1);
if (!name) return false; /* grr... */
memcpy(name, s, name_len);
name[name_len] = 0;
if (name_len) {
/* take off newline. */
if (name_len && name[name_len - 1] == '\n') {
name[name_len] = 0;
name_len--;
}
if (name_len && name[name_len - 1] == '\r') {
name[name_len] = 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
PR_FREEIF(name);
if (type_ret)
*type_ret = type;
else
PR_FREEIF(type);
return true;
}
static bool MimeUntypedText_yenc_end_line_p(const char* line, int32_t length) {
if (length < 11 || strncmp(line, "=yend size=", 11)) return false;
return true;
}
#define BINHEX_MAGIC "(This file must be converted with BinHex 4.0)"
#define BINHEX_MAGIC_LEN 45
static bool MimeUntypedText_binhex_begin_line_p(const char* line,
int32_t length,
MimeDisplayOptions* opt) {
if (length <= BINHEX_MAGIC_LEN) return false;
while (length > 0 && IS_SPACE(line[length - 1])) length--;
if (length != BINHEX_MAGIC_LEN) return false;
if (!strncmp(line, BINHEX_MAGIC, BINHEX_MAGIC_LEN))
return true;
else
return false;
}
static bool MimeUntypedText_binhex_end_line_p(const char* line,
int32_t length) {
if (length > 0 && line[length - 1] == '\n') length--;
if (length > 0 && line[length - 1] == '\r') length--;
if (length != 0 && length != 64)
return true;
else
return false;
}