#include "fm.h"
#include <errno.h>
#include <signal.h>
#include <sys/time.h>
#include <unistd.h>

#ifdef USE_IMAGE
static ImageCache *processImageRetrieve(char *b, Phase0Env *p0env_orig);
static void printImageCache(ImageCache *ic, FILE *fp);
#endif

static char xdigit[] = "0123456789ABCDEF";

Str
urgent_quote_cat_charp_n(Str d, const char *s, int n)
{
  const char *p, *ep;

  if (!d)
    d = Strnew_size(n);

  for (p = s, ep = &p[n] ; p < ep ; ++p)
    if (!*p || (unsigned char)*p & ~0x7F || !IS_ALNUM(*p)) {
      Strcat_char(d, '%');
      Strcat_char(d, xdigit[((unsigned char)*p >> 4) & 0xF]);
      Strcat_char(d, xdigit[(unsigned char)*p & 0xF]);
    }
    else
      Strcat_char(d, *p);

  return d;
}

Str
urgent_unquote_cat_charp_n(Str d, const char *s, int n)
{
  const char *p, *ep;
  int c;

  if (!d)
    d = Strnew_size(n);

  for (p = s, ep = &p[n] ; p < ep ;) {
    if (p[0] == '%' && IS_XDIGIT(p[1])) {
      if (IS_XDIGIT(p[2])) {
	c = (GET_MYCDIGIT(p[1]) << 4) | GET_MYCDIGIT(p[2]);
	p += 3;
      }
      else {
	p += 2;
	c = GET_MYCDIGIT(p[1]);
      }

      Strcat_char(d, c);
      continue;
    }

    Strcat_char(d, *p);
    ++p;
  }

  return d;
}

Str
tiny_safe_sprintf(const char *frmt, ...)
{
  va_list ap;
  Str s;
  const char *e, *b;
  int c, prec;
  char buf[(sizeof(long) * CHAR_BIT + 3) / 4 + sizeof("")];

  va_start(ap, frmt);
  s = Strnew();

  for (e = frmt ; (b = strchr(e, '%')) ;) {
    if (e < b)
      Strcat_charp_n(s, (char *)e, b - e);

    prec = -1;
    e = b + 1;
  eval:
    switch (c = (unsigned char)*e++) {
    case 's':
      if (prec >= 0)
	Strcat_charp_n(s, va_arg(ap, char *), prec);
      else
	Strcat_charp(s, va_arg(ap, char *));

      break;
    case '.':
      if (*e == '*') {
	prec = va_arg(ap, int);
	e += 2;
      }
      else if (IS_DIGIT(*e))
	prec = strtol(e, (char **)&e, 10);
      else
	prec = 0;

      goto eval;
    case '\0':
      goto end;
    case 'l':
      if (*e != 'X')
	goto normal;

      sprintf(buf, "%lX", va_arg(ap, unsigned long));
      Strcat_charp(s, buf);
      break;
    case 'X':
      sprintf(buf, "%X", va_arg(ap, int));
      Strcat_charp(s, buf);
      break;
    case 'c':
      c = va_arg(ap, int);
    default:
    normal:
      Strcat_char(s, c);
      break;
    }
  }

  if (*e)
    Strcat_charp(s, (char *)e);
end:
  return s;
}

Str
halfdump_buffer_quote_to_str(const char *s)
{
  if (s) {
    Str d;

    d = Strnew_charp("T");
    return urgent_quote_cat_charp(d, s);
  }
  else
    return Strnew_size(0);
}

char *
halfdump_buffer_quote(const char *s)
{
  return halfdump_buffer_quote_to_str(s)->ptr;
}

Str
halfdump_buffer_unquote_to_str(const char *s)
{
  return (s && s[0] == 'T') ? urgent_unquote_charp((char *)&s[1]) : NULL;
}

char *
halfdump_buffer_unquote(const char *s)
{
  Str d;

  return (d = halfdump_buffer_unquote_to_str(s)) ? d->ptr : NULL;
}

void
halfdump_buffer_send(Buffer *buf)
{
  halfdump_buffer_send_string(filename, buf->filename);
  halfdump_buffer_send_string(buffername, buf->buffername);
  halfdump_buffer_send_string(type, buf->type);
  halfdump_buffer_send_string(real_type, buf->real_type);
  halfdump_buffer_send_number(bufferprop, buf->bufferprop);
  halfdump_buffer_send_URL(currentURL, &buf->currentURL);
  halfdump_buffer_send_URL(baseURL, buf->baseURL);
  halfdump_buffer_send_string(baseTarget, buf->baseTarget);
  halfdump_buffer_send_number(real_scheme, buf->real_scheme);
  halfdump_buffer_send_string(sourcefile, buf->sourcefile);
  halfdump_buffer_send_number(trbyte, buf->trbyte);
#ifdef MANY_CHARSET
  halfdump_buffer_send_string(document_charset, buf->document_charset);
  halfdump_buffer_send_string(document_encoding, buf->document_encoding);
#elif defined(JP_CHARSET)
  halfdump_buffer_send_number(document_code, buf->document_code);
  halfdump_buffer_send_number(document_encoding, buf->document_encoding);
#endif

  if (buf->mailcap) {
    Str mcap_str;

    mcap_str = tiny_safe_sprintf("%s%c%s%c%lX%c%s%c%s%c%s",
				 halfdump_buffer_quote(buf->mailcap->type), '\0',
				 halfdump_buffer_quote(buf->mailcap->viewer), '\0',
				 buf->mailcap->flags, '\0',
				 halfdump_buffer_quote(buf->mailcap->test), '\0',
				 halfdump_buffer_quote(buf->mailcap->nametemplate), '\0',
				 halfdump_buffer_quote(buf->mailcap->edit));
    halfdump_buffer_send_string(mailcap, mcap_str->ptr);
  }
  else
    halfdump_buffer_send_string(mailcap, NULL);

  halfdump_buffer_send_string(name_by_header, buf->name_by_header);
#ifdef USE_SSL
  halfdump_buffer_send_string(ssl_certificate, buf->ssl_certificate);
#endif
#ifdef USE_IMAGE
  halfdump_buffer_send_number(image_flag, buf->image_flag);
#endif
  halfdump_buffer_send_number(http_response_code, buf->http_response_code);
  halfdump_buffer_send_number(search_header, buf->search_header);

  if (buf->document_header) {
    TextListItem *ti;

    for (ti = buf->document_header->first ; ti ; ti = ti->next)
      halfdump_buffer_send_string(document_header, ti->ptr);
  }

  fflush(urgent_out);

  if ((buf->bufferprop & BP_ASYNC_MASK) != BP_ASYNC_ABORT) {
    buf->bufferprop &= ~BP_ASYNC_MASK;
    buf->bufferprop |= BP_ASYNC_DONE;
  }
}

long
halfdump_buffer_receive_number(const char *s)
{
  return s ? strtoul(s, NULL, 16) : 0;
}

static void
halfdump_buffer_receive(Buffer *buf, char *l)
{
  char *c_url, *b_url, *mcap_str;
  btri_string_tab_t *tab;
  int which, propsave;

  which = strtoul(l, &l, 16);
  SKIP_BLANKS(l);

  switch (which) {
  case urgent_bufferinfo_filename:
    buf->filename = halfdump_buffer_unquote(l);
    break;
  case urgent_bufferinfo_buffername:
    buf->buffername = halfdump_buffer_unquote(l);
    break;
  case urgent_bufferinfo_type:
    buf->type = halfdump_buffer_unquote(l);
    break;
  case urgent_bufferinfo_real_type:
    buf->real_type = halfdump_buffer_unquote(l);
    break;
  case urgent_bufferinfo_bufferprop:
    propsave = buf->bufferprop & (BP_VISIBLE | BP_ON_VIEW | BP_PAGER | BP_ASYNC_MASK | BP_LINKED);
    buf->bufferprop = halfdump_buffer_receive_number(l);
    buf->bufferprop &= ~(BP_VISIBLE | BP_ON_VIEW | BP_PAGER | BP_ASYNC_MASK | BP_LINKED);
    buf->bufferprop |= propsave;
    break;
  case urgent_bufferinfo_currentURL:
    if ((c_url = halfdump_buffer_unquote(l)) && *c_url) {
      parseURL2(c_url, &buf->currentURL, NULL);

      if (buf->async_buf && buf->async_buf->p2env->p1env->p0env->flag & RG_HIST)
	pushHashHist(URLHist, parsedURL2Str(&buf->currentURL)->ptr);
    }

    break;
  case urgent_bufferinfo_baseURL:
    if ((b_url = halfdump_buffer_unquote(l)) && *b_url) {
      buf->baseURL = New(ParsedURL);
      parseURL(b_url, buf->baseURL, NULL);
    }

    break;
  case urgent_bufferinfo_baseTarget:
    buf->baseTarget = halfdump_buffer_unquote(l);
    break;
  case urgent_bufferinfo_real_scheme:
    buf->real_scheme = halfdump_buffer_receive_number(l);
    break;
  case urgent_bufferinfo_sourcefile:
    buf->sourcefile = halfdump_buffer_unquote(l);
    break;
  case urgent_bufferinfo_trbyte:
    buf->trbyte = halfdump_buffer_receive_number(l);
    break;
#ifdef MANY_CHARSET
  case urgent_bufferinfo_document_charset:
    buf->document_charset = halfdump_buffer_unquote(l);
    break;
  case urgent_bufferinfo_document_encoding:
    buf->document_encoding = halfdump_buffer_unquote(l);
    break;
#elif defined(JP_CHARSET)
  case urgent_bufferinfo_document_code:
    buf->document_code = halfdump_buffer_receive_number(l);
    break;
  case urgent_bufferinfo_document_encoding:
    buf->document_encoding = halfdump_buffer_receive_number(l);
    break;
#endif
  case urgent_bufferinfo_mailcap:
    if ((mcap_str = halfdump_buffer_unquote(l))) {
      struct mailcap *mcap;
      char *p;

      buf->mailcap = mcap = New(struct mailcap);
      mcap->type = halfdump_buffer_unquote(mcap_str);
      p = mcap_str + strlen(mcap_str) + 1;
      mcap->viewer = halfdump_buffer_unquote(p);
      p += strlen(p) + 1;
      mcap->flags = strtoul(p, NULL, 16);
      p += strlen(p) + 1;
      mcap->test = halfdump_buffer_unquote(p);
      p += strlen(p) + 1;
      mcap->nametemplate = halfdump_buffer_unquote(p);
      p += strlen(p) + 1;
      mcap->edit = halfdump_buffer_unquote(p);
    }
    else
      buf->mailcap = NULL;

    break;
  case urgent_bufferinfo_name_by_header:
    buf->name_by_header = halfdump_buffer_unquote(l);
    break;
#ifdef USE_SSL
  case urgent_bufferinfo_ssl_certificate:
    buf->ssl_certificate = halfdump_buffer_unquote(l);
    break;
#endif
#ifdef USE_IMAGE
  case urgent_bufferinfo_image_flag:
    buf->image_flag = halfdump_buffer_receive_number(l);
    break;
  case urgent_bufferinfo_image_unloaded:
    buf->image_unloaded = halfdump_buffer_receive_number(l);
    break;
#endif
  case urgent_bufferinfo_http_response_code:
    buf->http_response_code = halfdump_buffer_receive_number(l);
    break;
  case urgent_bufferinfo_search_header:
    buf->search_header = halfdump_buffer_receive_number(l);
    break;
  default:
    if (!buf->document_header)
      buf->document_header = newTextList();

    if (!buf->document_header_table)
      buf->document_header_table = tab = btri_new_node(&btri_string_ci_tab_desc);

    if ((l = halfdump_buffer_unquote(l))) {
      char *eon;

      pushValue((GeneralList *)buf->document_header, l);

      if (strncasecmp(l, "http ", sizeof("http ") - 1) &&
	  (eon = strchr(l, ':'))) {
	char *bob;

	bob = eon + 1;
	SKIP_BLANKS(bob);
	btri_search_mem(&btri_string_ci_tab_desc, BTRI_OP_ADD | BTRI_OP_WR,
			l, eon - l, buf->document_header_table, (void **)&bob);
      }
    }

    break;
  }
}

static int
fgets_async(int fd, Str buf, int keep, int *processed, int do_read, Str *ret)
{
  char *bol, *eol;

  bol = &buf->ptr[*processed];
  *ret = NULL;

  if ((eol = memchr(bol, '\n', buf->length - *processed))) {
    *ret = Strnew_charp_n(bol, eol - bol);

    if ((*processed = eol + 1 - buf->ptr) >= buf->length) {
      Strtruncate(buf, keep);
      *processed = buf->length;
    }

    return fgets_async_eol_without_read;
  }
  else if (!do_read)
    return fgets_async_no_eol;
  else {
    int n;

    Strassure(buf, PIPE_BUF);
    bol = &buf->ptr[*processed];

    if ((n = read(fd, &buf->ptr[buf->length], PIPE_BUF)) < 0)
      switch (errno) {
      case EINTR:
      case EAGAIN:
	return fgets_async_no_eol;
      default:
	return fgets_async_error;
      }

    if (!n) {
      if (*processed < buf->length)
	*ret = Strnew_charp_n(bol, buf->length - *processed);

      Strtruncate(buf, keep);
      *processed = buf->length;
      return *ret ? fgets_async_eol_and_eof : fgets_async_eof;
    }

    if ((eol = memchr(&buf->ptr[buf->length], '\n', n))) {
      *ret = Strnew_charp_n(bol, eol - bol);
      buf->length += n;
      buf->ptr[buf->length] = '\0';

      if ((*processed = eol + 1 - buf->ptr) >= buf->length) {
	Strtruncate(buf, keep);
	*processed = buf->length;
      }

      return fgets_async_eol_with_read;
    }

    buf->length += n;
    buf->ptr[buf->length] = '\0';
    return fgets_async_no_eol;
  }
}

void
processBufferEvents(Buffer *buf)
{
  if (buf && buf->events) {
    char *l;
    TextList *eq;

    for (eq = buf->events ; (l = popValue((GeneralList *)eq)) ;) {
      int cmd;

      cmd = strtoul(l, &l, 16);
      SKIP_BLANKS(l);
      CurrentKeyData = halfdump_buffer_unquote(l);
      ForcedKeyData = NULL;
      Argumentbuf = buf;
      CurrentCmdData = NULL;
      w3mFuncList[cmd].func.main_func();
    }

    ForcedKeyData = CurrentKeyData = NULL;
    TargetX = TargetY = PrevTargetX = PrevTargetY = -1;
    Argumentbuf = NULL;
    CurrentCmdData = NULL;
  }
}

int
onCurrentview(Buffer *buf)
{
  if (Currentbuf && Currentbuf->view) {
    BufferView *root, *v;

    root = Currentbuf->view->root;
    v = buf->view;

    if (v && v->root == root && v->top == buf) {
      for (; v->sup ; v = v->sup)
	if (v->sup->subv[v->y * v->sup->ncols + v->x].top != v)
	  return FALSE;

      return TRUE;
    }
  }

  return FALSE;
}

int
setCurrentbuf(Buffer *buf, Phase0Env *p0env, int redraw, int *p_flag)
{
  int rep_p = FALSE;

  if (buf->firstLine || buf->events) {
    if (Currentbuf && Currentbuf->bufferprop & BP_INTERNAL &&
	!strcmp(Currentbuf->buffername, FALLBACK_PAGE_TITLE)) {
      *p_flag =
#ifdef USE_IMAGE
	(activeImage && buf->image_flag == IMG_FLAG_AUTO && buf->img) ? B_REDRAW_IMAGE :
#endif
	B_FORCE_REDRAW;
      redraw = 1;
      delBuffer(Currentbuf);
    }

    if (p0env->flag & RG_REPBUF && p0env->curbuf &&
	(p0env->curbuf->bufferprop & BP_LINKED)) {
      deleteBuffer(p0env->curbuf);
      rep_p = TRUE;
    }

    if (!Currentbuf || isPrevBuffer(p0env->curbuf, Currentbuf) >= 0)
      pushBuffer(buf);
    else {
      if (isPrevBuffer(buf, Currentbuf))
	appendBuffer(Currentbuf, buf);

      buf->bufferprop |= BP_VISIBLE;

      if (!buf->view->top)
	insertBufferToView(NULL, buf);
      else {
	Buffer *ref;

	for (ref = buf->view->top ; ref ; ref = ref->down)
	  if (ref == buf)
	    break;
	  else if (isPrevBuffer(p0env->curbuf, ref) >= 0) {
	    insertBufferToView(ref, buf);
	    break;
	  }

	if (!ref)
	  appendBufferToView(NULL, buf);
      }
    }

    if (rep_p && onCurrentview(buf)) {
      *p_flag =
#ifdef USE_IMAGE
	(activeImage && buf->image_flag == IMG_FLAG_AUTO && buf->img) ? B_REDRAW_IMAGE :
#endif
	B_FORCE_REDRAW;
      redraw = 1;
    }

#ifdef USE_MENU
    if (CurrentMenu)
      reshapeMenu();
#endif
  }

  return redraw;
}

void
displayBufferMaybe(Buffer *buf, int nlines)
{
  int redraw, flag, cur_p;

  redraw = buf->async_buf->flag & ASYNC_FLAG_REDRAW;
  flag = B_NORMAL;

  if (!(buf->bufferprop & BP_DISCARDED)) {
    Phase0Env *p0env;

    if (buf->firstLine) {
      if (nlines > 0) {
	if (!buf->topLine) {
	  buf->topLine = buf->firstLine;
	  arrangeLine(buf);
	  flag =
#ifdef USE_IMAGE
	    (activeImage && buf->image_flag == IMG_FLAG_AUTO && buf->img) ? B_REDRAW_IMAGE :
#endif
	    B_FORCE_REDRAW;
	  redraw = 1;
	}

	if (buf->lastLine->linenumber - buf->firstLine->linenumber + 1 <= nlines) {
	  p0env = buf->async_buf->p2env->p1env->p0env;
	  redraw = setCurrentbuf(buf, p0env, redraw, &flag);
	}

	if (fmInitialized && onCurrentview(buf) &&
	    buf->lastLine->linenumber - nlines < buf->topLine->linenumber + buf->view->height - 1)
	  redraw = 1;
      }
      else if (nlines < 0) {
	p0env = buf->async_buf->p2env->p1env->p0env;

	if (p0env->RenderFrame && buf->frameset) {
	  Buffer *fbuf;
	  Phase0Env p0env_frame;

	  p0env_frame = main_p0env;
	  p0env_frame.flag &= ~RG_PROC_MASK;
	  p0env_frame.flag |= RG_PROC_FORK;

	  if ((fbuf = rFrame_internal(buf, &p0env_frame))) {
	    buf = fbuf;
	    nlines = 0;
	    flag =
#ifdef USE_IMAGE
	      (activeImage && buf->image_flag == IMG_FLAG_AUTO && buf->img) ? B_REDRAW_IMAGE :
#endif
	      B_FORCE_REDRAW;
	  }
	}

	redraw = 1;
      }
    }
    else if (nlines < 0 && buf->events) {
      p0env = buf->async_buf->p2env->p1env->p0env;
      redraw = setCurrentbuf(buf, p0env, redraw, &flag);
    }
  }

  if ((cur_p = onCurrentview(buf)))
    processBufferEvents(buf);
  else if (nlines < 0)
    tmpClearBuffer(buf);

  if (fmInitialized && redraw && cur_p) {
    displayBuffer(buf, flag);

    if (next_dcompl_top >= 0)
      next_dcompl(next_dcompl_next, next_dcompl_env);
#ifdef USE_MENU
    else if (CurrentMenu && CurrentMenuPopup) {
      draw_all_menu(CurrentMenu);
      select_menu(CurrentMenu, CurrentMenu->select);
    }
#endif

    if (cur_p && !buf->need_reshape && !buf->redraw_mode
	&& buf->async_buf)
      buf->async_buf->flag &= ~ASYNC_FLAG_REDRAW;
  }
}

void
gotoURLLabel(Buffer *b, char *label, int html_p)
{
  if (html_p < 0)
    html_p = b->type && !strcasecmp(b->type, "text/html") ;

  if (html_p) {
    Anchor *a;

#ifdef MANY_CHARSET
    a = searchURLLabel(b, conv_str2mbStr(label, &b->document_encoding, "")->ptr);
#else
#ifdef JP_CHARSET
    a = searchURLLabel(b, conv(label, b->document_encoding, InnerCode)->ptr);
#else				/* not JP_CHARSET */
    a = searchURLLabel(b, label);
#endif				/* not JP_CHARSET */
#endif
    if (a != NULL) {
      gotoLine(b, a->start.line);
      b->pos = a->start.pos;
      arrangeCursor(b);

      if (b->async_buf)
	b->async_buf->flag |= ASYNC_FLAG_REDRAW;
    }
  }
  else { /* plain text */
    gotoLine(b,atoi(label));
    b->pos = 0;
    arrangeCursor(b);

    if (b->async_buf)
      b->async_buf->flag |= ASYNC_FLAG_REDRAW;
  }
}

static void
blockNextLineMaybe(Buffer *b)
{
  if (b->bufferprop & BP_PAGER && b->firstLine && b->lastLine->linenumber - b->firstLine->linenumber > PagerMax) {
    Line *l;

    for (l = b->firstLine ; l && l != b->topLine && b->lastLine->linenumber - l->linenumber > PagerMax ; l = l->next)
      ;

    b->firstLine = l;
    clear_read_fd(&b->async_buf->rfd, CLEAR_READ_FD_KEEPFD | CLEAR_READ_FD_KEEPHOOK);
  }
}

#ifdef USE_COOKIE
static void
processSubCookie(char *b, FILE *w)
{
  ParsedURL pu;
  Str cookie;

  parseURL2(b, &pu, NULL);

  if ((cookie = find_cookie(&pu)))
    fputs(halfdump_buffer_quote(cookie->ptr), w);

  putc('\n', w);
  fflush(w);
}

static void
catCookieStr(Str msg, Str s)
{
  Strcat_char(msg, '/');

  if (s) {
    Strcat_char(msg, 'T');
    urgent_quote_cat(msg, s);
  }
}

static void
catCookieNum(Str msg, long n)
{
  char buf[sizeof(long) * CHAR_BIT / 4 + 1];

  Strcat_char(msg, '/');
  sprintf(buf, "%ld", n);
  Strcat_charp(msg, buf);
}

void
sendCookieInfo(ParsedURL * pu, Str name, Str value,
	       time_t expires, Str domain, Str path,
	       int flag, Str comment, int version,
	       Str port, Str commentURL)
{
  Str s, msg;

  s = parsedURL2Str(pu);
  msg = urgent_quote(s);
  catCookieStr(msg, name);
  catCookieStr(msg, value);
  catCookieNum(msg, expires);
  catCookieStr(msg, domain);
  catCookieStr(msg, path);
  catCookieNum(msg, flag);
  catCookieStr(msg, comment);
  catCookieNum(msg, version);
  catCookieStr(msg, port);
  catCookieStr(msg, commentURL);
  Strcat_char(msg, '\n');
  fprintf(urgent_out, "%X %s\n", urgent_cookie, msg->ptr);
  fflush(urgent_out);
}

static Str
decodeCookieStr(char **p_s)
{
  char *s;
  size_t n;
  Str d;

  s = *p_s;

  if ((n = strcspn(s, "/")) > 0 && s[0] == 'T')
    d = urgent_unquote_charp_n(&s[1], n - 1);
  else
    d = NULL;

  *p_s = s[n] ? s + n + 1 : s + n;
  return d;
}

static long
decodeCookieNum(char **p_s)
{
  char *s;
  long n;

  s = *p_s;
  n = strtol(s, p_s, 10);

  if (**p_s)
    ++(*p_s);

  return n;
}

static void
receiveCookieInfo(char *s)
{
  ParsedURL pu;
  Str name, value;
  time_t expires;
  Str domain, path;
  int flag;
  Str comment;
  int version;
  Str port, commentURL;
  char *p;

  p = strchr(s, '/');
  parseURL2(urgent_unquote_charp_n(s, p - s)->ptr, &pu, NULL);
  s = p + 1;
  name = decodeCookieStr(&s);
  value = decodeCookieStr(&s);
  expires = decodeCookieNum(&s);
  domain = decodeCookieStr(&s);
  path = decodeCookieStr(&s);
  flag = decodeCookieNum(&s);
  comment = decodeCookieStr(&s);
  version = decodeCookieNum(&s);
  port = decodeCookieStr(&s);
  commentURL = decodeCookieStr(&s);
  processSetCookie(&pu, name, value, expires, domain, path, flag, comment, version, port, commentURL, FALSE);
}
#endif /* !defined(USE_COOKIE) */

struct kept_sock_desc {
  char linked;
  struct kept_sock_desc *prev, *next;
  char *host;
  unsigned short port, busy;
  AsyncRWBuf *async_buf;
  clock_t touch;
};

static struct kept_sock_desc *kept_sock_head;
static struct kept_sock_desc *kept_sock_tail;
static struct kept_sock_desc *kept_sock_free;

struct kept_sock_hook {
  char *path;
  ParsedURL *current;
  char *referer;
  FormList *request;
  void (*pre_hook)(char *, ParsedURL *, char *, FormList *, void *);
  void *pre_arg;
};

static GeneralList *kept_sock_hooks;

static void
insertKeptSockDesc(struct kept_sock_desc *p)
{
  if ((p->next = kept_sock_head))
    kept_sock_head->prev = p;
  else
    kept_sock_tail = p;

  kept_sock_head = p;
  p->prev = NULL;
  p->linked = TRUE;
  ++kept_nsocks;
}

static void
appendKeptSockDesc(struct kept_sock_desc *p)
{
  if ((p->prev = kept_sock_tail))
    kept_sock_tail->next = p;
  else
    kept_sock_head = p;

  kept_sock_tail = p;
  p->next = NULL;
  p->linked = TRUE;
  ++kept_nsocks;
}

long
my_clock(void)
{
  struct timeval tv;

  gettimeofday(&tv, NULL);
  return tv.tv_sec;
}

int
keepAsyncRWBuf(char *host, unsigned short port, AsyncRWBuf *abuf, int busy)
{
  if (abuf) {
    if (kept_nsocks < concurrent) {
      int n;
      struct kept_sock_desc *p;

      for (n = 0, p = kept_sock_head; p ; p = p->next)
	if (p->async_buf == abuf)
	  return kept_nsocks;
	else if (!strcasecmp(p->host, host) && p->port == port)
	  ++n;

      if (n < concurrent_per_server) {
	if (kept_sock_free) {
	  p = kept_sock_free;
	  kept_sock_free = p->next;
	}
	else {
	  p = New(struct kept_sock_desc);
	  memset(p, 0, sizeof(*p));
	}

	if ((p->busy = busy))
	  insertKeptSockDesc(p);
	else {
	  appendKeptSockDesc(p);
	  record_read_fd(abuf->ufd, watchKeptAsyncRWBuf, NULL, p);
	  clear_read_fd(&abuf->rfd, CLEAR_READ_FD_KEEPFD);
	}

	p->touch = my_clock();
	p->host = host;
	p->port = port;
	p->async_buf = abuf;
	abuf->flag |= ASYNC_FLAG_KEEP_ALIVE;
	return kept_nsocks;
      }
    }
  }

  return 0;
}

static void
deleteKeptSockDesc(struct kept_sock_desc *p)
{
  struct kept_sock_desc *q;

  if ((q = p->prev))
    q->next = p->next;
  else
    kept_sock_head = p->next;

  if ((q = p->next))
    q->prev = p->prev;
  else
    kept_sock_tail = p->prev;

  p->linked = FALSE;

  if (kept_nsocks > 0)
    --kept_nsocks;
}

static void
freeKeptSockDesc(struct kept_sock_desc *p)
{
  p->host = NULL;
  p->port = 0;
  p->async_buf = NULL;
  p->busy = FALSE;
  p->touch = -1;
  p->prev = NULL;
  p->next = kept_sock_free;
  kept_sock_free = p;
}

static void
discardKeptSockDesc(struct kept_sock_desc *p)
{
  if (p->linked) {
    deleteKeptSockDesc(p);

    if (p->async_buf) {
      p->async_buf->flag &= ~ASYNC_FLAG_KEEP_ALIVE;

      if (!(p->busy && (is_recorded_read_fd(p->async_buf->rfd) || is_recorded_read_fd(p->async_buf->ufd)))) {
	clear_read_fd(&p->async_buf->ufd, 0);
	discardAsyncRWBuf(p->async_buf, FALSE);
      }
    }

    freeKeptSockDesc(p);
  }
}

AsyncRWBuf *
getKeptAsyncRWBuf(char *host, unsigned short port, int implicit_p)
{
  int n;
  struct kept_sock_desc *p;
  static AsyncRWBuf dummy;

  if (!host)
    host = "";

  for (p = kept_sock_tail, n = 0 ; p ; p = p->prev)
    if (!strcasecmp(p->host, host) && p->port == port) {
      if (!p->busy) {
	deleteKeptSockDesc(p);
	insertKeptSockDesc(p);
	p->busy = TRUE;
	p->touch = my_clock();
	return p->async_buf;
      }
      else
	++n;
    }

  if (concurrent <= 0 || (n < concurrent_per_server && kept_nsocks < concurrent))
    return &dummy;

  if (!implicit_p ||
      (n < concurrent_per_server &&
       (!kept_sock_tail->busy ||
	my_clock() - kept_sock_tail->touch >= KEPT_SOCK_EXPIRE_ANYWAY))) {
    discardKeptSockDesc(kept_sock_tail);
    return &dummy;
  }

  return NULL;
}

void
waitKeptAsyncRWBuf(char *path, ParsedURL *current, char *referer, FormList *request,
		   void (*pre_hook)(char *, ParsedURL *, char *, FormList *, void *),
		   void *pre_arg)
{
  struct kept_sock_hook *h;

  h = New(struct kept_sock_hook);
  h->path = allocStr(path, -1);

  if (current) {
    h->current = New(ParsedURL);
    copyParsedURL(h->current, current);
  }
  else
    h->current = NULL;

  h->referer = (!referer || referer == NO_REFERER) ? referer : allocStr(referer, -1);
  h->request = request;
  h->pre_hook = pre_hook;
  h->pre_arg = pre_arg;

  if (!kept_sock_hooks)
    kept_sock_hooks = newGeneralList();

  pushValue(kept_sock_hooks, h);
}

void
freeKeptAsyncRWBuf(AsyncRWBuf *abuf, int force)
{
  struct kept_sock_desc *p;
  int keep_alive, busy = FALSE;

  keep_alive = ((abuf->flag & (ASYNC_FLAG_KEEP_ALIVE | ASYNC_FLAG_DONT_KEEP_ALIVE)) == ASYNC_FLAG_KEEP_ALIVE
		&& abuf->pid > 0 && abuf->rfd >= 0 && abuf->ufd >= 0 && abuf->w);
  abuf->flag &= ~(ASYNC_FLAG_KEEP_ALIVE | ASYNC_FLAG_DONT_KEEP_ALIVE);

  for (p = kept_sock_head ; p ; p = p->next)
    if (p->async_buf == abuf) {
      char *host;
      unsigned short port;

      host = p->host;
      port = p->port;
      busy = p->busy;
      deleteKeptSockDesc(p);
      freeKeptSockDesc(p);

      if (keep_alive) {
	if (force && busy && abuf->pid > 0 &&
	    is_recorded_read_fd(abuf->rfd))
	  kill(abuf->pid, SIGINT);

	keepAsyncRWBuf(host, port, abuf, FALSE);
      }

      break;
    }

  if ((!busy || force) && !(abuf->flag & ASYNC_FLAG_KEEP_ALIVE)) {
    clear_read_fd(&abuf->ufd, 0);
    discardAsyncRWBuf(abuf, FALSE);
  }
}

void
expireKeptAsyncRWBuf(void)
{
  if (kept_sock_tail && !kept_sock_tail->busy &&
      my_clock() - kept_sock_tail->touch >= KEPT_SOCK_EXPIRE)
    discardKeptSockDesc(kept_sock_tail);

  if (kept_sock_hooks && kept_sock_hooks->first) {
    struct kept_sock_hook *h;

    h = popValue(kept_sock_hooks);
    h->pre_hook(h->path, h->current, h->referer, h->request, h->pre_arg);
  }
}

void
freeAllKeptAsyncRWBuffers(void)
{
  struct kept_sock_desc *p, *q;

  for (p = kept_sock_tail ; p ;) {
    q = p;
    p = p->prev;

    if (q->async_buf) {
      q->async_buf->flag &= ~ASYNC_FLAG_KEEP_ALIVE;

      if (!q->busy) {
	clear_read_fd(&q->async_buf->ufd, 0);
	discardAsyncRWBuf(q->async_buf, TRUE);
      }
    }
  }

  kept_sock_head = kept_sock_tail = kept_sock_free = NULL;
  kept_nsocks = 0;
}

void
discardKeptAsyncRWBuf(AsyncRWBuf *abuf)
{
  struct kept_sock_desc *p;

  for (p = kept_sock_head ; p ; p = p->next)
    if (p->async_buf == abuf) {
      deleteKeptSockDesc(p);
      freeKeptSockDesc(p);
    }

  abuf->flag &= ~ASYNC_FLAG_KEEP_ALIVE;
  clear_read_fd(&abuf->ufd, 0);
  discardAsyncRWBuf(abuf, TRUE);
}

void
watchKeptAsyncRWBuf(int fd, void *arg)
{
  struct kept_sock_desc *p;

  p = arg;
  p->busy = FALSE;

  if (p->linked)
    discardKeptSockDesc(p);
  else if (p->async_buf) {
    p->async_buf->flag &= ~ASYNC_FLAG_KEEP_ALIVE;
    clear_read_fd(&p->async_buf->ufd, 0);
    discardAsyncRWBuf(p->async_buf, TRUE);
  }

  if (kept_sock_hooks && kept_sock_hooks->first) {
    struct kept_sock_hook *h;

    h = popValue(kept_sock_hooks);
    h->pre_hook(h->path, h->current, h->referer, h->request, h->pre_arg);
  }
}

void
finishAsyncBuffer(Buffer *buf, int html_p)
{
  AsyncRWBuf *abuf;

  abuf = buf->async_buf;
  abuf->flag &= ~ASYNC_FLAG_USED;
  abuf->p2env->p1env->p0env->receiver(buf, -1);
  buf->async_buf = NULL;

  if (!(abuf->flag & ASYNC_FLAG_USED))
    switch (buf->bufferprop & BP_ASYNC_MASK) {
    case BP_ASYNC_ABORT:
      discardKeptAsyncRWBuf(abuf);
    case 0:
      break;
    default:
      if (!(abuf->flag & ASYNC_FLAG_KEEP_ALIVE))
	clear_read_fd(&abuf->ufd, 0);

      discardAsyncRWBuf(abuf, TRUE);
      abuf->flag &= ASYNC_FLAG_KEEP_ALIVE;
    }

  buf->bufferprop &= ~BP_ASYNC_MASK;

  if (buf->bufferprop & BP_LINKED) {
    if (abuf->p2env->a_form_n)
      addMultirowsAnchorsInLine(abuf->p2env, TRUE);

    reseqHmarker(buf->hmarklist);

    if (buf->bufferprop & BP_FOREVER) {
      record_raw_crontab(forever_try, buf, TRUE, FOREVER_INTERVAL);

      if (buf->lastLine && buf->currentLine != buf->lastLine) {
	buf->currentLine = buf->lastLine;
	buf->pos = 0;
	arrangeCursor(buf);
      }
    }

    if (abuf->label)
      gotoURLLabel(buf, abuf->label, html_p);

    if (fmInitialized && onCurrentview(buf)) {
      displayBuffer(buf,
#ifdef USE_IMAGE
		    (activeImage && buf->image_flag == IMG_FLAG_AUTO && buf->img) ? B_REDRAW_IMAGE :
#endif
		    B_FORCE_REDRAW);

      if (next_dcompl_top >= 0)
	next_dcompl(next_dcompl_next, next_dcompl_env);
#ifdef USE_MENU
      else if (CurrentMenu && CurrentMenuPopup) {
	draw_all_menu(CurrentMenu);
	select_menu(CurrentMenu, CurrentMenu->select);
      }
#endif
    }
  }
  else
    discardBuffer(buf);
}

void
discardAsyncRWBuf(AsyncRWBuf *abuf, int force)
{
  if (abuf->flag & ASYNC_FLAG_KEEP_ALIVE) {
    freeKeptAsyncRWBuf(abuf, force);
    return;
  }

  if (abuf->w) {
    fclose(abuf->w);
    abuf->w = NULL;
  }

  clear_read_fd(&abuf->rfd, 0);

  if (abuf->pid)
    kill(abuf->pid, SIGHUP);
}

static void
readHTMLText(int rfd, void *arg)
{
  Buffer *buf;
  AsyncRWBuf *abuf;
  Phase2Env *p2env;
  int do_read, conti, nlines;
  Str l;

  buf = arg;
  abuf = buf->async_buf;
  p2env = abuf->p2env;

  for (nlines = 0, do_read = abuf->flag & ASYNC_FLAG_MAYREAD, conti = 1 ; conti ;)
    switch (fgets_async(rfd, abuf->rbuf, 0, &abuf->rprocessed, do_read, &l)) {
    case fgets_async_eol_and_eof:
      doHTMLlineproc2(p2env, l);
      ++nlines;
    case fgets_async_eof:
      conti = 0;
      clear_read_fd(&abuf->rfd, 0);

      if (!is_recorded_read_fd(abuf->ufd))
	abuf->flag |= ASYNC_FLAG_COMPLETED;

      break;
    case fgets_async_eol_with_read:
      do_read = 0;
    case fgets_async_eol_without_read:
      if (l->length == sizeof(ASYNC_EOHTML) - 1 &&
	  !memcmp(l->ptr, ASYNC_EOHTML, sizeof(ASYNC_EOHTML))) {
	conti = 0;
	clear_read_fd(&abuf->rfd, CLEAR_READ_FD_KEEPFD);

	if (!is_recorded_read_fd(abuf->ufd))
	  abuf->flag |= ASYNC_FLAG_COMPLETED;
      }
      else {
	doHTMLlineproc2(p2env, l);
	++nlines;
      }

      break;
    case fgets_async_error:
    default: /* case fgets_async_no_eol: */
      conti = 0;
      break;
    }

  abuf->flag |= ASYNC_FLAG_MAYREAD;

  if (nlines) {
    p2env->p1env->p0env->receiver(buf, nlines);

    if (!(abuf->flag & ASYNC_FLAG_COMPLETED))
      blockNextLineMaybe(buf);
  }

  if (abuf->flag & ASYNC_FLAG_COMPLETED &&
      (buf->bufferprop & BP_ASYNC_MASK) == BP_ASYNC_WAIT)
    finishAsyncBuffer(buf, 1);
}

static void
readPlainText(int rfd, void *arg)
{
  Buffer *buf;
  AsyncRWBuf *abuf;
  Phase2Env *p2env;
  int n, nlines, rest;
  Str l;

  buf = arg;
  abuf = buf->async_buf;
  p2env = abuf->p2env;
  l = abuf->rbuf;
  n = l->length - abuf->rprocessed;

  if (!(abuf->flag & ASYNC_FLAG_MAYREAD)) {
    if (!n) {
      abuf->flag |= ASYNC_FLAG_MAYREAD;
      return;
    }

    goto found;
  }

  Strdelete(l, 0, abuf->rprocessed);
  abuf->rprocessed = 0;

  if ((n = read(abuf->rfd, &l->ptr[l->length], l->area_size - l->length - 1)) < 0)
    switch (errno) {
    case EINTR:
    case EAGAIN:
      return;
    default:
      n = 0;
      break;
    }

  if (!n) {
    clear_read_fd(&abuf->rfd, 0);

    if (!is_recorded_read_fd(abuf->ufd))
      abuf->flag |= ASYNC_FLAG_COMPLETED;
  }

  l->ptr[l->length += n] = '\0';
found:
  nlines = Plainlineproc(buf, p2env->p1env->p0env->flag, p2env->ctenv,
			 &l->ptr[abuf->rprocessed], &l->ptr[l->length],
			 !n && !(buf->bufferprop & BP_FOREVER), &rest);
  p2env->nlines += nlines;

  if (rest)
    Strdelete(l, 0, l->length - rest);
  else
    Strtruncate(l, 0);

  abuf->rprocessed = 0;
  abuf->flag |= ASYNC_FLAG_MAYREAD;

  if (nlines) {
    p2env->p1env->p0env->receiver(buf, nlines);

    if (!(abuf->flag & ASYNC_FLAG_COMPLETED))
      blockNextLineMaybe(buf);
  }

  if (abuf->flag & ASYNC_FLAG_COMPLETED &&
      (buf->bufferprop & BP_ASYNC_MASK) == BP_ASYNC_WAIT)
    finishAsyncBuffer(buf, 0);
}

static void
readChunkedPlainText(int rfd, void *arg)
{
  Buffer *buf;
  AsyncRWBuf *abuf;
  Phase2Env *p2env;
  int n, nlines, rest, eof_p;
  Str l, sizel;

  buf = arg;
  abuf = buf->async_buf;
  p2env = abuf->p2env;
  l = abuf->rbuf;

  for (eof_p = FALSE ;;) {
    if (abuf->chunk_size <= 0) {
      rest = n = l->length + abuf->chunk_size;

      switch (fgets_async(rfd, l, n, &rest, abuf->flag & ASYNC_FLAG_MAYREAD, &sizel)) {
      case fgets_async_eol_and_eof:
	/* Actually never happen */
      case fgets_async_eof:
	abuf->chunk_size = 0;
	clear_read_fd(&abuf->rfd, 0);

	if (!is_recorded_read_fd(abuf->ufd))
	  abuf->flag |= ASYNC_FLAG_COMPLETED;

	eof_p = TRUE;
	rest = 0;
	goto chunk_size_found;
      case fgets_async_eol_without_read:
      case fgets_async_eol_with_read:
	if (rest > n)
	  Strdelete(l, n, rest - n);

	if (!(rest = strtoul(sizel->ptr, NULL, 16))) {
	  clear_read_fd(&abuf->rfd, CLEAR_READ_FD_KEEPFD);

	  if (!is_recorded_read_fd(abuf->ufd))
	    abuf->flag |= ASYNC_FLAG_COMPLETED;

	  eof_p = TRUE;
	}
      chunk_size_found:
	abuf->chunk_size = rest + n - abuf->rprocessed;
	abuf->flag &= ~ASYNC_FLAG_MAYREAD;
	break;
      case fgets_async_error:
      default: /* case fgets_async_no_eol: */
	abuf->chunk_size = n - l->length;
	abuf->flag |= ASYNC_FLAG_MAYREAD;
	return;
      }
    }

    if (abuf->chunk_size > 0 || (!abuf->chunk_size && eof_p)) {
      if ((n = l->length - abuf->rprocessed) > abuf->chunk_size)
	n = abuf->chunk_size;
      else if (n < abuf->chunk_size) {
	if (abuf->flag & ASYNC_FLAG_MAYREAD) {
	  Strassure(l, PIPE_BUF);

	  if ((rest = read(rfd, &l->ptr[l->length], PIPE_BUF)) < 0)
	    switch (errno) {
	    case EINTR:
	    case EAGAIN:
	      return;
	    default:
	      rest = 0;
	      break;
	    }

	  if (!rest) {
	    clear_read_fd(&abuf->rfd, 0);

	    if (!is_recorded_read_fd(abuf->ufd))
	      abuf->flag |= ASYNC_FLAG_COMPLETED;

	    eof_p = TRUE;
	  }

	  l->ptr[l->length += rest] = '\0';

	  if ((n += rest) > abuf->chunk_size)
	    n = abuf->chunk_size;
	}
	else if (is_recorded_read_fd(rfd)) {
	  abuf->flag |= ASYNC_FLAG_MAYREAD;
	  return;
	}
	else if (!n)
	  goto end;
      }

      nlines = Plainlineproc(buf, p2env->p1env->p0env->flag, p2env->ctenv,
			     &l->ptr[abuf->rprocessed], &l->ptr[abuf->rprocessed + n],
			     eof_p && !(buf->bufferprop & BP_FOREVER), &rest);
      p2env->nlines += nlines;
      Strdelete(l, 0, abuf->rprocessed + n - rest);
      abuf->rprocessed = 0;

      if (abuf->chunk_size > n)
	abuf->chunk_size -= n - rest;
      else
	abuf->chunk_size = rest - l->length;

      if (nlines) {
	p2env->p1env->p0env->receiver(buf, nlines);

	if (!(abuf->flag & ASYNC_FLAG_COMPLETED))
	  blockNextLineMaybe(buf);
      }

      if (eof_p)
	goto end;

      if (abuf->chunk_size <= 0) {
	abuf->flag &= ~ASYNC_FLAG_MAYREAD;
	continue;
      }

      abuf->flag |= ASYNC_FLAG_MAYREAD;
    }

    break;
  }
end:
  if (abuf->flag & ASYNC_FLAG_COMPLETED &&
      (buf->bufferprop & BP_ASYNC_MASK) == BP_ASYNC_WAIT)
    finishAsyncBuffer(buf, 0);
}

static void
processUrgentInput(FILE **pfp, char *b, char *def_str)
{
  int flag, hist;
  Hist *phist;
  char *in;

  flag = strtoul(b, &b, 16);
  hist = strtoul(b, &b, 16);
  phist = (hist >= 0 && hist < HistV_size) ? &HistV[hist] : NULL;
  SKIP_BLANKS(b);

  if (inputLineBusy) {
    NEW_OBJV1(&inputLineQueue.n_max, inputLineQueue.n, &inputLineQueue.v, sizeof(inputLineQueue.v[0]), 0);
    inputLineQueue.v[inputLineQueue.n].prompt = allocStr(b, -1);
    inputLineQueue.v[inputLineQueue.n].def_str = allocStr(def_str, -1);
    inputLineQueue.v[inputLineQueue.n].flag = flag;
    inputLineQueue.v[inputLineQueue.n].hist = phist;
    inputLineQueue.v[inputLineQueue.n].pfp = pfp;
    return;
  }
  else if (inputLineQueue.n) {
    UrgentInputQueueElement temp;

    temp.prompt = allocStr(b, -1);
    temp.def_str = allocStr(def_str, -1);
    temp.flag = flag;
    temp.hist = phist;
    temp.pfp = pfp;
    b = inputLineQueue.v[0].prompt;
    def_str = inputLineQueue.v[0].def_str;
    flag = inputLineQueue.v[0].flag;
    phist = inputLineQueue.v[0].hist;
    pfp = inputLineQueue.v[0].pfp;
    memmove(inputLineQueue.v, &inputLineQueue.v[1], sizeof(inputLineQueue.v[0]) * (inputLineQueue.n - 1));
    inputLineQueue.v[inputLineQueue.n - 1] = temp;
  }

  in = inputLineHist(b, def_str, flag, phist);
  fprintf(*pfp, "%s\n", halfdump_buffer_quote(in));
  fflush(*pfp);
}

static void
processUrgentMessage(char *title, char *msg)
{
  Str l;

  l = Sprintf("%s%s", title, msg);
  Strchop(l);

  if (fmInitialized) {
    message(l->ptr);
    refresh();
  }
  else if (w3m_backend >= BACKEND_VERBOSE)
    backend_message(l->ptr, 1);
  else {
    Strfputs(l, stderr);
    putc('\n', stderr);
    fflush(stderr);
  }
}

static void
receiveAuthCookie(char *b)
{
  char *host, *file, *realm;
  int len, port;

  len = strlen(b);
  host = allocStr(b, len);
  b += len + 1;
  port = strtoul(b, &b, 16);
  ++b;
  file = halfdump_buffer_unquote(b);
  b += strlen(b) + 1;
  len = strlen(b);
  realm = allocStr(b, len);
  b += len + 1;
  add_auth_cookie(host, port, file, realm, Strnew_charp(b));
}

static void
closeAsyncRWBuf(int fd, void *arg)
{
  Buffer *buf;

  if ((buf = arg)) {
    AsyncRWBuf *abuf;

    if ((abuf = buf->async_buf)) {
      if (is_recorded_read_fd(abuf->rfd))
	clear_read_fd(&abuf->rfd, 0);

      if (is_recorded_read_fd(abuf->ufd))
	clear_read_fd(&abuf->ufd, 0);

      if (abuf->w) {
	fclose(abuf->w);
	abuf->w = NULL;
      }
    }
  }
}

void
readUrgentMessage(int ufd, void *arg)
{
  Buffer *buf;
  AsyncRWBuf *abuf;
  Phase2Env *p2env;
  int do_read, conti;
  TextListItem *ti;
  TextList *tl;
  Str l;
  char *def_str;
  void (*proc)(int, void *) = NULL;
#ifdef USE_IMAGE
  ImageCache *ic;
#endif

  buf = arg;
  abuf = buf->async_buf;
  p2env = abuf->p2env;
  tl = newTextList();

  for (do_read = conti = 1 ; conti ;)
    switch (fgets_async(ufd, abuf->ubuf, 0, &abuf->uprocessed, do_read, &l)) {
    case fgets_async_eol_and_eof:
      pushValue((GeneralList *)tl, l->ptr);
    case fgets_async_eof:
      conti = 0;
      clear_read_fd(&abuf->ufd, 0);

      if (!is_recorded_read_fd(abuf->rfd))
	abuf->flag |= ASYNC_FLAG_COMPLETED;

      break;
    case fgets_async_eol_with_read:
      do_read = 0;
    case fgets_async_eol_without_read:
      pushValue((GeneralList *)tl, l->ptr);
      break;
    case fgets_async_error:
    default: /* case fgets_async_no_eol: */
      conti = 0;
      break;
    }

  for (def_str = NULL, ti = tl->first ; ti ; ti = ti->next) {
    int mtype, event, event_delay;
    char *b, *host;
    unsigned short port;

    mtype = strtoul(ti->ptr, &b, 16);
    ++b;

    switch (mtype) {
    case urgent_default:
      def_str = b;
      break;
    case urgent_input:
      if (abuf->w)
	processUrgentInput(&abuf->w, b, def_str);

      break;
    case urgent_bufferinfo:
      halfdump_buffer_receive(buf, b);
      break;
    case urgent_label:
      abuf->label = urgent_unquote_charp(b)->ptr;
      break;
#ifdef USE_COOKIE
    case urgent_find_cookie:
      processSubCookie(b, abuf->w);
      break;
    case urgent_cookie:
      receiveCookieInfo(b);
      break;
#endif
    case urgent_find_auth_cookie:
      if ((l = find_sub_auth_cookie(urgent_unquote_charp(b)->ptr)))
	fputs(halfdump_buffer_quote(l->ptr), abuf->w);

      putc('\n', abuf->w);
      fflush(abuf->w);
      break;
    case urgent_auth_cookie:
      receiveAuthCookie(urgent_unquote_charp(b)->ptr);
      break;
    case urgent_proxy_auth_cookie:
      proxy_auth_cookie = (b = halfdump_buffer_unquote(b)) ? Strnew_charp(b) : NULL;
      break;
    case urgent_control:
      if (!buf->events)
	buf->events = newTextList();

      event = strtoul(b, &b, 16);
      event_delay = strtoul(b, &b, 16);
      SKIP_BLANKS(b);

      if (event_delay > 0)
	record_crontab(event, halfdump_buffer_unquote(b), 1, event_delay, buf);
      else
	pushValue((GeneralList *)buf->events, Sprintf("%X %s", event, b)->ptr);

      break;
    case urgent_tmpfile:
      b = urgent_unquote_charp(b)->ptr;
      pushValue((GeneralList *)fileToDelete, b);
      break;
    case urgent_record:
      l = Sprintf("%s%s", abuf->urgent_title->ptr, b);
      record_err_message(l->ptr);
      break;
    case urgent_fmsetup:
      switch (*b) {
      case 'I':
	fmInit(0);

	if (Currentbuf)
	  displayCurrentView(NULL);

	break;
      case 'T':
	fmTerm(0);
	break;
      default:
	break;
      }

      break;
    case urgent_failure:
      clear_read_fd(&abuf->rfd, CLEAR_READ_FD_KEEPFD);
      clear_read_fd(&abuf->ufd, CLEAR_READ_FD_KEEPFD);
      abuf->flag |= ASYNC_FLAG_COMPLETED;
      break;
    case urgent_nobuffer:
      buf->bufferprop |= BP_NOBUFFER;
      break;
#ifdef USE_IMAGE
    case urgent_image_retrieve:
      printImageCache(processImageRetrieve(urgent_unquote_charp(b)->ptr, p2env->p1env->p0env), abuf->w);
      break;
    case urgent_image_register:
      ic = unquoteImageCache(NULL, urgent_unquote_charp(b)->ptr);
      putImageCache(ic);
      printImageCache(ic, abuf->w);
      break;
    case urgent_image_size:
      ic = unquoteImageCache(NULL, urgent_unquote_charp(b)->ptr);

      if (getImageSize(ic, FALSE) < 0)
	putc('\n', abuf->w);
      else
	printImageCache(ic, abuf->w);

      break;
    case urgent_cur_baseURL:
      b = urgent_unquote_charp(b)->ptr;

      if (!p2env->p1env->p0env->cur_baseURL)
	p2env->p1env->p0env->cur_baseURL = New(ParsedURL);

      parseURL2(b, p2env->p1env->p0env->cur_baseURL, NULL);
      break;
#endif
    case urgent_keep_alive:
      host = b;
      b = strchr(host, ':');
      host = allocStr(host, b - host);
      port = strtoul(b + 1, NULL, 16);
      fputs(keepAsyncRWBuf(host, port, abuf, TRUE) ? "y\n" : "\n", abuf->w);
      fflush(abuf->w);
      break;
    case urgent_redirected:
      processRedirectedLoad(urgent_unquote_charp(b)->ptr, buf);
      return;
    case urgent_ready:
      clear_read_fd(&abuf->ufd, CLEAR_READ_FD_KEEPFD);

      if (*b != 'T')
	abuf->flag |= ASYNC_FLAG_DONT_KEEP_ALIVE;

      if (!is_recorded_read_fd(abuf->rfd))
	abuf->flag |= ASYNC_FLAG_COMPLETED;

      break;
    case urgent_body:
      switch (strtoul(b, NULL, 16)) {
      case async_body_html:
	if (!buf->view &&
	    !(buf->view = p2env->p1env->p0env->view))
	  buf->view = defaultBufferView();

	proc = readHTMLText;
	break;
      case async_body_plain:
	if (!buf->view &&
	    !(buf->view = p2env->p1env->p0env->view))
	  buf->view = defaultBufferView();

	proc = readPlainText;
	init_ctenv(abuf->p2env->ctenv = New(CheckTypeEnv), buf, WrapLine ? buf->view->width - buf->rmargin - buf->lmargin : 0
#ifdef USE_ANSI_COLOR
		   , TRUE
#endif
		   );
	break;
      case async_body_chunked:
	if (!buf->view &&
	    !(buf->view = p2env->p1env->p0env->view))
	  buf->view = defaultBufferView();

	proc = readChunkedPlainText;
	init_ctenv(abuf->p2env->ctenv = New(CheckTypeEnv), buf, WrapLine ? buf->view->width - buf->rmargin - buf->lmargin : 0
#ifdef USE_ANSI_COLOR
		   , TRUE
#endif
		   );
	abuf->chunk_size = abuf->rprocessed - abuf->rbuf->length;
	break;
      default:
	proc = NULL;
	break;
      }

      if (proc && abuf->rfd >= 0) {
	record_read_fd(abuf->rfd, proc, closeAsyncRWBuf, buf);
	abuf->flag |= ASYNC_FLAG_MAYREAD;
      }

      break;
    case urgent_echo:
      putc('\n', abuf->w);
      fflush(abuf->w);
      break;
    default: /* case urgent_output: */
      processUrgentMessage(abuf->urgent_title->ptr, b);

      if (fmInitialized)
	abuf->flag |= ASYNC_FLAG_REDRAW;

      break;
    }
  }

  if (abuf->flag & ASYNC_FLAG_COMPLETED &&
      (buf->bufferprop & BP_ASYNC_MASK) == BP_ASYNC_WAIT)
    finishAsyncBuffer(buf, -1);
}

Str
make_urgent_title(char *path, int pid)
{
  Str title, pidstr;

  path = html_quote(path);
  pidstr = Sprintf("[%d]: ", pid);
  title = Strnew();
  shrink_cat(COLS / 2 - pidstr->length, title, Strnew_charp(path));
  Strcat(title, pidstr);
  return title;
}

static MySignalHandler
exit_by_signal(SIGNAL_ARG)
{
  signal(SIGHUP, SIG_IGN);

  if (loadingBuffer && loadingBuffer != NO_BUFFER)
    discardBuffer(loadingBuffer);

  signal(SIGHUP, SIG_DFL);
  FtpExit();
  w3m_exit(1);
}

void
forkCleanup(void)
{
  struct kept_sock_desc *p;
  AsyncRWBuf *abuf;

  TopViewList.top = TopViewList.bot = NULL;
  Firstbuf = Currentbuf = NULL;
  fileToDelete->first = fileToDelete->last = NULL;
  fileToDelete->nitem = 0;
  close_tty();
  kept_sock_hooks = NULL;

  for (p = kept_sock_head ; p ; p = p->next)
    if ((abuf = p->async_buf)) {
      if (abuf->rfd >= 0)
	clear_read_fd(&abuf->rfd, 0);

      if (abuf->ufd >= 0)
	clear_read_fd(&abuf->ufd, 0);

      if (abuf->w) {
	fclose(abuf->w);
	abuf->w = NULL;
      }
    }

  clear_all_read_fds();
  fmInitialized = FALSE;
  w3m_backend = 0;

  if (urgent_out) {
    fclose(urgent_out);
    urgent_out = NULL;
  }

#ifdef USE_ROMAJI
  init_romaji_filter(0);
#endif
#ifdef USE_IMAGE
  if (activeImage) {
    termImage(-1);
    activeImage = TRUE;
  }
#endif

  kept_sock_head = kept_sock_tail = NULL;
  kept_nsocks = 0;
  kept_sock_hooks = NULL;
  signal(SIGPIPE, exit_by_signal);
  signal(SIGHUP, exit_by_signal);
  signal(SIGINT, SIG_IGN);
  signal(SIGQUIT, SIG_IGN);
  signal(SIGTERM, SIG_IGN);
  signal(SIGPIPE, SIG_IGN);
}

Str
quotePhase0Env(Phase0Env *p0env)
{
  Str tmp;

  tmp = tiny_safe_sprintf("%s%c%X %s%c"
#ifdef JP_CHARSET
			  "%c%c"
#ifdef USE_IMAGE
			  "%c"
#endif
#endif
#ifdef MANY_CHARSET
			  "%s%c%s%c"
#ifdef USE_IMAGE
			  "%s%c"
#endif
#endif
			  "%X %X %X %s"
#ifdef USE_IMAGE
			  "%c%s%c%s%c%X"
#endif
			  , halfdump_buffer_quote(proxy_auth_cookie ? proxy_auth_cookie->ptr : NULL), '\0'
			  , p0env->flag
			  , halfdump_buffer_quote(p0env->DefaultType), '\0'
#ifdef JP_CHARSET
			  , p0env->DocumentCode, p0env->content_charset
#ifdef USE_IMAGE
			  , p0env->cur_document_code
#endif
#endif
#ifdef MANY_CHARSET
			  , halfdump_buffer_quote(p0env->default_content_charset), '\0'
			  , halfdump_buffer_quote(p0env->content_charset), '\0'
#ifdef USE_IMAGE
			  , halfdump_buffer_quote(p0env->cur_content_charset), '\0'
#endif
#endif
			  , p0env->SearchHeader, p0env->RenderFrame
			  , p0env->view ? p0env->view->width : COLS
			  , p0env->URL ? parsedURL2Str(p0env->URL)->ptr : ""
#ifdef USE_IMAGE
			  , '\0'
			  , halfdump_buffer_quote(p0env->current_source), '\0'
			  , halfdump_buffer_quote(p0env->cur_baseURL ? parsedURL2Str(p0env->cur_baseURL)->ptr : NULL), '\0'
			  , p0env->displayImage
#endif
			  );
  return urgent_quote(tmp);
}

Phase0Env *
unquotePhase0Env(char *qs, Phase0Env *p0env, Str *p_proxy_auth_cookie)
{
  char *uri;

  if (p_proxy_auth_cookie) {
    char *cookie;

    *p_proxy_auth_cookie = (cookie = halfdump_buffer_unquote(qs)) ? Strnew_charp(cookie) : NULL;
  }

  qs += strlen(qs) + 1;

  if (!p0env)
    p0env = New(Phase0Env);

  p0env->receiver = displayBufferMaybe;
  p0env->receiver_arg = NULL;
  p0env->current_content_length = 0;
  p0env->curbuf = Currentbuf;
  p0env->redir_hist = NULL;
  p0env->flag = strtoul(qs, &qs, 16);
  SKIP_BLANKS(qs);
  p0env->DefaultType = halfdump_buffer_unquote(qs);
  qs += strlen(qs) + 1;
#ifdef JP_CHARSET
  p0env->DocumentCode = *qs++;
  p0env->content_charset = *qs++;
#ifdef USE_IMAGE
  p0env->cur_document_code = *qs++;
#endif
#endif
#ifdef MANY_CHARSET
  p0env->default_content_charset = halfdump_buffer_unquote(qs);
  qs += strlen(qs) + 1;
  p0env->content_charset = halfdump_buffer_unquote(qs);
  qs += strlen(qs) + 1;
#ifdef USE_IMAGE
  p0env->cur_content_charset = halfdump_buffer_unquote(qs);
  qs += strlen(qs) + 1;
#endif
#endif
  p0env->SearchHeader = strtoul(qs, &qs, 16);
  SKIP_BLANKS(qs);
  p0env->RenderFrame = strtoul(qs, &qs, 16);
  SKIP_BLANKS(qs);
  p0env->view = initBufferView(NULL, NULL, NULL, 0, 0, strtoul(qs, &qs, 16), LINES, 0, 0);
  SKIP_BLANKS(qs);

  if ((uri = halfdump_buffer_unquote(qs))) {
    p0env->URL = New(ParsedURL);
    parseURL2(uri, p0env->URL, NULL);
  }
  else
    p0env->URL = NULL;

#ifdef USE_IMAGE
  qs += strlen(qs) + 1;
  p0env->current_source = halfdump_buffer_unquote(qs);
  qs += strlen(qs) + 1;

  if ((uri = halfdump_buffer_unquote(qs))) {
    p0env->cur_baseURL = New(ParsedURL);
    parseURL2(uri, p0env->cur_baseURL, NULL);
  }
  else
    p0env->cur_baseURL = NULL;

  qs += strlen(qs) + 1;
  p0env->displayImage = strtoul(qs, NULL, 16);
#endif
  return p0env;
}

Str
quoteFormList(FormList *r)
{
  Str tmp;

  if (!r)
    return Strnew_size(0);

  tmp = tiny_safe_sprintf("%X%c%X%c%s%c%s%c%lX",
			  r->method, '\0', r->enctype, '\0', 
			  halfdump_buffer_quote(r->body), '\0',
			  halfdump_buffer_quote(r->boundary), '\0', (long)r->length);
  return urgent_quote(tmp);
}

FormList *
unquoteFormList(char *qs, FormList *r)
{
  int method, enctype;

  SKIP_BLANKS(qs);

  if (!*qs)
    return NULL;

  method = strtoul(qs, &qs, 16);
  ++qs;
  enctype = strtoul(qs, &qs, 16);
  ++qs;

  if (r) {
    r->method = method;
    r->enctype = enctype;
  }
  else
    r = newFormList_internal(NULL, method, NULL, enctype, NULL, NULL, NULL);

  r->body = halfdump_buffer_unquote(qs);
  qs += strlen(qs) + 1;
  r->boundary = halfdump_buffer_unquote(qs);
  qs += strlen(qs) + 1;
  r->length = strtoul(qs, NULL, 16);
  return r;
}

Str
quoteLoadRequest(char *path, ParsedURL *current, char *referer,
		 FormList *request, RedirectHistory *rh)
{
  Str tmp;

  tmp = tiny_safe_sprintf("%s%c%s%c%s%c%s%c",
			  halfdump_buffer_quote(path), '\0',
			  halfdump_buffer_quote(current ? parsedURL2Str(current)->ptr : NULL), '\0',
			  halfdump_buffer_quote(referer), '\0',
			  quoteFormList(request)->ptr, '\0');

  if (rh) {
    char buf[(sizeof(int) * CHAR_BIT + 3) / 4 * 2 + sizeof(" ")];
    int i;
	
    sprintf(buf, "%X %X", rh->n_max, rh->n);
    Strcat_charp(tmp, buf);

    for (i = 0 ; i < rh->n && i < rh->n_max ; ++i) {
      Strcat_char(tmp, '\0');
      Strcat(tmp, parsedURL2Str(&rh->v[i]));
    }
  }
  else
    Strcat_char(tmp, '0');

  return urgent_quote(tmp);
}

void
unquoteLoadRequest(char *qs, char **p_path, ParsedURL **p_current, char **p_referer,
		   FormList **p_request, RedirectHistory **p_rh)
{
  int len, i, n;
  char *curstr;

  SKIP_BLANKS(qs);
  *p_path = halfdump_buffer_unquote(qs);
  qs += strlen(qs) + 1;

  if ((curstr = halfdump_buffer_unquote(qs))) {
    *p_current = New(ParsedURL);
    parseURL2(curstr, *p_current, NULL);
  }
  else
    *p_current = NULL;

  qs += strlen(qs) + 1;
  *p_referer = halfdump_buffer_unquote(qs);
  qs += strlen(qs) + 1;

  if (*p_referer && !strcmp(*p_referer, NO_REFERER))
    *p_referer = NO_REFERER;

  len = strlen(qs);
  *p_request = unquoteFormList(urgent_unquote_charp_n(qs, len)->ptr, NULL);
  qs += len + 1;
  n = strtoul(qs, &qs, 16);

  if (n) {
    RedirectHistory *rh;

    ++qs;
    *p_rh = rh = New(RedirectHistory);
    rh->n_max = n;
    rh->v = New_N(ParsedURL, n);
    rh->n = n = strtoul(qs, &qs, 16);

    if (n > rh->n_max)
      n = rh->n_max;

    for (i = 0 ; i < n ; ++i) {
      parseURL2(qs, &rh->v[i], NULL);
      qs += strlen(qs) + 1;
    }
  }
  else
    *p_rh = NULL;
}

Buffer *
bindBufferWithAsyncRWBuf(Buffer *buf, AsyncRWBuf *abuf, char *path, Phase0Env *p0env)
{
  Phase0Env *new_p0env;

  abuf->rprocessed = abuf->uprocessed = 0;
  abuf->flag &= ASYNC_FLAG_KEEP_ALIVE;
  abuf->flag |= ASYNC_FLAG_USED;
  abuf->path = path;
  abuf->urgent_title = make_urgent_title(path, abuf->pid);
  abuf->label = NULL;
  new_p0env = New(Phase0Env);
  *new_p0env = *p0env;
  new_p0env->flag &= ~RG_PROC_MASK;
  new_p0env->flag |= RG_PROC_SUP;

  if (!buf) {
    buf = newBuffer(p0env->view);
    insertBuffer(NULL, buf);
  }

  abuf->p2env = init_phase2env(NULL, buf, -1, init_phase1env(NULL, buf, new_p0env));
  buf->bufferprop &= ~BP_ASYNC_MASK;
  buf->bufferprop |= BP_ASYNC_WAIT;
  buf->async_buf = abuf;
  buf->document_header = newTextList();

  if (abuf->pid > 0) {
    record_read_fd(abuf->ufd, readUrgentMessage, closeAsyncRWBuf, buf);
    clear_read_fd(&abuf->rfd, CLEAR_READ_FD_KEEPFD);
  }

  return buf;
}

pid_t
forkWithChannel(char *path, Phase0Env *p0env, Buffer **p_buf)
{
  int urgent_sub[2];
  int from_sub[2];
  int to_sub[2];
  pid_t pid;
  AsyncRWBuf *abuf;

  if (pipe(urgent_sub))
    goto error_urgent;

  if (pipe(from_sub))
    goto error_from;

  if (pipe(to_sub))
    goto error_to;

  flush_tty();

  if ((pid = fork()) == -1)
    goto error_fork;
  else if (!pid) {
    Str title;
    int tw;

    title = make_urgent_title(path, getpid());

    if ((tw =
#ifdef MANY_CHARSET
	 ttyfix_width(title->ptr)
#else
	 title->length
#endif
	 ) < COLS)
      MessageIndent = tw;

    forkCleanup();
    close(to_sub[1]);
    close(from_sub[0]);
    close(urgent_sub[0]);
    dup2(to_sub[0], 0);
    dup2(from_sub[1], 1);
    close(to_sub[0]);
    close(from_sub[1]);
    urgent_out = fdopen(urgent_sub[1], "wb");
    w3m_backend = BACKEND_VERY_VERBOSE;
    p0env->flag &= ~(RG_PROC_MASK | RG_HALFDUMP_OUT_MASK);
    p0env->flag |= RG_PROC_SUB | RG_HALFDUMP_OUT_PERLINE | RG_HALFDUMP_BUFFER;
    signal(SIGPIPE, exit_by_signal);
    signal(SIGHUP, exit_by_signal);
#ifdef MANY_CHARSET
    setup_tty_mb_w("");
#endif
    return pid;
  }

  close(urgent_sub[1]);
  close(from_sub[1]);
  close(to_sub[0]);

  abuf = New(AsyncRWBuf);
  abuf->pid = pid;
  abuf->rfd = from_sub[0];
  abuf->rbuf = Strnew_size(PIPE_BUF);
  abuf->ufd = urgent_sub[0];
  abuf->ubuf = Strnew_size(PIPE_BUF);

  if (!(abuf->w = fdopen(to_sub[1], "wb")))
    close(to_sub[1]);

  *p_buf = bindBufferWithAsyncRWBuf(*p_buf, abuf, path, p0env);
  return pid;
error_fork:
  close(to_sub[0]);
  close(to_sub[1]);
error_to:
  close(from_sub[0]);
  close(from_sub[1]);
error_from:
  close(urgent_sub[0]);
  close(urgent_sub[1]);
error_urgent:
  return -1;
}

void
flush_buffer_and_exit(Buffer *newBuf)
{
  fd_set fds;
  int stdin_fd = -1, aborted, n, kept_once;
  Str l = NULL;
  char *p, *path, *referer;
  Phase0Env p0env;
  ParsedURL *current;
  FormList *request;

  for (kept_once = FALSE ;; kept_once = TRUE) {
    if (newBuf && newBuf != NO_BUFFER) {
      aborted = (newBuf->bufferprop & BP_ASYNC_MASK) == BP_ASYNC_ABORT;
      loadingBuffer = NULL;
      halfdump_buffer_send(newBuf);
      discardBuffer(newBuf);
    }
    else {
      fprintf(urgent_out, "%X\n", newBuf ? urgent_nobuffer : urgent_failure);
      fflush(urgent_out);

      if (loadingBuffer && loadingBuffer != NO_BUFFER) {
	aborted = (loadingBuffer->bufferprop & BP_ASYNC_MASK) == BP_ASYNC_ABORT;
	discardBuffer(loadingBuffer);
      }
      else
	aborted = FALSE;
    }

    if (aborted || kept_sock < 0) {
      if (kept_once) {
	fprintf(urgent_out, "%X F\n", urgent_ready);
	fflush(urgent_out);
      }

      break;
    }

    fflush(stdout);
    newBuf = loadingBuffer = NULL;

    if (stdin_fd < 0) {
      stdin_fd = fileno(stdin);
      FD_ZERO(&fds);
#if defined(USE_MOUSE) && (defined(USE_GPM) || defined(USE_SYSMOUSE))
      rev.sigmouse = 0;
#endif
    }

    fprintf(urgent_out, "%X T\n", urgent_ready);
    fflush(urgent_out);
    FD_SET(stdin_fd, &fds);

    while ((n = select(stdin_fd + 1, &fds, NULL, NULL, NULL)) < 0)
      switch (errno) {
      case EINTR:
      case EAGAIN:
	break;
      default:
	goto end;
      }

    if (l)
      Strclear(l);
    else
      l = Strnew_size(PIPE_BUF);

    do {
      Strassure(l, PIPE_BUF);

      while ((n = read(stdin_fd, &l->ptr[l->length], PIPE_BUF)) < 0)
	switch (errno) {
	case EINTR:
	case EAGAIN:
	  break;
	default:
	  goto end;
	}

      if (!n)
	goto end;
    } while (!(p = memchr(&l->ptr[l->length], '\n', n)));

    while (p > l->ptr && *p == '\r')
      --p;

    l->ptr[l->length = p - l->ptr] = '\0';

    if (!l->length || !(p = strchr(l->ptr, ' ')))
      break;

    p0env = main_p0env;
    unquotePhase0Env(urgent_unquote_charp_n(l->ptr, p - l->ptr)->ptr, &p0env, &proxy_auth_cookie);
    SKIP_BLANKS(p);
    unquoteLoadRequest(urgent_unquote_charp(p)->ptr, &path, &current, &referer, &request, &p0env.redir_hist);
    p0env.flag &= ~(RG_PROC_MASK | RG_HALFDUMP_OUT_MASK);
    p0env.flag |= RG_PROC_SUB | RG_HALFDUMP_OUT_PERLINE | RG_HALFDUMP_BUFFER;
    newBuf = loadGeneralFileOnBuffer(path, current, referer, &p0env, request, NULL);
  }
end:
  signal(SIGHUP, SIG_DFL);
  FtpExit();

  if (kept_sock >= 0) {
    close(kept_sock);
    discardKeptSock();
  }

  w3m_exit(newBuf ? 0 : 1);
}

#ifdef USE_IMAGE
void
urgent_send_cur_baseURL(Phase0Env *p0env)
{
  if ((p0env->flag & RG_PROC_MASK) == RG_PROC_SUB) {
    Str url;

    url = parsedURL2Str(p0env->cur_baseURL);
    fprintf(urgent_out, "%X %s\n", urgent_cur_baseURL, urgent_quote(url)->ptr);
    fflush(urgent_out);
  }
}

Str
quoteImageCache(ImageCache *c)
{
  Str s;

  s = tiny_safe_sprintf("%s%c%s%c%s%c%c%X %X %X %ld %X",
			c->url, '\0',
			c->current ? parsedURL2Str(c->current)->ptr : "", '\0',
			c->file, '\0',
			c->loaded, c->index,
			c->width > 0 ? (int)c->width : 0,
			c->height > 0 ? (int)c->height : 0,
			c->time, c->retry);
  return urgent_quote(s);
}

ImageCache *
unquoteImageCache(ImageCache *d, char *t)
{
  int len;
  ImageCache tc;

  len = strlen(t);
  tc.url = allocStr(t, len);
  t += len + 1;

  if (*t) {
    tc.current = New(ParsedURL);
    parseURL2(t, tc.current, NULL);
    t += strlen(t) + 1;
  }
  else {
    tc.current = NULL;
    ++t;
  }

  len = strlen(t);
  tc.file = allocStr(t, len);
  t += len + 1;
  tc.loaded = *t++;
  tc.index = strtoul(t, &t, 16);
  SKIP_BLANKS(t);

  if (!(tc.width = strtoul(t, &t, 16)))
    tc.width = -1;

  SKIP_BLANKS(t);

  if (!(tc.height = strtoul(t, &t, 16)))
    tc.height = -1;

  SKIP_BLANKS(t);
  tc.time = strtol(t, NULL, 10);
  SKIP_BLANKS(t);
  tc.retry = strtoul(t, NULL, 16);

  if (!d) {
    Str key;

    key = Sprintf("%d;%d;%s",
		  tc.width > 0 ? tc.width : 0,
		  tc.height > 0 ? tc.height : 0,
		  tc.url);
    d = getImageCache(key->ptr, key->length);
  }

  if (d) {
    tc.displayed = d->displayed;
    tc.index = d->index;
  }
  else {
    d = New(ImageCache);
    tc.displayed = FALSE;
    tc.index = 0;
  }

  *d = tc;
  return d;
}

static ImageCache *
processImageRetrieve(char *b, Phase0Env *p0env_orig)
{
  int len, w, h, flag;
  char *url, *ext;
  Phase0Env p0env;

  p0env = *p0env_orig;
  p0env.flag &= ~RG_PROC_MASK;
  p0env.flag |= RG_PROC_FORK;
  len = strlen(b);
  url = allocStr(b, len);
  b += len + 1;
  len = strlen(b);
  ext = allocStr(b, len);
  b += len + 1;

  if (*b) {
    p0env.cur_baseURL = New(ParsedURL);
    parseURL2(b, p0env.cur_baseURL, NULL);
    b += strlen(b) + 1;
  }
  else {
    p0env.cur_baseURL = NULL;
    ++b;
  }

  if (!(w = strtoul(b, &b, 16)))
    w = -1;

  SKIP_BLANKS(b);

  if (!(h = strtoul(b, &b, 16)))
    h = -1;

  flag = strtoul(b, NULL, 16);
  return getOrLoadImageCache(url, ext, &p0env, w, h, flag);
}

static void
printImageCache(ImageCache *ic, FILE *fp)
{
  if (fp) {
    if (ic) {
      Str l;

      l = quoteImageCache(ic);
      Strfputs(l, fp);
    }

    putc('\n', fp);
    fflush(fp);
  }
}
#endif

void
processRedirectedLoad(char *b, Buffer *buf)
{
  char *path, *referer;
  ParsedURL *current;
  FormList *request;
  RedirectHistory *rh;

  unquoteLoadRequest(b, &path, &current, &referer, &request, &rh);
  buf->async_buf->p2env->p1env->p0env->redir_hist = rh;
  retryLoadGeneralFile(path, current, referer, request, buf);
}

Str
find_sup_auth_cookie(char *host, int port, char *file, char *realm, Phase0Env *p0env)
{
  if ((p0env->flag & RG_PROC_MASK) == RG_PROC_SUB) {
    Str l;

    l = tiny_safe_sprintf("%s%c%X%c%s%c%s", host, port, halfdump_buffer_quote(file), realm);
    fprintf(urgent_out, "%X %s\n", urgent_find_auth_cookie, urgent_quote(l)->ptr);
    fflush(urgent_out);
    l = Strfgets(stdin);
    Strchop(l);
    return halfdump_buffer_unquote_to_str(l->ptr);
  }
  else
    return find_auth_cookie(host, port, file, realm);
}

Str
find_sub_auth_cookie(char *qs)
{
  int port;
  char *host, *file;

  host = qs;
  qs += strlen(host) + 1;
  port = strtoul(qs, &qs, 16);
  ++qs;
  file = halfdump_buffer_unquote(qs);
  return find_auth_cookie(host, port, file, qs + strlen(qs) + 1);
}

void
add_sup_auth_cookie(char *host, int port, char *file, char *realm, Str cookie, Phase0Env *p0env)
{
  add_auth_cookie(host, port, file, realm, cookie);

  if ((p0env->flag & RG_PROC_MASK) == RG_PROC_SUB) {
    Str tmp;

    tmp = tiny_safe_sprintf("%s%c%X%c%s%c%s%c%s",
			    host, '\0', port, '\0', halfdump_buffer_quote(file), '\0',
			    realm, '\0', cookie->ptr);
    fprintf(urgent_out, "%X %s\n", urgent_auth_cookie, urgent_quote(tmp)->ptr);
    fflush(urgent_out);
  }
}
