//
// aegis - project change supervisor
// Copyright (C) 2005, 2006, 2008-2010, 2012, 2014 Peter Miller
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or (at
// your option) any later version.
//
// This program 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
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
rfc822::~rfc822()
{
}
rfc822::rfc822()
{
database.set_reaper();
}
rfc822::rfc822(const rfc822 &arg)
{
database.set_reaper();
database = arg.database;
}
rfc822 &
rfc822::operator=(const rfc822 &arg)
{
if (this != &arg)
database = arg.database;
return *this;
}
nstring
rfc822::sanitize_name(const nstring &name)
{
nstring_accumulator acc;
const char *cp = name.c_str();
for (;;)
{
unsigned char c = *cp++;
switch (c)
{
case 0:
while (acc.back() == '-')
acc.pop_back();
if (acc.empty())
acc.push_back("empty");
return acc.mkstr();
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
case '_':
case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
case 'g': case 'h': case 'i': case 'j': case 'k': case 'l':
case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':
case 's': case 't': case 'u': case 'v': case 'w': case 'x':
case 'y': case 'z':
acc.push_back(c);
break;
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
case 'S': case 'T': case 'U': case 'V': case 'W': case 'X':
case 'Y': case 'Z':
acc.push_back(tolower(c));
break;
default:
if (!acc.empty() && acc.back() != '-')
acc.push_back('-');
break;
}
}
}
void
rfc822::set(const nstring &name, const nstring &value)
{
//
// Make sure the name is RFC 822 compliant.
//
database.assign(sanitize_name(name), value);
}
void
rfc822::set(const nstring &name, const char *value)
{
set(name, nstring(value));
}
void
rfc822::set_minimalist(const nstring &name, const nstring &value)
{
nstring saname = sanitize_name(name);
if (value.empty())
database.remove(saname);
else
database.assign(saname, value);
}
void
rfc822::set(const nstring &name, bool value)
{
set(name, bool_to_string(value));
}
void
rfc822::set(const nstring &name, long value)
{
set(name, nstring::format("%ld", value));
}
void
rfc822::set(const nstring &name, unsigned long value)
{
set(name, nstring::format("%lu", value));
}
void
rfc822::set_off_t(const nstring &name, off_t value)
{
set(name, nstring::format(OFF_T_FMT, value));
}
const nstring &
rfc822::get(const nstring &name)
{
//
// Look in the database for the named item. The names are all
// case-insensitive and are stored in lower case.
//
nstring lc_name(sanitize_name(name));
nstring *value_p = database.query(lc_name);
if (!value_p)
{
//
// Default it to the empty string.
//
database.assign(lc_name, nstring());
value_p = database.query(lc_name);
assert(value_p);
}
//
// Return the value found.
//
return *value_p;
}
nstring
rfc822::get(const nstring &name)
const
{
//
// Look in the database for the named item. The names are all
// case-insensitive and are stored in lower case.
//
nstring lc_name(sanitize_name(name));
nstring *value_p = database.query(lc_name);
return (value_p ? *value_p : "");
}
long
rfc822::get_long(const nstring &name)
{
const nstring &value = get(name);
return strtol(value.c_str(), 0, 10);
}
unsigned long
rfc822::get_ulong(const nstring &name)
{
const nstring &value = get(name);
return strtoul(value.c_str(), 0, 10);
}
off_t
rfc822::get_off_t(const nstring &name)
{
const nstring &value = get(name);
off_t result;
#if _FILE_OFFSET_BITS == 64
result = strtoll(value.c_str(), 0, 10);
#else
result = strtoul(value.c_str(), 0, 10);
#endif
return result;
}
static bool
has_a_header(const input::pointer &ifp)
{
trace(("has_a_header()\n{\n"));
bool result = false;
nstring_accumulator buffer;
//
// MH (a mail handler) has a tendancy to add a line of the form
// (Message :)
// to the start of the message. This is particularly irritating
// when you want to say
// show | aepatch -rec
//
// Some other mail readers add a
// From ...
// to the start of the message. Toss these, too.
//
for (;;)
{
int c = ifp->getch();
if (c < 0)
break;
buffer.push_back(c);
if (c == '\n')
break;
}
int length_of_garbage = 0;
if
(
buffer.size() >= 14
&&
0 == memcmp("(Message ", buffer.get_data(), 9)
&&
buffer[buffer.size() - 2] == ')'
)
{
// it's a garbage MH line, toss it
length_of_garbage = buffer.size();
}
else if (buffer.size() >= 6 && 0 == memcmp("From ", buffer.get_data(), 5))
{
// it's a garbage From line, toss it
length_of_garbage = buffer.size();
}
else
{
// give the line back
ifp->unread(buffer.get_data(), buffer.size());
buffer.clear();
}
//
// Now look for a regular RFC 822 header.
//
// State 0: start of line
// State 1: seen valid name characters
// State 2: seen colon
// State 3: finished (see "result")
//
int state = 0;
while (state < 3 && buffer.size() < 80)
{
int c = ifp->getch();
if (c >= 0)
buffer.push_back(c);
if (c < 0)
{
state = 3;
break;
}
switch ((unsigned char)c)
{
case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g':
case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n':
case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u':
case 'v': case 'w': case 'x': case 'y': case 'z':
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N':
case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U':
case 'V': case 'W': case 'X': case 'Y': case 'Z':
//
// When this read
// if (state == 0 || state == 1)
// state = 1;
// else
// state = 3;
// gcc 2.96 got this function wrong when you said -O2
//
if (state == 0)
state = 1;
else if (state != 1)
state = 3;
break;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
case '-':
if (state != 1)
state = 3;
break;
case ':':
if (state == 1)
state = 2;
else
state = 3;
break;
case ' ':
if (state == 2)
result = true;
state = 3;
break;
default:
state = 3;
length_of_garbage = 0;
break;
}
}
ifp->unread
(
buffer.get_data() + length_of_garbage,
buffer.size() - length_of_garbage
);
trace(("return %d\n", result));
trace(("}\n"));
return result;
}
static bool
is_binary(const input::pointer &ifp)
{
//
// We are looking for NULs, because gzipped files have
// NULs in the header. This function is only used to fake a
// Content-Transfer-Encoding line in there case where there is
// no header to tell us.
//
bool result = false;
nstring_accumulator buffer;
while (buffer.size() < 800)
{
int c = ifp->getch();
if (c < 0)
break;
buffer.push_back(c);
if (c == 0)
{
result = true;
break;
}
}
ifp->unread(buffer.get_data(), buffer.size());
return result;
}
void
rfc822::load(const input::pointer &source, bool maybe_not)
{
trace(("rfc822::load()\n{\n"));
//
// If there is no 822 header, return an empty symbol table.
//
// Note: when has_a_header returns, no header lines have been
// consumed, EXCEPT that garbage from MH and some mail readers has
// been removed.
//
if (maybe_not && !has_a_header(source))
{
trace(("no header\n"));
if (is_binary(source))
{
trace(("looks like raw binary\n"));
}
else if (input_base64::candidate(source))
{
trace(("looks like base64\n"));
set(nstring("content-transfer-encoding"), nstring("base64"));
}
else if (input_uudecode::candidate(source))
{
trace(("looks like uuencode\n"));
set(nstring("content-transfer-encoding"), nstring("uuencode"));
}
trace(("}\n"));
return;
}
//
// Do the end-of-line wrapping transparently.
//
trace(("we expect to see a header\n"));
input::pointer ifp2 = input_crlf::create(source);
input::pointer ifp = input_822wrap::create(ifp2);
//
// We are looking for RFC822 style header lines.
// When we see a blank line, we stop.
//
nstring_accumulator name;
nstring_accumulator value;
for (;;)
{
int c = ifp->getch();
if (c < 0 || c == '\n')
break;
name.clear();
for (;;)
{
switch ((unsigned char)c)
{
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
case 'S': case 'T': case 'U': case 'V': case 'W': case 'X':
case 'Y': case 'Z':
case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
case 'g': case 'h': case 'i': case 'j': case 'k': case 'l':
case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':
case 's': case 't': case 'u': case 'v': case 'w': case 'x':
case 'y': case 'z':
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
case '_': case '-':
name.push_back(c);
c = ifp->getch();
if (c < 0)
goto malformed;
continue;
default:
break;
}
break;
}
trace(("name = \"%s\"\n", name.mkstr().c_str()));
if (name.empty() || c != ':')
{
malformed:
sub_context_ty sc;
sc.var_set_string("File_Name", ifp->name());
sc.fatal_intl(i18n("$filename: malformed RFC 822 header line"));
// NOTREACHED
}
//
// Skip white space between the colon and the start of the
// value. (There is no way to have a value which consists
// entirely of white space.)
//
for (;;)
{
c = ifp->getch();
if (c < 0 || c == '\n' || !isspace((unsigned char)c))
break;
}
//
// Read the value.
//
value.clear();
for (;;)
{
if (c < 0)
break;
if (c == '\n')
break;
value.push_back(c);
c = ifp->getch();
}
//
// Remove trailing white space.
//
while (isspace((unsigned char)value.back()))
value.pop_back();
trace(("value = \"%s\"\n", value.mkstr().c_str()));
//
// Stash the name-value pair in the database. This means later
// lines silently over-ride earlier lines. We aren't expecting
// duplicates, but some uses of this class can take advantage of
// this behaviour.
//
set(name.mkstr(), value.mkstr());
}
trace(("}\n"));
}
void
rfc822::load_from_file(const nstring &filename)
{
input::pointer in = input_file::open(filename, false, true);
load(in);
}
void
rfc822::store(output::pointer dest)
{
nstring_list names;
database.keys(names);
names.sort();
for (size_t j = 0; j < names.size(); ++j)
{
const nstring &name = names[j];
dest << name.capitalize() << ": ";
//
// We have to make sure that the value is written in an RFC822
// compliant format. That means we have to be careful of
// newlines embedded in the value and make sure they are always
// followed by a space or a tab.
//
const nstring &value = get(name);
const char *cp = value.c_str();
const char *end = cp + value.size();
while (end > cp && isspace((unsigned char)end[-1]))
--end;
while (cp < end)
{
unsigned char c = *cp++;
dest->fputc(c);
if (c == '\n' && *cp != ' ' && *cp != '\t')
dest->fputc(' ');
}
dest->fputc('\n');
}
dest->fputc('\n');
}
void
rfc822::store_to_file(const nstring &filename)
{
output::pointer os = output_file::text_open(filename);
store(os);
}
nstring
rfc822::date(void)
{
time_t now;
time(&now);
struct tm *now_tm = localtime(&now);
char buffer[100];
strftime(buffer, sizeof(buffer), "%a, %e %b %Y %H:%M:%S %z", now_tm);
return buffer;
}
bool
rfc822::is_set(const nstring &name)
const
{
//
// Look in the database for the named item. The names are all
// case-insensitive and are stored in lower case.
//
nstring lc_name(sanitize_name(name));
nstring *value_p = database.query(lc_name);
return (value_p != 0);
}
void
rfc822::clear(void)
{
database.clear();
}
// vim: set ts=8 sw=4 et :