httpd_req.c

/*
 *  Copyright (C) 2004, 2005, 2006  James Antill
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *  email: james@and.org
 */
/* main HTTP server request processing */

#define EX_UTILS_NO_FUNCS 1
#include "ex_utils.h"

#include "mk.h"

#include "vlg.h"

#define HTTPD_HAVE_GLOBAL_OPTS 1
#include "httpd.h"
#include "httpd_policy.h"

#include <syslog.h>

#if ! HTTPD_CONF_USE_MIME_XATTR
# define getxattr(w, x, y, z) (errno = ENOSYS, -1)
#else
# include <sys/xattr.h>
#endif

#if ! COMPILE_DEBUG
# define HTTP_CONF_MMAP_LIMIT_MIN (16 * 1024) /* a couple of pages */
# define HTTP_CONF_SAFE_PRINT_REQ TRUE
#else
# define HTTP_CONF_MMAP_LIMIT_MIN 8 /* debug... */
# define HTTP_CONF_SAFE_PRINT_REQ FALSE
#endif
#define HTTP_CONF_MMAP_LIMIT_MAX (50 * 1024 * 1024)

#define HTTPD_CONF_ZIP_LIMIT_MIN 8

#define CLEN COMPILE_STRLEN

/* is the cstr a prefix of the vstr */
#define VPREFIX(vstr, p, l, cstr)                                       \
    (((l) >= CLEN(cstr)) &&                                             \
     vstr_cmp_buf_eq(vstr, p, CLEN(cstr), cstr, CLEN(cstr)))
/* is the cstr a suffix of the vstr */
#define VSUFFIX(vstr, p, l, cstr)                                       \
    (((l) >= CLEN(cstr)) &&                                             \
     vstr_cmp_eod_buf_eq(vstr, p, l, cstr, CLEN(cstr)))

/* is the cstr a prefix of the vstr, no case */
#define VIPREFIX(vstr, p, l, cstr)                                      \
    (((l) >= CLEN(cstr)) &&                                             \
     vstr_cmp_case_buf_eq(vstr, p, CLEN(cstr), cstr, CLEN(cstr)))

/* for simplicity */
#define VEQ(vstr, p, l, cstr)  vstr_cmp_cstr_eq(vstr, p, l, cstr)
#define VIEQ(vstr, p, l, cstr) vstr_cmp_case_cstr_eq(vstr, p, l, cstr)

#define HTTP__HDR_SET(req, h, p, l) do {               \
      (req)-> http_hdrs -> hdr_ ## h ->pos = (p);          \
      (req)-> http_hdrs -> hdr_ ## h ->len = (l);          \
    } while (FALSE)
#define HTTP__HDR_MULTI_SET(req, h, p, l) do {         \
      (req)-> http_hdrs -> multi -> hdr_ ## h ->pos = (p); \
      (req)-> http_hdrs -> multi -> hdr_ ## h ->len = (l); \
    } while (FALSE)

#define HTTP__XTRA_HDR_INIT(x) do {             \
      req-> x ## _vs1 = NULL;                   \
      req-> x ## _pos = 0;                      \
      req-> x ## _len = 0;                      \
    } while (FALSE)

static Vlg *vlg = NULL;

void httpd_req_init(Vlg *passed_vlg)
{
  ASSERT(passed_vlg && !vlg);
  vlg = passed_vlg;
}

void httpd_req_exit(void)
{
  ASSERT(vlg);
  vlg = NULL;
}

Httpd_req_data *http_req_make(struct Con *con)
{
  static Httpd_req_data real_req[1];
  Httpd_req_data *req = real_req;
  const Httpd_policy_opts *policy = NULL;
  
  ASSERT(!req->using_req);

  if (!req->done_once)
  {
    Vstr_conf *conf = NULL;

    if (con)
      conf = con->evnt->io_w->conf;
      
    if (!(req->fname                            = vstr_make_base(conf)) ||
        !(req->http_hdrs->multi->combiner_store = vstr_make_base(conf)) ||
        !(req->sects                            = vstr_sects_make(8)) ||
        !(req->tag                              = vstr_make_base(conf)))
      return (NULL);
    
    req->f_mmap       = NULL;
    req->xtra_content = NULL;
  }

  http_parse_clear_hdrs(req);
  
  req->http_hdrs->multi->comb = con ? con->evnt->io_r : NULL;

  vstr_del(req->fname, 1, req->fname->len);

  req->now = evnt_sc_time();
  
  req->len = 0;

  req->path_pos = 0;
  req->path_len = 0;

  req->error_code = 0;
  req->error_line = "";
  req->error_msg  = "";
  req->error_xmsg = "";
  req->error_len  = 0;

  req->fs_len     = 0;

  req->f_stat_st_size = 0;

  req->sects->num = 0;
  /* f_stat */
  if (con)
    req->orig_io_w_len = con->evnt->io_w->len;

  /* NOTE: These should probably be allocated at init time, depending on the
   * option flags given */
  ASSERT(!req->f_mmap || !req->f_mmap->len);
  if (req->f_mmap)
    vstr_del(req->f_mmap, 1, req->f_mmap->len);
  ASSERT(!req->xtra_content || !req->xtra_content->len);
  if (req->xtra_content)
    vstr_del(req->xtra_content, 1, req->xtra_content->len);
  
  req->vhost_prefix_len = 0;
  
  req->sects->malloc_bad = FALSE;

  req->parsed_content_encoding   = FALSE;
  req->content_encoding_identity = TRUE;
  req->content_encoding_gzip     = FALSE;
  req->content_encoding_bzip2    = FALSE;
  req->content_encoding_xgzip    = FALSE;

  req->user_return_error_code = FALSE;

  req->vary_star = con ? con->vary_star : FALSE;
  req->vary_a    = FALSE;
  req->vary_ac   = FALSE;
  req->vary_ae   = FALSE;
  req->vary_al   = FALSE;
  req->vary_rf   = FALSE;
  req->vary_ua   = FALSE;
  req->vary_ims  = FALSE;
  req->vary_ius  = FALSE;
  req->vary_ir   = FALSE;
  req->vary_im   = FALSE;
  req->vary_inm  = FALSE;
  req->vary_xm   = FALSE;

  req->direct_uri         = FALSE;
  req->direct_filename    = FALSE;
  req->skip_document_root = FALSE;
  
  req->ver_0_9    = FALSE;
  req->ver_1_1    = FALSE;
  req->ver_1_x    = FALSE;
  req->head_op    = FALSE;

  req->chked_encoded_path = FALSE;
  
  req->neg_content_type_done = FALSE;
  req->neg_content_lang_done = FALSE;
      
  req->conf_secure_dirs   = FALSE;
  req->conf_friendly_file = FALSE;
  req->conf_friendly_dirs = FALSE;
  
  req->done_once  = TRUE;
  req->using_req  = TRUE;

  req->malloc_bad = FALSE;

  if (con && !vstr_sub_vstr(req->tag, 1, req->tag->len,
                            con->tag, 1, con->tag->len, VSTR_TYPE_SUB_BUF_REF))
    return (NULL);
  
  if (!con)
    req->policy = NULL;
  else
  {
    policy = con->policy;
    httpd_policy_change_req(con, req, policy);
  }
  
  return (req);
}

void http_req_free(Httpd_req_data *req)
{
  if (!req) /* for if/when move to malloc/free */
    return;

  ASSERT(req->done_once && req->using_req);

  /* we do vstr deletes here to return the nodes back to the pool */
  vstr_del(req->fname, 1, req->fname->len);
  ASSERT(!req->http_hdrs->multi->combiner_store->len);
  if (req->f_mmap)
    vstr_del(req->f_mmap, 1, req->f_mmap->len);

  req->http_hdrs->multi->comb = NULL;

  req->using_req = FALSE;
}

void http_req_exit(void)
{
  Httpd_req_data *req = http_req_make(NULL);
  struct Http_hdrs__multi *tmp = NULL;
  
  if (!req)
    return;

  tmp = req->http_hdrs->multi;
  
  vstr_free_base(req->fname);          req->fname          = NULL;
  vstr_free_base(tmp->combiner_store); tmp->combiner_store = NULL;
  vstr_free_base(req->f_mmap);         req->f_mmap         = NULL;
  vstr_free_base(req->xtra_content);   req->xtra_content   = NULL;
  vstr_sects_free(req->sects);         req->sects          = NULL;
  vstr_free_base(req->tag);            req->tag            = NULL;
  
  req->done_once = FALSE;
  req->using_req = FALSE;
}


void httpd_req_absolute_uri(struct Con *con, Httpd_req_data *req,
                            Vstr_base *lfn, size_t pos, size_t len)
{
  Vstr_base *data = con->evnt->io_r;
  size_t apos = pos - 1;
  size_t alen = lfn->len;
  int has_schema   = TRUE;
  int has_abs_path = TRUE;
  int has_data     = TRUE;
  unsigned int prev_num = 0;
  
  if (!VPREFIX(lfn, pos, len, "http://"))
  {
    has_schema = FALSE;
    if (!VPREFIX(lfn, pos, len, "/")) /* relative pathname */
    {
      if (VPREFIX(lfn, pos, len, "./"))
      {
        has_data = TRUE;
        vstr_del(lfn, pos, CLEN("./")); len -= CLEN("./");
        alen = lfn->len;
      }
      else
      {
        while (VPREFIX(lfn, pos, len, "../"))
        {
          ++prev_num;
          vstr_del(lfn, pos, CLEN("../")); len -= CLEN("../");
        }
        if (prev_num)
          alen = lfn->len;
        else
          has_data = !!lfn->len;
      }
      
      has_abs_path = FALSE;
    }
  }

  if (!has_schema)
  {
    vstr_add_cstr_buf(lfn, apos, "http://");
    apos += lfn->len - alen;
    alen = lfn->len;
    httpd_sc_add_hostname(con, req, lfn, apos);
    apos += lfn->len - alen;
  }
    
  if (!has_abs_path)
  {
    size_t path_len = req->path_len;

    if (has_data || prev_num)
    {
      path_len -= vstr_cspn_cstr_chrs_rev(data, req->path_pos, path_len, "/");
      
      while (path_len && prev_num--)
      {
        path_len -= vstr_spn_cstr_chrs_rev(data,  req->path_pos, path_len, "/");
        path_len -= vstr_cspn_cstr_chrs_rev(data, req->path_pos, path_len, "/");
      }
      if (!path_len) path_len = 1; /* make sure there is a / at the end */
    }

    vstr_add_vstr(lfn, apos, data, req->path_pos, path_len, VSTR_TYPE_ADD_DEF);
  }
}

/* doing http://www.example.com/foo/bar where bar is a dir is bad
   because all relative links will be relative to foo, not bar.
   Also note that location must be "http://www.example.com/foo/bar/" or maybe
   "http:/foo/bar/" (but we don't use the later anymore)
*/
int http_req_chk_dir(struct Con *con, Httpd_req_data *req, const char *xmsg)
{
  Vstr_base *fname = req->fname;
  
  /* fname == what was just passed to open() */
  ASSERT(fname->len);

  if (req->policy->use_secure_dirs && !req->conf_secure_dirs)
  { /* check if file exists before redirecting without leaking info. */
    const char *fname_cstr = NULL;
    struct stat64 d_stat[1];
  
    vstr_add_cstr_buf(fname, fname->len, "/");
    HTTPD_APP_REF_ALLVSTR(fname, req->policy->dir_filename);
    
    fname_cstr = vstr_export_cstr_ptr(fname, 1, fname->len);
    if (fname->conf->malloc_bad)
      return (http_fin_errmem_req(con, req));

    if ((stat64(fname_cstr, d_stat) == -1) || !S_ISREG(d_stat->st_mode))
      HTTPD_ERR_MSG_RET(req, 404, xmsg, http_fin_err_req(con, req));
  }
  
  vstr_del(fname, 1, fname->len);
  httpd_req_absolute_uri(con, req, fname, 1, fname->len);
  
  /* we got:       http://foo/bar/
   * and so tried: http://foo/bar/index.html
   *
   * but foo/bar/index.html is a directory (fun), so redirect to:
   *               http://foo/bar/index.html/
   */
  if (fname->len && (vstr_export_chr(fname, fname->len) == '/'))
    HTTPD_APP_REF_ALLVSTR(fname, req->policy->dir_filename);
  
  vstr_add_cstr_buf(fname, fname->len, "/");
  
  HTTPD_REDIR_MSG(req, 301, "dir -> filename");
  
  if (fname->conf->malloc_bad)
    return (http_fin_errmem_req(con, req));
  
  return (http_fin_err_req(con, req));
}

/* doing http://www.example.com/foo/bar/ when url is really
   http://www.example.com/foo/bar is a very simple mistake, so we almost
   certainly don't want a 404 */
int http_req_chk_file(struct Con *con, Httpd_req_data *req, const char *xmsg)
{
  Vstr_base *fname = req->fname;
  size_t len = 0;
  
  /* fname == what was just passed to open() */
  ASSERT(fname->len);

  if (!req->policy->use_friendly_dirs)
    HTTPD_ERR_MSG_RET(req, 404, xmsg, http_fin_err_req(con, req));
  else if (!req->conf_friendly_dirs)
  { /* check if file exists before redirecting without leaking info. */
    const char *fname_cstr = NULL;
    struct stat64 d_stat[1];
    len = vstr_cspn_cstr_chrs_rev(fname, 1, fname->len, "/") + 1;

    /* must be a filename, can't be toplevel */
    if ((len <= 1) || (len >= (fname->len - req->vhost_prefix_len)))
      HTTPD_ERR_MSG_RET(req, 404, xmsg, http_fin_err_req(con, req));

    vstr_sc_reduce(fname, 1, fname->len, len);
    
    fname_cstr = vstr_export_cstr_ptr(fname, 1, fname->len);
    if (fname->conf->malloc_bad)
      return (http_fin_errmem_req(con, req));    
    if ((stat64(fname_cstr, d_stat) == -1) && !S_ISREG(d_stat->st_mode))
      HTTPD_ERR_MSG_RET(req, 404, xmsg, http_fin_err_req(con, req));
  }
  
  vstr_sub_cstr_ptr(fname, 1, fname->len, "./");
  httpd_req_absolute_uri(con, req, fname, 1, fname->len);
  assert(VSUFFIX(fname, 1, fname->len, "/"));
  vstr_sc_reduce(fname, 1, fname->len, strlen("/"));

  HTTPD_REDIR_MSG(req, 301, "filename -> dir");
  
  if (fname->conf->malloc_bad)
    return (http_fin_errmem_req(con, req));
  
  return (http_fin_err_req(con, req));
}

/* FIXME: maybe should be in the parse section...? */
void http_req_split_method(struct Con *con, struct Httpd_req_data *req)
{
  Vstr_base *s1 = con->evnt->io_r;
  size_t pos = 1;
  size_t len = req->len;
  size_t el = 0;
  size_t skip_len = 0;
  unsigned int orig_num = req->sects->num;
  
  el = vstr_srch_cstr_buf_fwd(s1, pos, len, HTTP_EOL);
  ASSERT(el >= pos);
  len = el - pos; /* only parse the first line */

  /* split request */
  if (!(el = vstr_srch_cstr_chrs_fwd(s1, pos, len, HTTP_LWS)))
    return;
  vstr_sects_add(req->sects, pos, el - pos);
  len -= (el - pos); pos += (el - pos);

  /* just skip whitespace on method call... */
  if ((skip_len = vstr_spn_cstr_chrs_fwd(s1, pos, len, HTTP_LWS)))
  { len -= skip_len; pos += skip_len; }

  if (!len)
    goto req_line_parse_err;
  
  if (!(el = vstr_srch_cstr_chrs_fwd(s1, pos, len, HTTP_LWS)))
  {
    vstr_sects_add(req->sects, pos, len);
    len = 0;
  }
  else
  {
    vstr_sects_add(req->sects, pos, el - pos);
    len -= (el - pos); pos += (el - pos);
    
    /* just skip whitespace on method call... */
    if ((skip_len = vstr_spn_cstr_chrs_fwd(s1, pos, len, HTTP_LWS)))
    { len -= skip_len; pos += skip_len; }
  }

  if (len)
    vstr_sects_add(req->sects, pos, len);
  else if (!req->policy->allow_http_0_9)
    return; /* we keep it, for logging */
  else
    req->ver_0_9 = TRUE;

  if (req->sects->malloc_bad)
    goto req_line_parse_err;
  
  return;
  
 req_line_parse_err:
  req->sects->num = orig_num;
}

void http_req_split_hdrs(struct Con *con, struct Httpd_req_data *req)
{
  Vstr_base *s1 = con->evnt->io_r;
  size_t pos = 1;
  size_t len = req->len;
  size_t el = 0;
  size_t hpos = 0;

  ASSERT(req->sects->num >= 3);  
    
  /* skip first line */
  el = (VSTR_SECTS_NUM(req->sects, req->sects->num)->pos +
        VSTR_SECTS_NUM(req->sects, req->sects->num)->len);
  
  assert(VEQ(s1, el, CLEN(HTTP_EOL), HTTP_EOL));
  len -= (el - pos) + CLEN(HTTP_EOL); pos += (el - pos) + CLEN(HTTP_EOL);
  
  if (VPREFIX(s1, pos, len, HTTP_EOL))
    return; /* end of headers */

  ASSERT(vstr_srch_cstr_buf_fwd(s1, pos, len, HTTP_END_OF_REQUEST));
  /* split headers */
  hpos = pos;
  while ((el = vstr_srch_cstr_buf_fwd(s1, pos, len, HTTP_EOL)) != pos)
  {
    char chr = 0;
    
    len -= (el - pos) + CLEN(HTTP_EOL); pos += (el - pos) + CLEN(HTTP_EOL);

    chr = vstr_export_chr(s1, pos);
    if (chr == ' ' || chr == '\t') /* header continues to next line */
      continue;

    vstr_sects_add(req->sects, hpos, el - hpos);
    
    hpos = pos;
  }
}

/* try to set the content-type,
 * . first if it's manually set leave it,
 * . next try looking it up in the xattr for the file.
 * . next do a real "lookup" based on the filename
 * NOTE: If this lookup "fails" it still returns
 * the default content-type. So we just have to determine if we want to use
 * it or not. Can also return "content-types" like /404/ which returns a 404
 * error for the request */
int http_req_content_type(Httpd_req_data *req)
{
  const Vstr_base *vs1 = NULL;
  size_t     pos = 0;
  size_t     len = 0;

  if (req->content_type_vs1) /* manually set */
    return (TRUE);

  if (req->policy->use_mime_xattr)
  { /* lookup mime/type in the xattr of the filename -- this is racey
     * but we want to parse the accept line before, open(), so we can't use
     * fgetxattr() anyway. */
    static const char key[] = "user.mime_type";
    char buf[1024]; /* guess */
    ssize_t ret = -1;
    const char *fname_cstr = NULL;
    
    if (!req->xtra_content && !(req->xtra_content = vstr_make_base(NULL)))
      return (FALSE);

    fname_cstr = vstr_export_cstr_ptr(req->fname, 1, req->fname->len);
    ASSERT(fname_cstr); /* must have been done before call */

    /* don't use lgetxattr() as it does nothing on Linux */
    if ((ret = getxattr(fname_cstr, key, buf, sizeof(buf))) != -1)
    {
      pos = req->xtra_content->len + 1;
      len = ret;
      if (!vstr_add_buf(req->xtra_content, req->xtra_content->len, buf, len))
        return (FALSE);

      req->content_type_vs1 = req->xtra_content;
      req->content_type_pos = pos;
      req->content_type_len = len;
      
      HTTP_REQ__X_CONTENT_HDR_CHK(content_type);
      
      return (TRUE);
    }
    else if (errno == ENOSYS)
      httpd_disable_getxattr();
    else if ((errno == EOPNOTSUPP) || (errno == EPERM))
    { /* time limit the warn messages... */
      static time_t last = -1;
      time_t now = evnt_sc_time(); /* req->now might be "old" */

      if ((last == -1) || (difftime(last, now) > (10 * 60)))
      {
        vlg_warn(vlg, "getxattr($<vstr.all:%p>, %s): %m\n", req->fname, key);
        last = now;
      }
    }
  }
  
  mime_types_match(req->policy->mime_types,
                   req->fname, 1, req->fname->len, &vs1, &pos, &len);
  if (!len)
  {
    req->parse_accept = FALSE;
    return (TRUE);
  }
  
  if ((vstr_export_chr(vs1, pos) == '/') && (len > 2) &&
      (vstr_export_chr(vs1, vstr_sc_poslast(pos, len)) == '/'))
  {
    size_t num_len = 1;
    static const char xmsg[] = "Mime/Type";
    
    len -= 2;
    ++pos;
    req->user_return_error_code = TRUE;
    req->direct_filename = FALSE;
    switch (vstr_parse_uint(vs1, pos, len, 0, &num_len, NULL))
    {
      case 400: if (num_len == len) HTTPD_ERR_MSG_RET(req, 400, xmsg, FALSE);
      case 403: if (num_len == len) HTTPD_ERR_MSG_RET(req, 403, xmsg, FALSE);
      case 404: if (num_len == len) HTTPD_ERR_MSG_RET(req, 404, xmsg, FALSE);
      case 410: if (num_len == len) HTTPD_ERR_MSG_RET(req, 410, xmsg, FALSE);
      case 500: if (num_len == len) HTTPD_ERR_MSG_RET(req, 500, xmsg, FALSE);
      case 503: if (num_len == len) HTTPD_ERR_MSG_RET(req, 503, xmsg, FALSE);
        
      default: /* just ignore any other content */
        req->user_return_error_code = FALSE;
        return (TRUE);
    }
  }

  req->content_type_vs1 = vs1;
  req->content_type_pos = pos;
  req->content_type_len = len;

  return (TRUE);
}

static void httpd__req_etag_hex_num(Vstr_base *vs1, uintmax_t val, int more)
{
  char buf[(sizeof(uintmax_t) * CHAR_BIT) + 1];
  size_t len = 0;

  len = vstr_sc_conv_num_uintmax(buf, sizeof(buf), val, "0123456789abcdef", 16);
  vstr_add_buf(vs1, vs1->len, buf, len);
  if (more)
    vstr_add_cstr_buf(vs1, vs1->len, "-");
}

static int httpd__req_etag_auto(struct Httpd_req_data *req)
{
  size_t xpos = 0;
  Vstr_base *vs1 = NULL;
  
  ASSERT(!req->etag_vs1 && req->policy->etag_auto_type);
  
  if (!(xpos = http_req_xtra_content(req, NULL, 0, &req->etag_len)))
    return (FALSE);
  vs1 = req->xtra_content;

  /* If it's too soon, make it weak */
  if (difftime(req->now, req->f_stat->st_mtime) <= 1)
    vstr_add_cstr_buf(vs1, vs1->len, "W/");
  
  vstr_add_cstr_buf(vs1, vs1->len, "\"");
  switch (req->policy->etag_auto_type)
  {
    case HTTPD_ETAG_TYPE_AUTO_DISM:
      httpd__req_etag_hex_num(vs1, req->f_stat->st_dev,   TRUE);
      /* FALL THROUGH */
    case HTTPD_ETAG_TYPE_AUTO_ISM:
      httpd__req_etag_hex_num(vs1, req->f_stat->st_ino,   TRUE);
      /* FALL THROUGH */      
    case HTTPD_ETAG_TYPE_AUTO_SM:
      httpd__req_etag_hex_num(vs1, req->f_stat->st_size,  TRUE);
      httpd__req_etag_hex_num(vs1, req->f_stat->st_mtime, FALSE);
      /* Use st_mtime.tv_nsec ? */
      
      ASSERT_NO_SWITCH_DEF();
  }
  vstr_add_cstr_buf(vs1, vs1->len, "\"");

  req->etag_vs1 = vs1;
  req->etag_pos = xpos;
  req->etag_len = (req->xtra_content->len - xpos) + 1;

  return (!vs1->conf->malloc_bad);
}

#define HTTPD__HD_EQ(x)                                 \
    VEQ(hdrs, h_ ## x ->pos,  h_ ## x ->len,  date)

/* gets here if the GET/HEAD response is ok, we test for caching etc. using the
 * if-* headers */
/* FALSE = 412 Precondition Failed */
static int http_response_ok(struct Con *con, struct Httpd_req_data *req,
                            unsigned int *http_ret_code,
                            const char ** http_ret_line)
{
  const Vstr_base *hdrs = con->evnt->io_r;
  time_t mtime = req->f_stat->st_mtime;
  Vstr_sect_node *h_ims  = req->http_hdrs->hdr_if_modified_since;
  Vstr_sect_node *h_ir   = req->http_hdrs->hdr_if_range;
  Vstr_sect_node *h_iums = req->http_hdrs->hdr_if_unmodified_since;
  Vstr_sect_node *h_r    = req->http_hdrs->hdr_range;
  Vstr_base *comb = req->http_hdrs->multi->comb;
  Vstr_sect_node *h_im   = req->http_hdrs->multi->hdr_if_match;
  Vstr_sect_node *h_inm  = req->http_hdrs->multi->hdr_if_none_match;
  int h_ir_tst      = FALSE;
  int h_iums_tst    = FALSE;
  int req_if_range  = FALSE;
  int cached_output = FALSE;
  const char *date = NULL;
  
  if (HTTPD_VER_GE_1_1(req) && h_iums->pos)
    h_iums_tst = TRUE;

  if (HTTPD_VER_GE_1_1(req) && h_ir->pos && h_r->pos)
    h_ir_tst = TRUE;
  
  /* assumes time doesn't go backwards ... From rfc2616:
   *
   * Note: When handling an If-Modified-Since header field, some
   * servers will use an exact date comparison function, rather than a
   * less-than function, for deciding whether to send a 304 (Not
   * Modified) response. To get best results when sending an If-
   * Modified-Since header field for cache validation, clients are
   * advised to use the exact date string received in a previous Last-
   * Modified header field whenever possible.
   */
  if (difftime(req->now, mtime) > 0)
  { /* only allow checking if mtime is in the past */
    Date_store *ds = httpd_opts->date;

    date = date_rfc1123(ds, mtime);
    if (h_ims->pos && !cached_output && HTTPD__HD_EQ(ims))
      cached_output = TRUE;
    if (h_iums_tst &&                   HTTPD__HD_EQ(iums))
      return (FALSE);
    if (h_ir_tst   && !req_if_range  && HTTPD__HD_EQ(ir))
      req_if_range = TRUE;
  
    date = date_rfc850(ds, mtime);
    if (h_ims->pos && !cached_output && HTTPD__HD_EQ(ims))
      cached_output = TRUE;
    if (h_iums_tst &&                   HTTPD__HD_EQ(iums))
      return (FALSE);
    if (h_ir_tst   && !req_if_range  && HTTPD__HD_EQ(ir))
      req_if_range = TRUE;
  
    date = date_asctime(ds, mtime);
    if (h_ims->pos && !cached_output && HTTPD__HD_EQ(ims))
      cached_output = TRUE;
    if (h_iums_tst &&                   HTTPD__HD_EQ(iums))
      return (FALSE);
    if (h_ir_tst   && !req_if_range  && HTTPD__HD_EQ(ir))
      req_if_range = TRUE;
  }

  if (HTTPD_VER_GE_1_1(req))
  {
    const Vstr_base *vs1 = NULL;
    size_t pos = 0;
    size_t len = 0;

    if (req->policy->etag_auto_type && !req->etag_vs1)
    {
      if (!httpd__req_etag_auto(req))
      {
        con->evnt->io_w->conf->malloc_bad = TRUE;
        return (TRUE); /* dealt with in http_req_op_get, can't continue */
      }

      req->etag_time = mtime;
    }
    if (req->etag_vs1 && (req->etag_time >= mtime))
    {
      vs1 = req->etag_vs1;
      pos = req->etag_pos;
      len = req->etag_len;
    }

    if (h_ir_tst   && !req_if_range &&
        httpd_match_etags(req,
                          hdrs, h_ir->pos, h_ir->len, vs1, pos, len, FALSE))
      req_if_range = TRUE;
  
    if (h_ir_tst && !req_if_range)
      h_r->pos = 0;

    /* #13.3.3 says: don't trust weak for "complex" queries, ie. byteranges */
    if (h_inm->pos && (VEQ(hdrs, h_inm->pos, h_inm->len, "*") ||
                       httpd_match_etags(req, comb, h_inm->pos, h_inm->len,
                                         vs1, pos, len, !h_r->pos)))
      cached_output = TRUE; /* Note: should return 412 for POST/PUT */

    /* #14.24 says: must use strong comparison, and also...
       If the request would, without the If-Match header field, result in
       anything other than a 2xx or 412 status, then the If-Match header
       MUST be ignored. */
    if (!cached_output &&
        h_im->pos  && !(VEQ(hdrs, h_im->pos, h_im->len, "*") ||
                        httpd_match_etags(req, comb, h_im->pos, h_im->len,
                                          vs1, pos, len, FALSE)))
      return (FALSE);
  }
  else if (h_ir_tst && !req_if_range)
    h_r->pos = 0;
  
  if (cached_output)
  {
    req->head_op = TRUE;
    *http_ret_code = 304;
    *http_ret_line = "Not Modified";
  }
  else if (h_r->pos)
  {
    *http_ret_code = 206;
    *http_ret_line = "Partial content";
  }

  return (TRUE);
}

int http_req_1_x(struct Con *con, Httpd_req_data *req,
                 unsigned int *http_ret_code,
                 const char **http_ret_line)
{
  Vstr_base *out = con->evnt->io_w;
  Vstr_sect_node *h_r = req->http_hdrs->hdr_range;
  time_t mtime = -1;
  
  if (HTTPD_VER_GE_1_1(req) && req->http_hdrs->hdr_expect->len)
    /* I'm pretty sure we can ignore 100-continue, as no request will
     * have a body */
    HTTPD_ERR_RET(req, 417, FALSE);
          
  httpd_parse_sc_try_fd_encoding(con, req, req->f_stat, &req->f_stat_st_size,
                                 req->fname);
  
  if (req->policy->use_err_406 &&
      !req->content_encoding_identity &&
      !req->content_encoding_bzip2 && !req->content_encoding_gzip)
    HTTPD_ERR_MSG_RET(req, 406, "Encoding", FALSE);

  if (h_r->pos)
  {
    int ret_code = 0;

    if (!(req->policy->use_range &&
	  (HTTPD_VER_GE_1_1(req) || req->policy->use_range_1_0)))
      h_r->pos = 0;
    else if (!(ret_code = http_parse_range(con, req)))
      h_r->pos = 0;
    ASSERT(!ret_code || (ret_code == 200) || (ret_code == 416));
    if (ret_code == 416)
    {
      if (!req->http_hdrs->hdr_if_range->pos)
        HTTPD_ERR_RET(req, 416, FALSE);
      h_r->pos = 0;
    }
  }
  
  if (!http_response_ok(con, req, http_ret_code, http_ret_line))
    HTTPD_ERR_RET(req, 412, FALSE);

  if (!h_r->pos)
    httpd_serv_file_sects_none(con, req, req->f_stat_st_size);
  
  httpd_serv_call_file_init(con, req, http_ret_code, http_ret_line);

  ASSERT(con->fs && (con->fs_off < con->fs_num) && (con->fs_num <= con->fs_sz));
  ASSERT(!con->fs_off);

  mtime = req->f_stat->st_mtime;
  http_app_def_hdrs(con, req, *http_ret_code, *http_ret_line,
                    mtime, NULL, TRUE, http_serv_file_len(con, req));
  if (h_r->pos && !con->use_mpbr)
    http_app_hdr_fmt(out, "Content-Range", "%s %ju-%ju/%ju", "bytes",
                     con->fs->off, con->fs->off + (con->fs->len - 1),
                     (uintmax_t)req->f_stat_st_size);
  if (req->content_location_vs1)
    http_app_hdr_vstr_def(out, "Content-Location",
                          HTTP__XTRA_HDR_PARAMS(req, content_location));
  http_app_hdrs_url(con, req);
  http_app_hdrs_file(con, req);  
  
  http_app_end_hdrs(out);

  if (!req->head_op && h_r->pos && con->use_mpbr)
  {
    con->mpbr_fs_len = req->f_stat_st_size;
    http_app_hdrs_mpbr(con, con->fs);
  }
  
  return (TRUE);
}

static int httpd_req_add_vhost(struct Con *con, struct Httpd_req_data *req)
{
  Vstr_base *data = con->evnt->io_r;
  Vstr_base *fname = req->fname;
  Vstr_sect_node *h_h = req->http_hdrs->hdr_host;
  size_t h_h_pos = h_h->pos;
  size_t h_h_len = h_h->len;
  size_t orig_len = 0;
  size_t dots = 0;  
  
  /* a lot of clients will pass example.com. for example.com ... fix them
   * this can happen more than one time Eg. "wget www.and.org.." */
  if (h_h_len)
  {
    dots = vstr_spn_cstr_chrs_rev(data, h_h_pos, h_h_len, ".");
    if (dots == h_h_len)
      h_h_len = 1; /* give 400s to hostname "." */
    else
    {
      ASSERT(dots < h_h_len);
      
      h_h_len -= dots;
  
      if (req->policy->use_canonize_host)
      {
        if (VIPREFIX(data, h_h_pos, h_h_len, "www."))
        { h_h_len -= CLEN("www."); h_h_pos += CLEN("www."); }
      }
    }
    
    h_h->pos = h_h_pos;
    h_h->len = h_h_len;
  }
  
  if (!req->policy->use_vhosts_name)
    return (TRUE);

  orig_len = fname->len;
  if (!http_serv_add_vhost(con, req, fname, 0, TRUE))
    return (FALSE);

  vstr_add_cstr_ptr(fname, 0, "/");

  req->vhost_prefix_len = (fname->len - orig_len);

  return (TRUE);
}

/* Decode url-path,
   check url-path for a bunch of problems,
   if vhosts is on add vhost prefix,
   Note we don't do dir_filename additions yet */
int http_req_make_path(struct Con *con, Httpd_req_data *req)
{
  Vstr_base *data = con->evnt->io_r;
  Vstr_base *fname = req->fname;
  
  ASSERT(!fname->len);

  assert(VPREFIX(data, req->path_pos, req->path_len, "/") ||
         VEQ(data, req->path_pos, req->path_len, "*"));

  if (req->chk_encoded_slash &&
      vstr_srch_case_cstr_buf_fwd(data, req->path_pos, req->path_len, "%2f"))
    HTTPD_ERR_MSG_RET(req, 403, "Path has encoded /", FALSE);
  if (req->chk_encoded_dot &&
      vstr_srch_case_cstr_buf_fwd(data, req->path_pos, req->path_len, "%2e"))
    HTTPD_ERR_MSG_RET(req, 403, "Path has encoded .", FALSE);
  req->chked_encoded_path = TRUE;

  vstr_add_vstr(fname, 0,
                data, req->path_pos, req->path_len, VSTR_TYPE_ADD_BUF_PTR);
  vstr_conv_decode_uri(fname, 1, fname->len);

  if (fname->conf->malloc_bad) /* dealt with as errmem_req() later */
    return (TRUE);

  /* NOTE: need to split function here so we can more efficently alter the
   * path. */
  if (!httpd_req_add_vhost(con, req))
    return (FALSE);
  
  /* check posix path ... including hostname, for NIL and path escapes */
  if (vstr_srch_chr_fwd(fname, 1, fname->len, 0))
    HTTPD_ERR_MSG_RET(req, 403, "Path has NIL", FALSE);

  /* web servers don't have relative paths, so /./ and /../ aren't "special" */
  if (vstr_srch_cstr_buf_fwd(fname, 1, fname->len, "/../") ||
      VSUFFIX(req->fname, 1, req->fname->len, "/.."))
    HTTPD_ERR_MSG_RET(req, 403, "Path has /../", FALSE);
  if (req->policy->chk_dot_dir &&
      (vstr_srch_cstr_buf_fwd(fname, 1, fname->len, "/./") ||
       VSUFFIX(req->fname, 1, req->fname->len, "/.")))
    HTTPD_ERR_MSG_RET(req, 403, "Path has /./", FALSE);

  ASSERT(fname->len);
  assert(VPREFIX(fname, 1, fname->len, "/") ||
         VEQ(fname, 1, fname->len, "*") ||
         fname->conf->malloc_bad);

  if (fname->conf->malloc_bad)
    return (TRUE);
  
  return (TRUE);
}

size_t http_req_xtra_content(Httpd_req_data *req, const Vstr_base *s1,
                             size_t pos, size_t *len)
{
  Vstr_base *xs1 = req->xtra_content;
  size_t ret = 0;
  
  ASSERT(len);
  ASSERT((s1 == xs1) || !s1);
  ASSERT(s1 || !*len);
  
  if (!req->xtra_content && !(req->xtra_content = vstr_make_base(NULL)))
    return (0);
  xs1 = req->xtra_content;

  if (!s1 || !*len)
    return (xs1->len + 1);
  
  if (vstr_sc_poslast(pos, *len) == xs1->len)
    return (pos); /* we are last, so just overwrite */

  /* we aren't, so copy to last place */
  ret = xs1->len + 1;
  if (!vstr_add_vstr(xs1, xs1->len, s1, pos, *len, VSTR_TYPE_ADD_BUF_REF))
    return (0);

  return (ret);
}