
/* 
 * HTML forms
 */
#include "fm.h"
#include "parsetag.h"
#include "parsetagx.h"
#include "myctype.h"
#include "local.h"

#ifndef HAVE_LSTAT
/* lstat is identical to stat, only the link itself is statted, not the file
 * that is obtained by tracing the links. But on OS/2 systems, there is no
 * differences. */
#define lstat stat
#endif				/* not HAVE_LSTAT */

struct {
    char *action;
    void (*rout) (struct parsed_tagarg *);
} internal_action[] = {
    {"map", follow_map},
    {"option", panel_set_option},
#ifdef USE_COOKIE
    {"cookie", set_cookie_flag},
#endif				/* USE_COOKIE */
    {"none", NULL},
    {"process", close_selected_process},
    {"multipart", multipart_load_part},
#ifdef USE_GOPHER
    {"gopher", gopher_redirect},
#endif
    {NULL, NULL},
};

struct form_list *
newFormList(char *action, char *method, char *charset, char *enctype, char *target, char *name, struct form_list *_next)
{
    int m = FORM_METHOD_GET;
    int e = FORM_ENCTYPE_URLENCODED;

    if (method == NULL || !strcasecmp(method, "get"))
	m = FORM_METHOD_GET;
    else if (!strcasecmp(method, "post"))
	m = FORM_METHOD_POST;
    else if (!strcasecmp(method, "internal"))
	m = FORM_METHOD_INTERNAL;
    /* unknown method is regarded as 'get' */

    if (enctype != NULL && !strcasecmp(enctype, "multipart/form-data")) {
	e = FORM_ENCTYPE_MULTIPART;
	if (m == FORM_METHOD_GET)
	    m = FORM_METHOD_POST;
    }

    return newFormList_internal(action, m, charset, e, target, name, _next);
}

struct form_list *
newFormList_internal(char *action, int method, char *charset, int enctype, char *target, char *name, struct form_list *_next)
{
    struct form_list *l;
    Str a = Strnew_charp(action);
#ifndef MANY_CHARSET
    int c = 0;
#endif

#ifndef MANY_CHARSET
    if (charset != NULL)
	c = *charset;
#endif

    l = New(struct form_list);
    l->item = l->lastitem = NULL;
    l->action = a;
    l->method = method;
    l->method_str = NULL;
#ifdef MANY_CHARSET
    l->charset = charset;
#else
    l->charset = c;
#endif
    l->enctype = enctype;
    l->target = target;
    l->name = name;
    l->next = _next;
    l->nitems = 0;
    l->body = NULL;
    l->length = 0;
    return l;
}

/* 
 * add <input> element to form_list
 */
struct form_item_list *
formList_addInput(struct form_list *fl, struct parsed_tag *tag, Phase1Env *p1env)
{
    struct form_item_list *item;
    char *p;
    int i;

    /* if not in <form>..</form> environment, just ignore <input> tag */
    if (fl == NULL)
	return NULL;

    item = New(struct form_item_list);
    item->type = FORM_UNKNOWN;
    item->size = -1;
    item->rows = 0;
    item->checked = item->init_checked = 0;
    item->accept = 0;
    item->name = NULL;
    item->value = item->init_value = NULL;
    item->lines = NULL;
    item->readonly = 0;
    item->y = 0;
    item->hseq = -1;
    item->fold = FoldTextarea;
    if (parsedtag_get_value(tag, ATTR_TYPE, &p)) {
	item->type = formtype(p);
	if (item->size < 0 &&
	    (item->type == FORM_INPUT_TEXT ||
	     item->type == FORM_INPUT_FILE ||
	     item->type == FORM_INPUT_PASSWORD))
	    item->size = FORM_I_TEXT_DEFAULT_SIZE;
    }
    if (parsedtag_get_value(tag, ATTR_NAME, &p))
	item->name = Strnew_charp(p);
    if (parsedtag_get_value(tag, ATTR_VALUE, &p)) {
	item->value = item->init_value = Strnew_charp(p);
	if ((p1env->p0env->flag & RG_PROC_MASK) == RG_PROC_SUP) {
#ifdef MENU_SELECT
	  if (item->type == FORM_SELECT &&
	      parsedtag_get_value(tag, ATTR_SELECTNUMBER, &i)) {
	    Str uq, value, label;
	    int j, k, checked;

	    NEW_OBJV1(&p1env->max_select, i, &p1env->select_option, sizeof(FormSelectOption), 0);
	    p1env->select_option[i].first = p1env->select_option[i].last = NULL;
	    uq = urgent_unquote(item->value);

	    for (j = 0 ; j < uq->length ;) {
	      checked = uq->ptr[j++];
	      k = strlen(&uq->ptr[j]);
	      label = Strnew_charp_n(&uq->ptr[j], k);
	      j += k + 1;
	      k = strlen(&uq->ptr[j]);
	      value = Strnew_charp_n(&uq->ptr[j], k);
	      j += k + 1;
	      addSelectOption(&p1env->select_option[i], value, label, checked);
	    }
	  }
	  else
#endif
	    if (item->type == FORM_TEXTAREA &&
		parsedtag_get_value(tag, ATTR_TEXTAREANUMBER, &i)) {
	      NEW_OBJV1(&p1env->max_textarea, i, &p1env->textarea_str, sizeof(Str *), 0);
	      p1env->textarea_str[i] = urgent_unquote(item->value);
	      if (p1env->n_textarea <= i)
		p1env->n_textarea = i + 1;
	    }
	}
    }
    item->checked = item->init_checked = parsedtag_exists(tag, ATTR_CHECKED);
    item->accept = parsedtag_exists(tag, ATTR_ACCEPT);
    parsedtag_get_value(tag, ATTR_SIZE, &item->size);
    parsedtag_get_value(tag, ATTR_MAXLENGTH, &item->maxlength);
    item->readonly = parsedtag_exists(tag, ATTR_READONLY);
    if (parsedtag_get_value(tag, ATTR_TEXTAREANUMBER, &i))
	item->value = item->init_value = p1env->textarea_str[i];
#ifdef MENU_SELECT
    if (parsedtag_get_value(tag, ATTR_SELECTNUMBER, &i))
	item->select_option = p1env->select_option[i].first;
#endif				/* MENU_SELECT */
    if (parsedtag_get_value(tag, ATTR_ROWS, &p))
	item->rows = atoi(p);
    if (item->type == FORM_UNKNOWN) {
	/* type attribute is missing. Ignore the tag. */
	return NULL;
    }
#ifdef MENU_SELECT
    if (item->type == FORM_SELECT) {
	chooseSelectOption(item, item->select_option);
	item->init_selected = item->selected;
	item->init_value = item->value;
	item->init_label = item->label;
    }
#endif				/* MENU_SELECT */
    if (item->type == FORM_INPUT_FILE && item->value && item->value->length) {
	/* security hole ! */
	return NULL;
    }
    item->parent = fl;
    item->next = NULL;
    if (fl->item == NULL) {
	fl->item = fl->lastitem = item;
    }
    else {
	fl->lastitem->next = item;
	fl->lastitem = item;
    }
    if (item->type == FORM_INPUT_HIDDEN)
	return NULL;
    fl->nitems++;
    return item;
}

static char *_formtypetbl[] =
{
    "text", "password", "checkbox", "radio", "submit",
    "reset", "hidden", "image", "select", "textarea", "button", "file", 0,
};

static char *_formmethodtbl[] =
{
    "GET", "POST", "INTERNAL", "HEAD"
};

char *
form2str(FormItemList * fi)
{
    Str tmp;
    if (fi->type == FORM_INPUT_SUBMIT ||
	fi->type == FORM_INPUT_IMAGE ||
	fi->type == FORM_INPUT_BUTTON) {
	tmp = Strnew_charp(_formmethodtbl[fi->parent->method]);
	Strcat_char(tmp, ' ');
	Strcat(tmp, fi->parent->action);
	return tmp->ptr;
    }
    else
	return _formtypetbl[fi->type];
}

int
formtype(char *typestr)
{
    int i;
    for (i = 0; _formtypetbl[i]; i++) {
	if (!strcasecmp(typestr, _formtypetbl[i]))
	    return i;
    }
    return FORM_UNKNOWN;
}

void
formRecheckRadio(Anchor *a, Buffer *buf, FormItemList * fi)
{
  FormItemList *f2;

  for (f2 = fi->parent->item; f2; f2 = f2->next)
    if (f2 != fi && f2->type == FORM_INPUT_RADIO && f2->name && !Strcmp(f2->name, fi->name))
      f2->checked = 0;

  fi->checked = 1;
}

void
formResetBuffer(Buffer *buf, AnchorList *formitem)
{
  AnchorLink *dv, *sv;
  SeqMap map;
  int dn, sn, i, j;
  FormItemList *sfi, *dfi;

  if (!buf->formitem || !formitem)
    return;

  map = buf->hseqmap;
  dv = buf->formitem->alv;
  dn = buf->formitem->nlink;
  sv = formitem->alv;
  sn = formitem->nlink;

  for (i = 0 ; i < sn ; ++i)
    if ((sfi = sv[i].fi) &&
	sfi->hseq >= 0 && sfi->hseq < map.n &&
	(j = map.v[sfi->hseq]) >= 0 && j < dn &&
	(dfi = dv[j].fi) &&
	dfi->type == sfi->type &&
	!strcmp((dfi->name ? dfi->name->ptr : ""), (sfi->name ? sfi->name->ptr : "")))
      switch (dfi->type) {
      case FORM_INPUT_TEXT:
      case FORM_INPUT_PASSWORD:
      case FORM_INPUT_FILE:
      case FORM_TEXTAREA:
	dfi->value = sfi->value;
	dfi->lines = NULL;
	break;
      case FORM_INPUT_CHECKBOX:
      case FORM_INPUT_RADIO:
	dfi->checked = sfi->checked;
	break;
      case FORM_SELECT:
#ifdef MENU_SELECT
	dfi->select_option = sfi->select_option;
	dfi->selected = sfi->selected;
	dfi->value = sfi->value;
	dfi->label = sfi->label;
#endif				/* MENU_SELECT */
      default:
	break;
      }

  arrangeLine(buf);
}

void
formItemResetBuffer(Buffer *buf, FormItemList *sfi)
{
  AnchorLink *dv;
  int dn, j;
  FormItemList *dfi;

  if (!sfi || !buf->formitem)
    return;

  dv = buf->formitem->alv;
  dn = buf->formitem->nlink;

  if (sfi->hseq >= 0 && sfi->hseq < buf->hseqmap.n &&
      (j = buf->hseqmap.v[sfi->hseq]) >= 0 && j < dn &&
      (dfi = dv[j].fi) &&
      dfi != sfi &&
      dfi->type == sfi->type &&
      !strcmp((dfi->name ? dfi->name->ptr : ""), (sfi->name ? sfi->name->ptr : "")))
    switch (dfi->type) {
    case FORM_INPUT_TEXT:
    case FORM_INPUT_PASSWORD:
    case FORM_INPUT_FILE:
    case FORM_TEXTAREA:
      dfi->value = sfi->value;
      dfi->lines = NULL;
      break;
    case FORM_INPUT_CHECKBOX:
    case FORM_INPUT_RADIO:
      dfi->checked = sfi->checked;
      break;
    case FORM_SELECT:
#ifdef MENU_SELECT
      dfi->select_option = sfi->select_option;
      dfi->selected = sfi->selected;
      dfi->value = sfi->value;
      dfi->label = sfi->label;
#endif				/* MENU_SELECT */
    default:
      break;
    }

  arrangeLine(buf);
}

static void
form_fputs_decode(Str s, FILE * f)
{
    char *p;
    Str z = Strnew();

    for (p = s->ptr; *p;) {
	switch (*p) {
	case '<':
	    if (!strncasecmp(p, "<eol>", 5)) {
		Strcat_char(z, '\n');
		p += 5;
	    }
	    else {
		Strcat_char(z, *p);
		p++;
	    }
	    break;
#if !defined( __CYGWIN__ ) && !defined( __EMX__ )
	case '\r':
	    if (*(p + 1) == '\n')
		p++;
	    /* continue to the next label */
#endif         /* !defined( __CYGWIN__ ) && !defined( __EMX__ ) */
	default:
	    Strcat_char(z, *p);
	    p++;
	    break;
	}
    }
#ifdef MANY_CHARSET
    fputs(conv_mem2isoStr(z->ptr, z->length, "!", &tty_mb_w_setup)->ptr, f);
#else
#ifdef JP_CHARSET
    fputs(conv_str(z, InnerCode, DisplayCode)->ptr, f);
#else				/* not JP_CHARSET */
    fputs(z->ptr, f);
#endif				/* not JP_CHARSET */
#endif
}


Str
input_textarea_write(FormItemList * fi)
{
    Str tmpname = tmpfname(TMPF_DFL, NULL);
    FILE *f;

    f = fopen(tmpname->ptr, "w");
    if (f == NULL) {
	disp_err_message("Can't open temporary file", FALSE);
	return NULL;
    }
    if (fi->value)
	form_fputs_decode(fi->value, f);
    fclose(f);
    return tmpname;
}

void
input_textarea_read(FormItemList *fi, Str tmpname)
{
    FILE *f;
    Str tmp;
#ifdef JP_CHARSET
    char code = DisplayCode, ic;
#endif

    if (fi->readonly)
       return;
    f = fopen(tmpname->ptr, "r");
    if (f == NULL) {
	disp_err_message("Can't open temporary file", FALSE);
	unlink(tmpname->ptr);
	return;
    }
    fi->value = Strnew();
    while (tmp = Strfgets(f), tmp->length > 0) {
	if (tmp->length == 1 && tmp->ptr[tmp->length - 1] == '\n') {
	    /* null line with bare LF */
	    tmp = Strnew_charp("\r\n");
	}
	else if (tmp->length > 1 && tmp->ptr[tmp->length - 1] == '\n' &&
		 tmp->ptr[tmp->length - 2] != '\r') {
	    Strshrink(tmp, 1);
	    Strcat_charp(tmp, "\r\n");
	}
#ifdef MANY_CHARSET
	Strcat(fi->value, conv_Str2mbStr(tmp, NULL, "!", &tty_mb_r_setup));
#else
#ifdef JP_CHARSET
	if ((ic = checkShiftCode(tmp, code)) != '\0')
	    tmp = conv_str(tmp, (code = ic), InnerCode);
#endif				/* not JP_CHARSET */
	Strcat(fi->value, tmp);
#endif
    }
    fclose(f);
    unlink(tmpname->ptr);
    fi->lines = NULL;
}

void
do_internal(char *action, char *data)
{
    int i;

    for (i = 0; internal_action[i].action; i++) {
	if (strcasecmp(internal_action[i].action, action) == 0) {
	    if (internal_action[i].rout)
		internal_action[i].rout(cgistr2tagarg(data));
	    return;
	}
    }
}

#ifdef MENU_SELECT
void
addSelectOption(FormSelectOption * fso, Str value, Str label, int chk)
{
    FormSelectOptionItem *o;
    o = New(FormSelectOptionItem);
    if (value == NULL)
	value = label;
    o->value = value;
    Strremovefirstspaces(label);
    Strremovetrailingspaces(label);
    o->label = label;
    o->checked = chk;
    o->next = NULL;
    if (fso->first == NULL)
	fso->first = fso->last = o;
    else {
	fso->last->next = o;
	fso->last = o;
    }
}

void
chooseSelectOption(FormItemList *fi, FormSelectOptionItem *item)
{
    FormSelectOptionItem *opt;
    int i;

    fi->selected = 0;
    if (item == NULL) {
       fi->value = Strnew_size(0);
       fi->label = Strnew_size(0);
       return;
    }
    fi->value = item->value;
    fi->label = item->label;
    for (i = 0, opt = item; opt != NULL; i++, opt = opt->next) {
       if (opt->checked) {
           fi->value = opt->value;
           fi->label = opt->label;
           fi->selected = i;
           break;
       }
    }
    updateSelectOption(fi, item);
}

void
updateSelectOption(FormItemList *fi, FormSelectOptionItem *item)
{
    int i;

    if (fi == NULL || item == NULL)
       return;
    for (i = 0; item != NULL; i++, item = item->next) {
       if (i == fi->selected)
           item->checked = TRUE;
       else
           item->checked = FALSE;
    }
}

int
formChooseOptionByMenu(struct form_item_list *fi, short x, short y)
{
    int i, n, selected = -1, init_select = fi->selected;
    FormSelectOptionItem *opt;
    char **label;

    for (n = 0, opt = fi->select_option; opt != NULL; n++, opt = opt->next);
    label = New_N(char *, n + 1);
    for (i = 0, opt = fi->select_option; opt != NULL; i++, opt = opt->next)
	label[i] = opt->label->ptr;
    label[n] = NULL;

    optionMenu(x, y, label, &selected, init_select, NULL);

    if (selected < 0)
	return 0;
    for (i = 0, opt = fi->select_option; opt != NULL; i++, opt = opt->next) {
	if (i == selected) {
	    fi->selected = selected;
	    fi->value = opt->value;
	    fi->label = opt->label;
	    break;
	}
    }
    updateSelectOption(fi, fi->select_option);
    return 1;
}
#endif				/* MENU_SELECT */

void
form_write_data(FILE * f, char *boundary, char *name, char *value)
{
    fprintf(f, "--%s\r\n", boundary);
    fprintf(f, "Content-Disposition: form-data; name=\"%s\"\r\n\r\n", name);
    fprintf(f, "%s\r\n", value);
}

void
form_write_from_file(FILE * f, char *boundary, char *name, char *filename, char *file)
{
    FILE *fd;
    struct stat st;
    int c;
    char *type;

    fprintf(f, "--%s\r\n", boundary);
    fprintf(f, "Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\n",
	    name, mybasename(filename));
    type = guessContentType(file);
    fprintf(f, "Content-Type: %s\r\n\r\n",
	type ? type : "application/octet-stream");

    if (lstat(file, &st) < 0)
	goto write_end;
    if (S_ISDIR(st.st_mode))
	goto write_end;
    fd = fopen(file, "r");
    if (fd != NULL) {
	while ((c = fgetc(fd)) != EOF)
	    fputc(c, f);
	fclose(fd);
    }
  write_end:
    fprintf(f, "\r\n");
}

void
formSplitValue(FormItemList *fi)
{
  Str *lv;
  int i = 0;

  if (!fi->rows)
    fi->rows = 1;

  fi->lines = lv = New_N(Str, fi->rows);

  if (fi->value) {
    CheckTypeEnv ctenv;
    int flag;

    init_ctenv(&ctenv, NULL, FoldTextarea ? fi->size : 0
#ifdef USE_ANSI_COLOR
	       , FALSE
#endif
	       );
    ctenv.eol_p = TRUE;
    ctenv.from = fi->value->ptr;
    ctenv.from_end = &ctenv.from[fi->value->length];

    while (ctenv.from < ctenv.from_end && i < fi->rows) {
      flag = checkTypeCat(&ctenv);
      lv[i++] = Strnew_charp_n(ctenv.to_beg, ctenv.to - ctenv.to_beg);
      reset_ctenv(&ctenv, flag & CHECKTYPE_NL);
    }

  }

  while (i < fi->rows)
    lv[i++] = NULL;

  fi->fold = FoldTextarea;
}

/* Local Variables:    */
/* c-basic-offset: 4   */
/* tab-width: 8        */
/* End:                */
