/*
 * nntp.c: some code borrowed from Pan
 *
 * $Id: nntp.c,v 1.24 2000/06/06 15:45:46 sc Exp $
 */
/* Copyright (C) 1999-2000  Sergey Chernikov (sc@ivvs.ul.ru)
 *
 * Authors: Sergey Chernikov <sc@ivvs.ul.ru>
 *
 * 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 2 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
 */

#include "grn_consts.h"
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <glib.h>
#include "tcp.h"
#include "nntp.h"
#include "grn_vars.h"
#include "grn_config.h"
#include "grn_misc.h"
#include "queue.h"
#include "grn_util.h"
#include "grn_perl.h"
#include "grn_msgpost.h"
#include "grn_mime.h"


static pthread_cond_t online_c = PTHREAD_COND_INITIALIZER;
static pthread_mutex_t online_m = PTHREAD_MUTEX_INITIALIZER;


void nntp_wait_connect()
{
  pthread_mutex_lock(&online_m);
  pthread_cond_wait(&online_c, &online_m);
  pthread_mutex_unlock(&online_m);
}

static gchar *connect_to_server(grn_socket *sock)
{
  gchar *buffer;
  int rc;
  g_return_val_if_fail(sock != NULL, NULL);
  
  if (! str_check(grn_prefs.nntp_server))  return _("no server name given");
  sock->addr = g_strdup(grn_prefs.nntp_server);
  sock->service = g_strdup("nntp");
  
  grn_socket_open(sock);
  if (sock->error)  return sock->err_msg;
  if ((buffer = grn_socket_get(sock)) == NULL)  return _("server not responding");
  rc = atoi(buffer);
  if ((rc != 200) && (rc != 201))
  {
    grn_info(_("server said:\n%s"), buffer);  str_free(&buffer);
    return _("error in server reply");
  }
  str_free(&buffer);

  if (grn_prefs.nntp_auth)
  {
    if (grn_socket_put_va(sock, "AUTHINFO user %s\r\n", grn_prefs.nntp_login) == -1)
      return _("AUTHINFO user failed");
    if ((buffer = grn_socket_get(sock)) == NULL)  return _("server not responding");
    if (atoi(buffer) == 381)
    {
      str_free(&buffer);
      if (grn_socket_put_va(sock, "AUTHINFO pass %s\r\n", grn_prefs.nntp_passwd) == -1)
        return _("AUTHINFO pass failed");
      if ((buffer = grn_socket_get(sock)) == NULL)  return _("server not responding");
      if (atoi(buffer) != 281)
      {
	grn_info(_("server said:\n%s"), buffer);  str_free(&buffer);
	return _("Authentication failed");
      }
    }
    else {
      str_free(&buffer);
      grn_info(_("Authentication doesn't required for\n%s"), grn_prefs.nntp_server);
    }
  }
  if (grn_socket_put(sock, "MODE READER\r\n") == -1)  return _("MODE READER failed");
  if ((buffer = grn_socket_get(sock)) == NULL)  return _("server not responding");

  if (atoi(buffer) == 200)  GRN->srv.posting = TRUE;
  else if (atoi(buffer) == 201)  GRN->srv.posting = FALSE;
  else  { str_free(&buffer);  return _("error in server reply"); }
  str_free(&buffer);

  return NULL;
}

gchar *nntp_connect(grn_socket *sock)
{
  gchar *rc;
  g_return_val_if_fail(sock != NULL, NULL);

  if (! sock->closed)  return NULL;
  rc = connect_to_server(sock);
  if (rc)  return rc;
  pthread_mutex_lock(&online_m);
#if 0
  pthread_cond_broadcast(&online_c);
#endif
  pthread_mutex_unlock(&online_m);
  return NULL;
}

void nntp_disconnect(grn_socket *sock)
{
  if (sock->closed)  return;
  grn_socket_put(sock, "QUIT\r\n");
  grn_socket_close(sock);
  must_exit = TRUE;
  grnq_purge();
}

void nntp_offline(grn_socket *sock)
{
  if (sock->closed)  return;
  grn_socket_close(sock);
  grnq_purge();
}

gboolean nntp_is_online(grn_socket *sock)
{
  return (! sock->closed);
}


gchar *nntp_send_noop(grn_socket *sock)
{
  gchar *buffer;
  
  if (grn_socket_put(sock, "MODE READER\r\n") == -1)
    return _("can't send command to server");
  if ((buffer = grn_socket_get(sock)) == NULL)
    return _("server not responding");
  str_free(&buffer);
  return NULL;
}


gchar *nntp_set_group(grn_socket *sock, gchar *name, gint *total, gint *first, gint *last)
{
  gchar *buffer;
  
  if (grn_socket_put_va(sock, "GROUP %s\r\n", name) == -1)
    return _("can't send command to server");
  if ((buffer = grn_socket_get(sock)) != NULL)
  {
    if (atoi(buffer) == 211)
    {
      sscanf(buffer, "%*d %d %d %d", total, first, last);
      str_free(&buffer);
      return NULL;
    }
    str_free(&buffer);
    return _("error in server reply");
  }
  else return _("server not responding");
}

GList *nntp_get_grouplist(grn_socket *sock)
{
  gchar *buffer = NULL;
  grn_newsgroup *grp = NULL;
  gint count = 0;
  GList *ret = NULL;
  
  if (grn_socket_put(sock, "LIST active\r\n") == -1)
  {
    grn_socket_set_error(sock, _("can't send command to server"));
    return NULL;
  }
  if ((buffer = grn_socket_get(sock)) == NULL)
  {
    grn_socket_set_error(sock, _("server not responding"));
    return NULL;
  }
  if (atoi(buffer) != 215)
  {
    grn_socket_set_error(sock, _("error in server reply"));
    grn_info(_("server said:\n%s"), buffer);
    str_free(&buffer);   return NULL;
  }
  else {
    str_free(&buffer);
    if ((buffer = grn_socket_get(sock)) == NULL)
    {
      grn_socket_set_error(sock, _("server not responding"));
      return NULL;
    }
    while (strncmp(buffer, ".\r\n", 3))
    {
      gchar **arr;
      if (must_exit)
      {
        grouplist_free(&ret);
	nntp_offline(sock);
	return NULL;
      }
      arr = g_strsplit(buffer, " ", 4);
      if (arr[3])
      {
        if ((! strcmp(g_strstrip(arr[3]), "y")) || (! strcmp(g_strstrip(arr[3]), "m")))
        {
          grp = grn_newsgroup_alloc();
          if (arr[0])  grp->name = g_strdup(arr[0]);
          if (arr[1])  grp->total = atoi(arr[1]);
          if (arr[2])  grp->first = atoi(arr[2]);
          ret = g_list_append(ret, grp);
	  g_strfreev(arr);
          count++;
        }
      }
      str_free(&buffer);
      if ((buffer = grn_socket_get(sock)) == NULL)
      {
        grn_socket_set_error(sock, _("server not responding"));
	grouplist_free(&ret);
	return NULL;
      }
    }
    str_free(&buffer);
  }
  return ret;
}

GHashTable *nntp_get_groupdesc(grn_socket *sock)
{
  gchar *buffer = NULL;
  GHashTable *ret = NULL;

  if (! grn_prefs.groups_desc)  return NULL;

  if (grn_socket_put(sock, "LIST newsgroups\r\n") == -1)
  {
    grn_socket_set_error(sock, _("can't send command to server"));
    return NULL;
  }
  if ((buffer = grn_socket_get(sock)) == NULL)
  {
    grn_socket_set_error(sock, _("server not responding"));
    return NULL;
  }
  if (atoi(buffer) != 215)
  {
    grn_socket_set_error(sock, _("error in server reply"));
    grn_info(_("server said:\n%s"), buffer);
    str_free(&buffer);   return NULL;
  }
  else
  {
    str_free(&buffer);
    if ((buffer = grn_socket_get(sock)) == NULL)
    {
      grn_socket_set_error(sock, _("server not responding"));
      return NULL;
    }

    ret = g_hash_table_new(g_str_hash, g_str_equal);

    while (strncmp(buffer, ".\r\n", 3))
    {
      gchar *tmp1, *tmp2;
      gchar *name, *desc;

      if (must_exit)
      {
//        grouplist_free(ret);
	nntp_offline(sock);
	str_free(&buffer);  return NULL;
      }
      tmp1 = buffer;
      while ((*tmp1 != '\t') && (*tmp1 != ' ') && *tmp1) tmp1++;
      name = (gchar *) g_malloc(tmp1 - buffer + 1);
      strncpy(name, buffer, tmp1 - buffer);  name[tmp1 - buffer] = '\0';
      tmp2 = tmp1;
      while (((*tmp2 == '\t') || (*tmp2 == ' ')) && *tmp2)  tmp2++;
      tmp1 = tmp2;
      while ((*tmp1 != '\r') && (*tmp1 != '\n') && *tmp1)  tmp1++;
      desc = (gchar *) g_malloc(tmp1 - tmp2 + 1);
      strncpy(desc, tmp2, tmp1 - tmp2);  desc[tmp1 - tmp2] = '\0';
      g_hash_table_insert(ret, name, desc);

      str_free(&buffer);
      if ((buffer = grn_socket_get(sock)) == NULL)
      {
        grn_socket_set_error(sock, _("server not responding"));
//	grouplist_free(ret);
	return NULL;
      }
    }
    str_free(&buffer);
  }
  return ret;
}


GList *nntp_get_msghdrlist(grn_socket *sock, gint total, gint first, gint last,
			   grn_newsgroup *grp, GnomeAppBar *ab)
{
  gchar *buffer = NULL, *buf;
  GList *ret = NULL;
  GtkProgress *pb;
  
  if (total <= 0)  return NULL;
  
  if (grn_socket_put_va(sock, "XOVER %d-%d\r\n", first, last) == -1)
  {
    grn_socket_set_error(sock, _("can't send command to server"));
    return NULL;
  }
  if ((buffer = grn_socket_get(sock)) == NULL)
  {
    grn_socket_set_error(sock, _("server not responding"));
    return NULL;
  }
  if (atoi(buffer) != 224)
  {
    grn_socket_set_error(sock, _("error in server reply"));
    grn_info(_("server said:\n%s\n"), buffer);
    str_free(&buffer);  return NULL;
  }
  str_free(&buffer);
  
  if ((buffer = grn_socket_get(sock)) == NULL)
  {
    grn_socket_set_error(sock, _("server not responding"));
    return NULL;
  }

  grn_lock();
  pb = gnome_appbar_get_progress(ab);
  gtk_progress_set_show_text(pb, TRUE);
  grn_unlock();

  while (strncmp(buffer, ".\r\n", 3))
  {
    gchar **sarr;
    t_msgheaders *mh;

    if (must_exit)
    {
      msghdrlist_free(&ret);
      nntp_offline(sock);
      str_free(&buffer);
      return NULL;
    }
    sarr = g_strsplit(buffer, "\t", 0);
    mh = (t_msgheaders *) g_malloc0(sizeof(t_msgheaders));
    mh->number = atoi(sarr[0]);
    mh->subject = g_strdup(sarr[1]);
    mh->from = g_strdup(sarr[2]);
    mh->date = g_strdup(sarr[3]);
    mh->parsed_date = parse_date(mh->date);
    mh->id = g_strdup(sarr[4]);
    mh->ref = g_strdup(sarr[5]);
    mh->size = atoi(sarr[6]);
    mh->lines = atoi(sarr[7]);
    mh->xref = get_hdr_val(sarr[8]);
    mh->path = NULL;  mh->extra_hdrs = NULL;
    mh->grp = grp;
#ifdef USE_PERL
    mh->score = perl_hook_run("GetScore", GRN, mh, NULL);
    if (mh->score == (gulong) (-1L))  mh->score = 0;
#else
    mh->score = 0;
#endif
    g_strfreev(sarr);
    grn_decode_headers(mh);

    buf = g_strdup_printf("%ld/%d", mh->number, last);
    grn_lock();
    gnome_appbar_set_progress(ab, (mh->number - first + 1)/((gfloat) total));
    gtk_progress_set_format_string(pb, buf);
    grn_unlock();
    
    str_free(&buf);
    ret = g_list_prepend(ret, mh);

    str_free(&buffer);
    if ((buffer = grn_socket_get(sock)) == NULL)
    {
      grn_socket_set_error(sock, _("server not responding"));
      msghdrlist_free(&ret);
      break;
    }
  }
  str_free(&buffer);
  grn_lock();
  gtk_progress_set_format_string(pb, "%P %%");
  gtk_progress_set_show_text(pb, FALSE);
  grn_unlock();

  if (ret)  return g_list_reverse(ret);
  else  return NULL;
}


static gchar *update_header(t_message *msg, gchar *hdr)
{
  t_msgheader *mhdr;
  t_msgheaders *mh;
  gchar *key, *value, *ret=NULL;
  g_return_val_if_fail(msg != NULL, NULL);
  g_return_val_if_fail(msg->mh != NULL, NULL);
  g_return_val_if_fail(hdr != NULL, NULL);
  
  mh = msg->mh;
  mhdr = parse_hdr(hdr);
  if (! mhdr)  return NULL;
  key = g_strdup(mhdr->name);  value = g_strdup(mhdr->value);
  str_free(&(mhdr->name));  str_free(&(mhdr->value));  g_free(mhdr);

  if (! strcmp(key, "From"))
  {
    str_free(&(mh->from));
    mh->from = g_strdup(value);
    ret = g_strdup("From");
  }
  else if (! strcmp(key, "Subject"))
  {
    str_free(&(mh->subject));
    mh->subject = g_strdup(value);
    ret = g_strdup("Subject");
  }
  else if (! strcmp(key, "Date"))
  {
    str_free(&(mh->date));
    mh->date = g_strdup(value);
    mh->parsed_date = parse_date(mh->date);
    ret = g_strdup("Date");
  }
  else if (! strcmp(key, "Message-ID"))
  {
    str_free(&(mh->id));
    mh->id = g_strdup(value);
    ret = g_strdup("Message-ID");
  }
  else if (! strcmp(key, "References"))
  {
    str_free(&(mh->ref));
    mh->ref = g_strdup(value);
    ret = g_strdup("References");
  }
  else if (! strcmp(key, "Xref"))
  {
    str_free(&(mh->xref));
    mh->xref = g_strdup(value);
    ret = g_strdup("Xref");
  }
  else if (! strcmp(key, "Path"))
  {
    str_free(&(mh->path));
    mh->path = g_strdup(value);
    ret = g_strdup("Path");
  }
  else if (! strcmp(key, "Lines"))
  {
    mh->lines = atoi(value);
    ret = g_strdup("Lines");
  }
  else if (! strcmp(key, "Newsgroups"))
  {
    str_free(&(mh->newsgroups));
    mh->newsgroups = g_strdup(value);
    ret = g_strdup("Newsgroups");
  }
  else {
    gchar *new_val, *new_key;

    if (! mh->extra_hdrs)  mh->extra_hdrs = g_hash_table_new(g_str_hash, g_str_equal);
    if (g_hash_table_lookup_extended(mh->extra_hdrs, key, (gpointer *) &new_key,
                                     (gpointer *) &new_val))
    {
      gchar *tmp;

      g_hash_table_remove(mh->extra_hdrs, key);
      str_free(&new_key);
      tmp = new_val;
      new_val = g_strconcat(tmp, "\n", value, NULL);
      str_free(&tmp);
    }
    else  new_val = g_strdup(value);
    g_hash_table_insert(mh->extra_hdrs, g_strdup(key), new_val);
    ret = g_strdup(key);
  }
  str_free(&key);  str_free(&value);
  return ret;
}

gchar *nntp_get_article(grn_socket *sock, t_message *msg)
{
#define BUFSZ	16384
  gchar *buffer, *buf;
  gchar *prev_hdr=NULL, *hdr;
  gint bufsz = BUFSZ, bufpos = 0;
  g_return_val_if_fail(msg != NULL, FALSE);
  g_return_val_if_fail(msg->mh != NULL, FALSE);
  
  if (grn_socket_put_va(sock, "HEAD %lu\r\n", msg->mh->number) == -1)
    return _("server<-HEAD failed");
  if ((buffer = grn_socket_get(sock)) == NULL)  return _("server not responding");
  if (atoi(buffer) != 221)
  {
    g_free(buffer);
    return _("error in server reply");
  }
  g_free(buffer);
  if ((buffer = grn_socket_get(sock)) == NULL)  return _("server not responding");
  while (strncmp(buffer, ".\r\n", 3))
  {
    gchar *tmp;
    
    if (must_exit)
    {
      g_free(buffer);
      return _("transfer interrupted");
    }
    
    if ((tmp = strrchr(buffer, '\x0D')))  *tmp = '\0';
    if (strrchr(buffer, '\\'))
    {
      gchar *buf1 = str_subst(buffer, "\\t", "\t");  g_free(buffer);
      buffer = str_subst(buf1, "\\n", "\n");	     str_free(&buf1);
    }
    hdr = update_header(msg, buffer);
    if ((! hdr) && (prev_hdr))
    {
      gchar *s = g_strdup_printf("%s\n%s", t_msgheaders_get_hdr(msg->mh, prev_hdr), buffer);
      t_msgheaders_set_hdr(msg->mh, prev_hdr, s);
      str_free(&s);
      hdr = g_strdup(prev_hdr);
    }
    str_free(&prev_hdr);
    prev_hdr = hdr;
    
    g_free(buffer);
    if ((buffer = grn_socket_get(sock)) == NULL)  return _("server not responding");
  }
  g_free(buffer);

  if (grn_socket_put_va(sock, "BODY %lu\r\n", msg->mh->number) == -1)
    return _("server<-BODY failed");
  if ((buffer = grn_socket_get(sock)) == NULL)  return _("server not responding");
  if (atoi(buffer) != 222)
  {
    g_free(buffer);
    return _("error in server reply");
  }
  g_free(buffer);
  if ((buffer = grn_socket_get(sock)) == NULL)  return _("server not responding");
  str_free(&(msg->body));
  buf = g_malloc(bufsz);
  while (strncmp(buffer, ".\r\n", 3))
  {
    gchar *tmp;
    gint len;
    
    if (must_exit)
    {
      g_free(buffer);  g_free(buf);
      return _("transfer interrupted");
    }

    if ((tmp = strrchr(buffer, '\x0D')))  *tmp = '\0';
    len = strlen(buffer);
    if ((bufpos + len + 1) > bufsz)
    {
      bufsz += MAX(len + 1, BUFSZ);
      buf = (gchar *) g_realloc(buf, bufsz);
    }
    strcpy(buf + bufpos, buffer);	bufpos += len;
    buf[bufpos] = '\n';			bufpos++;

    g_free(buffer);
    if ((buffer = grn_socket_get(sock)) == NULL)
    {
      g_free(buf);
      return _("server not responding");
    }
  }
  if (buffer)  g_free(buffer);

  buf[bufpos++] = '\0';
  buf = (gchar *) g_realloc(buf, bufpos);
  msg->body = buf;
  grn_decode_headers(msg->mh);
  msg->cached = TRUE;
  return NULL;
#undef BUFSZ
}

void nntp_download_article(t_message *msg)
{
  gint total=0, first=0, last=0;
  gchar *rc;
  g_return_if_fail(msg != NULL);
  g_return_if_fail(msg->mh != NULL);
  g_return_if_fail(msg->mh->grp != NULL);
  g_return_if_fail(msg->mh->grp->name != NULL);
  
  grn_appbar_push(GRN->appbar, FALSE, _("Fetching article #%d from group %s..."),
                  msg->mh->number, msg->mh->grp->name);
  rc = nntp_connect(GRN->nntp_sock);
  if (! rc)
  {
    rc = nntp_set_group(GRN->nntp_sock, msg->mh->grp->name, &total, &first, &last);
    if (! rc)
    {
      if ((msg->mh->number <= last) && (msg->mh->number >= first))
        rc = nntp_get_article(GRN->nntp_sock, msg);
      if (rc)  grn_error(_("NNTP get article"), rc);
    }
    else  grn_error(_("NNTP set group"), rc);
  }
  else  grn_error(_("NNTP connect"), rc);
  grn_appbar_pop(GRN->appbar);
}


gchar *nntp_post_article(grn_socket *sock, t_message *msg)
{
  gchar *buffer;
  g_return_val_if_fail(msg != NULL, FALSE);

  if (grn_socket_put(sock, "POST\r\n") == -1)  return _("server<-POST failed");
  if ((buffer = grn_socket_get(sock)) == NULL)  return _("server not responding");
  if (atoi(buffer) == 340)
  {
    gchar *mbuf = msg_to_text(msg);
    str_free(&buffer);
    if (grn_socket_put_va(sock, "%s\r\n.\r\n", mbuf) == -1)
      return _("posting failed");
    str_free(&mbuf);
    if ((buffer = grn_socket_get(sock)) == NULL)  return _("server not responding");
    if (atoi(buffer) == 240)
    {
      str_free(&buffer);  return NULL;
    }
    str_free(&buffer);
    return _("article refused by server");
  }
  else {
    str_free(&buffer);
    return _("error in server reply");
  }
  return _("undefined error");
}


syntax highlighted by Code2HTML, v. 0.9.1