/*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*
* This library is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see .
*
* Authors: Michael Zucchi
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include "camel-mime-filter-bestenc.h"
#define CAMEL_MIME_FILTER_BESTENC_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE \
((obj), CAMEL_TYPE_MIME_FILTER_BESTENC, CamelMimeFilterBestencPrivate))
struct _CamelMimeFilterBestencPrivate {
guint flags; /* our creation flags */
guint count0; /* count of NUL characters */
guint count8; /* count of 8 bit characters */
guint total; /* total characters read */
guint lastc; /* the last character read */
gint crlfnoorder; /* if crlf's occurred where they shouldn't have */
gint startofline; /* are we at the start of a new line? */
gint fromcount;
gchar fromsave[6]; /* save a few characters if we found an \n near the end of the buffer */
gint hadfrom; /* did we encounter a "\nFrom " in the data? */
guint countline; /* current count of characters on a given line */
guint maxline; /* max length of any line */
CamelCharset charset; /* used to determine the best charset to use */
};
G_DEFINE_TYPE (CamelMimeFilterBestenc, camel_mime_filter_bestenc, CAMEL_TYPE_MIME_FILTER)
static void
mime_filter_bestenc_filter (CamelMimeFilter *mime_filter,
const gchar *in,
gsize len,
gsize prespace,
gchar **out,
gsize *outlen,
gsize *outprespace)
{
CamelMimeFilterBestencPrivate *priv;
register guchar *p, *pend;
priv = CAMEL_MIME_FILTER_BESTENC_GET_PRIVATE (mime_filter);
if (len == 0)
goto donothing;
if (priv->flags & CAMEL_BESTENC_GET_ENCODING) {
register guint /* hopefully reg's are assinged in the order they appear? */
c,
lastc = priv->lastc,
countline = priv->countline,
count0 = priv->count0,
count8 = priv->count8;
/* Check ^From lines first call, or have the start of a new line waiting? */
if ((priv->flags & CAMEL_BESTENC_NO_FROM) && !priv->hadfrom
&& (priv->fromcount > 0 || priv->startofline)) {
if (priv->fromcount + len >=5) {
memcpy (&priv->fromsave[priv->fromcount], in, 5 - priv->fromcount);
priv->hadfrom = strncmp (priv->fromsave, "From ", 5) == 0;
priv->fromcount = 0;
} else {
memcpy (&priv->fromsave[priv->fromcount], in, len);
priv->fromcount += len;
}
}
priv->startofline = FALSE;
/* See rfc2045 section 2 for definitions of 7bit/8bit/binary */
p = (guchar *) in;
pend = p + len;
while (p < pend) {
c = *p++;
/* check for 8 bit characters */
if (c & 0x80)
count8++;
/* check for nul's */
if (c == 0)
count0++;
/* check for wild '\r's in a unix format stream */
if (c == '\r' && (priv->flags & CAMEL_BESTENC_LF_IS_CRLF)) {
priv->crlfnoorder = TRUE;
}
/* check for end of line */
if (c == '\n') {
/* check for wild '\n's in canonical format stream */
if (lastc == '\r' || (priv->flags & CAMEL_BESTENC_LF_IS_CRLF)) {
if (countline > priv->maxline)
priv->maxline = countline;
countline = 0;
/* Check for "^From " lines */
if ((priv->flags & CAMEL_BESTENC_NO_FROM) && !priv->hadfrom) {
if (pend - p >= 5) {
priv->hadfrom = strncmp ((gchar *) p, (gchar *) "From ", 5) == 0;
} else if (pend - p == 0) {
priv->startofline = TRUE;
} else {
priv->fromcount = pend - p;
memcpy (priv->fromsave, p, pend - p);
}
}
} else {
priv->crlfnoorder = TRUE;
}
} else {
countline++;
}
lastc = c;
}
priv->count8 = count8;
priv->count0 = count0;
priv->countline = countline;
priv->lastc = lastc;
}
priv->total += len;
if (priv->flags & CAMEL_BESTENC_GET_CHARSET)
camel_charset_step (&priv->charset, in, len);
donothing:
*out = (gchar *) in;
*outlen = len;
*outprespace = prespace;
}
static void
mime_filter_bestenc_complete (CamelMimeFilter *mime_filter,
const gchar *in,
gsize len,
gsize prespace,
gchar **out,
gsize *outlen,
gsize *outprespace)
{
CamelMimeFilterBestencPrivate *priv;
priv = CAMEL_MIME_FILTER_BESTENC_GET_PRIVATE (mime_filter);
mime_filter_bestenc_filter (
mime_filter, in, len, prespace, out, outlen, outprespace);
if (priv->countline > priv->maxline)
priv->maxline = priv->countline;
priv->countline = 0;
}
static void
mime_filter_bestenc_reset (CamelMimeFilter *mime_filter)
{
CamelMimeFilterBestencPrivate *priv;
priv = CAMEL_MIME_FILTER_BESTENC_GET_PRIVATE (mime_filter);
priv->count0 = 0;
priv->count8 = 0;
priv->countline = 0;
priv->total = 0;
priv->lastc = ~0;
priv->crlfnoorder = FALSE;
priv->fromcount = 0;
priv->hadfrom = FALSE;
priv->startofline = TRUE;
camel_charset_init (&priv->charset);
}
static void
camel_mime_filter_bestenc_class_init (CamelMimeFilterBestencClass *class)
{
CamelMimeFilterClass *mime_filter_class;
g_type_class_add_private (class, sizeof (CamelMimeFilterBestencPrivate));
mime_filter_class = CAMEL_MIME_FILTER_CLASS (class);
mime_filter_class->filter = mime_filter_bestenc_filter;
mime_filter_class->complete = mime_filter_bestenc_complete;
mime_filter_class->reset = mime_filter_bestenc_reset;
}
static void
camel_mime_filter_bestenc_init (CamelMimeFilterBestenc *filter)
{
filter->priv = CAMEL_MIME_FILTER_BESTENC_GET_PRIVATE (filter);
mime_filter_bestenc_reset (CAMEL_MIME_FILTER (filter));
}
/**
* camel_mime_filter_bestenc_new:
* @flags: a bitmask of data required.
*
* Create a new #CamelMimeFilterBestenc object.
*
* Returns: a new #CamelMimeFilterBestenc object
**/
CamelMimeFilter *
camel_mime_filter_bestenc_new (guint flags)
{
CamelMimeFilter *new;
new = g_object_new (CAMEL_TYPE_MIME_FILTER_BESTENC, NULL);
CAMEL_MIME_FILTER_BESTENC (new)->priv->flags = flags;
return new;
}
/**
* camel_mime_filter_bestenc_get_best_encoding:
* @filter: a #CamelMimeFilterBestenc object
* @required: maximum level of output encoding allowed.
*
* Get the best encoding, given specific constraints, that can be used to
* encode a stream of bytes.
*
* Returns: the best encoding to use
**/
CamelTransferEncoding
camel_mime_filter_bestenc_get_best_encoding (CamelMimeFilterBestenc *filter,
CamelBestencEncoding required)
{
CamelMimeFilterBestencPrivate *priv;
CamelTransferEncoding bestenc;
gint istext;
priv = CAMEL_MIME_FILTER_BESTENC_GET_PRIVATE (filter);
istext = (required & CAMEL_BESTENC_TEXT) ? 1 : 0;
required = required & ~CAMEL_BESTENC_TEXT;
#if 0
printf ("count0 = %d, count8 = %d, total = %d\n", priv->count0, priv->count8, priv->total);
printf ("maxline = %d, crlfnoorder = %s\n", priv->maxline, priv->crlfnoorder?"TRUE":"FALSE");
printf (" %d%% require encoding?\n", (priv->count0 + priv->count8) * 100 / priv->total);
#endif
/* if we're not allowed to have From lines and we had one, use an encoding
* that will never let it show. Unfortunately only base64 can at present,
* although qp could be modified to allow it too */
if ((priv->flags & CAMEL_BESTENC_NO_FROM) && priv->hadfrom)
return CAMEL_TRANSFER_ENCODING_BASE64;
/* if we need to encode, see how we do it */
if (required == CAMEL_BESTENC_BINARY)
bestenc = CAMEL_TRANSFER_ENCODING_BINARY;
else if (istext && (priv->count0 == 0 && priv->count8 < (priv->total * 17 / 100)))
bestenc = CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE;
else
bestenc = CAMEL_TRANSFER_ENCODING_BASE64;
/* if we have nocrlf order, or long lines, we need to encode always */
if (priv->crlfnoorder || priv->maxline >= 998)
return bestenc;
/* if we have no 8 bit chars or nul's, we can just use 7 bit */
if (priv->count8 + priv->count0 == 0)
return CAMEL_TRANSFER_ENCODING_7BIT;
/* otherwise, we see if we can use 8 bit, or not */
switch (required) {
case CAMEL_BESTENC_7BIT:
return bestenc;
case CAMEL_BESTENC_8BIT:
case CAMEL_BESTENC_BINARY:
default:
if (priv->count0 == 0)
return CAMEL_TRANSFER_ENCODING_8BIT;
else
return bestenc;
}
}
/**
* camel_mime_filter_bestenc_get_best_charset:
* @filter: a #CamelMimeFilterBestenc object
*
* Gets the best charset that can be used to contain this content.
*
* Returns: the name of the best charset to use to encode the input
* text filtered by @filter
**/
const gchar *
camel_mime_filter_bestenc_get_best_charset (CamelMimeFilterBestenc *filter)
{
g_return_val_if_fail (CAMEL_IS_MIME_FILTER_BESTENC (filter), NULL);
return camel_charset_best_name (&filter->priv->charset);
}
/**
* camel_mime_filter_bestenc_set_flags:
* @filter: a #CamelMimeFilterBestenc object
* @flags: bestenc filter flags
*
* Set the flags for subsequent operations.
**/
void
camel_mime_filter_bestenc_set_flags (CamelMimeFilterBestenc *filter,
guint flags)
{
g_return_if_fail (CAMEL_IS_MIME_FILTER_BESTENC (filter));
filter->priv->flags = flags;
}