зеркало из https://github.com/mozilla/gecko-dev.git
1117 строки
27 KiB
C
1117 строки
27 KiB
C
/* -*- 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.0 (the "NPL"); you may not use this file except in
|
||
* compliance with the NPL. You may obtain a copy of the NPL at
|
||
* http://www.mozilla.org/NPL/
|
||
*
|
||
* Software distributed under the NPL is distributed on an "AS IS" basis,
|
||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
|
||
* for the specific language governing rights and limitations under the
|
||
* NPL.
|
||
*
|
||
* The Initial Developer of this code under the NPL is Netscape
|
||
* Communications Corporation. Portions created by Netscape are
|
||
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
|
||
* Reserved.
|
||
*/
|
||
|
||
/* mimeenc.c --- MIME encoders and decoders, version 2.
|
||
Created: Jamie Zawinski <jwz@netscape.com>, 15-May-96.
|
||
*/
|
||
|
||
|
||
#include "mimeenc.h"
|
||
#include "mimei.h"
|
||
|
||
typedef enum mime_encoding {
|
||
mime_Base64, mime_QuotedPrintable, mime_uuencode
|
||
} mime_encoding;
|
||
|
||
typedef enum mime_uue_state {
|
||
UUE_BEGIN, UUE_BODY, UUE_END
|
||
} mime_uue_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. */
|
||
mime_uue_state uue_state;
|
||
char uue_line_buffer [128];
|
||
|
||
/* Where to write the decoded data */
|
||
int (*write_buffer) (const char *buf, int32 size, void *closure);
|
||
void *closure;
|
||
};
|
||
|
||
|
||
static int
|
||
mime_decode_qp_buffer (MimeDecoderData *data, const char *buffer, int32 length)
|
||
{
|
||
/* 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;
|
||
|
||
XP_ASSERT(data->encoding == mime_QuotedPrintable);
|
||
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. */
|
||
|
||
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.
|
||
*/
|
||
XP_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] == CR || token[1] == LF)
|
||
{
|
||
/* =\n means ignore the newline. */
|
||
if (token[1] == CR && token[2] == LF)
|
||
; /* 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++ = (char) c;
|
||
}
|
||
else
|
||
{
|
||
*out++ = token[0];
|
||
|
||
token[0] = token[1];
|
||
token[1] = token[2];
|
||
i = 2;
|
||
}
|
||
}
|
||
|
||
/* 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
|
||
XP_ASSERT(0);
|
||
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 */
|
||
XP_ASSERT(0); /* be encoding only 6 bits, not the min of 8. */
|
||
return 1;
|
||
}
|
||
}
|
||
|
||
|
||
static int
|
||
mime_decode_base64_buffer (MimeDecoderData *data,
|
||
const char *buffer, int32 length)
|
||
{
|
||
/* 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;
|
||
XP_Bool leftover = (data->token_size > 0);
|
||
|
||
XP_ASSERT(data->encoding == mime_Base64);
|
||
|
||
/* 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. */
|
||
XP_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);
|
||
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;
|
||
}
|
||
}
|
||
|
||
/* 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 input_length)
|
||
{
|
||
/* First, copy input_buffer into state->uue_line_buffer until we have
|
||
a complete line.
|
||
|
||
Then decode that line in place (in the uue_line_buffer) and write
|
||
it out.
|
||
|
||
Then pull the next line into uue_line_buffer and continue.
|
||
*/
|
||
int status = 0;
|
||
char *line = data->uue_line_buffer;
|
||
char *line_end = data->uue_line_buffer + sizeof (data->uue_line_buffer) - 1;
|
||
|
||
XP_ASSERT(data->encoding == mime_uuencode);
|
||
if (data->encoding != mime_uuencode) return -1;
|
||
|
||
if (data->uue_state == UUE_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 + XP_STRLEN(line);
|
||
while (input_length > 0 &&
|
||
out < line_end)
|
||
{
|
||
*out++ = *input_buffer++;
|
||
input_length--;
|
||
|
||
if (out[-1] == CR || out[-1] == LF)
|
||
{
|
||
/* If we just copied a CR, and an LF is waiting, grab it too.
|
||
*/
|
||
if (out[-1] == CR &&
|
||
input_length > 0 &&
|
||
*input_buffer == LF)
|
||
input_buffer++, input_length--;
|
||
|
||
/* We have a line. */
|
||
break;
|
||
}
|
||
}
|
||
*out = 0;
|
||
|
||
/* Ignore blank lines.
|
||
*/
|
||
if (*line == CR || *line == LF)
|
||
{
|
||
*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] = CR;
|
||
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] != CR && out[-1] != LF)
|
||
{
|
||
XP_ASSERT (input_length == 0);
|
||
break;
|
||
}
|
||
}
|
||
|
||
|
||
/* Now we have a complete line. Deal with it.
|
||
*/
|
||
|
||
|
||
if (data->uue_state == UUE_BODY &&
|
||
line[0] == 'e' &&
|
||
line[1] == 'n' &&
|
||
line[2] == 'd' &&
|
||
(line[3] == CR ||
|
||
line[3] == LF))
|
||
{
|
||
/* done! */
|
||
data->uue_state = UUE_END;
|
||
*line = 0;
|
||
break;
|
||
}
|
||
else if (data->uue_state == UUE_BEGIN)
|
||
{
|
||
if (!XP_STRNCMP (line, "begin ", 6))
|
||
data->uue_state = UUE_BODY;
|
||
*line = 0;
|
||
continue;
|
||
}
|
||
else
|
||
{
|
||
/* We're in UUE_BODY. Decode the line. */
|
||
char *in, *out;
|
||
int32 i;
|
||
long lost;
|
||
|
||
XP_ASSERT (data->uue_state == UUE_BODY);
|
||
|
||
/* 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) XP_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;
|
||
XP_ASSERT(out <= in);
|
||
|
||
if (i >= 3)
|
||
{
|
||
/* We read four; write three. */
|
||
ch = DEC (in[0]) << 2 | DEC (in[1]) >> 4;
|
||
*out++ = ch;
|
||
|
||
XP_ASSERT(out <= in+1);
|
||
|
||
ch = DEC (in[1]) << 4 | DEC (in[2]) >> 2;
|
||
*out++ = ch;
|
||
|
||
XP_ASSERT(out <= in+2);
|
||
|
||
ch = DEC (in[2]) << 6 | DEC (in[3]);
|
||
*out++ = ch;
|
||
|
||
XP_ASSERT(out <= in+3);
|
||
}
|
||
else
|
||
{
|
||
/* Handle a line that isn't a multiple of 4 long.
|
||
(We read 1, 2, or 3, and will write 1 or 2.)
|
||
*/
|
||
XP_ASSERT (i > 0 && i < 3);
|
||
|
||
ch = DEC (in[0]) << 2 | DEC (in[1]) >> 4;
|
||
*out++ = ch;
|
||
|
||
XP_ASSERT(out <= in+1);
|
||
|
||
if (i == 2)
|
||
{
|
||
ch = DEC (in[1]) << 4 | DEC (in[2]) >> 2;
|
||
*out++ = ch;
|
||
|
||
XP_ASSERT(out <= in+2);
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 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.
|
||
*/
|
||
XP_ASSERT(out >= line && out < in);
|
||
if (out > line)
|
||
status = data->write_buffer (line, (out - line), data->closure);
|
||
|
||
/* 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;
|
||
}
|
||
|
||
|
||
int
|
||
MimeDecoderDestroy (MimeDecoderData *data, XP_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 (data->token_size < sizeof (data->token))
|
||
data->token [data->token_size++] = '=';
|
||
|
||
status = data->write_buffer (data->token, data->token_size,
|
||
data->closure);
|
||
}
|
||
|
||
XP_FREE (data);
|
||
return status;
|
||
}
|
||
|
||
|
||
static MimeDecoderData *
|
||
mime_decoder_init (enum mime_encoding which,
|
||
int (*output_fn) (const char *, int32, void *),
|
||
void *closure)
|
||
{
|
||
MimeDecoderData *data = XP_NEW(MimeDecoderData);
|
||
if (!data) return 0;
|
||
XP_MEMSET(data, 0, sizeof(*data));
|
||
data->encoding = which;
|
||
data->write_buffer = output_fn;
|
||
data->closure = closure;
|
||
return data;
|
||
}
|
||
|
||
MimeDecoderData *
|
||
MimeB64DecoderInit (int (*output_fn) (const char *, int32, void *),
|
||
void *closure)
|
||
{
|
||
return mime_decoder_init (mime_Base64, output_fn, closure);
|
||
}
|
||
|
||
MimeDecoderData *
|
||
MimeQPDecoderInit (int (*output_fn) (const char *, int32, void *),
|
||
void *closure)
|
||
{
|
||
return mime_decoder_init (mime_QuotedPrintable, output_fn, closure);
|
||
}
|
||
|
||
MimeDecoderData *
|
||
MimeUUDecoderInit (int (*output_fn) (const char *, int32, void *),
|
||
void *closure)
|
||
{
|
||
return mime_decoder_init (mime_uuencode, output_fn, closure);
|
||
}
|
||
|
||
int
|
||
MimeDecoderWrite (MimeDecoderData *data, const char *buffer, int32 size)
|
||
{
|
||
XP_ASSERT(data);
|
||
if (!data) return -1;
|
||
switch(data->encoding)
|
||
{
|
||
case mime_Base64:
|
||
return mime_decode_base64_buffer (data, buffer, size);
|
||
case mime_QuotedPrintable:
|
||
return mime_decode_qp_buffer (data, buffer, size);
|
||
case mime_uuencode:
|
||
return mime_decode_uue_buffer (data, buffer, size);
|
||
default:
|
||
XP_ASSERT(0);
|
||
return -1;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
/* ================== Encoders.
|
||
*/
|
||
|
||
struct MimeEncoderData {
|
||
mime_encoding encoding; /* Which encoding to use */
|
||
|
||
/* Buffer for the base64 encoder. */
|
||
unsigned char in_buffer[3];
|
||
int32 in_buffer_count;
|
||
|
||
/* Buffer for uuencoded data. (Need a line because of the length byte.) */
|
||
unsigned char uue_line_buf[128];
|
||
XP_Bool uue_wrote_begin;
|
||
|
||
int32 current_column, line_byte_count;
|
||
|
||
char *filename; /* filename for use with uuencoding */
|
||
|
||
/* Where to write the encoded data */
|
||
int (*write_buffer) (const char *buf, int32 size, void *closure);
|
||
void *closure;
|
||
};
|
||
|
||
/* Use what looks like a nice, safe value for a standard uue line length */
|
||
#define UUENCODE_LINE_LIMIT 60
|
||
|
||
#undef ENC
|
||
#define ENC(c) ((c & 0x3F) + ' ')
|
||
|
||
void
|
||
mime_uuencode_write_line(MimeEncoderData *data)
|
||
{
|
||
/* Set the length byte at the beginning:
|
||
encoded (data->line_byte_count). */
|
||
data->uue_line_buf[0] = ENC(data->line_byte_count);
|
||
|
||
/* Tack a CRLF onto the end. */
|
||
data->uue_line_buf[data->current_column++] = CR;
|
||
data->uue_line_buf[data->current_column++] = LF;
|
||
|
||
/* Write the line to output. */
|
||
data->write_buffer((const char*)data->uue_line_buf, data->current_column,
|
||
data->closure);
|
||
|
||
/* Reset data based on having just written a complete line. */
|
||
data->in_buffer_count = 0;
|
||
data->line_byte_count = 0;
|
||
data->current_column = 1;
|
||
}
|
||
|
||
void
|
||
mime_uuencode_convert_triplet(MimeEncoderData *data)
|
||
{
|
||
/*
|
||
If we have 3 bytes, encode them and add them to the current
|
||
line. The way we want to encode them is like this
|
||
(each digit corresponds to a bit in the binary source):
|
||
11111111 -> 00111111 + ' ' (six highest bits of 1)
|
||
22222222 00112222 + ' ' (low 2 of 1, high 4 of 2)
|
||
33333333 00222233 + ' ' (low 4 of 2, high 2 of 3)
|
||
00333333 + ' ' (low 6 of 3)
|
||
*/
|
||
char outData[4];
|
||
int i;
|
||
|
||
outData[0] = data->in_buffer[0] >> 2;
|
||
|
||
outData[1] = ((data->in_buffer[0] << 4) & 0x30);
|
||
outData[1] |= data->in_buffer[1] >> 4;
|
||
|
||
outData[2] = ((data->in_buffer[1] << 2) & 0x3C);
|
||
outData[2] |= data->in_buffer[2] >> 6;
|
||
|
||
outData[3] = data->in_buffer[2] & 0x3F;
|
||
|
||
for(i=0;i<4;i++)
|
||
data->uue_line_buf[data->current_column++] = ENC(outData[i]);
|
||
|
||
data->in_buffer_count = 0;
|
||
}
|
||
|
||
int
|
||
mime_uuencode_buffer(MimeEncoderData *data,
|
||
const char *buffer, int32 size)
|
||
{
|
||
/* If this is the first time through, write a begin statement. */
|
||
if (!(data->uue_wrote_begin))
|
||
{
|
||
char firstLine[256];
|
||
XP_SPRINTF(firstLine, "begin 644 %s\015\012", data->filename ? data->filename : "");
|
||
data->write_buffer(firstLine, strlen(firstLine), data->closure);
|
||
data->uue_wrote_begin = TRUE;
|
||
data->current_column = 1; /* initialization unique to uuencode */
|
||
}
|
||
|
||
/* Pick up where we left off. */
|
||
while(size > 0)
|
||
{
|
||
/* If we've reached the end of a line, write the line out. */
|
||
if (data->current_column >= UUENCODE_LINE_LIMIT)
|
||
{
|
||
/* End of a line. Write the line out. */
|
||
mime_uuencode_write_line(data);
|
||
}
|
||
|
||
/* Get the next 3 bytes if we have them, or whatever we can get. */
|
||
while(size > 0 && data->in_buffer_count < 3)
|
||
{
|
||
data->in_buffer[data->in_buffer_count++] = *(buffer++);
|
||
size--; data->line_byte_count++;
|
||
}
|
||
|
||
if (data->in_buffer_count == 3)
|
||
{
|
||
mime_uuencode_convert_triplet(data);
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
int
|
||
mime_uuencode_finish(MimeEncoderData *data)
|
||
{
|
||
int i;
|
||
static const char *endStr = "end\015\012";
|
||
|
||
/* If we have converted binary data to write to output, do it now. */
|
||
if (data->line_byte_count > 0)
|
||
{
|
||
/* If we have binary data yet to be converted,
|
||
pad and convert it. */
|
||
if (data->in_buffer_count > 0)
|
||
{
|
||
for(i=data->in_buffer_count;i<3;i++)
|
||
data->in_buffer[i] = '\0'; /* pad with zeroes */
|
||
|
||
mime_uuencode_convert_triplet(data);
|
||
}
|
||
|
||
mime_uuencode_write_line(data);
|
||
}
|
||
|
||
/* Write 'end' on a line by itself. */
|
||
return data->write_buffer(endStr, strlen(endStr), data->closure);
|
||
}
|
||
|
||
#undef ENC
|
||
|
||
int
|
||
mime_encode_base64_buffer (MimeEncoderData *data,
|
||
const char *buffer, int32 size)
|
||
{
|
||
int status = 0;
|
||
const unsigned char *in = (unsigned char *) buffer;
|
||
const unsigned char *end = in + size;
|
||
char out_buffer[80];
|
||
char *out = out_buffer;
|
||
uint32 i = 0, n = 0;
|
||
uint32 off;
|
||
|
||
if (size == 0)
|
||
return 0;
|
||
else if (size < 0)
|
||
{
|
||
XP_ASSERT(0);
|
||
return -1;
|
||
}
|
||
|
||
|
||
/* If this input buffer is too small, wait until next time. */
|
||
if (size < (3 - data->in_buffer_count))
|
||
{
|
||
XP_ASSERT(size < 3 && size > 0);
|
||
data->in_buffer[data->in_buffer_count++] = buffer[0];
|
||
if (size > 1)
|
||
data->in_buffer[data->in_buffer_count++] = buffer[1];
|
||
XP_ASSERT(data->in_buffer_count < 3);
|
||
return 0;
|
||
}
|
||
|
||
|
||
/* If there are bytes that were put back last time, take them now.
|
||
*/
|
||
i = 0;
|
||
if (data->in_buffer_count > 0) n = data->in_buffer[0];
|
||
if (data->in_buffer_count > 1) n = (n << 8) + data->in_buffer[1];
|
||
i = data->in_buffer_count;
|
||
data->in_buffer_count = 0;
|
||
|
||
/* If this buffer is not a multiple of three, put one or two bytes back.
|
||
*/
|
||
off = ((size + i) % 3);
|
||
if (off)
|
||
{
|
||
data->in_buffer[0] = buffer [size - off];
|
||
if (off > 1)
|
||
data->in_buffer [1] = buffer [size - off + 1];
|
||
data->in_buffer_count = off;
|
||
size -= off;
|
||
XP_ASSERT (! ((size + i) % 3));
|
||
end = (unsigned char *) (buffer + size);
|
||
}
|
||
|
||
/* Populate the out_buffer with base64 data, one line at a time.
|
||
*/
|
||
while (in < end)
|
||
{
|
||
int32 j;
|
||
|
||
while (i < 3)
|
||
{
|
||
n = (n << 8) | *in++;
|
||
i++;
|
||
}
|
||
i = 0;
|
||
|
||
for (j = 18; j >= 0; j -= 6)
|
||
{
|
||
unsigned int k = (n >> 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 abort ();
|
||
}
|
||
|
||
data->current_column += 4;
|
||
if (data->current_column >= 72)
|
||
{
|
||
/* Do a linebreak before column 76. Flush out the line buffer. */
|
||
data->current_column = 0;
|
||
*out++ = '\015';
|
||
*out++ = '\012';
|
||
status = data->write_buffer (out_buffer, (out - out_buffer),
|
||
data->closure);
|
||
out = out_buffer;
|
||
if (status < 0) return status;
|
||
}
|
||
}
|
||
|
||
/* Write out the unwritten portion of the last line buffer. */
|
||
if (out > out_buffer)
|
||
{
|
||
status = data->write_buffer (out_buffer, (out - out_buffer),
|
||
data->closure);
|
||
if (status < 0) return status;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
int
|
||
mime_encode_qp_buffer (MimeEncoderData *data, const char *buffer, int32 size)
|
||
{
|
||
int status = 0;
|
||
static const char hexdigits[] = "0123456789ABCDEF";
|
||
const unsigned char *in = (unsigned char *) buffer;
|
||
const unsigned char *end = in + size;
|
||
char out_buffer[80];
|
||
char *out = out_buffer;
|
||
uint32 i = 0, n = 0;
|
||
XP_Bool white = FALSE;
|
||
XP_Bool mb_p = FALSE;
|
||
|
||
/*
|
||
#### I don't know how to hook this back up:
|
||
#### mb_p = INTL_DefaultWinCharSetID(state->context) & 0x300 ;
|
||
*/
|
||
|
||
|
||
XP_ASSERT(data->in_buffer_count == 0);
|
||
|
||
/* Populate the out_buffer with quoted-printable data, one line at a time.
|
||
*/
|
||
for (; in < end; in++)
|
||
{
|
||
if (*in == CR || *in == LF)
|
||
{
|
||
/* Whitespace cannot be allowed to occur at the end of the line.
|
||
So we encode " \n" as " =\n\n", that is, the whitespace, a
|
||
soft line break, and then a hard line break.
|
||
*/
|
||
if (white)
|
||
{
|
||
*out++ = '=';
|
||
*out++ = CR;
|
||
*out++ = LF;
|
||
}
|
||
|
||
/* Now write out the newline. */
|
||
*out++ = CR;
|
||
*out++ = LF;
|
||
white = FALSE;
|
||
|
||
status = data->write_buffer (out_buffer, (out - out_buffer),
|
||
data->closure);
|
||
if (status < 0) return status;
|
||
out = out_buffer;
|
||
|
||
/* If its CRLF, swallow two chars instead of one. */
|
||
if (in[0] == CR && in[1] == LF)
|
||
in++;
|
||
|
||
out = out_buffer;
|
||
white = FALSE;
|
||
data->current_column = 0;
|
||
}
|
||
else if (data->current_column == 0 && *in == '.')
|
||
{
|
||
/* Just to be SMTP-safe, if "." appears in column 0, encode it.
|
||
(mmencode does this too.)
|
||
*/
|
||
goto HEX;
|
||
}
|
||
else if (data->current_column == 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 'F' and we cannot determine that
|
||
this line does not begin with "From " then do the safe thing
|
||
and assume that it does, and encode the 'F' in hex to avoid
|
||
BSD mailbox lossage. (We might not be able to tell that it
|
||
is really "From " if the end of the buffer was early. So
|
||
this means that "\nFoot" will have the F encoded if the end of
|
||
the buffer happens to fall just after the F; but will not have
|
||
it encoded if it's after the first "o" or later. Oh well.
|
||
It's a little inconsistent, but it errs on the safe side.)
|
||
*/
|
||
goto HEX;
|
||
}
|
||
else if ((*in >= 33 && *in <= 60) || /* safe printing chars */
|
||
(*in >= 62 && *in <= 126) ||
|
||
(mb_p && (*in == 61 || *in == 127 || *in == 0x1B)))
|
||
{
|
||
white = FALSE;
|
||
*out++ = *in;
|
||
data->current_column++;
|
||
}
|
||
else if (*in == ' ' || *in == '\t') /* whitespace */
|
||
{
|
||
white = TRUE;
|
||
*out++ = *in;
|
||
data->current_column++;
|
||
}
|
||
else /* print as =FF */
|
||
{
|
||
HEX:
|
||
white = FALSE;
|
||
*out++ = '=';
|
||
*out++ = hexdigits[*in >> 4];
|
||
*out++ = hexdigits[*in & 0xF];
|
||
data->current_column += 3;
|
||
}
|
||
|
||
XP_ASSERT (data->current_column <= 76); /* Hard limit required by spec */
|
||
|
||
if (data->current_column >= 73) /* soft line break: "=\r\n" */
|
||
{
|
||
*out++ = '=';
|
||
*out++ = CR;
|
||
*out++ = LF;
|
||
|
||
status = data->write_buffer (out_buffer, (out - out_buffer),
|
||
data->closure);
|
||
if (status < 0) return status;
|
||
out = out_buffer;
|
||
white = FALSE;
|
||
data->current_column = 0;
|
||
}
|
||
}
|
||
|
||
/* Write out the unwritten portion of the last line buffer. */
|
||
if (out > out_buffer)
|
||
{
|
||
status = data->write_buffer (out_buffer, (out - out_buffer),
|
||
data->closure);
|
||
if (status < 0) return status;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
|
||
int
|
||
MimeEncoderDestroy (MimeEncoderData *data, XP_Bool abort_p)
|
||
{
|
||
int status = 0;
|
||
|
||
/* If we're uuencoding, we have our own finishing routine. */
|
||
if (data->encoding == mime_uuencode)
|
||
mime_uuencode_finish(data);
|
||
|
||
/* Since Base64 (and uuencode) output needs to do 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.
|
||
*/
|
||
|
||
XP_ASSERT (data->encoding == mime_Base64 ||
|
||
data->in_buffer_count == 0);
|
||
|
||
if (!abort_p &&
|
||
data->in_buffer_count > 0)
|
||
{
|
||
char buf2 [6];
|
||
char *buf = buf2 + 2;
|
||
char *out = buf;
|
||
int j;
|
||
/* fixed bug 55998, 61302, 61866
|
||
* type casting to uint32 before shifting
|
||
*/
|
||
uint32 n = ((uint32) data->in_buffer[0]) << 16;
|
||
if (data->in_buffer_count > 1)
|
||
n = n | (((uint32) data->in_buffer[1]) << 8);
|
||
|
||
buf2[0] = CR;
|
||
buf2[1] = LF;
|
||
|
||
for (j = 18; j >= 0; j -= 6)
|
||
{
|
||
unsigned int k = (n >> 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 abort ();
|
||
}
|
||
|
||
/* Pad with equal-signs. */
|
||
if (data->in_buffer_count == 1)
|
||
buf[2] = '=';
|
||
buf[3] = '=';
|
||
|
||
if (data->current_column >= 72)
|
||
status = data->write_buffer (buf2, 6, data->closure);
|
||
else
|
||
status = data->write_buffer (buf, 4, data->closure);
|
||
}
|
||
|
||
XP_FREEIF(data->filename);
|
||
XP_FREE (data);
|
||
return status;
|
||
}
|
||
|
||
|
||
static MimeEncoderData *
|
||
mime_encoder_init (enum mime_encoding which,
|
||
int (*output_fn) (const char *, int32, void *),
|
||
void *closure)
|
||
{
|
||
MimeEncoderData *data = XP_NEW(MimeEncoderData);
|
||
if (!data) return 0;
|
||
XP_MEMSET(data, 0, sizeof(*data));
|
||
data->encoding = which;
|
||
data->write_buffer = output_fn;
|
||
data->closure = closure;
|
||
return data;
|
||
}
|
||
|
||
MimeEncoderData *
|
||
MimeB64EncoderInit (int (*output_fn) (const char *, int32, void *),
|
||
void *closure)
|
||
{
|
||
return mime_encoder_init (mime_Base64, output_fn, closure);
|
||
}
|
||
|
||
MimeEncoderData *
|
||
MimeQPEncoderInit (int (*output_fn) (const char *, int32, void *),
|
||
void *closure)
|
||
{
|
||
return mime_encoder_init (mime_QuotedPrintable, output_fn, closure);
|
||
}
|
||
|
||
MimeEncoderData *
|
||
MimeUUEncoderInit (char *filename,
|
||
int (*output_fn) (const char *, int32, void *),
|
||
void *closure)
|
||
{
|
||
MimeEncoderData *enc = mime_encoder_init (mime_uuencode, output_fn, closure);
|
||
|
||
if (filename)
|
||
enc->filename = XP_STRDUP(filename);
|
||
|
||
return enc;
|
||
}
|
||
|
||
int
|
||
MimeEncoderWrite (MimeEncoderData *data, const char *buffer, int32 size)
|
||
{
|
||
XP_ASSERT(data);
|
||
if (!data) return -1;
|
||
switch(data->encoding)
|
||
{
|
||
case mime_Base64:
|
||
return mime_encode_base64_buffer (data, buffer, size);
|
||
case mime_QuotedPrintable:
|
||
return mime_encode_qp_buffer (data, buffer, size);
|
||
case mime_uuencode:
|
||
return mime_uuencode_buffer(data, buffer, size);
|
||
default:
|
||
XP_ASSERT(0);
|
||
return -1;
|
||
}
|
||
}
|