summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/mime/src/mimeenc.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--comm/mailnews/mime/src/mimeenc.cpp999
1 files changed, 999 insertions, 0 deletions
diff --git a/comm/mailnews/mime/src/mimeenc.cpp b/comm/mailnews/mime/src/mimeenc.cpp
new file mode 100644
index 0000000000..1c9df3794b
--- /dev/null
+++ b/comm/mailnews/mime/src/mimeenc.cpp
@@ -0,0 +1,999 @@
+/* -*- 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 <stdio.h>
+#include "mimei.h"
+#include "prmem.h"
+#include "mimeobj.h"
+#include "mozilla/RangedPtr.h"
+#include "mozilla/mailnews/MimeEncoder.h"
+#include "modmimee.h" // for MimeConverterOutputCallback
+
+typedef enum mime_encoding {
+ mime_Base64,
+ mime_QuotedPrintable,
+ mime_uuencode,
+ mime_yencode
+} mime_encoding;
+
+typedef enum mime_decoder_state {
+ DS_BEGIN,
+ DS_BODY,
+ DS_END
+} mime_decoder_state;
+
+struct MimeDecoderData {
+ mime_encoding encoding; /* Which encoding to use */
+
+ /* A read-buffer used for QP and B64. */
+ char token[4];
+ int token_size;
+
+ /* State and read-buffer used for uudecode and yencode. */
+ mime_decoder_state ds_state;
+ char* line_buffer;
+ int line_buffer_size;
+
+ MimeObject* objectToDecode; // might be null, only used for QP currently
+ /* Where to write the decoded data */
+ MimeConverterOutputCallback write_buffer;
+ void* closure;
+};
+
+static int mime_decode_qp_buffer(MimeDecoderData* data, const char* buffer,
+ int32_t length, int32_t* outSize) {
+ /* Warning, we are overwriting the buffer which was passed in.
+ This is ok, because decoding these formats will never result
+ in larger data than the input, only smaller. */
+ const char* in = buffer;
+ char* out = (char*)buffer;
+ char token[3];
+ int i;
+
+ NS_ASSERTION(data->encoding == mime_QuotedPrintable,
+ "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ if (data->encoding != mime_QuotedPrintable) return -1;
+
+ /* For the first pass, initialize the token from the unread-buffer. */
+ i = 0;
+ while (i < 3 && data->token_size > 0) {
+ token[i] = data->token[i];
+ data->token_size--;
+ i++;
+ }
+
+ /* #### BUG: when decoding quoted-printable, we are required to
+ strip trailing whitespace from lines -- since when encoding in
+ qp, one is required to quote such trailing whitespace, any
+ trailing whitespace which remains must have been introduced
+ by a stupid gateway. */
+
+ /* Treat null bytes as spaces when format_out is
+ nsMimeOutput::nsMimeMessageBodyDisplay (see bug 243199 comment 7) */
+ bool treatNullAsSpace =
+ data->objectToDecode && data->objectToDecode->options->format_out ==
+ nsMimeOutput::nsMimeMessageBodyDisplay;
+
+ while (length > 0 || i != 0) {
+ while (i < 3 && length > 0) {
+ token[i++] = *in;
+ in++;
+ length--;
+ }
+
+ if (i < 3) {
+ /* Didn't get enough for a complete token.
+ If it might be a token, unread it.
+ Otherwise, just dump it.
+ */
+ memcpy(data->token, token, i);
+ data->token_size = i;
+ i = 0;
+ length = 0;
+ break;
+ }
+ i = 0;
+
+ if (token[0] == '=') {
+ unsigned char c = 0;
+ if (token[1] >= '0' && token[1] <= '9')
+ c = token[1] - '0';
+ else if (token[1] >= 'A' && token[1] <= 'F')
+ c = token[1] - ('A' - 10);
+ else if (token[1] >= 'a' && token[1] <= 'f')
+ c = token[1] - ('a' - 10);
+ else if (token[1] == '\r' || token[1] == '\n') {
+ /* =\n means ignore the newline. */
+ if (token[1] == '\r' && token[2] == '\n')
+ ; /* swallow all three chars */
+ else {
+ in--; /* put the third char back */
+ length++;
+ }
+ continue;
+ } else {
+ /* = followed by something other than hex or newline -
+ pass it through unaltered, I guess. (But, if
+ this bogus token happened to occur over a buffer
+ boundary, we can't do this, since we don't have
+ space for it. Oh well. Screw it.) */
+ if (in > out) *out++ = token[0];
+ if (in > out) *out++ = token[1];
+ if (in > out) *out++ = token[2];
+ continue;
+ }
+
+ /* Second hex digit */
+ c = (c << 4);
+ if (token[2] >= '0' && token[2] <= '9')
+ c += token[2] - '0';
+ else if (token[2] >= 'A' && token[2] <= 'F')
+ c += token[2] - ('A' - 10);
+ else if (token[2] >= 'a' && token[2] <= 'f')
+ c += token[2] - ('a' - 10);
+ else {
+ /* We got =xy where "x" was hex and "y" was not, so
+ treat that as a literal "=", x, and y. (But, if
+ this bogus token happened to occur over a buffer
+ boundary, we can't do this, since we don't have
+ space for it. Oh well. Screw it.) */
+ if (in > out) *out++ = token[0];
+ if (in > out) *out++ = token[1];
+ if (in > out) *out++ = token[2];
+ continue;
+ }
+
+ *out++ = c ? (char)c : ((treatNullAsSpace) ? ' ' : (char)c);
+ } else {
+ *out++ = token[0];
+
+ token[0] = token[1];
+ token[1] = token[2];
+ i = 2;
+ }
+ }
+
+ // Fill the size
+ if (outSize) *outSize = out - buffer;
+
+ /* Now that we've altered the data in place, write it. */
+ if (out > buffer)
+ return data->write_buffer(buffer, (out - buffer), data->closure);
+ else
+ return 1;
+}
+
+static int mime_decode_base64_token(const char* in, char* out) {
+ /* reads 4, writes 0-3. Returns bytes written.
+ (Writes less than 3 only at EOF.) */
+ int j;
+ int eq_count = 0;
+ unsigned long num = 0;
+
+ for (j = 0; j < 4; j++) {
+ unsigned char c = 0;
+ if (in[j] >= 'A' && in[j] <= 'Z')
+ c = in[j] - 'A';
+ else if (in[j] >= 'a' && in[j] <= 'z')
+ c = in[j] - ('a' - 26);
+ else if (in[j] >= '0' && in[j] <= '9')
+ c = in[j] - ('0' - 52);
+ else if (in[j] == '+')
+ c = 62;
+ else if (in[j] == '/')
+ c = 63;
+ else if (in[j] == '=') {
+ c = 0;
+ eq_count++;
+ } else
+ NS_ERROR("Invalid character");
+ num = (num << 6) | c;
+ }
+
+ *out++ = (char)(num >> 16);
+ *out++ = (char)((num >> 8) & 0xFF);
+ *out++ = (char)(num & 0xFF);
+
+ if (eq_count == 0)
+ return 3; /* No "=" padding means 4 bytes mapped to 3. */
+ else if (eq_count == 1)
+ return 2; /* "xxx=" means 3 bytes mapped to 2. */
+ else if (eq_count == 2)
+ return 1; /* "xx==" means 2 bytes mapped to 1. */
+ else {
+ // "x===" can't happen, because "x" would then be encoding only
+ // 6 bits, not the min of 8.
+ NS_ERROR("Count is 6 bits, should be at least 8");
+ return 1;
+ }
+}
+
+static int mime_decode_base64_buffer(MimeDecoderData* data, const char* buffer,
+ int32_t length, int32_t* outSize) {
+ if (outSize) *outSize = 0;
+
+ // Without input there is nothing to do.
+ if (length == 0) return 1;
+
+ /* Warning, we are overwriting the buffer which was passed in.
+ This is ok, because decoding these formats will never result
+ in larger data than the input, only smaller. */
+ const char* in = buffer;
+ char* out = (char*)buffer;
+ char token[4];
+ int i;
+ bool leftover = (data->token_size > 0);
+
+ NS_ASSERTION(data->encoding == mime_Base64,
+ "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+
+ /* For the first pass, initialize the token from the unread-buffer. */
+ i = 0;
+ while (i < 4 && data->token_size > 0) {
+ token[i] = data->token[i];
+ data->token_size--;
+ i++;
+ }
+
+ while (length > 0) {
+ while (i < 4 && length > 0) {
+ if ((*in >= 'A' && *in <= 'Z') || (*in >= 'a' && *in <= 'z') ||
+ (*in >= '0' && *in <= '9') || *in == '+' || *in == '/' || *in == '=')
+ token[i++] = *in;
+ in++;
+ length--;
+ }
+
+ if (i < 4) {
+ /* Didn't get enough for a complete token. */
+ memcpy(data->token, token, i);
+ data->token_size = i;
+ length = 0;
+ break;
+ }
+ i = 0;
+
+ if (leftover) {
+ /* If there are characters left over from the last time around,
+ we might not have space in the buffer to do our dirty work
+ (if there were 2 or 3 left over, then there is only room for
+ 1 or 2 in the buffer right now, and we need 3.) This is only
+ a problem for the first chunk in each buffer, so in that
+ case, just write prematurely. */
+ int n;
+ n = mime_decode_base64_token(token, token);
+ if (outSize) *outSize += n;
+ n = data->write_buffer(token, n, data->closure);
+ if (n < 0) /* abort */
+ return n;
+
+ /* increment buffer so that we don't write the 1 or 2 unused
+ characters now at the front. */
+ buffer = in;
+ out = (char*)buffer;
+
+ leftover = false;
+ } else {
+ int n = mime_decode_base64_token(token, out);
+ /* Advance "out" by the number of bytes just written to it. */
+ out += n;
+ }
+ }
+
+ if (outSize) *outSize += out - buffer;
+ /* Now that we've altered the data in place, write it. */
+ if (out > buffer)
+ return data->write_buffer(buffer, (out - buffer), data->closure);
+ else
+ return 1;
+}
+
+static int mime_decode_uue_buffer(MimeDecoderData* data,
+ const char* input_buffer,
+ int32_t input_length, int32_t* outSize) {
+ /* First, copy input_buffer into state->line_buffer until we have
+ a complete line.
+
+ Then decode that line in place (in the line_buffer) and write
+ it out.
+
+ Then pull the next line into line_buffer and continue.
+ */
+ if (!data->line_buffer) {
+ data->line_buffer_size = 128;
+ data->line_buffer = (char*)PR_MALLOC(data->line_buffer_size);
+ if (!data->line_buffer) return -1;
+ data->line_buffer[0] = 0;
+ }
+
+ int status = 0;
+ char* line = data->line_buffer;
+ char* line_end = data->line_buffer + data->line_buffer_size - 1;
+
+ NS_ASSERTION(data->encoding == mime_uuencode,
+ "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ if (data->encoding != mime_uuencode) return -1;
+
+ if (data->ds_state == DS_END) {
+ status = 0;
+ goto DONE;
+ }
+
+ while (input_length > 0) {
+ /* Copy data from input_buffer to `line' until we have a complete line,
+ or until we've run out of input.
+
+ (line may have data in it already if the last time we were called,
+ we weren't called with a buffer that ended on a line boundary.)
+ */
+ {
+ char* out = line + strlen(line);
+ while (input_length > 0 && out < line_end) {
+ *out++ = *input_buffer++;
+ input_length--;
+
+ if (out[-1] == '\r' || out[-1] == '\n') {
+ /* If we just copied a CR, and an LF is waiting, grab it too.
+ */
+ if (out[-1] == '\r' && input_length > 0 && *input_buffer == '\n') {
+ input_buffer++;
+ input_length--;
+ }
+
+ /* We have a line. */
+ break;
+ }
+ }
+ *out = 0;
+
+ /* Ignore blank lines.
+ */
+ if (*line == '\r' || *line == '\n') {
+ *line = 0;
+ continue;
+ }
+
+ /* If this line was bigger than our buffer, truncate it.
+ (This means the data was way corrupted, and there's basically
+ no chance of decoding it properly, but give it a shot anyway.)
+ */
+ if (out == line_end) {
+ out--;
+ out[-1] = '\r';
+ out[0] = 0;
+ }
+
+ /* If we didn't get a complete line, simply return; we'll be called
+ with the rest of this line next time.
+ */
+ if (out[-1] != '\r' && out[-1] != '\n') {
+ NS_ASSERTION(input_length == 0,
+ "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ break;
+ }
+ }
+
+ /* Now we have a complete line. Deal with it.
+ */
+
+ if (data->ds_state == DS_BODY && line[0] == 'e' && line[1] == 'n' &&
+ line[2] == 'd' && (line[3] == '\r' || line[3] == '\n')) {
+ /* done! */
+ data->ds_state = DS_END;
+ *line = 0;
+ break;
+ } else if (data->ds_state == DS_BEGIN) {
+ if (!strncmp(line, "begin ", 6)) data->ds_state = DS_BODY;
+ *line = 0;
+ continue;
+ } else {
+ /* We're in DS_BODY. Decode the line. */
+ char *in, *out;
+ int32_t i;
+ long lost;
+
+ NS_ASSERTION(data->ds_state == DS_BODY,
+ "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+
+ /* We map down `line', reading four bytes and writing three.
+ That means that `out' always stays safely behind `in'.
+ */
+ in = line;
+ out = line;
+
+#undef DEC
+#define DEC(c) (((c) - ' ') & 077)
+ i = DEC(*in); /* get length */
+
+ /* all the parens and casts are because gcc was doing something evil.
+ */
+ lost = ((long)i) - (((((long)strlen(in)) - 2L) * 3L) / 4L);
+
+ if (lost > 0) /* Short line!! */
+ {
+ /* If we get here, then the line is shorter than the length byte
+ at the beginning says it should be. However, the case where
+ the line is short because it was at the end of the buffer and
+ we didn't get the whole line was handled earlier (up by the
+ "didn't get a complete line" comment.) So if we've gotten
+ here, then this is a complete line which is internally
+ inconsistent. We will parse from it what we can...
+
+ This probably happened because some gateway stripped trailing
+ whitespace from the end of the line -- so pretend the line
+ was padded with spaces (which map to \000.)
+ */
+ i -= lost;
+ }
+
+ for (++in; i > 0; in += 4, i -= 3) {
+ char ch;
+ NS_ASSERTION(out <= in, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+
+ if (i >= 3) {
+ /* We read four; write three. */
+ ch = DEC(in[0]) << 2 | DEC(in[1]) >> 4;
+ *out++ = ch;
+
+ NS_ASSERTION(out <= in + 1,
+ "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+
+ ch = DEC(in[1]) << 4 | DEC(in[2]) >> 2;
+ *out++ = ch;
+
+ NS_ASSERTION(out <= in + 2,
+ "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+
+ ch = DEC(in[2]) << 6 | DEC(in[3]);
+ *out++ = ch;
+
+ NS_ASSERTION(out <= in + 3,
+ "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ } else {
+ /* Handle a line that isn't a multiple of 4 long.
+ (We read 1, 2, or 3, and will write 1 or 2.)
+ */
+ NS_ASSERTION(i > 0 && i < 3,
+ "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+
+ ch = DEC(in[0]) << 2 | DEC(in[1]) >> 4;
+ *out++ = ch;
+
+ NS_ASSERTION(out <= in + 1,
+ "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+
+ if (i == 2) {
+ ch = DEC(in[1]) << 4 | DEC(in[2]) >> 2;
+ *out++ = ch;
+
+ NS_ASSERTION(out <= in + 2,
+ "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ }
+ }
+ }
+
+ /* If the line was truncated, pad the missing bytes with 0 (SPC). */
+ while (lost > 0) {
+ *out++ = 0;
+ lost--;
+ in = out + 1; /* just to prevent the assert, below. */
+ }
+#undef DEC
+
+ /* Now write out what we decoded for this line.
+ */
+ NS_ASSERTION(out >= line && out < in,
+ "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ if (out > line)
+ status = data->write_buffer(line, (out - line), data->closure);
+
+ // The assertion above tells us this is >= 0
+ if (outSize) *outSize = out - line;
+
+ /* Reset the line so that we don't think it's partial next time. */
+ *line = 0;
+
+ if (status < 0) /* abort */
+ goto DONE;
+ }
+ }
+
+ status = 1;
+
+DONE:
+
+ return status;
+}
+
+static int mime_decode_yenc_buffer(MimeDecoderData* data,
+ const char* input_buffer,
+ int32_t input_length, int32_t* outSize) {
+ /* First, copy input_buffer into state->line_buffer until we have
+ a complete line.
+
+ Then decode that line in place (in the line_buffer) and write
+ it out.
+
+ Then pull the next line into line_buffer and continue.
+ */
+ if (!data->line_buffer) {
+ data->line_buffer_size =
+ 1000; // let make sure we have plenty of space for the header line
+ data->line_buffer = (char*)PR_MALLOC(data->line_buffer_size);
+ if (!data->line_buffer) return -1;
+ data->line_buffer[0] = 0;
+ }
+
+ int status = 0;
+ char* line = data->line_buffer;
+ char* line_end = data->line_buffer + data->line_buffer_size - 1;
+
+ NS_ASSERTION(data->encoding == mime_yencode, "wrong decoder!");
+ if (data->encoding != mime_yencode) return -1;
+
+ if (data->ds_state == DS_END) return 0;
+
+ while (input_length > 0) {
+ /* Copy data from input_buffer to `line' until we have a complete line,
+ or until we've run out of input.
+
+ (line may have data in it already if the last time we were called,
+ we weren't called with a buffer that ended on a line boundary.)
+ */
+ {
+ char* out = line + strlen(line);
+ while (input_length > 0 && out < line_end) {
+ *out++ = *input_buffer++;
+ input_length--;
+
+ if (out[-1] == '\r' || out[-1] == '\n') {
+ /* If we just copied a CR, and an LF is waiting, grab it too. */
+ if (out[-1] == '\r' && input_length > 0 && *input_buffer == '\n') {
+ input_buffer++;
+ input_length--;
+ }
+
+ /* We have a line. */
+ break;
+ }
+ }
+ *out = 0;
+
+ /* Ignore blank lines. */
+ if (*line == '\r' || *line == '\n') {
+ *line = 0;
+ continue;
+ }
+
+ /* If this line was bigger than our buffer, truncate it.
+ (This means the data was way corrupted, and there's basically
+ no chance of decoding it properly, but give it a shot anyway.)
+ */
+ if (out == line_end) {
+ out--;
+ out[-1] = '\r';
+ out[0] = 0;
+ }
+
+ /* If we didn't get a complete line, simply return; we'll be called
+ with the rest of this line next time.
+ */
+ if (out[-1] != '\r' && out[-1] != '\n') {
+ NS_ASSERTION(input_length == 0, "empty buffer!");
+ break;
+ }
+ }
+
+ /* Now we have a complete line. Deal with it.
+ */
+ const char* endOfLine = line + strlen(line);
+
+ if (data->ds_state == DS_BEGIN) {
+ int new_line_size = 0;
+ /* this yenc decoder does not support yenc v2 or multipart yenc.
+ Therefore, we are looking first for "=ybegin line="
+ */
+ if ((endOfLine - line) >= 13 && !strncmp(line, "=ybegin line=", 13)) {
+ /* ...then couple digits. */
+ for (line += 13; line < endOfLine; line++) {
+ if (*line < '0' || *line > '9') break;
+ new_line_size = (new_line_size * 10) + *line - '0';
+ }
+
+ /* ...next, look for <space>size= */
+ if ((endOfLine - line) >= 6 && !strncmp(line, " size=", 6)) {
+ /* ...then couple digits. */
+ for (line += 6; line < endOfLine; line++)
+ if (*line < '0' || *line > '9') break;
+
+ /* ...next, look for <space>name= */
+ if ((endOfLine - line) >= 6 && !strncmp(line, " name=", 6)) {
+ /* we have found the yenc header line.
+ Now check if we need to grow our buffer line
+ */
+ data->ds_state = DS_BODY;
+ if (new_line_size > data->line_buffer_size &&
+ new_line_size <= 997) /* don't let bad value hurt us! */
+ {
+ PR_Free(data->line_buffer);
+ data->line_buffer_size =
+ new_line_size +
+ 4; // extra chars for line ending and potential escape char
+ data->line_buffer = (char*)PR_MALLOC(data->line_buffer_size);
+ if (!data->line_buffer) return -1;
+ }
+ }
+ }
+ }
+ *data->line_buffer = 0;
+ continue;
+ }
+
+ if (data->ds_state == DS_BODY && line[0] == '=') {
+ /* look if this this the final line */
+ if (!strncmp(line, "=yend size=", 11)) {
+ /* done! */
+ data->ds_state = DS_END;
+ *line = 0;
+ break;
+ }
+ }
+
+ /* We're in DS_BODY. Decode the line in place. */
+ {
+ char* src = line;
+ char* dest = src;
+ char c;
+ for (; src < line_end; src++) {
+ c = *src;
+ if (!c || c == '\r' || c == '\n') break;
+
+ if (c == '=') {
+ src++;
+ c = *src;
+ if (c == 0) return -1; /* last character cannot be escape char */
+ c -= 64;
+ }
+ c -= 42;
+ *dest = c;
+ dest++;
+ }
+
+ // The assertion below is helpful, too
+ if (outSize) *outSize = dest - line;
+
+ /* Now write out what we decoded for this line. */
+ NS_ASSERTION(dest >= line && dest <= src, "nothing to write!");
+ if (dest > line) {
+ status = data->write_buffer(line, dest - line, data->closure);
+ if (status < 0) /* abort */
+ return status;
+ }
+
+ /* Reset the line so that we don't think it's partial next time. */
+ *line = 0;
+ }
+ }
+
+ return 1;
+}
+
+int MimeDecoderDestroy(MimeDecoderData* data, bool abort_p) {
+ int status = 0;
+ /* Flush out the last few buffered characters. */
+ if (!abort_p && data->token_size > 0 && data->token[0] != '=') {
+ if (data->encoding == mime_Base64)
+ while ((unsigned int)data->token_size < sizeof(data->token))
+ data->token[data->token_size++] = '=';
+
+ status = data->write_buffer(data->token, data->token_size, data->closure);
+ }
+
+ if (data->line_buffer) PR_Free(data->line_buffer);
+ PR_Free(data);
+ return status;
+}
+
+static MimeDecoderData* mime_decoder_init(mime_encoding which,
+ MimeConverterOutputCallback output_fn,
+ void* closure) {
+ MimeDecoderData* data = PR_NEW(MimeDecoderData);
+ if (!data) return 0;
+ memset(data, 0, sizeof(*data));
+ data->encoding = which;
+ data->write_buffer = output_fn;
+ data->closure = closure;
+ data->line_buffer_size = 0;
+ data->line_buffer = nullptr;
+
+ return data;
+}
+
+MimeDecoderData* MimeB64DecoderInit(MimeConverterOutputCallback output_fn,
+ void* closure) {
+ return mime_decoder_init(mime_Base64, output_fn, closure);
+}
+
+MimeDecoderData* MimeQPDecoderInit(MimeConverterOutputCallback output_fn,
+ void* closure, MimeObject* object) {
+ MimeDecoderData* retData =
+ mime_decoder_init(mime_QuotedPrintable, output_fn, closure);
+ if (retData) retData->objectToDecode = object;
+ return retData;
+}
+
+MimeDecoderData* MimeUUDecoderInit(MimeConverterOutputCallback output_fn,
+ void* closure) {
+ return mime_decoder_init(mime_uuencode, output_fn, closure);
+}
+
+MimeDecoderData* MimeYDecoderInit(MimeConverterOutputCallback output_fn,
+ void* closure) {
+ return mime_decoder_init(mime_yencode, output_fn, closure);
+}
+
+int MimeDecoderWrite(MimeDecoderData* data, const char* buffer, int32_t size,
+ int32_t* outSize) {
+ NS_ASSERTION(data, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ if (!data) return -1;
+ switch (data->encoding) {
+ case mime_Base64:
+ return mime_decode_base64_buffer(data, buffer, size, outSize);
+ case mime_QuotedPrintable:
+ return mime_decode_qp_buffer(data, buffer, size, outSize);
+ case mime_uuencode:
+ return mime_decode_uue_buffer(data, buffer, size, outSize);
+ case mime_yencode:
+ return mime_decode_yenc_buffer(data, buffer, size, outSize);
+ default:
+ NS_ERROR("Invalid decoding");
+ return -1;
+ }
+}
+
+namespace mozilla {
+namespace mailnews {
+
+MimeEncoder::MimeEncoder(OutputCallback callback, void* closure)
+ : mCallback(callback), mClosure(closure), mCurrentColumn(0) {}
+
+class Base64Encoder : public MimeEncoder {
+ unsigned char in_buffer[3];
+ int32_t in_buffer_count;
+
+ public:
+ Base64Encoder(OutputCallback callback, void* closure)
+ : MimeEncoder(callback, closure), in_buffer_count(0) {}
+ virtual ~Base64Encoder() {}
+
+ virtual nsresult Write(const char* buffer, int32_t size) override;
+ virtual nsresult Flush() override;
+
+ private:
+ static void Base64EncodeBits(RangedPtr<char>& out, uint32_t bits);
+};
+
+nsresult Base64Encoder::Write(const char* buffer, int32_t size) {
+ if (size == 0)
+ return NS_OK;
+ else if (size < 0) {
+ NS_ERROR("Size is less than 0");
+ return NS_ERROR_FAILURE;
+ }
+
+ // If this input buffer is too small, wait until next time.
+ if (size < (3 - in_buffer_count)) {
+ NS_ASSERTION(size == 1 || size == 2, "Unexpected size");
+ in_buffer[in_buffer_count++] = buffer[0];
+ if (size == 2) in_buffer[in_buffer_count++] = buffer[1];
+ NS_ASSERTION(in_buffer_count < 3, "Unexpected out buffer size");
+ return NS_OK;
+ }
+
+ // If there are bytes that were put back last time, take them now.
+ uint32_t i = in_buffer_count, bits = 0;
+ if (in_buffer_count > 0) bits = in_buffer[0];
+ if (in_buffer_count > 1) bits = (bits << 8) + in_buffer[1];
+ in_buffer_count = 0;
+
+ // If this buffer is not a multiple of three, put one or two bytes back.
+ uint32_t excess = ((size + i) % 3);
+ if (excess) {
+ in_buffer[0] = buffer[size - excess];
+ if (excess > 1) in_buffer[1] = buffer[size - excess + 1];
+ in_buffer_count = excess;
+ size -= excess;
+ NS_ASSERTION(!((size + i) % 3), "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ }
+
+ const uint8_t* in = (const uint8_t*)buffer;
+ const uint8_t* end = (const uint8_t*)(buffer + size);
+ MOZ_ASSERT((end - in + i) % 3 == 0, "Need a multiple of 3 bytes to decode");
+
+ // Populate the out_buffer with base64 data, one line at a time.
+ char out_buffer[80]; // Max line length will be 80, so this is safe.
+ RangedPtr<char> out(out_buffer);
+ while (in < end) {
+ // Accumulate the input bits.
+ while (i < 3) {
+ bits = (bits << 8) | *in++;
+ i++;
+ }
+ i = 0;
+
+ Base64EncodeBits(out, bits);
+
+ mCurrentColumn += 4;
+ if (mCurrentColumn >= 72) {
+ // Do a linebreak before column 76. Flush out the line buffer.
+ mCurrentColumn = 0;
+ *out++ = '\x0D';
+ *out++ = '\x0A';
+ nsresult rv = mCallback(out_buffer, (out.get() - out_buffer), mClosure);
+ NS_ENSURE_SUCCESS(rv, rv);
+ out = out_buffer;
+ }
+ }
+
+ // Write out the unwritten portion of the last line buffer.
+ if (out.get() > out_buffer) {
+ nsresult rv = mCallback(out_buffer, out.get() - out_buffer, mClosure);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult Base64Encoder::Flush() {
+ if (in_buffer_count == 0) return NS_OK;
+
+ // Since we need to some buffering to get a multiple of three bytes on each
+ // block, there may be a few bytes left in the buffer after the last block has
+ // been written. We need to flush those out now.
+ char buf[4];
+ RangedPtr<char> out(buf);
+ uint32_t bits = ((uint32_t)in_buffer[0]) << 16;
+ if (in_buffer_count > 1) bits |= (((uint32_t)in_buffer[1]) << 8);
+
+ Base64EncodeBits(out, bits);
+
+ // Pad with equal-signs.
+ if (in_buffer_count == 1) buf[2] = '=';
+ buf[3] = '=';
+
+ return mCallback(buf, 4, mClosure);
+}
+
+void Base64Encoder::Base64EncodeBits(RangedPtr<char>& out, uint32_t bits) {
+ // Convert 3 bytes to 4 base64 bytes
+ for (int32_t j = 18; j >= 0; j -= 6) {
+ unsigned int k = (bits >> j) & 0x3F;
+ if (k < 26)
+ *out++ = k + 'A';
+ else if (k < 52)
+ *out++ = k - 26 + 'a';
+ else if (k < 62)
+ *out++ = k - 52 + '0';
+ else if (k == 62)
+ *out++ = '+';
+ else if (k == 63)
+ *out++ = '/';
+ else
+ MOZ_CRASH("6 bits should only be between 0 and 64");
+ }
+}
+
+class QPEncoder : public MimeEncoder {
+ public:
+ QPEncoder(OutputCallback callback, void* closure)
+ : MimeEncoder(callback, closure) {}
+ virtual ~QPEncoder() {}
+
+ virtual nsresult Write(const char* buffer, int32_t size) override;
+};
+
+nsresult QPEncoder::Write(const char* buffer, int32_t size) {
+ nsresult rv = NS_OK;
+ static const char* hexdigits = "0123456789ABCDEF";
+ char out_buffer[80];
+ RangedPtr<char> out(out_buffer);
+ bool white = false;
+
+ // Populate the out_buffer with quoted-printable data, one line at a time.
+ const uint8_t* in = (uint8_t*)buffer;
+ const uint8_t* end = in + size;
+ for (; in < end; in++) {
+ if (*in == '\r' || *in == '\n') {
+ // If it's CRLF, swallow two chars instead of one.
+ if (in + 1 < end && in[0] == '\r' && in[1] == '\n') in++;
+
+ // Whitespace cannot be allowed to occur at the end of the line, so we
+ // back up and replace the whitespace with its code.
+ if (white) {
+ out--;
+ char whitespace_char = *out;
+ *out++ = '=';
+ *out++ = hexdigits[whitespace_char >> 4];
+ *out++ = hexdigits[whitespace_char & 0xF];
+ }
+
+ // Now write out the newline.
+ *out++ = '\r';
+ *out++ = '\n';
+ white = false;
+
+ rv = mCallback(out_buffer, out.get() - out_buffer, mClosure);
+ NS_ENSURE_SUCCESS(rv, rv);
+ out = out_buffer;
+ mCurrentColumn = 0;
+ } else if (mCurrentColumn == 0 && *in == '.') {
+ // Just to be SMTP-safe, if "." appears in column 0, encode it.
+ goto HEX;
+ } else if (mCurrentColumn == 0 && *in == 'F' &&
+ (in >= end - 1 || in[1] == 'r') &&
+ (in >= end - 2 || in[2] == 'o') &&
+ (in >= end - 3 || in[3] == 'm') &&
+ (in >= end - 4 || in[4] == ' ')) {
+ // If this line begins with "From " (or it could but we don't have enough
+ // data in the buffer to be certain), encode the 'F' in hex to avoid
+ // potential problems with BSD mailbox formats.
+ goto HEX;
+ } else if ((*in >= 33 && *in <= 60) |
+ (*in >= 62 &&
+ *in <= 126)) // Printable characters except for '='
+ {
+ white = false;
+ *out++ = *in;
+ mCurrentColumn++;
+ } else if (*in == ' ' || *in == '\t') // Whitespace
+ {
+ white = true;
+ *out++ = *in;
+ mCurrentColumn++;
+ } else {
+ // Encode the characters here
+ HEX:
+ white = false;
+ *out++ = '=';
+ *out++ = hexdigits[*in >> 4];
+ *out++ = hexdigits[*in & 0xF];
+ mCurrentColumn += 3;
+ }
+
+ MOZ_ASSERT(mCurrentColumn <= 76, "Why haven't we added a line break yet?");
+
+ if (mCurrentColumn >= 73) // Soft line break for readability
+ {
+ *out++ = '=';
+ *out++ = '\r';
+ *out++ = '\n';
+
+ rv = mCallback(out_buffer, out.get() - out_buffer, mClosure);
+ NS_ENSURE_SUCCESS(rv, rv);
+ out = out_buffer;
+ white = false;
+ mCurrentColumn = 0;
+ }
+ }
+
+ // Write out the unwritten portion of the last line buffer.
+ if (out.get() != out_buffer) {
+ rv = mCallback(out_buffer, out.get() - out_buffer, mClosure);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+MimeEncoder* MimeEncoder::GetBase64Encoder(OutputCallback callback,
+ void* closure) {
+ return new Base64Encoder(callback, closure);
+}
+
+MimeEncoder* MimeEncoder::GetQPEncoder(OutputCallback callback, void* closure) {
+ return new QPEncoder(callback, closure);
+}
+
+} // namespace mailnews
+} // namespace mozilla