#include "pathdecode.h"
#include "file.h"
#include "filetype.h"
#include "percent.h"
#include "stralloc.h"
#include "sig.h"
#include "exit.h"
#include "fmt.h"
#include "case.h"
#include "str.h"
#include "tai.h"
#include "publicfile_server.h"
#include "httpdate.h"
#include "buffer.h"
#include "error.h"
#include "byte.h"
#include "ucspi.h"
#include "subfd.h"
#include "env.h"
#include "conf.h"
#include <unistd.h>
#include <sys/socket.h>	// for shutdown()

static void log(const char *code,const char *msg)
{
  const char *x;

  x = ucspi_get_remoteip_str("0", "0", "0");
  substdio_puts(subfderr,x);
  substdio_puts(subfderr," barf ");
  substdio_puts(subfderr,code);
  substdio_puts(subfderr,msg);
  substdio_puts(subfderr,"\n");
  substdio_flush(subfderr);
}

static char strnum[FMT_ULONG];

static stralloc protocol = stralloc_static_0;
static int protocolnum = 0;
static stralloc method = stralloc_static_0;
static stralloc url = stralloc_static_0;
static stralloc host = stralloc_static_0;
static stralloc path = stralloc_static_0;
static stralloc ims = stralloc_static_0;
static int flagbody = 1;
static int flagoldprotocol = 0;
static int flaglogunsupported = 0;

static char filebuf[BUFFER_INSIZE];

static struct tai now;
static stralloc nowstr = stralloc_static_0;
static struct tai mtime;
static struct tai mtimeage;
static stralloc mtimestr = stralloc_static_0;

static void header(const char *code,const char *message)
{
  if (protocolnum == 1)
    out_puts("HTTP/1.0 ");
  else
    out_puts("HTTP/1.1 ");
  out_puts(code);
  out_puts(message);
  out_puts("\r\nServer: publicfile\r\nDate:");
  if (!httpdate(&nowstr,&now)) _exit(21);
  out_put(nowstr.s,nowstr.len);
  out_puts("\r\n");
}

static void barf(const char *code,const char *message)
{
  if (flaglogunsupported)
    log(code,message);
  if (protocolnum > 0) {
    tai_now(&now);
    header(code,message);
    out_puts("Content-Length: ");
    out_put(strnum,fmt_ulong(strnum,str_len(message) + 28));
    out_puts("\r\n");
    if (protocolnum == 2)
      out_puts("Connection: close\r\n");
    out_puts("Content-Type: text/html\r\n\r\n");
  }
  if (flagbody) {
    out_puts("<html><body>");
    out_puts(message);
    out_puts("</body></html>\r\n");
  }
  out_flush();
  if (protocolnum >= 2) {
    shutdown(1,1);
    sleep(1); /* XXX */
  }
  _exit(0);
}

static stralloc fn = stralloc_static_0;
static stralloc contenttype = stralloc_static_0;

static void get(void)
{
  unsigned long length;
  int fd;
  int r;

  host.len = byte_chr(host.s,host.len,':');
  if (!host.len) {
    if (protocolnum > 1)
      barf("400 ","HTTP/1.1 requests must include a host name");
    if (!stralloc_copys(&host,"0")) _exit(21);
  }
  case_lowerb(host.s,host.len);
  percent(&path);

  if (!stralloc_copys(&fn,"./")) _exit(21);
  if (!stralloc_cat(&fn,&host)) _exit(21);
  if (!stralloc_cats(&fn,"/")) _exit(21);
  if (!stralloc_cat(&fn,&path)) _exit(21);
  pathdecode(&fn);
  if (!stralloc_0(&fn)) _exit(21);

  fd = file_open(fn.s,&mtime,&length,1,0,0);
  if (fd == -1)
    barf("404 ",error_str(errno));

  if (protocolnum > 0) {
    tai_now(&now);
    if (!httpdate(&mtimestr,&mtime)) _exit(21);
    if ((ims.len < mtimestr.len) || byte_diff(mtimestr.s,mtimestr.len,ims.s))
      header("200 ","OK");
    else {
      header("304 ","OK");
      flagbody = 0;
    }
    if (tai_less(&mtime,&now)) {
      tai_sub(&mtimeage,&now,&mtime);
      if (tai_approx(&mtimeage) >= 60.0) {
        out_puts("Last-Modified:");
	out_put(mtimestr.s,mtimestr.len);
	out_puts("\r\n");
      }
    }
    filetype(fn.s,&contenttype);
    out_puts("Content-Type: ");
    out_put(contenttype.s,contenttype.len);
    out_puts("\r\n");
    if (protocolnum >= 2)
      out_puts("Transfer-Encoding: chunked\r\n");
    else {
      out_puts("Content-Length: ");
      out_put(strnum,fmt_ulong(strnum,length)); /* XXX: could change */
      out_puts("\r\n");
    }
    out_puts("\r\n");
  }

  if (protocolnum < 2) {
    if (flagbody)
      for (;;) {
        r = read(fd,filebuf,sizeof filebuf);
        if (r == -1) _exit(23);
        if (r == 0) break;
        out_put(filebuf,r);
      }
    out_flush();
    _exit(0);
  }

  if (flagbody)
    for (;;) {
      r = read(fd,filebuf,sizeof filebuf);
      if (r == -1) _exit(23);
      if (r == 0) { out_puts("0\r\n\r\n"); out_flush(); break; }
      out_put(strnum,fmt_xlong(strnum,(unsigned long) r));
      out_puts("\r\n");
      out_put(filebuf,r);
      out_puts("\r\n");
    }

  close(fd);
}

static stralloc field = stralloc_static_0;

void doit(void)
{
  unsigned int i, maxlen;
  int spaces;
  const char *localhost;
  int done_host;

  localhost = ucspi_get_localhost_str(NULL, NULL, NULL);
  if (!localhost)
    localhost = ucspi_get_localip_str("0", "0", "0");
  if (env_get("OLDPROTOCOLS"))
    flagoldprotocol = 1;
  if (env_get("LOGUNSUPPORTED"))
    flaglogunsupported = 1;
  maxlen = conf_path_max();

  sig_ignore(sig_pipe);

  for (;;) {
    if (!readline()) break;

    if (!line.len) continue;

    // Albeit not a GEMINI requirement, either; a sensible-enough restriction for content served from the filesystem.
    if (line.len >= maxlen)
      barf("414 ","That HTTP request is too long for this system.");

    if (!stralloc_copys(&method,"")) _exit(21);
    if (!stralloc_copys(&url,"")) _exit(21);
    if (!stralloc_copys(&host,localhost)) _exit(21);
    if (!stralloc_copys(&path,"")) _exit(21);
    if (!stralloc_copys(&protocol,"")) _exit(21);
    if (!stralloc_copys(&ims,"")) _exit(21);
    protocolnum = 2;
    done_host = 0;

    spaces = 0;
    for (i = 0;i < line.len;++i)
      if (line.s[i] == ' ') {
        if (!i || (line.s[i - 1] != ' ')) {
          ++spaces;
          if (spaces >= 3) break;
        }
      }
      else
        switch(spaces) {
          case 0:
            if (!stralloc_append(&method,line.s + i)) _exit(21);
            break;
          case 1:
            if (!stralloc_append(&url,line.s + i)) _exit(21);
            break;
          case 2:
            if (!stralloc_append(&protocol,line.s + i)) _exit(21);
            break;
        }

    if (!protocol.len)
      protocolnum = 0;
    else {
      if (!stralloc_0(&protocol)) _exit(21);
      if (!case_equals(protocol.s,"http/1.1"))
        protocolnum = 1; /* if client uses http/001.00, tough luck */
    }
    if (!flagoldprotocol && protocolnum < 2)
      barf("426 ","Please use HTTP/1.1.");

    if (!stralloc_0(&method)) _exit(21);
    flagbody = 1;
    if (str_equal(method.s,"HEAD"))
      flagbody = 0;
    else if (!str_equal(method.s,"GET"))
      barf("405 ","That method is not implemented.");

    if (case_startb(url.s,url.len,"http://")) {
      if (!stralloc_copyb(&host,url.s + 7,url.len - 7)) _exit(21);
      i = byte_chr(host.s,host.len,'/');
      if (!stralloc_copyb(&path,host.s + i,host.len - i)) _exit(21);
      host.len = i;
      done_host = 1;
    }
    else
      if (!stralloc_copy(&path,&url)) _exit(21);

    if (protocolnum > 0) {
      if (!stralloc_copys(&field,"")) _exit(21);
      for (;;) {
        readline();
        if (!line.len || ((line.s[0] != ' ') && (line.s[0] != '\t'))) {
          if (case_startb(field.s,field.len,"content-length:"))
            barf("413 ","I do not accept messages.");
          if (case_startb(field.s,field.len,"transfer-encoding:"))
            barf("413 ","I do not accept messages.");
          if (case_startb(field.s,field.len,"expect:"))
            barf("417 ","I do not accept Expect.");
          if (case_startb(field.s,field.len,"if-match:"))
            barf("412 ","I do not accept If-Match.");
          if (case_startb(field.s,field.len,"if-none-match:"))
            barf("412 ","I do not accept If-None-Match.");
          if (case_startb(field.s,field.len,"if-unmodified-since:"))
            barf("412 ","I do not accept If-Unmodified-Since.");
          if (case_startb(field.s,field.len,"host:"))
            if (!done_host) {
              host.len = 0;
              for (i = 5;i < field.len;++i)
                if (field.s[i] != ' ')
                  if (field.s[i] != '\t')
                    if (!stralloc_append(&host,&field.s[i])) _exit(21);
              done_host = 1;
            }
          if (case_startb(field.s,field.len,"if-modified-since:"))
	    if (!stralloc_copyb(&ims,field.s + 18,field.len - 18)) _exit(21);
          field.len = 0;
        }
        if (!line.len) break;
        if (!stralloc_cat(&field,&line)) _exit(21);
      }
    }

    // This rule borrowed from GEMINI; a sensible-enough restriction for anonymous service.
    i = byte_chr(host.s,host.len,'@');
    if (i != host.len)
      barf("400 ","HTTP requests may not have a user part.");
    // This rule borrowed from GEMINI, also.
    if (!path.len)
      if (!stralloc_cats(&path,"/")) _exit(21);
    // This rule borrowed from GEMINI, also.
    i = byte_chr(path.s,path.len,'#');
    if (i != path.len)
      barf("400 ","HTTP requests may not have a fragment part.");
    // By analogy, albeit not a GEMINI requirement, this rule; a sensible-enough restriction for static content.
    i = byte_chr(path.s,path.len,'?');
    if (i != path.len)
      barf("400 ","HTTP requests may not have a query part.");

    // Just strip the port.
    i = byte_chr(host.s,host.len,':');
    host.len = i;

    if (!path.len || (path.s[path.len - 1] == '/'))
      if (!stralloc_cats(&path,"index.html")) _exit(21);

    get();
  }
}
