/*
                                  NETWOX
                             Network toolbox
                Copyright(c) 1999-2005 Laurent Constantin
                                  -----

  Main server    : http://www.laurentconstantin.com/
  Backup servers : http://go.to/laurentconstantin/
                   http://laurentconstantin.est-la.com/
                   http://laurentconstantin.free.fr/
                   http://membres.lycos.fr/lauconstantin/
  [my current email address is on the web servers]

                                  -----
  This file is part of Netwox.

  Netwox is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License
  version 2 as published by the Free Software Foundation.

  Netwox 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
  General Public License for more details (http://www.gnu.org/).

------------------------------------------------------------------------
*/

/*-------------------------------------------------------------*/
#include "../netwox.h"

/*-------------------------------------------------------------*/
typedef enum {
  NETWOX_URLLOCAL_FILEINFO_MD5,
  NETWOX_URLLOCAL_FILEINFO_ADDED,
  NETWOX_URLLOCAL_FILEINFO_NEEDSEP,
  NETWOX_URLLOCAL_FILEINFO_NONEEDSEP
} netwox_urllocal_fileinfo;

/*-------------------------------------------------------------*/
static netwib_err netwox_priv_urllocal_frag(netwib_constbuf *purl,
                                            netwib_bufext *purlext)
{
  netwib_data datain, pc;
  netwib_uint32 datainsize;

  datain = netwib__buf_ref_data_ptr(purl);
  datainsize = netwib__buf_ref_data_size(purl);

  *purlext = *purl;
  pc = netwib_c_memchr(datain, '#', datainsize);
  if (pc != NULL) {
    purlext->endoffset = purlext->beginoffset + pc - datain;
  }

  return(NETWIB_ERR_OK);
}

/*-------------------------------------------------------------*/
static netwib_err netwox_priv_urllocal_encode_md5(netwib_constbuf *prootdir,
                                                  netwib_constbuf *purl,
                                                  netwib_bool iserror,
                                                  netwib_bool ishtml,
                                                  netwib_buf *plocalfilename,
                                           netwox_urllocal_fileinfo *pfileinfo)
{
  netwib_data data;
  netwib_byte array[16];
  netwib_buf buf, ext;
  netwib_uint32 i;
  netwib_bool realext = NETWIB_FALSE;
  netwib_err ret;

  netwib_er(netwib_buf_append_buf(prootdir, plocalfilename));
  netwib_er(netwib_buf_append_text("/md5", plocalfilename));

  netwib_er(netwib_buf_init_ext_arrayempty(array, sizeof(array), &buf));
  netwib_er(netwox_md5_compute(purl, &buf));
#define NETWOX_URLLOCAL_MD5_SLASH 3
  netwib_er(netwib_buf_wantspace(plocalfilename,
                                 2*16 + NETWOX_URLLOCAL_MD5_SLASH, &data));
  for (i = 0; i < NETWOX_URLLOCAL_MD5_SLASH; i++) {
    *data++ = '/';
    *data++ = netwib_c2_16toc(((array[i])>>4));
    *data++ = netwib_c2_16toc(((array[i])&0xF));
  }
  for (i = NETWOX_URLLOCAL_MD5_SLASH; i < 16; i++) {
    *data++ = netwib_c2_16toc(((array[i])>>4));
    *data++ = netwib_c2_16toc(((array[i])&0xF));
  }
  plocalfilename->endoffset += 2*16 + NETWOX_URLLOCAL_MD5_SLASH;

  if (iserror) {
    realext = NETWIB_FALSE;
  } else if (ishtml) {
    realext = NETWIB_FALSE;
    netwib_er(netwox_urllocal_isfileext(purl, NETWOX_URLLOCAL_FILEEXT_NOTHTML,
                                        &realext));
  } else {
    realext = NETWIB_TRUE;
  }

  if (realext) {
    ret = netwib_path_decode_extension(purl, &ext);
    if (ret == NETWIB_ERR_OK) {
      netwib_er(netwib_buf_append_buf(&ext, plocalfilename));
    }
  } else {
    netwib_er(netwib_buf_append_text(".html", plocalfilename));
  }

  if (pfileinfo != NULL) {
    *pfileinfo = NETWOX_URLLOCAL_FILEINFO_MD5;
  }

  return(NETWIB_ERR_OK);
}

/*-------------------------------------------------------------*/
static netwib_err netwox_priv_urllocal_encode_file(netwib_constbuf *prootdir,
                                                   netwib_constbuf *purl,
                                                   netwib_uint32 maxsegmentlen,
                                                   netwib_bool iserror,
                                                   netwib_bool ishtml,
                                                   netwib_buf *plocalfilename,
                                           netwox_urllocal_fileinfo *pfileinfo)
{
  netwib_data datain, dataout, dataoutsave;
  netwib_uint32 datainsize, charssinceslash;
  netwib_bool addhtm, addseparator, noaddhtm;

  netwib_er(netwib_buf_append_buf(prootdir, plocalfilename));

  datain = netwib__buf_ref_data_ptr(purl);
  datainsize = netwib__buf_ref_data_size(purl);

  /* "xx/" && maxlen==3 --> /_3f/_3f/_/__.htm, so 7*datainsize should be ok */
  netwib_er(netwib_buf_wantspace(plocalfilename, 7*datainsize, &dataout));
  dataoutsave = dataout;

  maxsegmentlen--; /* because a segment always ends with "_/" */
  *dataout++ = '/';
  charssinceslash = 0;
  while(datainsize) {
    if (*datain == '/') {
      if (charssinceslash == 0) {
        *dataout++ = '_'; *dataout++ = '2'; *dataout++ = 'f';
        charssinceslash = 3;
      } else {
        *dataout++ = '/';
        charssinceslash = 0;
      }
    } else if (netwib_c2_isalnum(*datain) || *datain == '-') {
      if (charssinceslash >= maxsegmentlen) {
        if (charssinceslash == maxsegmentlen) {
          if (datainsize>2 && datain[1]=='/') {
            /* use "/dir/" instead of "/di_/r/" */
            *dataout++ = *datain;
            *dataout++ = '/';
            charssinceslash = 0;
            datain += 2;
            datainsize -= 2;
            continue;
          } else if (datainsize == 1) {
            /* use "/dir" instead of "/di_/r" */
            *dataout++ = *datain;
            charssinceslash++;
            datain++;
            datainsize--;
            continue;
          }
        }
        *dataout++ = '_';
        *dataout++ = '/';
        charssinceslash = 0;
      }
      charssinceslash++;
      *dataout++ = *datain;
    } else if (*datain == '.') {
      if (charssinceslash >= maxsegmentlen) {
        *dataout++ = '_';
        *dataout++ = '/';
        *dataout++ = '_'; /* under Windows, a filename starting with a dot
                             such as "/.name" is invalid */
        charssinceslash = 1;
      }
      charssinceslash++;
      *dataout++ = *datain;
    } else {
      if (charssinceslash + 3 > maxsegmentlen) {
        *dataout++ = '_';
        *dataout++ = '/';
        charssinceslash = 0;
      }
      *dataout++ = '_';
      *dataout++ = netwib_c2_16toc(((*datain)>>4));
      *dataout++ = netwib_c2_16toc(((*datain)&0xF));
      charssinceslash += 3;
    }
    datain++;
    datainsize--;
    /* we do not want to truncate an extension "x.abcde" */
    if (datainsize == 7 && charssinceslash != 0) {
      if (charssinceslash+7 > maxsegmentlen) {
        if (datain[1] == '.' || datain[2] == '.' ||
            datain[3] == '.' || datain[4] == '.' ||
            datain[5] == '.') {
          *dataout++ = '_';
          *dataout++ = '/';
          charssinceslash = 0;
        }
      }
    }
  }
  plocalfilename->endoffset += dataout - dataoutsave;

  /* check if we add "__.htm" */
  addhtm = NETWIB_FALSE;
  addseparator = NETWIB_TRUE;
  if (iserror) {
    addhtm = NETWIB_TRUE;
  } else if (charssinceslash == 0) {
    addhtm = NETWIB_TRUE;
    addseparator = NETWIB_FALSE;
  } else if (ishtml) {
    addhtm = NETWIB_TRUE;
    netwib_er(netwox_urllocal_isfileext(plocalfilename,
                                        NETWOX_URLLOCAL_FILEEXT_NOADDHTM,
                                        &noaddhtm));
    if (noaddhtm) {
      addhtm = NETWIB_FALSE;
    }
  }

  if (addhtm) {
    if (addseparator) {
      if (charssinceslash+strlen(NETWOX_URLLOCAL_APPEND_HTM) >
          maxsegmentlen+1) {
        netwib_er(netwib_buf_append_text("_/", plocalfilename));
      }
    }
    netwib_er(netwib_buf_append_text(NETWOX_URLLOCAL_APPEND_HTM,
                                     plocalfilename));
  }

  if (pfileinfo != NULL) {
    if (addhtm) {
      *pfileinfo = NETWOX_URLLOCAL_FILEINFO_ADDED;
    } else {
      if (charssinceslash+strlen(NETWOX_URLLOCAL_APPEND_HTM) >
          maxsegmentlen+1) {
        *pfileinfo = NETWOX_URLLOCAL_FILEINFO_NEEDSEP;
      } else {
        *pfileinfo = NETWOX_URLLOCAL_FILEINFO_NONEEDSEP;
      }
    }
  }

  return(NETWIB_ERR_OK);
}

/*-------------------------------------------------------------*/
static netwib_err netwox_priv_urllocal_encode(netwib_constbuf *prootdir,
                                              netwib_constbuf *purl,
                                              netwib_uint32 maxsegmentlen,
                                              netwib_uint32 maxtotallen,
                                              netwib_bool iserror,
                                              netwib_bool ishtml,
                                              netwib_buf *plocalfilename,
                                           netwox_urllocal_fileinfo *pfileinfo)
{
  netwib_uint32 originalsize;

  if (maxsegmentlen < 7) {
    return(NETWIB_ERR_PATOOLOW);
  }

  if (netwib__buf_ref_data_size(prootdir) + netwib__buf_ref_data_size(purl)
      >= maxtotallen) {
    /* we are sure md5 is needed, so do it directly */
    netwib_er(netwox_priv_urllocal_encode_md5(prootdir, purl, iserror, ishtml,
                                              plocalfilename, pfileinfo));
  } else {
    originalsize = netwib__buf_ref_data_size(plocalfilename);
    netwib_er(netwox_priv_urllocal_encode_file(prootdir, purl, maxsegmentlen,
                                               iserror, ishtml,
                                               plocalfilename, pfileinfo));
    if (netwib__buf_ref_data_size(plocalfilename) >= maxtotallen) {
      /* do not fit, so use md5 */
      plocalfilename->endoffset = plocalfilename->beginoffset + originalsize;
      netwib_er(netwox_priv_urllocal_encode_md5(prootdir, purl, iserror,
                                                ishtml, plocalfilename,
                                                pfileinfo));
    }
  }

  return(NETWIB_ERR_OK);
}

/*-------------------------------------------------------------*/
netwib_err netwox_urllocal_encode(netwib_constbuf *prootdir,
                                  netwib_constbuf *purl,
                                  netwib_uint32 maxsegmentlen,
                                  netwib_uint32 maxtotallen,
                                  netwib_bool iserror,
                                  netwib_bool ishtml,
                                  netwib_buf *plocalfilename)
{
  netwib_buf url;

  netwib_er(netwox_priv_urllocal_frag(purl, &url));

  return(netwox_priv_urllocal_encode(prootdir, &url, maxsegmentlen,
                                     maxtotallen, iserror, ishtml,
                                     plocalfilename, NULL));
}

/*-------------------------------------------------------------*/
netwib_err netwox_urllocal_decode(netwib_constbuf *prootdir,
                                  netwib_constbuf *plocalfilename,
                                  netwib_buf *purl)
{
  netwib_data datain, dataout, dataoutsave;
  netwib_uint32 datainsize, rootdirsize;

  /* remove rootdir in localfilename (no check, should be ok) */
  datain = netwib__buf_ref_data_ptr(plocalfilename);
  datainsize = netwib__buf_ref_data_size(plocalfilename);
  if (prootdir != NULL) {
    rootdirsize = netwib__buf_ref_data_size(prootdir) + 1;
    if (datainsize < rootdirsize) {
      return(NETWIB_ERR_NOTCONVERTED);
    }
    datain += rootdirsize;
    datainsize -= rootdirsize;
  }

  netwib_er(netwib_buf_wantspace(purl, datainsize, &dataout));
  dataoutsave = dataout;

  while(datainsize) {
    if (*datain == '_') {
      if (datainsize < 2) {
        return(NETWIB_ERR_NOTCONVERTED);
      }
      if (datain[1] == '_') {
        /* ignore everything after "__" */
        break;
      }
      if (datain[1] == '/') {
        /* an additional '/' */
        datain += 2;
        datainsize -= 2;
        continue;
      }
      if (datain[1] == '.') {
        /* just a dot */
        *dataout++ = '.';
        datain += 2;
        datainsize -= 2;
        continue;
      }
      if (datainsize < 3) {
        return(NETWIB_ERR_NOTCONVERTED);
      }
      if (!netwib_c2_isxdigit(datain[1]) || !netwib_c2_isxdigit(datain[2])) {
        return(NETWIB_ERR_NOTCONVERTED);
      }
      *dataout++ = (netwib_byte)((netwib_c2_cto16(datain[1])<<4) |
                                 netwib_c2_cto16(datain[2]));
      datain += 3;
      datainsize -= 3;
    } else {
      *dataout++ = *datain++;
      datainsize--;
    }
  }

  purl->endoffset += dataout - dataoutsave;

  return(NETWIB_ERR_OK);
}

/*-------------------------------------------------------------*/
netwib_err netwox_urllocal_exists(netwib_constbuf *prootdir,
                                  netwib_constbuf *purl,
                                  netwib_uint32 maxsegmentlen,
                                  netwib_uint32 maxtotallen,
                                  netwib_bool *pexists,
                                  netwib_buf *plocalfilename)
{
  netwib_bool exists;
  netwox_urllocal_fileinfo fileinfo;
  netwib_uint32 originalsize;
  netwib_buf url;

  netwib_er(netwox_priv_urllocal_frag(purl, &url));

  originalsize = netwib__buf_ref_data_size(plocalfilename);

  netwib_er(netwox_priv_urllocal_encode(prootdir, &url, maxsegmentlen,
                                        maxtotallen, NETWIB_FALSE,
                                        NETWIB_FALSE, plocalfilename,
                                        &fileinfo));
  netwib_er(netwib_filename_exists(plocalfilename, &exists));
  if (exists) {
    if (pexists != NULL) *pexists = NETWIB_TRUE;
    return(NETWIB_ERR_OK);
  }

  switch(fileinfo) {
  case NETWOX_URLLOCAL_FILEINFO_ADDED :
    if (pexists != NULL) *pexists = NETWIB_FALSE;
    break;
  case NETWOX_URLLOCAL_FILEINFO_MD5 :
    /* real extension was tried because of urllocal_encode(..., FALSE, FALSE),
       so try with .html */
    plocalfilename->endoffset = plocalfilename->beginoffset + originalsize;
    netwib_er(netwox_priv_urllocal_encode_md5(prootdir, &url, NETWIB_TRUE,
                                              NETWIB_FALSE, plocalfilename,
                                              NULL));
    netwib_er(netwib_filename_exists(plocalfilename, pexists));
    break;
  case NETWOX_URLLOCAL_FILEINFO_NEEDSEP :
    netwib_er(netwib_buf_append_text("_/", plocalfilename));
    /* follow */
  case NETWOX_URLLOCAL_FILEINFO_NONEEDSEP :
    /* check adding __.htm */
    netwib_er(netwib_buf_append_text(NETWOX_URLLOCAL_APPEND_HTM,
                                     plocalfilename));
    if (netwib__buf_ref_data_size(plocalfilename)-originalsize
        < maxtotallen) {
      netwib_er(netwib_filename_exists(plocalfilename, pexists));
    } else {
      /* adding __.htm caused the md5 limit to be reached, so check md5
         using .html and .extension */
      plocalfilename->endoffset = plocalfilename->beginoffset + originalsize;
      netwib_er(netwox_priv_urllocal_encode_md5(prootdir, &url, NETWIB_TRUE,
                                                NETWIB_FALSE, plocalfilename,
                                                NULL));
      netwib_er(netwib_filename_exists(plocalfilename, &exists));
      if (!exists) {
        plocalfilename->endoffset = plocalfilename->beginoffset + originalsize;
        netwib_er(netwox_priv_urllocal_encode_md5(prootdir, &url, NETWIB_FALSE,
                                                  NETWIB_FALSE, plocalfilename,
                                                  NULL));
        netwib_er(netwib_filename_exists(plocalfilename, &exists));
      }
      if (pexists != NULL) *pexists = exists;
    }
    break;
  }

  return(NETWIB_ERR_OK);
}

/*-------------------------------------------------------------*/
netwib_err netwox_urllocal_isfileext(netwib_constbuf *pfilename,
                                     netwox_urllocal_fileext fileext,
                                     netwib_bool *pyes)
{
  netwib_data data, datacmp;
  netwib_uint32 datasize;

  *pyes = NETWIB_FALSE;

  datasize = netwib__buf_ref_data_size(pfilename);
  if (datasize < 2) {
    return(NETWIB_ERR_OK);
  }
  data = netwib__buf_ref_data_ptr(pfilename);

  /* easy case of htm and html only */
  if (fileext == NETWOX_URLLOCAL_FILEEXT_HTML) {
    if (datasize >= 4) {
      datacmp = data + datasize - 4;
      if (!netwib_c_memcasecmp(datacmp, (netwib_constdata)".htm", 4)) {
        *pyes = NETWIB_TRUE;
        return(NETWIB_ERR_OK);
      }
    }
    if (datasize >= 5) {
      datacmp = data + datasize - 5;
      if (!netwib_c_memcasecmp(datacmp, (netwib_constdata)".html", 5)) {
        *pyes = NETWIB_TRUE;
        return(NETWIB_ERR_OK);
      }
    }
    return(NETWIB_ERR_OK);
  }

  /* "x." */
  switch (data[datasize-1]) {
  case '.' :
  case '/' :
    return(NETWIB_ERR_OK);
  }

  /* "x.a" */
  switch (data[datasize-2]) {
  case '.' :
    datacmp = data + datasize - 1;
    switch (datacmp[0]) {
    case 'c' :
    case 'C' :
    case 'z' :
    case 'Z' :
    case 'h' :
    case 'H' :
      *pyes = NETWIB_TRUE;
      return(NETWIB_ERR_OK);
    }
    break;
  case '/' :
    return(NETWIB_ERR_OK);
  }

  /* "x.ab" */
  if (datasize < 3) {
    return(NETWIB_ERR_OK);
  }
  switch (data[datasize-3]) {
  case '.' :
    datacmp = data + datasize - 2;
    if (!netwib_c_memcasecmp(datacmp, (netwib_constdata)"cc", 2) ||
        !netwib_c_memcasecmp(datacmp, (netwib_constdata)"gz", 2) ||
        !netwib_c_memcasecmp(datacmp, (netwib_constdata)"ps", 2)) {
      *pyes = NETWIB_TRUE;
      return(NETWIB_ERR_OK);
    }
    break;
  case '/' :
    return(NETWIB_ERR_OK);
  }

  /* "x.abc" */
  if (datasize < 4) {
    return(NETWIB_ERR_OK);
  }
  switch (data[datasize-4]) {
  case '.' :
    datacmp = data + datasize - 3;
    if (!netwib_c_memcasecmp(datacmp, (netwib_constdata)"asm", 3) ||
        !netwib_c_memcasecmp(datacmp, (netwib_constdata)"bat", 3) ||
        !netwib_c_memcasecmp(datacmp, (netwib_constdata)"com", 3) ||
        !netwib_c_memcasecmp(datacmp, (netwib_constdata)"cpp", 3) ||
        !netwib_c_memcasecmp(datacmp, (netwib_constdata)"dll", 3) ||
        !netwib_c_memcasecmp(datacmp, (netwib_constdata)"doc", 3) ||
        !netwib_c_memcasecmp(datacmp, (netwib_constdata)"exe", 3) ||
        !netwib_c_memcasecmp(datacmp, (netwib_constdata)"gif", 3) ||
        !netwib_c_memcasecmp(datacmp, (netwib_constdata)"ico", 3) ||
        !netwib_c_memcasecmp(datacmp, (netwib_constdata)"jar", 3) ||
        !netwib_c_memcasecmp(datacmp, (netwib_constdata)"jpg", 3) ||
        !netwib_c_memcasecmp(datacmp, (netwib_constdata)"mp3", 3) ||
        !netwib_c_memcasecmp(datacmp, (netwib_constdata)"pdf", 3) ||
        !netwib_c_memcasecmp(datacmp, (netwib_constdata)"png", 3) ||
        !netwib_c_memcasecmp(datacmp, (netwib_constdata)"ppt", 3) ||
        !netwib_c_memcasecmp(datacmp, (netwib_constdata)"rpm", 3) ||
        !netwib_c_memcasecmp(datacmp, (netwib_constdata)"rtf", 3) ||
        !netwib_c_memcasecmp(datacmp, (netwib_constdata)"tar", 3) ||
        !netwib_c_memcasecmp(datacmp, (netwib_constdata)"tgz", 3) ||
        !netwib_c_memcasecmp(datacmp, (netwib_constdata)"txt", 3) ||
        !netwib_c_memcasecmp(datacmp, (netwib_constdata)"xls", 3) ||
        !netwib_c_memcasecmp(datacmp, (netwib_constdata)"zip", 3)) {
      *pyes = NETWIB_TRUE;
      return(NETWIB_ERR_OK);
    }
    if (fileext == NETWOX_URLLOCAL_FILEEXT_NOADDHTM) {
      if (!netwib_c_memcasecmp(datacmp, (netwib_constdata)"htm", 3)) {
        *pyes = NETWIB_TRUE;
        return(NETWIB_ERR_OK);
      }
    }
    break;
  case '/' :
    return(NETWIB_ERR_OK);
  }

  /* "x.abcd" */
  if (datasize < 5) {
    return(NETWIB_ERR_OK);
  }
  switch (data[datasize-5]) {
  case '.' :
    datacmp = data + datasize - 4;
    if (!netwib_c_memcasecmp(datacmp, (netwib_constdata)"conf", 4) ||
        !netwib_c_memcasecmp(datacmp, (netwib_constdata)"diff", 4) ||
        !netwib_c_memcasecmp(datacmp, (netwib_constdata)"jpeg", 4) ||
        !netwib_c_memcasecmp(datacmp, (netwib_constdata)"nasl", 4)) {
      *pyes = NETWIB_TRUE;
      return(NETWIB_ERR_OK);
    }
    if (fileext == NETWOX_URLLOCAL_FILEEXT_NOADDHTM) {
      if (!netwib_c_memcasecmp(datacmp, (netwib_constdata)"html", 4)) {
        *pyes = NETWIB_TRUE;
        return(NETWIB_ERR_OK);
      }
    }
    break;
  case '/' :
    return(NETWIB_ERR_OK);
  }

  return(NETWIB_ERR_OK);
}
