/*
 * Copyright (c) 2002, The EROS Group, LLC and Johns Hopkins
 * University. All rights reserved.
 * 
 * This software was developed to support the EROS secure operating
 * system project (http://www.eros-os.org). The latest version of
 * the OpenCM software can be found at http://www.opencm.org.
 * 
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above
 *    copyright notice, this list of conditions and the following
 *    disclaimer in the documentation and/or other materials
 *    provided with the distribution.
 * 
 * 3. Neither the name of the The EROS Group, LLC nor the name of
 *    Johns Hopkins University, nor the names of its contributors
 *    may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <opencm.h>
#include <repos/Repository.h>
#include "../opencmclient.h"

int show_c(SDR_stream *strm, const Buffer *input);
int show_make(SDR_stream *strm, const Buffer *input);
int show_shell(SDR_stream *strm, const Buffer *input);
int show_text(SDR_stream *strm, const Buffer *input);

#define MUTS "<font size=\"-1\">%s</font>"
#define NAME(s) "<font size=\"-1\">" s "</font>"
/* #define CONTENT(s) "<font color=\"blue\">" s "</font>" */
#define CONTENT(s) s
#define LINK(uri,s) "<a href=\"" uri "\">" s "</a>"
#define BODY "<body BGCOLOR='#ffeedd' text='#000000' \
               link='#0000ee' vlink='#551a8b' alink='#ff0000'>"

extern Repository *this_repos;

typedef struct FileEntry {
  Entity *e;
  CommitInfo *ci;
  User *u;
  const char *name;
  OC_bool isDir;
} FileEntry;

static const char *
uri_encode(const char *s)
{
  const char *from;
  char *to;
  char *out;
  unsigned to_len = 0u;

  char *hex = "0123456789abcdef";

  /* Pass 1: Count the characters that need encoding: */
  for (from = s; *from; from++) {
    to_len ++;			/* either % or the character */

    if ( isalpha(*from) ||
	 isdigit(*from) ||
	 (*from == '/') ||
	 (*from == '.') ||
	 (*from == ':') )
      continue;			/* treat these literally */

    else
      to_len += 2;		/* need to hex encode */

  }

  out = to = GC_MALLOC_ATOMIC(to_len + 1);

  for (from = s; *from; from++) {
    if ( isalpha(*from) ||
	 isdigit(*from) ||
	 (*from == '/') ||
	 (*from == '.') ||
	 (*from == ':') ) {
      *to++ = *from;
    }
    else {
      unsigned char c = ((unsigned)*from) & 0xffu;

      *to++ = '%';
      *to++ = hex[(c >> 4) & 0x0F];
      *to++ = hex[(c     ) & 0x0F];
    }
  }
  *to = '\0';

  return out;
}

static const char *
uri_decode(const char *from)
{
  size_t len = strlen(from);
  char *out;
  char *to;

  out = to = GC_MALLOC_ATOMIC(len + 1);

  for (; *from; from++) {
    unsigned value = *from;

    if (*from == '%') {
      const char *hexstr = from + 1;

      if (strlen(from) < 3) {
	*to = 0;
	break;
      }

      from += 2;

      if (isdigit(hexstr[0]))
	value = (hexstr[0] - '0');
      else if (isupper(hexstr[0]))
	value = (10 + hexstr[0] - 'A');
      else
	value = (10 + hexstr[0] - 'a');

      value <<= 4;

      if (isdigit(hexstr[0]))
	value |= (hexstr[0] - '0');
      else if (isupper(hexstr[0]))
	value |= (10 + hexstr[0] - 'A');
      else
	value |= (10 + hexstr[0] - 'a');
    }

    *to++ = (char)value;
  }

  *to = 0;

  return out;
}

static int
cmp_fname(const void *v1, const void *v2)
{
  const FileEntry *e1 = *((const FileEntry **) v1);
  const FileEntry *e2 = *((const FileEntry **) v2);

  assert(e1 != 0);
  assert(e2 != 0);
  return strcmp(e1->name, e2->name);
}

static int
cmp_dirname(const void *v1, const void *v2)
{
  const Dirent *de1 = *((const Dirent **) v1);
  const Dirent *de2 = *((const Dirent **) v2);

  assert(de1 != 0);
  assert(de2 != 0);
  return strcmp(de1->key, de2->key);
}

static const char *
xlate_time(const char *tmISO)
{
  char *time = xstrdup(tmISO);
  char *sep = strchr(time, 'T');
  char *zone = "";
  if (sep) *sep = ' ';

  sep = time;
  /* chop off the time zone */

  for (sep = time; *sep && !isalpha(*sep); sep++)
    ;

  if (*sep) {
    zone = xstrdup(sep);
    *sep = '\0';
  }

  time = xstrcat(time, xstrcat(" ", zone));

  return time;
}

static void
show_files(SDR_stream *out, Repository *repos, Resolution *res, 
	   Change *chg, const char *prefix, const char *thisPageURI)
{
  ObVec *ev;
  unsigned w;

#if 0
  /* Get all the needed Entity objects with one call */
  TRY {
    ents = repos_GetEntityVec(repos, res->m->uri, chg->entNames);
  }
  DEFAULT(AnyException) {
  }
  END_CATCH;
#endif

  ev = obvec_create();

  for (w = 0; w < vec_size(CMGET(chg,entities)); w++) {
    Entity *e = vec_fetch(CMGET(chg,entities), w, Entity *);
    const char *rest = 
      glob_match(e->fsName, prefix, GM_FS_COMPONENT);

    if (rest) {
      FileEntry * fe = GC_MALLOC(sizeof(FileEntry));
      fe->e = e;
      fe->name = path_car(rest);
      fe->isDir = path_cdr(rest) ? 1 : 0;
      fe->ci = 0;
      if (fe->isDir == 0) {
	TRY {
	  fe->ci = repos_GetEntity(repos, res->m->uri, e->commitInfoTrueName);
	}
	DEFAULT(ex) {
	}
	END_CATCH;
      }
      if (fe->ci) {
	TRY {
	  Mutable *m = repos_GetMutable(repos, fe->CMGET(ci,authorURI));
	  fe->u = (User *) repos_GetMutableContent(repos, m);
	}
	DEFAULT(ex) {
	}
	END_CATCH;
      }
      obvec_append(ev, fe);
    }
  }

  vec_sort_using(ev, cmp_fname);

  if (vec_size(ev) > 0) {
    const char *last = 0;

    stream_printf(out, "<table>");

    stream_printf(out, "<tr valign=\"top\">");
    stream_printf(out, "<td><b>Type</b></td>");
    stream_printf(out, "<td><b>Name</b></td>");
    stream_printf(out, "<td><b>Size</b></td>");
    stream_printf(out, "<td><b>Content</b></td>");
    stream_printf(out, "<td><b>Date</b></td>");
    stream_printf(out, "<td><b>Alleged Author</b></td>");
    stream_printf(out, "</tr>");

    for (w = 0; w < vec_size(ev); w++) {
      const FileEntry *fe = vec_fetch(ev, w, const FileEntry*);
      const char *email = fe->u ? pubkey_GetEmail(fe->u->pubKey) : "??";
      const char *author = email;
      if (fe->u)
	author = format(LINK("/opencm/everyone/%s", CONTENT("%s")),
			email, email);

      if (last && strcmp(fe->name, last) == 0) 
	continue;

      fe = vec_fetch(ev,w,const FileEntry*);
      last = fe->name;

      stream_printf(out, "<tr valign=\"top\">");
      stream_printf(out, "<td>" "%s" "</td>", fe->isDir ? "[Dir]" : "[File]");
      stream_printf(out, "<td>" LINK("/opencm/%s/%s", "%s") "</td>",
                    thisPageURI, uri_encode(last), last);
      stream_printf(out, "<td>" "%s" "</td>", 
                    fe->isDir ? "" : xunsigned64_str(fe->e->length));
      stream_printf(out, "<td>" "%c" "</td>",
                    fe->isDir ? ' ' : fe->e->entityType);
      stream_printf(out, "<td>" "%s" "</td>", 
	      fe->ci ? xlate_time(fe->CMGET(ci,time)) : "");
      stream_printf(out, "<td>" "%s" "</td>", author);

      stream_printf(out, "</tr>");
    }

    stream_printf(out, "</table>");
  }

}

static const char *
page_header(const char *title)
{
  return format("<head><title>%s</title>"
		"<style type=\"text/css\"> <!--"
		"A { text-decoration: none }"
		"A:visited { text-decoration: none; color: blue }"
		"A:hover { text-decoration: none; color: red }"
		"A:active { text-decoration: none; color: blue }"
		"--></style>"
		"</head>\n", title);
}

static void
show_html(SDR_stream *out, Repository *repos, Resolution *res, 
	  const char *thisPageURI)
{
  const char *decodedURI = uri_decode(thisPageURI);

  stream_printf(out, "<html>\n");

  if (res->tail) {
    switch(GETTYPE(res->tail)) {
    case TY_User:
      {
	User *u =  (User *) res->tail;
	const char *u_s = u ? pubkey_GetEmail(u->pubKey) : "??";

	stream_printf(out, page_header(format("User %s", u_s)));
	stream_printf(out, BODY);
	stream_printf(out, "<center>\n");
	stream_printf(out, "User<br>%s\n", u_s);
	stream_printf(out, "</center>\n");
	stream_printf(out, "<hr>\n");

	switch(u->pubKey->type) {
	case pk_X509:
	  {
	    PubKey *pk = u->pubKey;
	    X509 *x = pk->keys.x509_cert;
	    char commonName[100];
	    char country[100];
	    char state[100];
	    char locality[100];
	    char org[100];
	    char unit[100];
	    const char *expires;
            const char *email = u_s;

	    commonName[0] = '\0';
	    country[0] = '\0';
	    state[0] = '\0';
	    locality[0] = '\0';
	    org[0] = '\0';
	    unit[0] = '\0';

	    X509_NAME_get_text_by_NID(X509_get_subject_name(x), 
				      NID_countryName, country, 100);
	    X509_NAME_get_text_by_NID(X509_get_subject_name(x), 
				      NID_stateOrProvinceName, state, 100);
	    X509_NAME_get_text_by_NID(X509_get_subject_name(x),
				      NID_localityName, locality, 100);

	    X509_NAME_get_text_by_NID(X509_get_subject_name(x),
				      NID_organizationName, org, 100);

	    X509_NAME_get_text_by_NID(X509_get_subject_name(x),
				      NID_commonName, commonName, 100);
	    X509_NAME_get_text_by_NID(X509_get_subject_name(x), 
				      NID_organizationalUnitName, unit, 100);

	    /* Get the expiration date of user's cert */
	    expires = pubkey_GetExpiration(pk);

	    stream_printf(out, "<table>");

	    stream_printf(out, "<tr valign=\"top\">");
	    stream_printf(out, "<td>" NAME("URI") "</td>");
	    stream_printf(out, "<td>" MUTS "</td>", res->m->uri);
	    stream_printf(out, "</tr>");
	    if (commonName[0]) {
	      stream_printf(out, "<tr valign=\"top\">");
	      stream_printf(out, "<td>" NAME("Name") "</td>");
	      stream_printf(out, "<td>" CONTENT("%s") "</td>", commonName);
	      stream_printf(out, "</tr>");
	    }
	    if (email[0] != '?') {
	      stream_printf(out, "<tr valign=\"top\">");
	      stream_printf(out, "<td>" NAME("Email") "</td>");
	      stream_printf(out, "<td>" CONTENT("%s") "</td>", email);
	      stream_printf(out, "</tr>");
	    }
	    if (org[0]) {
	      stream_printf(out, "<tr valign=\"top\">");
	      stream_printf(out, "<td>" NAME("Organization") "</td>");
	      stream_printf(out, "<td>" CONTENT("%s") "</td>", org);
	      stream_printf(out, "</tr>");
	    }
	    if (unit[0]) {
	      stream_printf(out, "<tr valign=\"top\">");
	      stream_printf(out, "<td>" NAME("Unit") "</td>");
	      stream_printf(out, "<td>" CONTENT("%s") "</td>", unit);
	      stream_printf(out, "</tr>");
	    }
	    if (country[0] || state[0] || locality[0]) {
	      stream_printf(out, "<tr valign=\"top\">");
	      stream_printf(out, "<td>" NAME("Location") "</td>");
	      stream_printf(out, "<td>" CONTENT("%s/%s/%s") "</td>",
		      country, state, locality);
	      stream_printf(out, "</tr>");
	    }
	    if (expires[0]) {
	      stream_printf(out, "<tr valign=\"top\">");
	      stream_printf(out, "<td>" NAME("Expires") "</td>");
	      stream_printf(out, "<td>" CONTENT("%s") "</td>",
		      expires);
	      stream_printf(out, "</tr>");
	    }
	    stream_printf(out, "<tr valign=\"top\">");
	    stream_printf(out, "<td>" NAME("Home Directory") "</td>");
	    stream_printf(out, "<td>" LINK("/opencm/%s/home", "home") "</td>",
                          thisPageURI);
	    stream_printf(out, "</tr>");
	    stream_printf(out, "</table>");
	  }

	  break;
	default:
	  stream_printf(out, "Unknown certificate type.<br>");
	  break;
	}
	stream_printf(out, "</body>\n");
	break;
      }
    case TY_Directory:
      {
	Directory *dir = (Directory *) res->tail;
	unsigned u;

	stream_printf(out, page_header(format("Directory %s", decodedURI)));
	stream_printf(out, BODY);
	stream_printf(out, "<center>\n");
	stream_printf(out, "Directory %s<br>", decodedURI);
	stream_printf(out, "</center>\n");
	stream_printf(out, "<hr>\n");

	if (vec_size(dir->entries)) {
	  stream_printf(out, "<table>");

	  stream_printf(out, "<tr valign=\"top\">");
	  stream_printf(out, "<td><b>Flags</b></td>");
	  stream_printf(out, "<td><b>Name</b></td>");
	  stream_printf(out, "<td><b>nRev</b></td>");
	  stream_printf(out, "<td><b>Type</b></td>");
	  stream_printf(out, "<td><b>Last Change</b></td>");
	  stream_printf(out, "<td><b>Author</b></td>");
	  stream_printf(out, "</tr>");

          vec_sort_using(dir->entries, cmp_dirname);

	  for (u = 0; u < vec_size(dir->entries); u++) {
	    Dirent *de = vec_fetch(dir->entries, u, Dirent *);
	    Mutable *m = 0;
	    Revision *rev = 0;
	    Serializable *s = 0;
	    User *u = 0;

	    TRY {
	      m = repos_GetMutable(repos, de->value);
	      rev = repos_GetTopRev(repos, m);
	      s = repos_GetEntity(repos, m->uri, rev->newObject);
	      u = repos_GetEntity(repos, m->uri, rev->revisor);
	    }
	    DEFAULT(ex) {
	      /* Nothing to do */
	    }
	    END_CATCH;

	    stream_printf(out, "<tr valign=\"top\">");

	    stream_printf(out, "<td>%s%s</td>", 
		    (m && (m->flags & MF_FROZEN)) ? "F" : "",
		    (m && (m->flags & MF_NOTRAIL)) ? "N" : "");

	    stream_printf(out, "<td>" LINK("/opencm/%s/%s", CONTENT("%s")) "</td>\n",
		    thisPageURI,
		    uri_encode(de->key), de->key);
	    if (s) {
	      const char *email = u ? pubkey_GetEmail(u->pubKey) : "???";
	      const char *author = email;
	      if (u)
		author = format(LINK("/opencm/everyone/%s", CONTENT("%s")),
				email, email);
	      stream_printf(out, "<td>%s</td>", xunsigned64_str(m->nRevisions));
	      stream_printf(out, "<td>%s</td>", s->ser_type->tyName);
	      stream_printf(out, "<td>%s</td>", xlate_time(rev->reviseTime));
	      stream_printf(out, "<td>%s</td>", author);
	    }
	    else {
	      stream_printf(out, "<td colspan='5'><em>inaccessable</em></td>");
	    }
	    stream_printf(out, "</tr>");
	  }

	  stream_printf(out, "</table>");
	  stream_printf(out, "</body>\n");
	}

	break;
      }

    case TY_Group:
      {
	Group *grp = (Group *) res->tail;
	unsigned u;

	stream_printf(out, page_header(format("Group %s", decodedURI)));
	stream_printf(out, BODY);
	stream_printf(out, "<center>\n");
	stream_printf(out, "Group<br>%s\n", decodedURI);
	stream_printf(out, "</center>\n");
	stream_printf(out, "<hr>\n");

	stream_printf(out, "<table>");

	for (u = 0; u < vec_size(grp->members); u++) {
	  const char *tn = vec_fetch(grp->members, u, const char *);
	  Mutable *m = NULL;
	  Serializable *s = NULL;

	  TRY {
	    Revision *rev;
	    m = repos_GetMutable(repos, tn);
	    rev = repos_GetTopRev(repos, m);
	    s = repos_GetEntity(repos, m->uri, rev->newObject);
	  }
	  DEFAULT(ex) {
	  }
	  END_CATCH;

	  {
	    const char *name = "<unknown>";

	    if (s && GETTYPE(s) == TY_User)
	      name = pubkey_GetEmail(((User *) s)->pubKey);
	    else if (s && GETTYPE(s) == TY_Group)
	      name = m->name;

	    stream_printf(out, "<tr valign=\"top\">");

	    stream_printf(out, "<td>" LINK("/opencm/%s/%s", "%s") "</td>", 
		    thisPageURI, uri_encode(name), name);

#if 0
	    stream_printf(out, "<td>" LINK("/opencm/%s", "%s") "</td>", tn, name);
	    stream_printf(out, "<td>" MUTS "</td>", tn);
#endif
	    stream_printf(out, "</tr>");
	  }
	}

	stream_printf(out, "</table>");
	stream_printf(out, "</body>\n");

	break;
      }

    case TY_Entity:
      {
	Entity *e = (Entity *) res->tail;
	Buffer *content = 
	  (Buffer *)repos_GetEntity(repos, res->m->uri,
				    e->contentTrueName);

	stream_printf(out, page_header(format("File %s", res->fp_frag)));
	stream_printf(out, BODY);
	stream_printf(out, "<center>\n");
	stream_printf(out, "File %s\n", res->fp_frag);
	stream_printf(out, "</center>\n");
	stream_printf(out, "<hr>\n");

	/* Try to guess the file type */
	if (glob_match(res->fp_frag, "[Mm]akefile", 0) ||
	    glob_match(res->fp_frag, "GNU[Mm]akefile", 0) ||
	    glob_match(res->fp_frag, "*.mk", 0)) {
	  show_make(out, content);
	}
	else if (glob_match(res->fp_frag, "*.[ch]", 0) ||
	    glob_match(res->fp_frag, "*.cpp", 0) ||
	    glob_match(res->fp_frag, "*.[ch]xx", 0)) {

	  show_c(out, content);
	}
	else if (glob_match(res->fp_frag, "*.sh", 0)) {
	  show_shell(out, content);
	}
	else {
	  show_text(out, content);
	}

	stream_printf(out, "</body>\n");
	break;
      }
    case TY_Change:
      {
	ObVec *revs = NULL;
	unsigned w;
	uint64_t last = res->m->nRevisions - 1;
	uint64_t first = (last < 20) ? 0 : last - 19;

	stream_printf(out, page_header(format("Branch %s", res->fp_frag)));
	stream_printf(out, BODY);
	stream_printf(out, "<center>\n");
	stream_printf(out, "Branch %s\n", res->fp_frag);
	stream_printf(out, "</center>\n");
	stream_printf(out, "<hr>\n");

	revs = repos_GetRevisions(repos, res->m->uri, first, last);

	if (vec_size(revs) != 0) {

	  stream_printf(out, "<table>");
	  
	  stream_printf(out, "<tr valign=\"top\">");
	  stream_printf(out, "<td><b>Revision</b></td>");
	  stream_printf(out, "<td><b>Time</b></td>");
	  stream_printf(out, "<td><b>Who</b></td>");
	  stream_printf(out, "</tr>");
	  
	  w = vec_size(revs);

	  do {
	    const char *revno;
	    Revision *rev;
	    User *u = 0;
	    const char *userName = "<unavailable>";

	    w--;

	    rev = vec_fetch(revs, w, Revision *);

	    if (rev->newObject == 0)
	      continue;

	    TRY {
	      u = repos_GetEntity(repos, res->m->uri, rev->revisor);
	      userName = pubkey_GetEmail(u->pubKey);
	      userName = format(LINK("/opencm/everyone/%s", CONTENT("%s")),
				userName, userName);
	    }
	    DEFAULT(ex) {
	    }
	    END_CATCH;

	    revno = xunsigned64_str(rev->seq_number);

	    stream_printf(out, "<tr valign=\"top\">");
	    stream_printf(out, "<td>" LINK("/opencm/%s/%s", "%s") "</td>", 
		    thisPageURI, revno, revno);
	    stream_printf(out, "<td>" CONTENT("%s") "</td>", xlate_time(rev->reviseTime));
	    stream_printf(out, "<td>" CONTENT("%s") "</td>", 
		    userName);
	    stream_printf(out, "</tr>");

	  } while (w > 0);

	  stream_printf(out, "</table>");
	}

	stream_printf(out, "</body>\n");
	break;
      }
    case TY_Version:
      {
	Version *ver = (Version *) res->tail;
	Change *chg = 
	  (Change *)repos_GetEntity(repos, res->m->uri, ver->cfgName);

	CommitInfo *ci =
	  (CommitInfo *)repos_GetEntity(repos, res->m->uri,
					CMGET(chg,commitInfoTrueName));
	
	stream_printf(out, page_header(format("Commit %s", res->fp_frag)));
	stream_printf(out, BODY);
	stream_printf(out, "<center>\n");
	stream_printf(out, "Commit %s (%s)\n", res->fp_frag, xunsigned64_str(ver->rev));
	stream_printf(out, "</center>\n");
	stream_printf(out, "<hr>\n");

	stream_printf(out, "Committed at: %s<br>\n", xlate_time(CMGET(ci,time)));

	show_text(out, CMGET(ci,descrip));
	
	show_files(out, repos, res, chg, ".", thisPageURI);

	stream_printf(out, "</body>\n");

	break;
      }

    case TY_FsDir:
      {
	FsDir *fsDir = (FsDir *) res->tail;
	Change *chg = fsDir->cfg;

	CommitInfo *ci =
	  (CommitInfo *)repos_GetEntity(repos, res->m->uri,
					CMGET(chg,commitInfoTrueName));

	const char *revNo = xunsigned64_str(fsDir->rev);

	stream_printf(out, page_header(format("Commit %s, %s/", 
					      revNo, fsDir->path)));
	stream_printf(out, BODY);
	stream_printf(out, "<center>\n");
	stream_printf(out, "Commit %s, %s/\n", revNo, fsDir->path);
	stream_printf(out, "</center>\n");
	stream_printf(out, "<hr>\n");

	stream_printf(out, "Committed at: %s<br>\n", xlate_time(CMGET(ci,time)));

	show_text(out, CMGET(ci,descrip));
	
	show_files(out, repos, res, chg, fsDir->path, thisPageURI);

	stream_printf(out, "</body>\n");

	break;
      }

    default:
      {
	const char *tname = res->tail->ser_type->tyName;

	stream_printf(out, page_header(format("Unhandled %s", 
					      res->fp_frag)));
	stream_printf(out, BODY);
	stream_printf(out, "<center>\n");
	stream_printf(out, "Unhandled type %s\n", tname);
	stream_printf(out, "</center>\n");
	stream_printf(out, "<hr>\n");

	stream_printf(out, "</body>\n");

	break;
      }
    }
  }

  stream_printf(out, "</html>\n");
}

void
opencm_browse(WorkSpace *ws, int argc, char **argv)
{
  ObVec *v;
  const char *arg;

#if 0
  /* NOTE: This is preserved as a note to myself -- shap */
  xprintf("Content-type: text/html\n");
  xprintf("Status: 404 Object Not Found\n");
  xprintf("\n");
  return;
#endif

  if (argc == 0)
    arg = "~";
  else
    arg = argv[0];

  v = resolve(this_repos, arg, 0);

  xprintf("Content-type: text/html\n");
  xprintf("\n");
#if 0
  xprintf("<pre>");
  xprintf("Resolving: %s\n", arg);
  xprintf("confdir: %s\n", opt_ConfigDir);
  xprintf("repos: %s\n", RepositoryURI);
#endif

  if (v == 0 || vec_size(v) != 1) {
    xprintf("<html>\n");
    xprintf(page_header(arg));
    xprintf("<body BGCOLOR='#ffeedd' text='#000000' link='#0000ee'"
	    " vlink='#551a8b' alink='#ff0000'>\n");
    xprintf("<center>\n");
    xprintf("%s NOT FOUND (or ambiguous)\n", arg);
    xprintf("</center>\n");
    xprintf("<hr>\n");
    xprintf("</body>\n");
    xprintf("</html>\n");
    return;
  }

  {
    OC_bool ok = TRUE; 
    SDR_stream *htmlout = stream_createBuffer(SDR_RAW);

    TRY {
      Resolution *res = vec_fetch(v, 0, Resolution *);

      show_html(htmlout, this_repos, res, arg);
    }
    DEFAULT(ex) {
      ok = FALSE;

      xprintf("<html>");
      xprintf(page_header("EXCEPTION!"));
      xprintf("<body>");
      xprintf("<pre>");

      xprintf("Exception at %s:%d: %s\n", _catch.fname, _catch.line, 
	      _catch.str);
      xprintf("Exception: %s.\n", EXCEPTION_NAME(ex));
      xprintf("</pre>");
      xprintf("<hr>");

      {
	Buffer *buf = stream_asBuffer(htmlout);
	ocmoff_t end = buffer_length(buf);
	ocmoff_t pos = 0;

	while (pos < end) {
	  size_t chunkPos;
	  BufferChunk bc = buffer_getChunk(buf, pos, end - pos);
	  assert(bc.len <= (end - pos));

	  for (chunkPos = 0; chunkPos < bc.len; chunkPos++) {
	    int c = bc.ptr[chunkPos];

	    switch(c) {
	    case '<':
	      xprintf("&lt;");
	      break;
	    case '>':
	      xprintf("&gt;");
	      break;
	    case '&':
	      xprintf("&amp;");
	      break;
	    default:
	      putchar(c);
	      break;
	    }
	  }

	  pos += bc.len;
	}
      }

      xprintf("</body>");
      xprintf("</html>");
    }
    END_CATCH;

    if (ok) {
      Buffer *buf = stream_asBuffer(htmlout);
      ocmoff_t end = buffer_length(buf);
      ocmoff_t pos = 0;

      while (pos < end) {
	BufferChunk bc = buffer_getChunk(buf, pos, end - pos);
	assert(bc.len <= (end - pos));

	fwrite(bc.ptr, 1, bc.len, stdout);

	pos += bc.len;
      }
    }
  }

  return;
}
