diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /comm/mailnews/mime/src/mimeenc.cpp | |
parent | Initial commit. (diff) | |
download | thunderbird-upstream.tar.xz thunderbird-upstream.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/mailnews/mime/src/mimeenc.cpp')
-rw-r--r-- | comm/mailnews/mime/src/mimeenc.cpp | 999 |
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 |