Logo Search packages:      
Sourcecode: pan version File versions  Download package

gnksa.c

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * Pan - A Newsreader for Gtk+
 * Copyright (C) 2002  Charles Kerr <charles@rebelbase.com>
 *
 * 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; version 2 of the License.
 *
 * 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*********************
**********************  Includes
*********************/

#include <config.h>

#include <ctype.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <time.h>

#include <glib.h>

#include <unistd.h>

#include <pan/base/debug.h>
#include <pan/base/gnksa.h>
#include <pan/base/pan-i18n.h>
#include <pan/base/pan-glib-extensions.h>

/*********************
**********************  BEGINNING OF SOURCE
*********************/

#define is_code_char(A) (_code_chars[(guchar)(A)])
#define is_tag_char(A) (_tag_chars[(guchar)(A)])
#define is_unquoted_char(A) (_unquoted_chars[(guchar)(A)])
#define is_quoted_char(A) (_quoted_chars[(guchar)(A)])
#define is_paren_char(A) (_paren_chars[(guchar)(A)])

#if 0
#define PRINT_TABLE(A) \
      printf ("static char " #A "[UCHAR_MAX] = {"); \
      for (i=0; i<UCHAR_MAX; ++i) { \
            if (!(i%40)) \
                  printf ("\n\t"); \
            printf ("%d,", A[i]); \
      } \
      printf ("};\n\n");

static char _unquoted_chars[UCHAR_MAX];
static char _quoted_chars[UCHAR_MAX];
static char _tag_chars[UCHAR_MAX];
static char _code_chars[UCHAR_MAX];
static char _paren_chars[UCHAR_MAX];

void
gnksa_init (void)
{
      int i;
      unsigned char ch;

      for (ch=0; ch<UCHAR_MAX; ++ch) {
            _unquoted_chars[ch] = isgraph(ch) && ch!='!' && ch!='(' && ch!=')' && ch!='<'
                                              && ch!='>' && ch!='@' && ch!=',' && ch!=';'
                                              && ch!=':' && ch!='\\' && ch!='"' && ch!='.'
                                              && ch!='[' && ch!=']';
            _quoted_chars[ch] = isgraph(ch) && ch!='"' && ch!='<' && ch!='>' && ch!='\\';
            _tag_chars[ch] = isgraph(ch) && ch!='!' && ch!='(' && ch!=')' && ch!='<'
                                         && ch!='>' && ch!='@' && ch!=',' && ch!=';'
                                         && ch!=':' && ch!='\\' && ch!='"' && ch!='['
                                         && ch!=']' && ch!='/' && ch!='?' && ch!='=';
            _paren_chars[ch] = isgraph(ch) && ch!='(' && ch!=')'
                                           && ch!='<' && ch!='>'
                                           && ch!='\\';
            _code_chars[ch] = isgraph(ch) && ch!='?';
      }

      PRINT_TABLE(_unquoted_chars)
      PRINT_TABLE(_quoted_chars)
      PRINT_TABLE(_tag_chars)
      PRINT_TABLE(_code_chars)
      PRINT_TABLE(_paren_chars)
}
#else

static char _unquoted_chars[UCHAR_MAX] = {
      0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,
      0,0,1,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
      1,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
      1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
      0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
      0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
      0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,};

static char _quoted_chars[UCHAR_MAX] = {
      0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,1,1,1,
      1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
      1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
      1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
      0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
      0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
      0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,};

static char _tag_chars[UCHAR_MAX] = {
      0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,
      0,0,1,1,0,1,1,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
      1,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
      1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
      0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
      0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
      0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,};

static char _code_chars[UCHAR_MAX] = {
      0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,
      1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
      1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
      1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
      0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
      0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
      0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,};

static char _paren_chars[UCHAR_MAX] = {
      0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,
      0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
      1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
      1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
      0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
      0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
      0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,};

#endif


/**
***
**/

static gboolean
read_space (const char * start, const char ** end)
{
      /* 1*( <HT (ASCII 9)> / <blank (ASCII 32)> */
      const char * pch = start;
      while (*pch=='\t' || *pch==' ') ++pch;
      if (pch != start) {
            *end = pch;
            return TRUE;
      }
      return FALSE;
      
}

static gboolean
read_codes (const char * pch, const char ** end)
{
      if (!is_code_char(*pch)) return FALSE;
      while (is_code_char(*pch)) ++pch;
      *end = pch;
      return TRUE;
}

static gboolean
read_encoding (const char * pch, const char ** end)
{
      if (!is_tag_char(*pch)) return FALSE;
      while (is_tag_char(*pch)) ++pch;
      *end = pch;
      return TRUE;
}

static gboolean
read_charset (const char * pch, const char ** end)
{
      return read_encoding (pch, end);
}

static gboolean
read_encoded_word (const char * pch, const char ** end)
{
      /* "=?" charset "?" encoding "?" codes "?=" */
      if (pch[0]!='=' || pch[1]!='?') return FALSE;
      pch += 2;
      if (!read_charset (pch, &pch)) return FALSE;
      if (*pch != '?') return FALSE;
      ++pch;
      if (!read_encoding (pch, &pch)) return FALSE;
      if (*pch != '?') return FALSE;
      ++pch;
      if (!read_codes (pch, &pch)) return FALSE;
      if (pch[0]!='?' || pch[1]!='=') return FALSE;
      *end = pch + 2;
      return TRUE;
}

static gboolean
read_unquoted_word (const char * pch, const char ** end)
{
      /* 1*unquoted-char */
      if (!is_unquoted_char(*pch)) return FALSE;
      while (is_unquoted_char((guchar)*pch)) ++pch;
      *end = pch;
      return TRUE;
}

static gboolean
read_quoted_word (const char * pch, const char ** end)
{
      /* quote 1*(quoted-char / space) quote */
      if (*pch!='"') return FALSE;
      ++pch;
      for (;;) {
            if (read_space(pch,&pch))
                  continue;
            if (is_quoted_char ((guchar)*pch)) {
                  ++pch;
                  continue;
            }
            break;
      }
      if (*pch!='"') return FALSE;
      ++pch;
      *end = pch;
      return TRUE;
}

static gboolean
read_plain_word (const char * pch, const char ** end)
{
      /* unquoted-word / quoted-word / encoded-word */
      if (read_quoted_word(pch,end)) return TRUE;
      if (read_encoded_word(pch,end)) return TRUE;
      if (read_unquoted_word(pch,end)) return TRUE;
      return FALSE;
}

static gboolean
read_plain_phrase (const char * pch, const char ** end)
{
      /* plain-word *(space plain-word) */
      const char * tmp = NULL;
      if (!read_plain_word(pch,&pch))
            return FALSE;
      for (;;) {
            tmp = pch;
            if (!read_space(pch,&pch)) break;
            if (!read_plain_word(pch,&pch)) break;
      }
      *end = tmp;
      return TRUE;
}

static gboolean
read_paren_char (const char * pch, const char ** end)
{
      if (!is_paren_char((guchar)*pch))
            return FALSE;
      *end = pch + 1;
      return TRUE;
}


static gboolean
read_paren_phrase (const char * pch, const char ** end)
{
      /* 1* (paren-char / space / encoded-word */

      if (!read_paren_char(pch,&pch)
            && !read_space(pch,&pch)
            && !read_encoded_word(pch,&pch))
                  return FALSE;

      for (;;)
      {
            if (!read_paren_char(pch,&pch)
                  && !read_space(pch,&pch)
                  && !read_encoded_word(pch,&pch))
                        break;
      }

      *end = pch;
      return TRUE;
}

/*****
******
*****/

/**
 *  "[244.14.124.12]"
 *  "244.12.14.12"
 */
static int
gnksa_check_domain_literal (const char* domain)
{
      int i;
      int x[4];
      gboolean need_closing_brace;
      const char * pch;

      /* parse domain literal into ip number */

      pch = domain;
      need_closing_brace = *pch == '[';
      if (need_closing_brace) ++pch; /* move past open brace */

      /* %u.%u.%u.%u */
      for (i=0; i<4; ++i) {
            char * end = NULL;
            x[i] = strtoul (pch, &end, 10);
            if (end == pch)
                  return GNKSA_BAD_DOMAIN_LITERAL;
            if (x[i]<0 || x[i]>255)
                  return GNKSA_BAD_DOMAIN_LITERAL;
            if (i!=3) {
                  if (*end != '.')
                        return GNKSA_BAD_DOMAIN_LITERAL;
                  ++end;
            }
            pch = end;
      }

      if (need_closing_brace && *pch!=']')
            return GNKSA_BAD_DOMAIN_LITERAL;

      return GNKSA_OK;
}

/*****
******
*****/

static int
gnksa_check_domain (const char * domain, guint domain_len)
{
      int i;
      int label_qty;
      const char * top;
      const char * pch;
      const char * label = NULL;
      int label_len = 0;

      debug1 (DEBUG_GNKSA, "gnksa_check_domain: [%s]", domain);

      /* check for domain literal */
      if (*domain == '[')
            return gnksa_check_domain_literal (domain);

      /* check for empty */
      if (!is_nonempty_string (domain)
            || *domain=='.'
            || domain[domain_len-1]=='.'
            || pan_strstr(domain,"..")!=NULL)
            return GNKSA_ZERO_LENGTH_LABEL;

      /* count the labels */
      label_qty = 0;
      pch = domain;
      while (get_next_token_run (pch, '.', &pch, &label, &label_len))
            ++label_qty;

      /* make sure we have more than one label in the domain */
      if (label_qty < 2)
            return GNKSA_SINGLE_DOMAIN;

      /* check for illegal labels */
      pch = domain;
      for (i=0; i<label_qty-1; ++i)
      {
            get_next_token_run (pch, '.', &pch, &label, &label_len);
            if (label_len > 63)
                  return GNKSA_ILLEGAL_LABEL_LENGTH;
            if (label[0]=='-' || label[label_len-1]=='-')
                  return GNKSA_ILLEGAL_LABEL_HYPHEN;
      }

      /* last label -- toplevel domain */
      top = get_next_token_run (pch, '.', &pch, &label, &label_len);
      debug1 (DEBUG_GNKSA, "top level domain: [%s]", top);
      switch ((int)strlen(top))
      {
            case 1:
                  if (isdigit((guchar)top[0]))
                        return gnksa_check_domain_literal (domain);

                  /* single-letter TLDs dont exist */
                  return GNKSA_ILLEGAL_DOMAIN;

            case 2:
                  if (isdigit((guchar)top[0]) || isdigit((guchar)top[1]))
                        return gnksa_check_domain_literal (domain);
                  break;
            case 3:
                  if (isdigit((guchar)top[0]) || isdigit((guchar)top[2]) || isdigit((guchar)top[3]))
                        return gnksa_check_domain_literal (domain);
                  break;
            default:
                  break;
      }

      return GNKSA_OK;
}

/*****
******
*****/

static int
gnksa_check_localpart (const char * localpart)
{
      int word_len = 0;
      const char * pch;
      const char * word = NULL;

      /* make sure it's not empty... */
      if (!is_nonempty_string(localpart))
            return GNKSA_LOCALPART_MISSING;

      /* break localpart up into its unquoted words */
      pch = localpart;
      while (get_next_token_run (pch, '.', &pch, &word, &word_len))
      {
            int i;

            if (word_len < 1)
                  return GNKSA_ZERO_LENGTH_LOCAL_WORD;

            for (i=0; i<word_len; ++i)
                  if (!is_unquoted_char(word[i]))
                        return GNKSA_INVALID_LOCALPART;
      }

      return GNKSA_OK;
}

/*****
******
*****/

static int
gnksa_check_address (const char * address, int address_len)
{
      int retval = GNKSA_OK;
      char * work;
      char * begin;

      /* get rid of vacuous case */
      if (!is_nonempty_string(address))
            return GNKSA_LOCALPART_MISSING;

      /* check the address */
      pan_strndup_alloca (work, address, address_len);
      begin = strrchr (work, '@');
      if (begin==NULL)
      {
            if (retval == GNKSA_OK)
                  retval = GNKSA_INVALID_DOMAIN;
      }
      else
      {
            /* temporarily split string between username & fqdn */
            *begin++ = '\0';
            g_strdown (begin);

            /* check the domain */
            if (retval == GNKSA_OK)
                  retval = gnksa_check_domain (begin, address_len - (begin-work));

            /* check the localpart */
            if (retval == GNKSA_OK)
                  retval = gnksa_check_localpart (work);
      }

      return retval;
}

/*****
******
*****/

static int
gnksa_split_from (const char  * from,
                  char        * addr_buf,
                  int           addr_max,
                  char        * name_buf,
                  int           name_max,
              int         * addrtype,
              gboolean      strict)
{
      char * begin, * addr_buf_rangle;
      char * end;
      char * work;
      char * lparen;

      *addr_buf = '\0';
      *name_buf = '\0';

      work = pan_strdup_alloca (from);
      g_strstrip (work);

      /* empty string */
      if (!*work) {
            *addrtype = GNKSA_ADDRTYPE_OLDSTYLE;
            return GNKSA_LPAREN_MISSING;
      }

      end = &work [ strlen(work) - 1 ];
      debug2 (DEBUG_GNKSA, "work: [%s], end: [%s]", work, end);
      if (*end == '>') /* Charles Kerr <charles@rebelbase.com> */
      {
            *addrtype = GNKSA_ADDRTYPE_ROUTE;

            /* get address part */
            begin = strrchr (work, '<');
            if (begin == NULL)
                  return GNKSA_LANGLE_MISSING;

            /* copy route address from inside the <> brackets */
            g_strlcpy (addr_buf, begin+1, addr_max);
            if (((addr_buf_rangle = strchr (addr_buf, '>'))) != NULL)
                  *addr_buf_rangle = '\0';
            /* From: [plain-phrase space] "<" address ">" */
            *begin = '\0';
            if (strict) {
                  const char * tmp = work;
                  if ((*tmp) && (!read_plain_phrase(tmp,&tmp)||!read_space(tmp,&tmp)))
                        return GNKSA_ILLEGAL_PLAIN_PHRASE;
            }

            /* get realname part */
            g_strstrip (work);
            g_strlcpy (name_buf, work, name_max);
      }
      else if ((lparen = strchr (work,'(')) != NULL) /* charles@rebelbase.com (Charles Kerr) */
      {
            size_t len;
            char * real;
            const char * end;

            *addrtype = GNKSA_ADDRTYPE_OLDSTYLE;

            /* address part */
            pan_strncpy_len (addr_buf, addr_max, work, lparen-work);
            g_strstrip (addr_buf);

            if (strict) {
                  int val = gnksa_check_address (addr_buf, strlen(addr_buf));
                  if (val)
                        return val;
            }

            /* real name part */
            real = lparen + 1;
            g_strstrip (real);
            len = strlen (real);
            if (real[len-1]!=')')
                  return GNKSA_RPAREN_MISSING;

            real[len-1] = '\0';
            end = NULL;
            if (strict && (!read_paren_phrase(lparen+1,&end) || end==NULL))
                  return GNKSA_ILLEGAL_PAREN_PHRASE;

            pan_strncpy_len (name_buf, name_max, real, len);
      }
      else if (strchr(work,'@') != NULL) /* charles@rebelbase.com */
      {
            g_strlcpy (addr_buf, work, addr_max);
            return strict ? gnksa_check_address(addr_buf, strlen(addr_buf)) : GNKSA_OK;
      }
      else /* who knows what this thing is... */
      {
            g_strlcpy (name_buf, work, name_max);
            return GNKSA_LPAREN_MISSING;
      }

      return GNKSA_OK;
}


/*****
******
*****/

/************
*************  PUBLIC
************/

/*****
******
*****/












































int
gnksa_do_check_from (const char   * from,
                     char        * addr,
                     guint          addr_max,
                     char        * name,
                     guint          name_max,
                 gboolean       strict)
{
      int addrtype = 0;
      int retval;
      debug1 (DEBUG_GNKSA, "From: [%s]", from);

      /* split from */
      *addr = *name = '\0';
      retval = gnksa_split_from (from,
                                 addr, addr_max,
                           name, name_max, 
                             &addrtype,
                           strict);

      debug1 (DEBUG_GNKSA, "Name: [%s]", name);
      debug1 (DEBUG_GNKSA, "Address: [%s]", addr);

      /* check address */
      if (*addr) {
            if (retval == GNKSA_OK)
                  retval = gnksa_check_address (addr, strlen(addr));
      }

      debug1 (DEBUG_GNKSA, "GNKSA [%d]", retval);
      return retval;
}

/*****
******
*****/

void
gnksa_strip_realname (char * realname)
{
      char * pch;

      g_return_if_fail (is_nonempty_string(realname));

      g_strstrip (realname);
      if (*realname=='(') {
            size_t len = strlen (realname+1);
            memmove (realname, realname+1, len+1);
            pch = realname + len - 1;
            if (*pch == ')')
                  *pch = '\0';
      }

      while ((pch = strchr(realname,'"')) != NULL)
            memmove (pch, pch+1, strlen(pch+1)+1);
}

/*****
******
*****/

int
gnksa_check_from (const char* from, gboolean strict)
{
      int retval;
      char addr[512];
      char name[512];

      retval = gnksa_do_check_from (from,
                                    addr, sizeof(addr),
                                    name, sizeof(name),
                                    strict);

      return retval;
}

/*****
******
*****/

/**
 * Validates a Message-ID from a References: header.
 *
 * The purpose of this function is to remove severely broken,
 * usually suntactically-invalid Message-IDs, such as those
 * missing '<', '@', or '>'. 
 *
 * However we want to retain Message-IDs that might be sloppy,
 * such as ones that have possibly-invalid domains.
 *
 * This balance is from wanting to adhere to GNKSA that wants us
 * to remove `damaged' Message-IDs, but we want to be pretty
 * forgiving because these References are required for threading
 * to work properly.
 */
static int
gnksa_check_message_id (const char* message_id, int message_id_len)
{
      int val;
      char * tmp;
      char * pch;

      /* make a temporary copy that we can poke */
            pan_strndup_alloca (tmp, message_id, message_id_len);
      if (is_nonempty_string(tmp))
            g_strstrip (tmp);

      /* make sure it's <= 250 octets (son of gnksa 1036 5.3) */
      if (strlen(tmp) > 250)
            return GNKSA_ILLEGAL_LABEL_LENGTH;

      /* make sure the message-id is wrapped in < > */
      if (*tmp!='<')
            return GNKSA_LANGLE_MISSING;
      if (tmp[strlen(tmp)-1]!='>')
            return GNKSA_RANGLE_MISSING;

      /* find the '@' separator */
      pch = strrchr (tmp, '@');
      if (pch == NULL)
            return GNKSA_ATSIGN_MISSING;

      /* check the domain name */
      ++pch;
      *strrchr(pch,'>') = '\0';
      if (!is_nonempty_string (pch))
            return GNKSA_ILLEGAL_DOMAIN;

      /* check the local-part */
      --pch;
      *pch = '\0';
      if (!g_strcasecmp (tmp+1, "postmaster")) /* son-of-1036 */
            return GNKSA_INVALID_LOCALPART;

      val = gnksa_check_localpart (tmp+1);
      if (val != GNKSA_OK)
            return val;

      return GNKSA_OK;
}

/*****
******
*****/

static void
remove_broken_message_ids_from_references (const char * references, GString * setme)
{
      const char * pch;

      /* sanity checks */
      g_return_if_fail (setme!=NULL);
      g_string_truncate (setme, 0);
      g_return_if_fail (is_nonempty_string(references));

      /* remove broken message-ids
         FIXME: this tokenizing works, but it's hard to read. */
            pch = references;
      for (;;)
      {
            const char * begin;
            const char * end;

            /* find the beginning of the message-id */
            while (*pch!='<')
                  ++pch;
            begin = pch;
            ++pch;

            /* find the end of the message-id */
            end = strpbrk (pch, "<> ");
            if (end==NULL)
                  end = pch + strlen(pch);
            else if (*end == '>')
                  ++end;

            /* check the message-id for validity */
            if (gnksa_check_message_id (begin, end-begin) == GNKSA_OK) {
                  g_string_append_len (setme, begin, end-begin);
                  g_string_append_c (setme, ' ');
            }

            /* if that was the last token, then we're done */
            if (*end == '\0')
                  break;

            pch = end;

      }
      pan_g_string_strstrip (setme); /* remove trailing space  */
}

/**
 * Try to trim references down to an acceptable length for the NNTP server,
 * @param untrimmed "references" string
 * @return newly-allocated trimmed "references" string
 */
char*
gnksa_trim_references_to_len (const char* refs, int cutoff)
{
      char * retval;
      GString * fixed;
      gboolean do_free = TRUE;

      fixed = g_string_sized_new (1024);
      remove_broken_message_ids_from_references (refs, fixed);

      if (fixed->len < cutoff) /* this is too easy. :) */
      {
            retval = fixed->str;
            do_free = FALSE;
      }
      else /* plow through them */
      {
            int leader_len;
            int len_left;
            const char * str_end = fixed->str + fixed->len;
            const char * pch;

            /* isolate first id, which we must always keep */
                  pch = fixed->str;
            while (*pch!='<') ++pch; /* find beginning of first id */
            while (*pch!='>') ++pch; /* find end of first id */
            pch += 2; /* keep the "> " */
            leader_len = pch - fixed->str;

            /* trim out 2..n until we fit.  This implicitly lets later
               ids make the final cut, which is in line with gnksa's
               requirement that the final three ids not be trimmed out. */
            len_left = cutoff - leader_len;
            while (str_end-pch > len_left) {
                  ++pch; /* skip until we fit */
            }
            while (*pch && *pch!='<' ) ++pch; /* can't include half an id */

            /* generate return string */
            retval = g_malloc (leader_len + (str_end-pch) + 1);
            memcpy (retval, fixed->str, leader_len);
            memcpy (retval+leader_len, pch, (str_end-pch)+1); /*+1 for end*/
      }

      /* return */
      g_string_free (fixed, do_free);
      pan_warn_if_fail (strlen(retval) <= cutoff);
      return retval;
}

static const char* default_domain = "nospam.com";

/**
 * thus spake son-of-1036: "the most popular method of generating local parts
 * is to use the date and time, plus some way of distinguishing between
 * simultaneous postings on the same host (e.g., a process number), and encode
 * them in a suitably-reduced alphabet.
 */
char*
gnksa_generate_message_id (const char * domain_name)
{
      GString * msgid = g_string_sized_new (256);

      /* start with '<' */
      g_string_append_c (msgid, '<');

      /* add unique local part to message-id */
      {
            GTimeVal now;
            struct tm local_now;
            char buf[64];

            g_get_current_time (&now);
            pan_gmtime_r (&now.tv_sec, &local_now);
            strftime (buf, sizeof(buf), "%Y.%m.%d.%H.%M.%S", &local_now);

            g_string_append (msgid, "pan.");
            g_string_append (msgid, buf);
            g_string_append_printf (msgid, ".%ld", now.tv_usec);
      }

      /* delimit */
      g_string_append_c (msgid, '@');

      /* add domain name to message-id */
      if (!is_nonempty_string(domain_name))
            domain_name = default_domain;
      g_string_append (msgid, domain_name);

      /* end with '>' */
      g_string_append_c (msgid, '>');

      /* return */
      return g_string_free (msgid, FALSE);
}

char*
gnksa_generate_message_id_from_email_addr (const char * addr)
{
      const char * pch;
      char * message_id;

      /* find the domain in the email address */
      pch = NULL;
      if (is_nonempty_string(addr)) {
            pch = strchr (addr, '@');
            if (pch == NULL)
                  pch = addr;
            else
                  ++pch;
      }
      if (!is_nonempty_string(pch)) {
            g_warning (_("No email address provided; generating message-id with domain \"%s\""), default_domain);
            pch = default_domain;
      }

      /* make a copy of the rhs of the email address and trim out the '>' */
      if (1) {
            char * domain = pan_strdup_alloca (pch);
            pch = strchr (domain, '>');
            if (pch != NULL)
                  *((char*)pch) = '\0';
            g_strstrip (domain);
            message_id = gnksa_generate_message_id (domain);
      }

      /* cleanup */
      return message_id;
}


static char*
gnksa_trim_references (const char* refs)
{
      const int GNKSA_CUTOFF = 986; /* GNKSA rule 7: 998 chars - 12 for "References: " */
      return gnksa_trim_references_to_len (refs, GNKSA_CUTOFF);
}

char*
gnksa_generate_references (const char * references,
                           const char * message_id)
{
      char * retval = NULL;

      if (is_nonempty_string (message_id))
      {
            /* build a full references string */
            GString * str = g_string_sized_new (1024);
            if (is_nonempty_string(references)) {
                  g_string_append (str, references);
                  g_string_append_c (str, ' ');
            }
            g_string_append (str, message_id);

            /* make sure they fit gnksa guidelines */
            retval = gnksa_trim_references (str->str);
            g_string_free (str, TRUE);
      }

      return retval;
}

/***
****
****  DATE
****
***/

time_t
tzoffset_sec (time_t * now)
{
      struct tm gmt, lt;
      int off;

      pan_gmtime_r (now, &gmt);
      pan_localtime_r (now, &lt);

      off = (lt.tm_hour - gmt.tm_hour) * 60 + lt.tm_min - gmt.tm_min;

      if (lt.tm_year < gmt.tm_year)
            off -= 24 * 60;
      else if (lt.tm_year > gmt.tm_year)
            off += 24 * 60;
      else if (lt.tm_yday < gmt.tm_yday)
            off -= 24 * 60;
      else if (lt.tm_yday > gmt.tm_yday)
            off += 24 * 60;

      if (off >= 24 * 60)             /* should be impossible */
            off = 23 * 60 + 59;     /* if not, insert silly value */
      if (off <= -24 * 60)
            off = -(23 * 60 + 59);
      if (off > 13 * 60) 
            off -= 24 * 60;
      if (off < -12 * 60)
            off += 24 * 60;

      return off * 60;
}

/***
****  Signatures
***/

SigType
pan_is_signature_delimiter (const char * line, const int line_len)
{
      switch (line_len)
      {
            case 2: if (!strncmp (line,"--"   ,2)) return SIG_NONSTANDARD;
                              break;
            
            case 3: if (!strncmp (line,"--\r" ,3)) return SIG_NONSTANDARD;
                        if (!strncmp (line,"-- "  ,3)) return SIG_STANDARD;
                        break;
            
            case 4: if (!strncmp (line,"-- \r",4)) return SIG_STANDARD;
            
            default:
                  return SIG_NONE;
      }
      return SIG_NONE;
}

SigType
pan_find_signature_delimiter  (const char     * body,
                               char          ** setme_delimiter)
{
      const static int SIG_THRESHOLD = 6;
      const char * march = body;
      char * line = NULL;
      char * sig_point = NULL;
      int sig_type = SIG_NONE;
      int lines_below = 0;
      int line_len = 0;

      if (setme_delimiter != NULL)
            *setme_delimiter = NULL;

      /*iterate through the text, line by line*/
      while (get_next_token_run (march, '\n', &march, (const char**)&line, &line_len))
      {
            const int st = pan_is_signature_delimiter (line, line_len);
            if (st == SIG_STANDARD || st == SIG_NONSTANDARD)
            {
                  sig_type = st;
                  sig_point = line;
                  lines_below = 0;
                  
            }
            else 
            {
                  if (sig_point)
                        ++lines_below;
            }
      }
      if (sig_type == SIG_NONE) return SIG_NONE;
      if (sig_point == NULL) return SIG_NONE;
      if (sig_type == SIG_STANDARD)
      {
            if (setme_delimiter != NULL)
                  *setme_delimiter = sig_point;
            return SIG_STANDARD;
      }
      
      
      /*if we have a non-standard sig, make sure it's the last one and that
       * there are less than SIG_THRESHOLD lines
       */
      if (sig_type == SIG_NONSTANDARD && lines_below <= SIG_THRESHOLD )
      {
            if (setme_delimiter != NULL)
                  *setme_delimiter = sig_point;
            return SIG_NONSTANDARD;
      }
      
      return SIG_NONE;
}


gboolean
pan_remove_signature (char * body)
{
      char * sig_point = NULL;
      const gboolean has_signature = pan_find_signature_delimiter (body, &sig_point) != SIG_NONE;

      if (has_signature)
            *sig_point = '\0';

      return has_signature;
}

Generated by  Doxygen 1.6.0   Back to index