/* pfs.c:
 *
 ****************************************************************
 * Copyright (C) 2003 Tom Lord
 *
 * See the file "COPYING" for further information about
 * the copyright and warranty status of this work.
 */


#include "hackerlab/bugs/panic.h"
#include "hackerlab/bugs/exception.h"
#include "hackerlab/char/str.h"
#include "hackerlab/fs/file-names.h"
#include "hackerlab/fs/cwd.h"
#include "hackerlab/vu/safe.h"
#include "hackerlab/arrays/ar.h"
#include "libfsutils/tmp-files.h"
#include "libarch/archive.h"
#include "libarch/archives.h"
#include "libarch/pfs-dav.h"
#include "libarch/pfs-sftp.h"
#include "libarch/pfs-ftp.h"
#include "libarch/pfs-fs.h"
#include "libarch/pfs.h"
#include "libarch/cached-archive.h"


/* __STDC__ prototypes for static functions */
static int dot_listings_equal (rel_table a, rel_table b);



void
arch_pfs_rmrf_file (struct arch_pfs_session * pfs, t_uchar * path)
{
  int dir_check;

  dir_check = arch_pfs_is_dir (pfs, path);

  if (!dir_check)
    {
      arch_pfs_rm (pfs, path, 1);
    }
  else if (dir_check > 0)
    {
      rel_table contents = 0;
      int x;

      contents = arch_pfs_directory_files (pfs, path, 1);

      for (x = 0; x < rel_n_records (contents); ++x)
	if (str_cmp (".", contents[x][0]) && str_cmp ("..", contents[x][0]))
        {
          t_uchar * subpath = 0;

          subpath = file_name_in_vicinity (0, path, contents[x][0]);
          arch_pfs_rmrf_file (pfs, subpath);

          lim_free (0, subpath);
        }

      arch_pfs_rmdir (pfs, path, 1);

      rel_free_table (contents);
    }

  /* dir_check < 0 indicates an error, presumably ENOENT */
}




int
arch_pfs_set_file_contents (struct arch_pfs_session *pfs, t_uchar const *path, t_uchar const *contents, int soft_errors)
{
  t_uchar * tmp_path = tmp_file_name_in_tmp (",,pfs-sftp-file-contents");
  int fd = safe_open (tmp_path, O_RDWR | O_CREAT | O_EXCL, 0000);
  int err;
  safe_unlink (tmp_path);
  
  safe_printfmt (fd, "%s\n", contents);
  
  safe_lseek (fd, (off_t)0, SEEK_SET);
  err = pfs->vtable->put_file (pfs, (t_uchar *)path, 0666, fd, 1);
      
  safe_close (fd);
  lim_free (0, tmp_path);
  return err;
}

int
arch_pfs_set_file_contents_in_dir (struct arch_pfs_session *pfs, t_uchar const *dir, t_uchar const *relpath, t_uchar const *contents, int soft_errors)
{
  t_uchar * tmp_path = file_name_in_vicinity (0, dir, relpath);
  int result = arch_pfs_set_file_contents (pfs, tmp_path, contents, soft_errors);
  lim_free (0, tmp_path);
  return result;
}

void
arch_pfs_pfs_make_archive (t_uchar * name, t_uchar * uri, t_uchar * version, t_uchar *mirror_of, int dot_listing_lossage, int signed_archive)
{
  struct arch_pfs_session *pfs;
  t_uchar * meta_info_path = 0;
  t_uchar * tmp_path;
  t_uchar * archdir;
  t_uchar * hosturi;

  /* TODO: handle URIs like 'sftp://amit@cvs:~f' semi-reasonably
   * The following code can mangle URIs, since it doesn't ignore the protocol
   * part. IMHO, it doesn't belong in a general function like this
   * one. - aaron.bentley@utoronto.ca
   */ 
  tmp_path = file_name_from_directory (0, uri); /* remove any trailing '/' */
  archdir = file_name_tail (0, tmp_path);
  hosturi = file_name_directory (0, tmp_path);
  lim_free (0, tmp_path);

  pfs = arch_pfs_connect (hosturi, 0);
  pfs->vtable->mkdir (pfs, archdir, 0777, 0);

  meta_info_path = file_name_in_vicinity (0, archdir, "=meta-info");

  pfs->vtable->mkdir (pfs, meta_info_path, 0777, 0);

  if (mirror_of)
    {
      arch_pfs_set_file_contents_in_dir (pfs, meta_info_path, "name", mirror_of, 0);
      arch_pfs_set_file_contents_in_dir (pfs, meta_info_path, "mirror", mirror_of, 0);
    }
  else
      arch_pfs_set_file_contents_in_dir (pfs, meta_info_path, "name", name, 0);

  if (dot_listing_lossage)
      arch_pfs_set_file_contents_in_dir (pfs, meta_info_path, "http-blows", "it sure does", 0);

  if (signed_archive)
      arch_pfs_set_file_contents_in_dir (pfs, meta_info_path, "signed-archive", "system cracking is (nearly always) lame", 0);

  arch_pfs_set_file_contents_in_dir (pfs, archdir, ".archive-version", version, 0);

  if (dot_listing_lossage)
    {
      arch_pfs_update_listing_file (pfs, meta_info_path);
      arch_pfs_update_root_listing_file (pfs, archdir);
    }

  lim_free (0, meta_info_path);
  lim_free (0, hosturi);
  lim_free (0, archdir);
}

/* TODO: layer violation here - sftp urls are not the only
 * ones to probe - RBC 20050130
 */
extern int 
arch_valid_uri (t_uchar * uri)
{
  int answer = 1;
  if (arch_pfs_sftp_supported_protocol (uri))
    {
      t_uchar * user = 0, * hostname = 0, * port = 0;
      char * path = 0;
      answer = (arch_pfs_sftp_parse_uri (&user, &hostname, &port, &path, uri) == 0);
      lim_free (0, user);
      lim_free (0, hostname);
      lim_free (0, port);
      lim_free (0, path);
    }
  return answer;
}

struct arch_pfs_session *
arch_pfs_connect (t_uchar * uri, int soft_errors)
{
  struct arch_pfs_session * answer;

  if (arch_pfs_dav_supported_protocol (uri))
    {
      answer = arch_pfs_dav_connect (uri, soft_errors);
    }
  else if (arch_pfs_sftp_supported_protocol (uri))
    {
      return arch_pfs_sftp_connect (uri, soft_errors);
    }
  else if (arch_pfs_ftp_supported_protocol (uri))
    {
      return arch_pfs_ftp_connect (uri, soft_errors);
    }
  else
    {
      return arch_pfs_fs_connect (uri, soft_errors);
    }

  if (!answer && !soft_errors)
      panic ("Failed to connect to archive with soft_errors off");
  return answer;
}

void
arch_pfs_disconnect (struct arch_pfs_session **session)
{
    if (*session)
	(*session)->vtable->disconnect (session);
    *session = NULL;
}

int
arch_pfs_is_local(t_uchar const * uri)
{
  t_uchar * colon = str_chr_index (uri, ':');
  t_uchar * slash = str_chr_index (uri, '/');
  return (colon == 0 || (slash != 0 && slash < colon ) );
}


/* TODO: move this to hackerlab or fsutils */
static t_uchar *
path_normalise (t_uchar const * in_path)
{
    /* FIXME, this is a random hack for now.
     * expected semantics are - /./'s are removed
     * foo/../ are removed
     */
    t_uchar * result = NULL;
    rel_table sections = rel_delim_split (in_path, "/");
    rel_table answer_sections = 0;
    int index = 0;
    for (index = 0; index < rel_n_records(sections); ++index)
      {
	int answer_len;
	if (!str_cmp(sections[index][0], "."))
	    continue;
	else if (!str_cmp(sections[index][0], "..") && 
		 (answer_len = rel_n_records(answer_sections)) && 
		 str_cmp (answer_sections[answer_len - 1][0], ".."))
	  {
	    rel_remove_records (&answer_sections, answer_len - 1, answer_len - 1);
	  }
	else
	  rel_add_records (&answer_sections, rel_copy_record (sections[index]), 0);
      }
    result = str_save (0, "");
    for (index = 0; index < rel_n_records(answer_sections); ++index)
      {
	result = str_realloc_cat_many (0, result, "/", answer_sections[index][0], str_end);
      }
    rel_free_table(sections);
    rel_free_table(answer_sections);
    return result;
}

t_uchar *
arch_pfs_abs_path (t_uchar * path)
{
  if (!arch_pfs_is_local (path))
      return str_save (0, path);
  else
    return arch_abs_path (path);
}


/**
 * \brief Determines the absolute filesystem path for a file
 * \param path The path to absolutize
 * \return the absolute path
 */
t_uchar *
arch_abs_path (t_uchar const * const path)
{
  t_uchar * cwd;
  t_uchar * temp;
  t_uchar * result;
  if (!str_length (path))
    return safe_current_working_directory ();
  if (path[0]=='/')
      return path_normalise (path);
  cwd = safe_current_working_directory ();
  temp = str_realloc_cat (0, file_name_as_directory(0, cwd), path);
  result = path_normalise(temp);
  lim_free (0, cwd);
  lim_free (0, temp);
  return result;
}

t_uchar *
arch_pfs_file_contents (struct arch_pfs_session * pfs, t_uchar * path, int soft_errors)
{
  return pfs->vtable->file_contents (pfs, path, soft_errors);
}

rel_table
arch_pfs_directory_files (struct arch_pfs_session * pfs, t_uchar * path, int soft_errors)
{
  return pfs->vtable->directory_files (pfs, path, soft_errors);
}

int
arch_pfs_file_exists (struct arch_pfs_session * pfs, t_uchar * path)
{
  return pfs->vtable->file_exists (pfs, path);
}

int
arch_pfs_get_file (struct arch_pfs_session * pfs, int out_fd, t_uchar * path, int soft_errors)
{
  return pfs->vtable->get_file (pfs, out_fd, path, soft_errors);
}

int
arch_pfs_put_file (struct arch_pfs_session * pfs, t_uchar * path, mode_t perms, int in_fd, int soft_errors)
{
  return pfs->vtable->put_file (pfs, path, perms, in_fd, soft_errors);
}

int
arch_pfs_put_atomic (struct arch_pfs_session * pfs, t_uchar ** errstr, t_uchar * path, mode_t perms, int in_fd, int replace, int soft_errors)
{
  int result = 0;
  t_uchar * temp_dir = file_name_directory_file (0, path);
  t_uchar * tmp_file = talloc_tmp_file_name (talloc_context, temp_dir, ",,upload-tmp");

  arch_pfs_rmrf_file (pfs, tmp_file);
  if (!arch_pfs_put_file (pfs, tmp_file, perms, in_fd, soft_errors))
    {
      if (replace)
	  arch_pfs_rmrf_file (pfs, path);
      if (arch_pfs_rename (pfs, errstr, tmp_file, path, soft_errors))
	result = -1;
    }
  else
    result = -1;
  lim_free (0, temp_dir);
  talloc_free (tmp_file);
  return result;
}

int
arch_pfs_mkdir (struct arch_pfs_session * pfs, t_uchar * path, mode_t perms, int soft_errors)
{
  return pfs->vtable->mkdir (pfs, path, perms, soft_errors);
}

int
arch_pfs_rename (struct arch_pfs_session * pfs, t_uchar ** errstr, t_uchar * from, t_uchar * to, int soft_errors)
{
  return pfs->vtable->rename (pfs, errstr, from, to, soft_errors);
}

int
arch_pfs_is_dir (struct arch_pfs_session * pfs, t_uchar * path)
{
  return pfs->vtable->is_dir (pfs, path);
}

int
arch_pfs_rmdir (struct arch_pfs_session * pfs, t_uchar * path, int soft_errors)
{
  return pfs->vtable->rmdir (pfs, path, soft_errors);
}

int
arch_pfs_rm (struct arch_pfs_session * pfs, t_uchar * path, int soft_errors)
{
  return pfs->vtable->rm (pfs, path, soft_errors);
}

void
arch_pfs_update_listing_file (struct arch_pfs_session * session, t_uchar * dir)
{
  arch_pfs_update_listing_file_full (session, dir, 0);
}

void
arch_pfs_update_root_listing_file (struct arch_pfs_session * session, t_uchar *dir)
{
  rel_table archive_version_list = 0;
  rel_add_records (&archive_version_list, 
                   rel_make_record (".archive-version", 0), 0);
  arch_pfs_update_listing_file_full (session, dir, archive_version_list);
  rel_free_table (archive_version_list);
}

void
arch_pfs_update_listing_file_full (struct arch_pfs_session * session, t_uchar * dir, rel_table additional_files)
{
  t_uchar * tmp_path = 0;
  int tmp_fd = 0;
  t_uchar * dot_listing_path = 0;
  t_uchar * dot_listing_tmp = 0;
  rel_table files_before = 0;
  rel_table files_after = 0;

  tmp_path = tmp_file_name_in_tmp (",,pfs-dot-listing");
  tmp_fd = safe_open (tmp_path, O_RDWR | O_CREAT | O_EXCL, 0000);
  safe_unlink (tmp_path);

  dot_listing_path = file_name_in_vicinity (0, dir, ".listing");
  dot_listing_tmp = archive_tmp_file_name (dir, ",,dot-listing");

  while (1)
    {
      int x;

      files_before = arch_pfs_directory_files (session, dir, 0);
      if (files_before) rel_sort_table_by_field (0, files_before, 0);
      
      /* TODO: remove files in files_before that are present in additional_files */

      for (x = 0; x < rel_n_records (files_before); ++x)
        {
          if (('.' != files_before[x][0][0]) && (',' != files_before[x][0][0]))
            {
              safe_printfmt (tmp_fd, "%s\r\n", files_before[x][0]);
            }
        }
      
      if (additional_files)
        for (x = 0; x < rel_n_records (additional_files); ++x)
          {
            safe_printfmt (tmp_fd, "%s\r\n", additional_files[x][0]);
          }


      safe_lseek (tmp_fd, (off_t)0, SEEK_SET);
      arch_pfs_put_atomic (session, 0, dot_listing_path, 0444, tmp_fd, 1, 0);

      files_after = arch_pfs_directory_files (session, dir, 0);
      if (files_after) rel_sort_table_by_field (0, files_after, 0);

      if (!dot_listings_equal (files_before, files_after))
        {
          safe_ftruncate (tmp_fd, (long)0);
          rel_free_table (files_before);
          rel_free_table (files_after);
          files_before = 0;
          files_after = 0;
        }
      else
        {
          break;
        }
    }

  safe_close (tmp_fd);
  lim_free (0, dot_listing_path);
  lim_free (0, dot_listing_tmp);
  rel_free_table (files_before);
  rel_free_table (files_after);
  lim_free (0, tmp_path);
}


static int
dot_listings_equal (rel_table a, rel_table b)
{
  int ax;
  int bx;

  if ((!a && b) || (a && !b))
    return 0;

  ax = 0;
  bx = 0;
  while ((ax < rel_n_records (a))  || (bx < rel_n_records (b)))
    {
      if ((ax < rel_n_records (a)) && (a[ax][0][0] == '.'))
        {
          ++ax;
          continue;
        }
      if ((ax < rel_n_records (a)) && (a[ax][0][0] == ','))
        {
          ++ax;
          continue;
        }
      if ((bx < rel_n_records (b)) && (b[bx][0][0] == '.'))
        {
          ++bx;
          continue;
        }
      if ((bx < rel_n_records (b)) && (b[bx][0][0] == ','))
        {
          ++bx;
          continue;
        }

      if ((ax == rel_n_records (a)) || (bx == rel_n_records (b)))
        return 0;

      if (str_cmp (a[ax][0], b[bx][0]))
        return 0;

      ++ax;
      ++bx;
    }

  if ((ax != rel_n_records (a)) || (bx != rel_n_records (b)))
    return 0;

  return 1;
}

/**
 * \brief adjust a parsed uri for user-data-entry heuristics
 *
 * \param uri the uri to apply heuristics too
 */
void
arch_uri_heuristics (ne_uri *parsed_uri)
{
    char *at_pos = str_chr_index (parsed_uri->host, '@');
    if (!at_pos)
        return;
    parsed_uri->userinfo = str_replace (parsed_uri->userinfo, 
					str_alloc_cat (0, parsed_uri->userinfo, "@"));
    parsed_uri->userinfo = str_replace (parsed_uri->userinfo, 
					str_alloc_cat_n (0, parsed_uri->userinfo, parsed_uri->host, at_pos - parsed_uri->host));
    parsed_uri->host = str_replace (parsed_uri->host, str_save (0, at_pos + 1));
}

/* RFC2396 spake:
 * "Data must be escaped if it does not have a representation 
 * using an unreserved character".
 */

/* Lookup table: character classes from 2396. (This is overkill) */

#define SP 0   /* space    = <US-ASCII coded character 20 hexadecimal>                 */
#define CO 0   /* control  = <US-ASCII coded characters 00-1F and 7F hexadecimal>      */
#define DE 0   /* delims   = "<" | ">" | "#" | "%" | <">                               */
#define UW 0   /* unwise   = "{" | "}" | "|" | "\" | "^" | "[" | "]" | "`"             */
#define MA 1   /* mark     = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"       */
#define AN 2   /* alphanum = alpha | digit                                             */
#define RE 2   /* reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | "," */

static const char uri_chars[128] = {
/*                +2      +4      +6      +8     +10     +12     +14     */
/*   0 */ CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, CO,
/*  16 */ CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, CO, CO,
/*  32 */ SP, MA, DE, DE, RE, DE, RE, MA, MA, MA, MA, RE, RE, MA, MA, RE,
/*  48 */ AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, RE, RE, DE, RE, DE, RE,
/*  64 */ RE, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN,
/*  80 */ AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, UW, UW, UW, UW, MA,
/*  96 */ UW, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN,
/* 112 */ AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, AN, UW, UW, UW, MA, CO 
};

#define ESCAPE(ch) (((const signed char)(ch) < 0 || \
		uri_chars[(unsigned int)(ch)] == 0))

char *oldneon_ne_path_escape(const char *abs_path) 
{
    const char *pnt;
    char *ret, *retpos;
    int count = 0;
    for (pnt = abs_path; *pnt != '\0'; pnt++) {
	if (ESCAPE(*pnt)) {
	    count++;
	}
    }
    if (count == 0) {
	return ne_strdup(abs_path);
    }
    /* An escaped character is "%xx", i.e., two MORE
     * characters than the original string */
    retpos = ret = ne_malloc(strlen(abs_path) + 2*count + 1);
    for (pnt = abs_path; *pnt != '\0'; pnt++) {
	if (ESCAPE(*pnt)) {
	    /* Escape it - %<hex><hex> */
	    sprintf(retpos, "%%%02x", (unsigned char) *pnt);
	    retpos += 3;
	} else {
	    /* It's cool */
	    *retpos++ = *pnt;
	}
    }
    *retpos = '\0';
    return ret;
}

/**
 * \brief escape a location
 *
 * will eventuall break into sections and be smart about things
 */
t_uchar *
escape_location (t_uchar const *location)
{
    return oldneon_ne_path_escape(location);
}

/**
 * \brief unescape a location
 *
 * will eventuall break into sections and be smart about things
 */
t_uchar *
unescape_location (t_uchar const *location)
{
    return ne_path_unescape(location);
}

/* tag: Tom Lord Thu Jun  5 15:12:22 2003 (pfs.c)
 */


syntax highlighted by Code2HTML, v. 0.9.1