#include <stdio.h>
#include <stdlib.h>  		/* for rand() */
#include <string.h>
#include <ctype.h>		/* for tolower() */
#include <fcntl.h>		/* for mkdir() */

#include <sys/types.h>		/* for exec(), wait() et. al */
#include <sys/stat.h>		/* for stat() */
#include <unistd.h>
#include <sys/wait.h>
#include <errno.h>

#include <dirent.h>

#include "global.h"
#include "custom.h"		/* for date FORMAT strings */

#include "manhat-lib/shared_cs_util.h"
#include "manhat-lib/shared_util.h"
#include "manhat-lib/shared_news_util.h"
#include "manhat-lib/shared_clipboard_util.h"
#include "manhat-lib/shared_teamstring.h"
#include "manhat-lib/shared_lock.h"
#include "manhat-lib/shared_copy_file.h"
#include "manhat-lib/shared_translate_url.h"
#include "manhat-lib/shared_file_util.h"
#include "manhat-lib/shared_access.h"
#include "manhat-lib/shared_fold_file.h"
#include "manhat-lib/shared_bb_dat_parser.h"


typedef struct html_file_node
{
  char *fname;
  struct html_file_node *next;
}
HTML_FILE_NODE;

HTML_FILE_NODE *html_file_head;




FILE_NODE *file_head = (FILE_NODE *) 0;	/*list of attached files */
int attachments = 0;		/* how many files are attached? - 
				   **same as number of nodes pointed to 
				   ** by file_head
				 */



FILE_NODE *pasted_file_head = (FILE_NODE *) 0;	/* list of 'pasted' files */
int pasted_attachments = 0;	/* how many pasted files are attached? - 
				   **same as number of nodes pointed to 
				   ** by pasted_file_head
				 */


int attachments_written = 0;	/* set to 1 when uploaded files are written to disk */




/* deletes all files in list headed by file_head 
** called to clean up sender directory when a 
** critical error occurs after the files were written
*/
void
delete_attachment_files ()
{
  FILE_NODE *ptr;
  char fname[MAX_PATH + 1];

  for (ptr = file_head; ptr; ptr = ptr->next)
    {
      snprintf (fname, MAX_PATH + 1, "%s/%s", discussion_path,
		ptr->unique_fname);
      unlink (fname);
    }
}




void
big_error (const char *error_code, char *msg)
{
  if (attachments_written)
    delete_attachment_files ();
  cs_critical_error (error_code, msg);
}






static void
read_parameters (char *course, char *key, char *fromprog,
		 int *grp, int *topic, int *reply_to, int *website,
		 int *pasted_website, int *pasted_files, char *to, char *subject, int *hide, int *hotpot_exam,
		 int *width)
{
  char string[80];
  int temp_int;
  

  cs_get_required_parameter ("crs", course, MAX_PATH);
  cs_get_required_parameter ("id", key, MAX_KEY);
  cs_get_required_parameter ("from", fromprog, MAX_PATH);

  cs_get_required_parameter ("grp", string, 79);
  *grp = atoi (string);

  cs_get_required_parameter ("topic", string, 79);
  *topic = atoi (string);

  cs_get_required_parameter("reply_to", string, 79);
  *reply_to = atoi(string);

  cs_get_optional_parameter("to", to, MAX_USERNAME);
    
  
  /* did the teacher check the 'post message as hidden' box */
  cs_get_optional_parameter("hide", string, 79);
  if(strlen(string))
	*hide =1;
  else
	*hide =0;

  
  
  /* did user check the "I'm attaching a website' box? or the "I'm attaching a Hot Potatoes exam" box? */
  cs_get_optional_parameter("website", string, 79);
  if(strlen(string))
   {
    temp_int = !isdigit(*string)? 1: atoi(string);
    switch(temp_int)
     {
      case 0:  *website = 0;
             *hotpot_exam = 0;
             break;
             
      case 1:  *website = 1;
             *hotpot_exam = 0;
             break;
      case 2:  *website = 0;
             *hotpot_exam = 1;
             break;
      default: *website = 0;
             *hotpot_exam = 0;
             break;
     }
   }
  else
     {
      *website = 0;
      *hotpot_exam = 0;
     }                              
     
 /* the width parameter specifies the width of the
 ** textarea used to type the msg, and is used to
 ** split the msg into lines.  It should always be 
 ** there, but we'll treat it as an optional parameter.
 ** If the parameter is not supplied, *width will be set to
 ** zero and the calling program must get a valid value from
 ** the course configuration file.
 */
  cs_get_optional_parameter("width", string, 79);
  if(strlen(string))
	*width = atoi(string);
  else
	*width = 0;
	
                 
  cs_get_required_parameter("subject", subject, MAX_SUBJECT);
  /* was a website pasted from the clipboard? If so, this will be set to '1' */
  cs_get_optional_parameter("pasted_website", string, 79);
  if(strlen(string))
      *pasted_website = atoi(string); 
  else
      *pasted_website = 0; 
  
  /* were files pasted from the clipboard, If, so this will be set to the MAXIMUM number of
  *  pasted attachments we could find.  the user could have de-selected some or all of them
  */
  cs_get_optional_parameter("pasted_files", string, 79);
  if(strlen(string))
      *pasted_files = atoi(string);
  else
      *pasted_files = 0;

}



/* make sure no empty files have been uploaded and that there is an entry in
** the first file field, that is 'file1' must not be empty.
**/

static void
check_website_individual_files (CONFIG_STRUCT *conf)
{
  #define MAX_TMP_NAME 30
  int homefile = 0;
  int i;
  char name[MAX_TMP_NAME];
  char filename[MAX_PATH +1];
  FILE *fp;
  struct stat buf;

  for(i =1; i<= conf->mail_attachments; i++)
  {
     snprintf(name, MAX_TMP_NAME, "file%d", i);
     cs_get_optional_parameter(name, filename, MAX_PATH);

    if (strlen(filename))
      {
	fp = cgi_filehandle(global_cgi, name);
	fstat(fileno(fp), &buf);
	if (buf.st_size == 0)
	  big_error(ERR_EMPTY_FILE, filename);
	else if (!strcmp (name, "file1"))
	  homefile = 1;
      }
  }
  if (!homefile)
     big_error (ERR_FIRST_ATTACHMENT_FIELD_BLANK, "");
  #undef MAX_TMP_NAME 
}


static void
strtolower (char *string)
{
  char *ptr;

  for (ptr = string; *ptr; ptr++)
    *ptr = tolower (*ptr);
}

void
check_more_than_one_files(CONFIG_STRUCT *conf)
{
   int i;
   char fname[MAX_PATH +1];
   char value[MAX_PATH +1];
   int count =0;
   for(i =0; i<conf->mail_attachments; i++)
   {
      snprintf(fname, MAX_PATH +1, "file%d", i);
      cs_get_optional_parameter(fname, value, MAX_PATH);
      if(strlen(value))
          count++;
   }
   if(count > 1)
     cs_critical_error(ERR_ATTACH_ZIP_ONLY_ONE_FILE, "");
}


/** when attaching a website or a Hot Potatoes exam, the first attachment must contain
*** either a zip file (ending in *.zip) in which case it is the
*** only attachment, or an html file (ending in html or htm),
*** or a Hot Potatoes file (ending in *.jbc, *.jqz) etc.
*** the iszip parameter returns whether a zip file was found 
**/

static void
check_website_upload_type (int *iszip, int hotpot_exam, CONFIG_STRUCT *conf)
{
#define MAX_MSG 500

  char fname[MAX_PATH +1];
  char *orig_fname;
  char ext[MAX_FILENAME + 1];
  char msg[MAX_MSG];
  
  cs_get_required_parameter("file1",fname, MAX_PATH);

  /* get lowercased extention of the first attachment */
  orig_fname = (char *) malloc((strlen(fname) + 1) * sizeof(char));
  if(!orig_fname)
      big_error(ERR_MALLOC_FAILED, "check_website_upload_type()");
  strcpy (orig_fname, fname);
  strtolower (fname);
  get_extension (fname, ext);       /* shared_news_util.c */

  if (!strcmp (ext, "zip"))         /* did the first attachment end in *.zip? */
    {
      *iszip = 1;                   /* this is returned to the caller */
      check_more_than_one_files(conf);
    }
  else
    {				/* it's not a zip file -check to see if file1 has proper extension */
      *iszip = 0;
      if(hotpot_exam)  /* then first file must end in 'jbc' or 'jqz' */
       {
        if(strcmp(ext,"jbc") && strcmp(ext,"jqz"))
	   {
	    snprintf (msg, MAX_MSG, "(%s)",orig_fname);
	    big_error (ERR_FIRST_FIELD_NOT_ZIP_OR_HOTPOT, msg);
	   }
       }
      else           /* first file must end in 'htm' or 'html' */
       {  
         if (strcmp (ext, "htm") && strcmp (ext, "html"))
   	   {
	    snprintf (msg, MAX_MSG,"(%s)",orig_fname);
	    big_error (ERR_FIRST_FIELD_NOT_ZIP_OR_HTML, msg);
	   }
       }
    }  /* not a zip file */
    
 free(orig_fname);    
#undef MAX_MSG
}






/* this simply ensures that the zipfile is not empty
** additional integrity checks are done later
**/
static void
check_empty_zipfile ()
{
  char str[MAX_PATH +1];
  FILE *fp;
  struct stat buf;

  cs_get_required_parameter("file1", str, MAX_PATH);
  fp = cgi_filehandle(global_cgi, "file1");
  fstat(fileno(fp), &buf);
  if(buf.st_size ==0) 
    big_error (ERR_EMPTY_FILE, str);
}


/** user attached a website
*** did they leave the subject: blank?
*** did they attach a zip file or individual files?
*** did they follow the rules?
*** did they attach any empty files?
**/

static void
check_website_data (int *iszip, int hotpot_exam, CONFIG_STRUCT *conf)
{

  check_website_upload_type (iszip, hotpot_exam, conf);	/* did user upload a zip file or multiple files, and did they follow the rules?*/
  if (*iszip)
    check_empty_zipfile ();              /* make sure the zip file is not empty */
  else
    check_website_individual_files (conf);   /* make sure individual files are not empty */
}




static void
add_to_file_list ( FILE_NODE **head, const char *unique_fname, const char *orig_fname)
{
  FILE_NODE *ptr;

  if (!*head)
    {
      *head = (FILE_NODE *) malloc (sizeof (FILE_NODE));
      if (!*head)
	{
	  big_error (ERR_MALLOC_FAILED, "add_to_file_list()");
	}
      ptr = *head;
    }
  else
    {
      for (ptr = *head; ptr->next; ptr = ptr->next);
      ptr->next = (FILE_NODE *) malloc (sizeof (FILE_NODE));
      if (!ptr->next)
	{
	  big_error (ERR_MALLOC_FAILED, "add_to_file_list()");
	}
      ptr = ptr->next;
    }

  strncpy (ptr->unique_fname, unique_fname, MAX_FILENAME + 1);
  strncpy (ptr->orig_fname, orig_fname, MAX_FILENAME + 1);
  ptr->next = (FILE_NODE *) 0;
}



/** breaks raw_fname, which is of the form orig_fname::unique_fname
** into its component parts
**/

static void
parse_raw_filename (char *raw_fname, char *orig_fname, char *unique_fname)
{
  char *ptr;

  ptr = strstr (raw_fname, "::");
  if (!ptr)
    big_error (ERR_PARAM_FORMAT, raw_fname);
  *ptr = '\0';
  strncpy (orig_fname, raw_fname, MAX_FILENAME + 1);
  ptr += 2;			/* skip past "::" */
  strncpy (unique_fname, ptr, MAX_FILENAME + 1);
}



/* pasted_website is '1' if a website has been pasted
** pasted_files == the MAXIMUM number of files that could
** have been pasted to this message.  The actual number of
** files can be less, since the user may have de-selected them
** on the news_new_memo form.
**
** this fcn builds a linked list of the files that have been
** pasted to this message
**/

static void
build_pasted_attachment_list (int pasted_website, int pasted_files)
{
  int i;
  char parameter[10];		/* letter 'f' followed by a small integer, eg 'f1' f2' */
  char raw_filename[2 * (MAX_FILENAME + 1)];	/* consists of "orig_fname::unique_fname" */
  char orig_fname[MAX_FILENAME + 1];
  char unique_fname[MAX_FILENAME + 1];


 /* the files that have been pasted are represented by hidden parameters named
 ** f1, f2, f3...fn  The values of these parameters are of the form orig_fname::unique_fname
 **/
  for (i = 1; i <= pasted_files + pasted_website; i++)
    {
      snprintf (parameter, 10, "f%d", i);
      cs_get_optional_parameter (parameter, raw_filename, 2*MAX_FILENAME);
      if(strlen(raw_filename))
      {
	  parse_raw_filename (raw_filename, orig_fname, unique_fname);
	  add_to_file_list (&pasted_file_head, unique_fname, orig_fname);
	  pasted_attachments++;
      }

    }

}




  



/* note that this creates the file */
void
get_unique_fname(char *unique_fname, const char *base_dir, const char *filetype, time_t *time_sent)
{
 int fd;
 
 fd = get_unique_fd(base_dir, filetype, unique_fname, time_sent);
 close(fd);
} 








static void
get_system_filenames (const char *full_orig_name,
		      char *unique_fname,
		      char *orig_fname, time_t * time_sent)
{

  get_unique_fname (unique_fname, discussion_path, "fil", time_sent); /* shared_news_util.c */
  get_orig_fname (full_orig_name, orig_fname);  /* shared_news_uitl.c */
}



static void
build_normal_file_list (time_t * time_sent, CONFIG_STRUCT *conf)
{
 #define MAX_TMP_NAME 30
  char unique_fname[MAX_PATH + 1];
  char orig_fname[MAX_PATH + 1];
  char name[MAX_TMP_NAME];
  char filename[MAX_PATH +1];
  int i;
  FILE *fp;
  struct stat buf;

  for(i =1; i<= conf->mail_attachments; i++) 
  {
    snprintf(name, MAX_TMP_NAME, "file%d", i);
    cs_get_optional_parameter(name, filename, MAX_PATH );
    if (strlen(filename))
      {
	fp = cgi_filehandle(global_cgi, name);
	if(fstat(fileno(fp), &buf))
	    big_error(ERR_STAT_FAILED, "");
	if(buf.st_size == 0)
	    big_error(ERR_EMPTY_FILE, "");
	get_system_filenames (filename, unique_fname, orig_fname,
			      time_sent);
	add_to_file_list (&file_head, unique_fname, orig_fname);
	write_cgi_file (fp, buf.st_size, unique_fname); /* shared_news_util.c */
	attachments_written = 1;	/* set global flag for possible use in big_error() */
	attachments++;
      }
  }
 #undef MAX_TMP_NAME 
}



    

/** tests whether:
*** msg is empty
*** user uploaded a website (or Hot Potatoes exam) properly (preliminary checks)
*** user isn't trying to post both file attachments and a pasted website 
*** Also builds a linked list of 'normal' file attachments
***/

static void
check_form_data (int *is_web_zipfile,
		 int website,
	         int hotpot_exam,
		 int pasted_website,
		 int pasted_files, time_t * time_sent, CONFIG_STRUCT *conf)
{

  /* the following test should always fail, unless someone
   * is mucking around with the form - you cannot paste both
   * a website and files
   */
  if (pasted_website && pasted_files)
    big_error (ERR_INTERNAL_ERROR, "");

  /* build a linked list of attachments that have been pasted to this message */
  build_pasted_attachment_list (pasted_website, pasted_files);

  if (website || hotpot_exam)
    {
      check_incomplete_upload(); /* shared_news_util.c */
      check_website_data (is_web_zipfile, hotpot_exam, conf);	/* make sure message is not empty + do preliminary checks on web files */

      /* still here? make sure nothing but the new web site is attached */
      if (pasted_attachments)
	big_error (ERR_CANT_ATTACH_NEW_WEB_SITE, "");
    }
  else
    {
      *is_web_zipfile = 0;
      build_normal_file_list (time_sent, conf);
      //verify_non_empty ();	/* make sure message is not empty */
     
       if(attachments)
          check_incomplete_upload();  /*shared_news_util.c */
          
      if (pasted_website && pasted_attachments && attachments)
	big_error (ERR_CANT_MAKE_ADDITIONAL_ATTACHMENTS, "");
    }
}



static void
free_file_list (FILE_NODE ** head)
{
  FILE_NODE *ptr;

  while (*head)
    {
      ptr = *head;
      *head = (*head)->next;
      free (ptr);
    }
}

static void
free_html_filelist (void)
{
  HTML_FILE_NODE *ptr;

  while (html_file_head)
    {
      ptr = html_file_head;
      html_file_head = html_file_head->next;
      if(ptr->fname)
	 free(ptr->fname);
      free (ptr);
    }


}



static void
process_message_file (char *msg_fname, time_t *time_sent, int width)
{
  char *str =0;
  FILE *fp;
  char filepath[MAX_PATH +1];
  int msg_len;

  hdf_get_copy(global_cgi->hdf, "Query.msg", &str, "");

  get_unique_fname (msg_fname, discussion_path, "msg", time_sent);  /* shared_news_util.c */
  snprintf(filepath, MAX_PATH +1, "%s%s", discussion_path, msg_fname);
  
  msg_len = !str? 0 : strlen(str);
  if( (msg_len < width) || USE_TEXTAREA_HARD_WRAP)  /* custom.h */
    {
     fp = fopen(filepath,"w");
     if(!fp)
       cs_critical_error(ERR_FOPEN_WRITE_FAILED, filepath);
     if(msg_len)
       {   
        if(fwrite(str, strlen(str), 1, fp) != 1)
          cs_critical_error(ERR_FWRITE_FAILED, filepath);
       }    
     fclose(fp);   
     }
   else 
      write_folded_text(filepath, str, width);    /* shared_fold_file.c */
}


static void
process_pasted_attached_files (int pasted_files, int pasted_website,
			       time_t * time_sent, SESSION *user)
{
  FILE_NODE *fptr;
  char clipboard_path[MAX_PATH + 1];
  char unique_fname[MAX_FILENAME + 1];
  char sourcepath[MAX_PATH + 1];
  char destpath[MAX_PATH + 1];

  if ((pasted_files || pasted_website) && pasted_file_head)
    {
      snprintf (clipboard_path, MAX_PATH + 1, "%s%s/", CLIPBOARD_PATH,
		user->id);
      if (pasted_website)
	{
	  get_unique_fname (unique_fname, discussion_path, 
                     is_hotpot_exam_dir(pasted_file_head->unique_fname)?HOTPOT_EXAM_DIR_PREFIX:NORMAL_WEBSITE_DIR_PREFIX, 
                     time_sent);
          copy_dir (clipboard_path,   /* shared_copy_file.c */
		    discussion_path,
		    pasted_file_head->unique_fname, unique_fname);
	  strncpy (pasted_file_head->unique_fname, unique_fname,
		   MAX_FILENAME + 1);
	}
      else
	{
	  for (fptr = pasted_file_head; fptr; fptr = fptr->next)
	    {
	      get_unique_fname (unique_fname, discussion_path, "fil", time_sent);
	      snprintf (sourcepath, MAX_PATH + 1, "%s%s", clipboard_path,
			fptr->unique_fname);
	      snprintf (destpath, MAX_PATH + 1, "%s%s", discussion_path,
			unique_fname);
	      copy_file (sourcepath, destpath);	/* shared_copy_file.c */
	      strncpy (fptr->unique_fname, unique_fname, MAX_FILENAME + 1);
	    }
	}
    }
}





static void
complete_send (int grp, const char *msg_fname, SESSION *user, const char *receiver_username, time_t time_sent,
	          int website, int hotpot_exam, int pasted_website, int reply_to, int topic, 
	          const char *subject, int hide, long *offset, char *info_fname)
{
  ATTACHMENT attach;
  FILE_NODE *fptr;
  FILE  *inbox_fp;
  int info_fd, inbox_fd;
  struct stat buf;
  NEWS_PERSON person;
  
  char inbox_fname[MAX_PATH + 1];
  int msg_id;
  CENTRAL_INBOX_RECORD inbox_record, new_inbox_record;
  int inbox_record_size, attachment_size;
  
 /* memorize the sizes of these data structure, 
 ** so we don't have to repeatedly call sizeof()
 */
  inbox_record_size = sizeof(CENTRAL_INBOX_RECORD); 
  attachment_size = sizeof(ATTACHMENT);
  
 /* this user has already read this message
 ** set a NEWS_PERSON record to be written
 ** to the end of the new inf file.
 */
 strncpy(person.username,user->username, MAX_USERNAME + 1);
 strncpy(person.realname, user->realname, MAX_NAME + 1);
 person.time_read = time_sent;
 person.user_unlocked = 0;



  /* set up as much of the new inbox record as we can before locking
  ** the inbox.dat file
  */  
  strncpy(new_inbox_record.info.msg_fname, msg_fname, MAX_FILENAME + 1);
  strncpy(new_inbox_record.info.sender, user->username, MAX_USERNAME + 1);
  strncpy(new_inbox_record.info.sender_realname, grp== ANONYMOUS_DISCUSSION?user->alias:user->realname, MAX_NAME + 1);
  strncpy(new_inbox_record.info.receiver_username,receiver_username,MAX_USERNAME + 1);  
  strncpy(new_inbox_record.info.subject, subject, MAX_SUBJECT + 1);
  new_inbox_record.info.time_sent = time_sent;
  new_inbox_record.info.attachments =
                            website || hotpot_exam || (pasted_website && pasted_attachments) ? -1 :
                                                               attachments + pasted_attachments;
  new_inbox_record.info.sender_team = user->team;
  new_inbox_record.info.reply_to = reply_to;
  
  /* get the name of the 'inf' file to do so, we need to open
  ** (create) the file
  */
  info_fd = get_unique_fd (discussion_path,"inf", info_fname, &time_sent);
  /* don't bother to lock it, since no one knows it's there yet! */
  
  strncpy(new_inbox_record.info_fname,info_fname,MAX_FILENAME + 1);
  new_inbox_record.release_time = 0;       /* no release time set */
  new_inbox_record.unrelease_time = 0;     /* no unrelease time either */
  new_inbox_record.status =  hide? TEACHER_HIDDEN: NORMAL;       /* mark msg as teacher hidden, if appropriate*/
  
  /* now open and lock the inbox.dat file */
  snprintf(inbox_fname,MAX_PATH + 1,"%s%s",central_discussion_path,INBOX_FNAME);
  inbox_fp = fopen(inbox_fname,"a+");
  if(!inbox_fp)                              /* couldn't open or create inbox.dat */
    big_error(ERR_FOPEN_WRITE_FAILED,"");
  inbox_fd = fileno(inbox_fp);
  get_exclusive_lock(inbox_fd, 1);                      /* shared_lock.c */
  
  
  /* get the next available msg_id, by incrementing the msg_id of the
  ** last msg in the inbox.dat file
  */
  fstat(inbox_fd, &buf);
  if(!buf.st_size)    /* this is the first message */
     msg_id = 1;
  else
    {
     if(fseek(inbox_fp,-inbox_record_size,SEEK_END) == -1)
       big_error(ERR_FSEEK_FAILED, "");
     if(fread( &inbox_record, inbox_record_size, 1, inbox_fp) != 1)
       big_error(ERR_FREAD_FAILED, "");
     msg_id =  inbox_record.info.msg_id + 1;
    }
    
    
   new_inbox_record.info.msg_id = msg_id;
   new_inbox_record.info.topic_id = topic == -1? msg_id : topic;
   if(fseek(inbox_fp,0,SEEK_END) == -1)
     big_error(ERR_FSEEK_FAILED, "");

   *offset = ftell(inbox_fp);   /* offset is needed if this is a hotpot exam */
   
   if(fwrite(&new_inbox_record, inbox_record_size,1,inbox_fp) != 1)
      big_error(ERR_FWRITE_FAILED, "");



  /* now write the info file */   
  if (write (info_fd, &new_inbox_record.info, sizeof (NEWS_INFO)) != sizeof(NEWS_INFO))
    big_error (ERR_FWRITE_FAILED, "");

  for (fptr = file_head; fptr; fptr = fptr->next)
    {
      strncpy (attach.unique_fname, fptr->unique_fname, MAX_FILENAME + 1);
      strncpy (attach.orig_fname, fptr->orig_fname, MAX_FILENAME + 1);
      if (write (info_fd, &attach, attachment_size) != attachment_size)
	big_error (ERR_FWRITE_FAILED, "");
    }


  for (fptr = pasted_file_head; fptr; fptr = fptr->next)
    {
      strncpy (attach.unique_fname, fptr->unique_fname, MAX_FILENAME + 1);
      strncpy (attach.orig_fname, fptr->orig_fname, MAX_FILENAME + 1);
      if (write (info_fd, &attach, attachment_size) != attachment_size)
	big_error (ERR_FWRITE_FAILED, "");
    }

   if(write (info_fd,&person, sizeof(NEWS_PERSON))!= sizeof(NEWS_PERSON))
        big_error (ERR_FWRITE_FAILED, "");
        
   close (info_fd);

   release_lock(inbox_fd);     /* shared_lock.c */
   fclose(inbox_fp);
   
   write_user_inbox_record(msg_id, time_sent);    /* shared_news_util.c */
}



static void
back_up (char *program)
{
  char temp[MAX_PATH + 1];

  
  translate_url_substitutes (temp, program, MAX_PATH + 1, 0);	/* shared_translate_url.c 0= use &, not &amp; */
//  cs_cgi_redirect(temp);   /* shared_cs_util.h */
    printf ("Location:%s\n\n", temp);   
}




/* reads the 'team' parameter from the CGI input stream
** sets this user's team to that value 
*/
static void
get_set_team (SESSION *user)
{
  char team[2];

  cs_get_required_parameter ("team", team, 1);	/* a one character team= parameter MUST be there */
  if(!is_valid_team(*team))   /* shared_teamstring.c */
      cs_critical_error (ERR_TEAM_PARAM_NOT_FROM_A_Z, "get_set_team()");   /* shared_cs_util.c */
  user->team = *team;		/* this user is on that team for the rest of this program */
}




int
isdir (const char *dirname, const char *fname)
{
  struct stat buf;
  char path[MAX_PATH + 1];

  snprintf (path, MAX_PATH + 1, "%s/%s", dirname, fname);

  stat (path, &buf);
  return S_ISDIR (buf.st_mode);
}





void
clean_zip_temp_dir ()
{
  char dirname[MAX_PATH + 1];

  snprintf (dirname, MAX_PATH + 1, "%s/%s", discussion_path, ZIP_TMP_DIRNAME);
  kill_directory(dirname);     /* shared_file_util.c */

}




void
create_tmp_zip_dir ()
{
  char dirname[MAX_PATH + 1];


  snprintf (dirname, MAX_PATH + 1, "%s/%s", discussion_path, ZIP_TMP_DIRNAME);

  if (mkdir (dirname, 0777))
    big_error (ERR_MKDIR_FAILED, dirname);

}




void
delete_zip_tmp_file ()
{
  char fname[MAX_PATH + 1];

  snprintf (fname, MAX_PATH + 1, "%s/%s", discussion_path, ZIP_TMP_FNAME);
  unlink (fname);
}


static void
check_unzip_status (int error)
{

  delete_zip_tmp_file ();


  switch (error)
    {
      /* this is my error code if exec() failed */
    case 88:
      big_error (ERR_CANT_START_UNZIP, "exec()");
      break;



      /* remaining errors reported by unzip */
    case 0:
      return;			/* everything went OK */

    case 1:
      big_error (ERR_UNZIP_CODE_1, "unzip1");
      break;


    case 2:
      big_error (ERR_UNZIP_CODE_2, "unzip2");
      break;

    case 3:
      big_error (ERR_UNZIP_CODE_3, "unzip3");
      break;

    case 51:
      big_error (ERR_UNZIP_CODE_51, "unzip51");
      break;

    case 4:			/* memory allocation error 4-8 */
    case 5:
    case 6:
    case 7:
    case 8:
      big_error (ERR_UNZIP_MEMORY_ERROR, "allocation_error");
      break;

    case 9:
      big_error (ERR_UNZIP_CODE_9, "unzip9");
      break;



    case 10:
      big_error (ERR_UNZIP_CODE_10, "unzip10");
      break;

    case 11:
      big_error (ERR_UNZIP_CODE_11, "unzip11");
      break;


    case 50:
      big_error (ERR_UNZIP_CODE_50, "unzip50");
      break;

    default:
      big_error (ERR_UNZIP_UNDOCUMENTED_ERROR, "unzip undocumented error");
      break;

    }
}




static void
unzip_zipfile ()
{
  char zip_path[MAX_PATH + 1];
  char zipdir_path[MAX_PATH + 1];
  char zipdir_switch[MAX_PATH + 1];
  char zipfile[MAX_PATH +1];
  FILE *fp;
  struct stat buf;

  pid_t pid, status;

  cs_get_required_parameter("file1", zipfile, MAX_PATH);
  fp = cgi_filehandle(global_cgi, "file1");
  if(fstat(fileno(fp), &buf))
     big_error (ERR_CANT_FIND_ZIPFILE, "");
  if(buf.st_size ==0)
     big_error (ERR_EMPTY_FILE, "");

  write_cgi_file (fp, buf.st_size, ZIP_TMP_FNAME);

  snprintf (zipdir_path, MAX_PATH + 1, "%s/%s", discussion_path,
	    ZIP_TMP_DIRNAME);
  clean_zip_temp_dir ();
  snprintf (zipdir_switch, MAX_PATH + 1, "-d%s", zipdir_path);	/* tells unzip to extract to this directory */

  snprintf (zip_path, MAX_PATH + 1, "%s/%s", discussion_path, ZIP_TMP_FNAME);


  if ((pid = fork ()) < 0)
    big_error (ERR_CANT_START_NEW_PROCESS, "unzip_zipfile()");


  if (pid == 0)
    {				/* child process */
      execl (UNZIP_PATH, "unzip", zipdir_switch, "-o",  "-qq", zip_path, (char *) 0);
      exit (88);		/* this is returned if above execl() fails */
    }
 /*** parent process */
  while (wait (&status) != pid);

  check_unzip_status (WEXITSTATUS (status));	/* report on status */

}




static void
get_lowercase_extension (const char *filename, char *extension)
{
  char *ptr;

  ptr = strrchr (filename, '.');
  if (!ptr)
    {
      *extension = '\0';
      return;
    }

  strncpy (extension, ptr + 1, MAX_FILENAME + 1);
  for (ptr = extension; *ptr; ptr++)
    *ptr = tolower (*ptr);
}



static void
add_to_html_file_list (const char *fname)
{
  HTML_FILE_NODE *ptr;

  if (!html_file_head)
    {
      html_file_head = (HTML_FILE_NODE *) malloc (sizeof (HTML_FILE_NODE));
      if (!html_file_head)
	big_error (ERR_MALLOC_FAILED, "add_to_html_file_list()");
      ptr = html_file_head;
    }
  else
    {
      for (ptr = html_file_head; ptr->next; ptr = ptr->next);
      ptr->next = (HTML_FILE_NODE *) malloc (sizeof (HTML_FILE_NODE));
      if (!ptr->next)
	big_error (ERR_MALLOC_FAILED, "add_to_html_file_list()");
      ptr = ptr->next;
    }
  ptr->fname = (char *)malloc(sizeof(char)*strlen(fname)+1);
  strncpy (ptr->fname, fname, strlen(fname) + 1);
  ptr->next = (HTML_FILE_NODE *) 0;
}




/** if hotpot_exam, then build a list of files ending in *.jbc or *.jqz
*** otherwise, build a list of files ending in *.htm or *.html
**/

static void
build_html_file_list (int *file_count, int hotpot_exam)
{
  char dirname[MAX_PATH + 1];
  DIR *dir_p;
  struct dirent *dir_entry_p;
  char extension[MAX_FILENAME + 1];
  char new_file[MAX_PATH + 1];


  *file_count = 0;
  snprintf (dirname, MAX_PATH + 1, "%s/%s", discussion_path, ZIP_TMP_DIRNAME);
  dir_p = opendir (dirname);
  if (!dir_p)
    big_error (ERR_OPENDIR_FAILED, "");

  while (NULL != (dir_entry_p = readdir (dir_p)))
    {
      if (!isdir (dirname, dir_entry_p->d_name))
	{
	  get_lowercase_extension (dir_entry_p->d_name, extension);
	  if ( (hotpot_exam && (!strcmp(extension, "jbc") || !strcmp(extension, "dat") || !strcmp(extension, "jqz")) )  ||
               (!hotpot_exam && (!strcmp (extension, "htm") || !strcmp (extension, "html"))) )
	    {
	      if(!strcmp(extension, "dat"))
               {
                     convert_bb_to_jbc( dirname, dir_entry_p->d_name, new_file);  /* shared_bb_dat_parser.c */
                     add_to_html_file_list (new_file);
               }
              else
	        add_to_html_file_list (dir_entry_p->d_name);
	      (*file_count)++;
	    }
	}
    }
  closedir (dir_p);
}




/** This function examines the files that were unzipped to the 
*** temporary ZIP_TMP_DIRNAME directory, looking for the 'start'
*** file for the attached website.
***
*** If the uploaded zip file looks like a Lectern II RealAudio
*** lecture, then the start_fname is set to a special flag value
*** ZIP_LECTERN_START_FNAME (#defined in global.h).  This will flag
*** other programs, including web_page_start, to generate some on-the-fly
*** HTML to make this lecture behave properly.
***
*** otherwise....
***
*** If a file named ZIP_START_FNAME (i.e. 'index.html') is found
***                   then that is the start file.
*** Else if there is only one file ending in .htm or .html
***   in the extracted zip file, then that is the file that is
***   used as the start file.
*** Else if there is more than one file ending in .html or
***   .html in the directory, then done is set to 0, flagging that
***   we'll have to ask the user later which file to use as the
***   start file
**/




static void
verify_start_file (char *start_fname, int *done)
{

#define MAX_MSG 100

  char start_path[MAX_PATH + 1];
  struct stat buf;
  int file_count;
  char msg[MAX_MSG];


  if(is_lectern_lecture())    /* shared_news_util.c */
   {
     strncpy(start_fname, ZIP_LECTERN_START_FNAME, MAX_FILENAME + 1);
     *done = 1;
     return ;
   }  
     

  *done = 1;	
  snprintf (start_path, MAX_PATH + 1, "%s/%s/%s", discussion_path,
	    ZIP_TMP_DIRNAME, ZIP_START_FNAME);
  if (!stat (start_path, &buf))                                             /* if a file named ZIP_START_FNAME exists ... */
    {				
      if (S_ISDIR (buf.st_mode))                                            /* does the user's website include a DIRECTORY named ZIP_START_FNAME? */
	{			
	  build_html_file_list (&file_count,0);                               /* examine the rest of the files in the directory */
	  if (file_count == 1)
	    strncpy (start_fname, html_file_head->fname, MAX_FILENAME + 1); /* there's only one html file */
	  else                                                              /* no html files, but a directory named ZIP_START_FNAME */
	    {
	      snprintf (msg, MAX_MSG, "(%s)", ZIP_START_FNAME);
	      big_error (ERR_DIR_NAMED_X_FOUND_X_IS_RESERVED, msg);
	    }
	}
      else			                                             /* ZIP_START_FNAME was not a directory */
	strncpy (start_fname, ZIP_START_FNAME, MAX_FILENAME + 1);	     /* use it as the start_fname */
    }
  else
    {				                                             /* no file named ZIP_START_FNAME was found */
      build_html_file_list (&file_count, 0);                                    /* build a list of files in the directory */
      if (!file_count)                                                       /* if no html files were found */ 
	big_error (ERR_NO_HTML_FILES_FOUND, "");         /* report the error */  
      else if (file_count == 1)                                              /* only one html file found */
	strncpy (start_fname, html_file_head->fname, MAX_FILENAME + 1);      /* then use it */
      else                                                                   /* multiple html files were found, none of them named ZIP_START_FNAME */ 
	{
	  *done = 0;		                                              /* we'll have to ask the user to name the file (in another CGI program) */
	  strncpy (start_fname, ZIP_START_FNAME, MAX_FILENAME + 1);           /* use ZIP_START_FNAME as the start file, we'll symlink it to the one the user specifies */
	}
    }
#undef MAX_MSG
}




/** This function examines the files which were unzipped to the 
*** temporary ZIP_TMP_DIRNAME directory, looking for the 'start'
*** file for the attached HOT POTATOES exam.
**/

static void
verify_hotpot_start_file (char *start_fname)
{

  int file_count;

   build_html_file_list (&file_count,1);                                  /* build a list of Hot Potato files in the directory */
   if (!file_count)                                                       /* if no Hot Potato  files were found */ 
      big_error (ERR_NO_HOTPOTATO_FILES_FOUND, "");      /* report the error */  
   else if (file_count == 1)                                              /* only one hotpot file found */
	strncpy (start_fname, html_file_head->fname, MAX_FILENAME + 1);      /* then use it */
   else                                                                   /* multiple hot potato files were found */ 
        big_error (ERR_MORE_THAN_ONE_HOTPOT_FILE_FOUND, "");      /* report the error */  	

}






/* copies the individual files attached by the user to 
** the (newly created) ZIP_TMP_DIR
*/
static void
process_individual_website_files (char *start_fname, CONFIG_STRUCT *conf)
{
 #define MAX_TMP_NAME 30

  char name[MAX_TMP_NAME];
  char orig_fname[MAX_PATH + 1];
  char write_dest_fname[MAX_PATH + 1];
  int i;
  char filename[MAX_PATH +1];
  FILE *fp;
  struct stat buf;
  

  clean_zip_temp_dir ();
  create_tmp_zip_dir ();
  strncpy (start_fname, "index.html", MAX_FILENAME + 1);	/* assume this, just to be safe */
  for(i =1; i<= conf->mail_attachments; i++)
  {
    snprintf(name, MAX_TMP_NAME, "file%d", i);
    cs_get_optional_parameter(name, filename, MAX_PATH);
    if (strlen(filename))
      {
	fp = cgi_filehandle(global_cgi, name);
	if(fstat(fileno(fp), &buf))
             big_error(ERR_STAT_FAILED, "");
	if(buf.st_size == 0)
             big_error(ERR_EMPTY_FILE, "");

	get_orig_fname (filename, orig_fname);
	snprintf (write_dest_fname, MAX_PATH + 1, "%s/%s", ZIP_TMP_DIRNAME, orig_fname);	/* so we can spoof write_cgi_file */
	write_cgi_file (fp,buf.st_size,  write_dest_fname);
	if (!strcmp (name, "file1"))
	  strncpy (start_fname, orig_fname, MAX_FILENAME + 1);
      }
  }
 #undef MAX_TMP_NAME 
}


static void
rename_tmp_dir (char *directory_fname, time_t * time_sent, int hotpot_exam)
{
  char oldname[MAX_PATH + 1];
  char newname[MAX_PATH + 1];

  snprintf (oldname, MAX_PATH + 1, "%s%s", discussion_path, ZIP_TMP_DIRNAME);
 
  /* if this is a hot potatoes exam, the directory name starts with 'hot' instead of the
  ** 'dir' used by ordinary websites
  */
  get_unique_fname (directory_fname, discussion_path, hotpot_exam?HOTPOT_EXAM_DIR_PREFIX:NORMAL_WEBSITE_DIR_PREFIX, time_sent);
  snprintf (newname, MAX_PATH + 1, "%s%s", discussion_path, directory_fname);
  unlink(newname);  /* because get_unique_fname() created the file */
  if (rename (oldname, newname))
     big_error (ERR_CANT_RENAME_TEMP_DIR, oldname);
        

}


static void
process_website_attached_files (time_t * time_sent, int is_web_zipfile, int hotpot_exam, int *done, CONFIG_STRUCT *conf)
{
  char start_file[MAX_FILENAME + 1];
  char directory_fname[MAX_FILENAME + 1];

  if (is_web_zipfile)
    {
      unzip_zipfile ();
      if(!hotpot_exam)   /* if an ordinary web site is being attached */
           verify_start_file (start_file, done);
      else
       {
           verify_hotpot_start_file(start_file);
           *done = 1;
        }         
    }
  else
    {
      process_individual_website_files (start_file, conf);
      *done = 1;
    }

  rename_tmp_dir (directory_fname, time_sent,hotpot_exam);
  add_to_file_list (&file_head, directory_fname, start_file);

}



void
ask_which_fname ( char *course,  char *key,
		 int grp,  char *from, CONFIG_STRUCT *conf)
{
 #define MAX_TMP_NAME 30
  HTML_FILE_NODE *ptr;
  int i =0;
  char name[MAX_TMP_NAME];
  
  cs_set_course_info(conf);
  cs_set_current_time();

  cs_set_int_value("grp", grp);
  cs_set_value("crs", course);
  cs_set_value("id", key);
  cs_set_value("from", from);
  cs_set_value("dir", file_head->unique_fname);

  for (ptr = html_file_head; ptr; ptr = ptr->next)
  {
    snprintf(name, MAX_TMP_NAME, "file.%d.name", i);
    cs_set_value(name, "fname");
    snprintf(name, MAX_TMP_NAME, "file.%d.value", i);
    cs_set_value(name, ptr->fname);
    if(i ==0)
    {
       snprintf(name, MAX_TMP_NAME, "file.%d.checked", i);
       cs_set_int_value(name, 1);
    }
   i++;
  }
  free_html_filelist ();     
  free_file_list (&file_head);
 #undef MAX_TMP_NAME 
}



static
void hotpot_exam_check(const char *crs, const char *key, const char *sender, const char *info_fname, long offset)
{
    char url[MAX_PATH +1];
    
    snprintf(url, MAX_PATH +1, "%s?crs=%s&id=%s&sender=%s&inf=%s&loc=%ld",
                           "hotpot_exam_check", crs, key, sender, info_fname, offset);
    cs_set_value("hotpot_url", url);
}
    








int
main (void)
{
  char course[MAX_PATH + 1];	/* from crs=? command line */
  char key[MAX_KEY + 1];	/* from id=? command line */
  char from[MAX_PATH + 1];	/* from from=? command line */
  int grp;			/* from grp=? command line */
  int topic;			/* from topic=? command line */
  int reply_to;                 /* from reply_to=? command line */
  char to[MAX_USERNAME + 1];	/* from to=? command line for ASSIGNMENTS */

  int pasted_website;		/* from optional pasted_website=? cmd line */
  int pasted_files;		/* from optional pasted_files=? cmd line */
  int hide;                     /* from optional hide=? cmd line */
  
  int width;                    /* from width=? cmd line, used to split msg into lines */
  
  char msg_fname[MAX_FILENAME + 1];	/* name given to the message */
  time_t time_sent;
  char subject[MAX_SUBJECT + 1];

  int website;			/* is the user posting a website? */
  int is_web_zipfile;		/* was a web page attached as a single zip file? */
  int hotpot_exam =0;              /* is the user posting a Hot Potatoes exam? */
  
  int done;			/* are we done, or do we have to ask user to pick the starting html file from a list */
  
  /*** next two are used for hotpot exams to redirect user to hotpot_exam_check program */
  long offset;                       /** offset into central inbox.dat file where this msg is stored */
  char info_fname[MAX_FILENAME + 1]; /* name of info_fname for this message */
  
  SESSION user;
  CONFIG_STRUCT conf;	

  cs_cgi_init();      /* shared_cs_util.c */
  read_parameters (course, key, from, &grp, &topic, &reply_to, &website,
		   &pasted_website, &pasted_files, to, subject, &hide, &hotpot_exam, &width);

  
  read_configuration_file (course, &conf);	/* shared_util.c */
  validate_key (key, &user, &conf);	        /* shared_util.c */

  if(!has_write_permission(&user, &conf))   /* shared_access.c */
      access_denied_error();                /* shared_access.c */
  
  
  /** If a width (for the textarea) was not supplied on the parameter list,
  *** then default to the standard editor width defined for this classroom.
  **/   
  if(!width)
    width=editor_width_config(conf.misc);    /* shared_news_util.c */   
     
  /* the 'hide' parameter, which controls whether or not this message is posted as
  ** hidden by the teacher, can only be set if the user is FACULTY and if the msg
  ** posted is a new topic, OR if we are in the Assignments module, where students
  ** taking a Hot Potatoes exam can (behind the scenes) post a message as teacher-hidden
  ** Instead of printing an error when bogus values are
  ** received, we'll just unset the 'hide' flag
  */
  
  if( (grp != ASSIGNMENTS) && ((user.group != FACULTY) || (topic != -1)) )
       hide = 0;
       
 /*** only teachers in the Assignments module should be able to post a Hot Potatoes exam
 **** the next test is done just to foil people trying to break the system 
 ***/
 if(!allow_hotpot_exam_post(&user, grp, topic))   /* shared_news_util.c */
      hotpot_exam = 0;
            

  /* if this it the Team/Teach module and a faculty member is running the program, there must be a 'team'
  ** parameter.  This user will switch to that team for the duration of the run
  */
  if ((grp == TEAM_TEACH) && (user.group == FACULTY))
    get_set_team (&user);

  time_sent = time (NULL);   /* this is the timestamp of when this msg was sent */

  set_discussion_names (conf.course_path, user.username, grp);	/* shared_news_util.c */

  /* perform a variety of sanity checks on what the user is trying to send */
  check_form_data (&is_web_zipfile, website, hotpot_exam, pasted_website,
		   pasted_files, &time_sent, &conf);

  /* if the user is attaching a website, we next deal with those attachments
  ** the 'done' variable is used to determine whether we need to ask the user
  ** an additional question re: the website via another CGI program
  */
  
  if (website || hotpot_exam)
    process_website_attached_files (&time_sent, is_web_zipfile, hotpot_exam, &done, &conf);
  else
    done = 1;			/* no need for more info from user */



  /* save the text message to a unique filename */
  process_message_file (msg_fname, &time_sent, width);
   
  /* copy pasted files from clipboard */
  process_pasted_attached_files (pasted_files, pasted_website, &time_sent, &user);

  /* free the data structures used in shared_multipart.c */
  
  /* send the message */
  complete_send(grp,msg_fname, &user, to, time_sent, website, hotpot_exam, pasted_website, reply_to, 
                            topic,subject, hide, &offset, info_fname);

  

  /* more cleanup */
  free_file_list (&pasted_file_head);

  
   if (done)  /* if we don't need to ask the user anything about an attached website */
    {
          
      free_html_filelist ();
      free_file_list (&file_head);

     if(hotpot_exam)
        {
          hotpot_exam_check(course, key, user.username, info_fname, offset);
	  cs_cgi_display("hotpot_exam_check_opener", 0);
        }  
      else    
          back_up (from);        /* redirect to 'from' page */
          
     }
   else
   {
      ask_which_fname (course, key, grp, from, &conf);  /* redirect to another CGI to ask a question */
      cs_cgi_display("select_home_html_file", 1);
   }
  cs_cgi_destroy();
  return 0;
}




