/*@-modfilesys@*/
/** \ingroup rpmio
 * \file rpmio/rpmdav.c
 */

#include "system.h"

#ifdef WITH_NEON

#include "ne_alloc.h"
#include "ne_auth.h"
#include "ne_basic.h"
#include "ne_dates.h"
#include "ne_locks.h"

#define	NEONBLOWSCHUNKS
#ifndef	NEONBLOWSCHUNKS
/* HACK: include ne_private.h to access sess->socket for now. */
#include "../neon/src/ne_private.h"
#endif

#include "ne_props.h"
#include "ne_request.h"
#include "ne_socket.h"
#include "ne_string.h"

#include "ne_utils.h"
#if !defined(HEADER_ERR_H)
#if defined(WITH_OPENSSL)
/* cheats to avoid having to explicitly build against OpenSSL */
/*@-exportheader@*/
extern void ERR_remove_state(int foo);
extern void ENGINE_cleanup(void);
extern void CONF_modules_unload(int foo);
extern void ERR_free_strings(void);
extern void EVP_cleanup(void);
extern void CRYPTO_cleanup_all_ex_data(void);
extern void CRYPTO_mem_leaks(void * ptr);
/*@=exportheader@*/
#endif
#endif

#include "ne_md5.h" /* for version detection only */

/* poor-man's NEON version determination */
#if defined(NE_MD5_H)
#define WITH_NEON_MIN_VERSION 0x002700
#elif defined(NE_FEATURE_I18N)
#define WITH_NEON_MIN_VERSION 0x002600
#else
#define WITH_NEON_MIN_VERSION 0x002500
#endif

/* XXX API changes for NEON 0.26 */
#if WITH_NEON_MIN_VERSION >= 0x002600
#define	ne_set_persist(_sess, _flag)
#define	ne_propfind_set_private(_pfh, _create_item, NULL) \
	ne_propfind_set_private(_pfh, _create_item, NULL, NULL)
#endif

#endif /* WITH_NEON */

#include <rpmio_internal.h>

#include <rpmhash.h>
#include <ugid.h>

#define _RPMAV_INTERNAL
#define _RPMDAV_INTERNAL
#include <rpmdav.h>

#include "debug.h"

/* Should http OPTIONS be run only once? */
/* XXX TODO: determine precisely when OPTIONS need to be run. */
int _dav_nooptions = 0;

/*@access DIR @*/
/*@access FD_t @*/
/*@access urlinfo @*/

#if 0	/* HACK: reasonable value needed. */
#define TIMEOUT_SECS 60
#else
#define TIMEOUT_SECS 5
#endif
/*@unchecked@*/
static int httpTimeoutSecs = TIMEOUT_SECS;

/* =============================================================== */
void * avContextDestroy(avContext ctx)
{
    if (ctx == NULL)
	return NULL;
    if (ctx->av != NULL)
	ctx->av = argvFree(ctx->av);
    ctx->modes = _free(ctx->modes);
    ctx->sizes = _free(ctx->sizes);
    ctx->mtimes = _free(ctx->mtimes);
    ctx->u = urlFree(ctx->u, "avContextDestroy");
    ctx->uri = _free(ctx->uri);
    memset(ctx, 0, sizeof(*ctx));	/* trash & burn */
    ctx = _free(ctx);
    return NULL;
}

void * avContextCreate(const char *uri, struct stat *st)
{
    avContext ctx;
    urlinfo u;

/*@-globs@*/	/* FIX: h_errno annoyance. */
    if (urlSplit(uri, &u))
	return NULL;
/*@=globs@*/

    ctx = xcalloc(1, sizeof(*ctx));
    ctx->uri = xstrdup(uri);
    ctx->u = urlLink(u, "avContextCreate");
/*@-temptrans@*/	/* XXX note the assignment */
    if ((ctx->st = st) != NULL)
	memset(ctx->st, 0, sizeof(*ctx->st));
/*@=temptrans@*/
    return ctx;
}

int avContextAdd(avContext ctx, const char * path,
		mode_t mode, size_t size, time_t mtime)
{
    int xx;

if (_av_debug < 0)
fprintf(stderr, "*** avContextAdd(%p,\"%s\", %06o, 0x%x, 0x%x)\n", ctx, path, (unsigned)mode, (unsigned)size, (unsigned)mtime);

    xx = argvAdd(&ctx->av, path);

    while (ctx->ac >= ctx->nalloced) {
	if (ctx->nalloced <= 0)
	    ctx->nalloced = 1;
	ctx->nalloced *= 2;
	ctx->modes = xrealloc(ctx->modes,
				(sizeof(*ctx->modes) * ctx->nalloced));
	ctx->sizes = xrealloc(ctx->sizes,
				(sizeof(*ctx->sizes) * ctx->nalloced));
	ctx->mtimes = xrealloc(ctx->mtimes,
				(sizeof(*ctx->mtimes) * ctx->nalloced));
    }

    ctx->modes[ctx->ac] = mode;
    ctx->sizes[ctx->ac] = size;
    ctx->mtimes[ctx->ac] = mtime;
    ctx->ac++;
    return 0;
}

int avClosedir(/*@only@*/ DIR * dir)
{
    AVDIR avdir = (AVDIR)dir;

if (_av_debug)
fprintf(stderr, "*** avClosedir(%p)\n", avdir);

#if defined(HAVE_PTHREAD_H)
/*@-moduncon -noeffectuncon @*/
    (void) pthread_mutex_destroy(&avdir->lock);
/*@=moduncon =noeffectuncon @*/
#endif

    avdir = _free(avdir);
    return 0;
}

struct dirent * avReaddir(DIR * dir)
{
    AVDIR avdir = (AVDIR)dir;
    struct dirent * dp;
    const char ** av;
    unsigned char * dt;
    int ac;
    int i;

    if (avdir == NULL || !ISAVMAGIC(avdir) || avdir->data == NULL) {
	/* XXX TODO: EBADF errno. */
	return NULL;
    }

    dp = (struct dirent *) avdir->data;
    av = (const char **) (dp + 1);
    ac = (int)avdir->size;
    dt = (unsigned char *) (av + (ac + 1));
    i = avdir->offset + 1;

    if (i < 0 || i >= ac || av[i] == NULL)
	return NULL;

    avdir->offset = i;

    /* XXX glob(3) uses REAL_DIR_ENTRY(dp) test on d_ino */
/*@-type@*/
    /* Hash the name (starting with parent hash) for a d_ino analogue. */
    dp->d_ino = hashFunctionString(avdir->filepos, dp->d_name, 0);

#if !defined(__DragonFly__) && !defined(__CYGWIN__)
    dp->d_reclen = 0;		/* W2DO? */
#endif

#if !(defined(hpux) || defined(__hpux) || defined(sun) || defined(RPM_OS_AIX) || defined(__CYGWIN__) || defined(__QNXNTO__))
#if !defined(__APPLE__) && !defined(__FreeBSD_kernel__) && !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__DragonFly__)
    dp->d_off = 0;		/* W2DO? */
#endif
    dp->d_type = dt[i];
#endif
/*@=type@*/

    strncpy(dp->d_name, av[i], sizeof(dp->d_name));
if (_av_debug)
fprintf(stderr, "*** avReaddir(%p) %p \"%s\"\n", (void *)avdir, dp, dp->d_name);

    return dp;
}

DIR * avOpendir(const char * path, const char ** av, uint16_t * modes)
{
    AVDIR avdir;
    struct dirent * dp;
    size_t nb;
    const char ** nav;
    unsigned char * dt;
    char * t;
    int ac, nac;

if (_av_debug)
fprintf(stderr, "*** avOpendir(%s, %p, %p)\n", path, av, modes);

    nb = 0;
    ac = 0;
    if (av != NULL)
    while (av[ac] != NULL)
	nb += strlen(av[ac++]) + 1;
    ac += 2;	/* for "." and ".." */
    nb += sizeof(".") + sizeof("..");

    nb += sizeof(*avdir) + sizeof(*dp) + ((ac + 1) * sizeof(*av)) + (ac + 1);
    avdir = xcalloc(1, nb);
/*@-abstract@*/
    dp = (struct dirent *) (avdir + 1);
    nav = (const char **) (dp + 1);
    dt = (unsigned char *) (nav + (ac + 1));
    t = (char *) (dt + ac + 1);
/*@=abstract@*/

    avdir->fd = avmagicdir;
/*@-usereleased@*/
    avdir->data = (char *) dp;
/*@=usereleased@*/
    avdir->allocation = nb;
    avdir->size = ac;
    avdir->offset = -1;
    /* Hash the directory path for a d_ino analogue. */
    avdir->filepos = hashFunctionString(0, path, 0);

#if defined(HAVE_PTHREAD_H)
/*@-moduncon -noeffectuncon -nullpass @*/
    (void) pthread_mutex_init(&avdir->lock, NULL);
/*@=moduncon =noeffectuncon =nullpass @*/
#endif

    nac = 0;
    /*@-dependenttrans -unrecog@*/
    dt[nac] = (unsigned char)DT_DIR; nav[nac++] = t; t = stpcpy(t, "."); t++;
    dt[nac] = (unsigned char)DT_DIR; nav[nac++] = t; t = stpcpy(t, ".."); t++;
    /*@=dependenttrans =unrecog@*/

    /* Append av strings to DIR elements. */
    ac = 0;
    if (av != NULL)
    while (av[ac] != NULL) {
	if (modes != NULL)
	    switch (modes[ac] & S_IFMT) {
	    case S_IFIFO: dt[nac]=(unsigned char)DT_FIFO;/*@switchbreak@*/break;
	    case S_IFCHR: dt[nac]=(unsigned char)DT_CHR; /*@switchbreak@*/break;
	    case S_IFDIR: dt[nac]=(unsigned char)DT_DIR; /*@switchbreak@*/break;
	    case S_IFBLK: dt[nac]=(unsigned char)DT_BLK; /*@switchbreak@*/break;
	    case S_IFREG: dt[nac]=(unsigned char)DT_REG; /*@switchbreak@*/break;
	    case S_IFLNK: dt[nac]=(unsigned char)DT_LNK; /*@switchbreak@*/break;
/*@-unrecog@*/
	    case S_IFSOCK:dt[nac]=(unsigned char)DT_SOCK;/*@switchbreak@*/break;
/*@=unrecog@*/
	    default:	  dt[nac]=(unsigned char)DT_UNKNOWN;/*@switchbreak@*/break;
	    }
	else
	    dt[nac] = (unsigned char)DT_UNKNOWN;
	nav[nac++] = t;
	t = stpcpy(t, av[ac++]);
	t++;	/* trailing \0 */
    }
    nav[nac] = NULL;

/*@-kepttrans@*/
    return (DIR *) avdir;
/*@=kepttrans@*/
}

#ifdef WITH_NEON
/* =============================================================== */
void davDestroy(void)
{
#ifdef NE_FEATURE_SSL
#if defined(WITH_OPENSSL)
    if (ne_has_support(NE_FEATURE_SSL)) {
/* XXX http://www.nabble.com/Memory-Leaks-in-SSL_Library_init()-t3431875.html */
	ENGINE_cleanup();
	CRYPTO_cleanup_all_ex_data();
	ERR_free_strings();
	ERR_remove_state(0);
	EVP_cleanup();
	CRYPTO_mem_leaks(NULL);
	CONF_modules_unload(1);
    }
#endif
#endif
}

int davFree(urlinfo u)
	/*@globals internalState @*/
	/*@modifies u, internalState @*/
{
    if (u != NULL) {
	if (u->sess != NULL) {
	    ne_session_destroy(u->sess);
	    u->sess = NULL;
	}
	switch (u->urltype) {
	default:
	    /*@notreached@*/ break;
	case URL_IS_HTTPS:
	case URL_IS_HTTP:
	case URL_IS_HKP:
	    u->capabilities = _free(u->capabilities);
	    if (u->lockstore != NULL)
		ne_lockstore_destroy(u->lockstore);
	    u->lockstore = NULL;
	    ne_sock_exit();
	    break;
	}
    }
    return 0;
}

static void davProgress(void * userdata, off_t current, off_t total)
	/*@*/
{
    urlinfo u = userdata;
    ne_session * sess;

assert(u != NULL);
    sess = u->sess;
assert(sess != NULL);
/*@-sefuncon@*/
assert(u == ne_get_session_private(sess, "urlinfo"));
/*@=sefuncon@*/

    u->current = current;
    u->total = total;

if (_dav_debug < 0)
fprintf(stderr, "*** davProgress(%p,0x%x:0x%x) sess %p u %p\n", userdata, (unsigned int)current, (unsigned int)total, sess, u);
}

#if WITH_NEON_MIN_VERSION >= 0x002700
static void davNotify(void * userdata,
		ne_session_status connstatus, const ne_session_status_info *info)
#else
static void davNotify(void * userdata,
		ne_conn_status connstatus, const char * info)
#endif
	/*@*/
{
    urlinfo u = userdata;
    ne_session * sess;
    /*@observer@*/
    static const char * connstates[] = {
	"namelookup",
	"connecting",
	"connected",
	"secure",
	"unknown"
    };

assert(u != NULL);
    sess = u->sess;
assert(sess != NULL);
/*@-sefuncon@*/
assert(u == ne_get_session_private(sess, "urlinfo"));
/*@=sefuncon@*/

#ifdef	REFERENCE
typedef enum {
    ne_conn_namelookup, /* lookup up hostname (info = hostname) */
    ne_conn_connecting, /* connecting to host (info = hostname) */
    ne_conn_connected, /* connected to host (info = hostname) */
    ne_conn_secure /* connection now secure (info = crypto level) */
} ne_conn_status;
#endif

#if WITH_NEON_MIN_VERSION < 0x002700
    u->connstatus = connstatus;
#endif

if (_dav_debug < 0)
fprintf(stderr, "*** davNotify(%p,%d,%p) sess %p u %p %s\n", userdata, connstatus, info, sess, u, connstates[ (connstatus < 4 ? connstatus : 4)]);

}

static void davCreateRequest(ne_request * req, void * userdata,
		const char * method, const char * uri)
	/*@*/
{
    urlinfo u = userdata;
    ne_session * sess;
    void * private = NULL;
    const char * id = "urlinfo";

assert(u != NULL);
assert(u->sess != NULL);
assert(req != NULL);
    sess = ne_get_session(req);
assert(sess == u->sess);
/*@-sefuncon@*/
assert(u == ne_get_session_private(sess, "urlinfo"));
/*@=sefuncon@*/

assert(sess != NULL);
    private = ne_get_session_private(sess, id);
assert(u == private);

if (_dav_debug < 0)
fprintf(stderr, "*** davCreateRequest(%p,%p,%s,%s) %s:%p\n", req, userdata, method, uri, id, private);
}

static void davPreSend(ne_request * req, void * userdata, ne_buffer * buf)
{
    urlinfo u = userdata;
    ne_session * sess;
    const char * id = "fd";
    FD_t fd = NULL;

/*@-modunconnomods@*/
assert(u != NULL);
assert(u->sess != NULL);
assert(req != NULL);
    sess = ne_get_session(req);
assert(sess == u->sess);
/*@-sefuncon@*/
assert(u == ne_get_session_private(sess, "urlinfo"));
/*@=sefuncon@*/

    fd = ne_get_request_private(req, id);
/*@=modunconnomods@*/

if (_dav_debug < 0)
fprintf(stderr, "*** davPreSend(%p,%p,%p) sess %p %s %p\n", req, userdata, buf, sess, id, fd);
if (_dav_debug)
fprintf(stderr, "-> %s\n", buf->data);

}

static int davPostSend(ne_request * req, void * userdata, const ne_status * status)
	/*@*/
{
    urlinfo u = userdata;
    ne_session * sess;
    const char * id = "fd";
    FD_t fd = NULL;

assert(u != NULL);
assert(u->sess != NULL);
assert(req != NULL);
    sess = ne_get_session(req);
assert(sess == u->sess);
/*@-sefuncon@*/
assert(u == ne_get_session_private(sess, "urlinfo"));
/*@=sefuncon@*/

    fd = ne_get_request_private(req, id);

/*@-evalorder@*/
if (_dav_debug < 0)
fprintf(stderr, "*** davPostSend(%p,%p,%p) sess %p %s %p %s\n", req, userdata, status, sess, id, fd, ne_get_error(sess));
/*@=evalorder@*/
    return NE_OK;
}

static void davDestroyRequest(ne_request * req, void * userdata)
	/*@*/
{
    urlinfo u = userdata;
    ne_session * sess;
    const char * id = "fd";
    FD_t fd = NULL;

assert(u != NULL);
assert(u->sess != NULL);
assert(req != NULL);
    sess = ne_get_session(req);
assert(sess == u->sess);
/*@-sefuncon@*/
assert(u == ne_get_session_private(sess, "urlinfo"));
/*@=sefuncon@*/

    fd = ne_get_request_private(req, id);

if (_dav_debug < 0)
fprintf(stderr, "*** davDestroyRequest(%p,%p) sess %p %s %p\n", req, userdata, sess, id, fd);
}

static void davDestroySession(void * userdata)
	/*@*/
{
    urlinfo u = userdata;
    ne_session * sess;
    void * private = NULL;
    const char * id = "urlinfo";

assert(u != NULL);
assert(u->sess != NULL);
    sess = u->sess;
/*@-sefuncon@*/
assert(u == ne_get_session_private(sess, "urlinfo"));
/*@=sefuncon@*/

assert(sess != NULL);
    private = ne_get_session_private(sess, id);
assert(u == private);

if (_dav_debug < 0)
fprintf(stderr, "*** davDestroySession(%p) sess %p %s %p\n", userdata, sess, id, private);
}

static int
davVerifyCert(void *userdata, int failures, const ne_ssl_certificate *cert)
	/*@*/
{
    const char *hostname = userdata;

if (_dav_debug < 0)
fprintf(stderr, "*** davVerifyCert(%p,%d,%p) %s\n", userdata, failures, cert, hostname);

    return 0;	/* HACK: trust all server certificates. */
}

static int davConnect(urlinfo u)
	/*@globals errno, internalState @*/
	/*@modifies u, errno, internalState @*/
{
    const char * path = NULL;
    int rc;

    /* HACK: hkp:// has no steenkin' options */
    if (!(u->urltype == URL_IS_HTTP || u->urltype == URL_IS_HTTPS))
	return 0;

    if (_dav_nooptions && u->allow & RPMURL_SERVER_OPTIONSDONE)
	return 0;

    u->allow &= ~(RPMURL_SERVER_HASDAVCLASS1 |
		  RPMURL_SERVER_HASDAVCLASS2 |
		  RPMURL_SERVER_HASDAVEXEC);

    /* HACK: where should server capabilities be read? */
    (void) urlPath(u->url, &path);
    /* HACK: perhaps capture Allow: tag, look for PUT permitted. */
    /* XXX [hdr] Allow: GET,HEAD,POST,OPTIONS,TRACE */
    rc = ne_options(u->sess, path, u->capabilities);
    switch (rc) {
    case NE_OK:
	u->allow |= RPMURL_SERVER_OPTIONSDONE;
    {	ne_server_capabilities *cap = u->capabilities;
	if (cap->dav_class1)
	    u->allow |= RPMURL_SERVER_HASDAVCLASS1;
	else
	    u->allow &= ~RPMURL_SERVER_HASDAVCLASS1;
	if (cap->dav_class2)
	    u->allow |= RPMURL_SERVER_HASDAVCLASS2;
	else
	    u->allow &= ~RPMURL_SERVER_HASDAVCLASS2;
	if (cap->dav_executable)
	    u->allow |= RPMURL_SERVER_HASDAVEXEC;
	else
	    u->allow &= ~RPMURL_SERVER_HASDAVEXEC;
    }	break;
    case NE_ERROR:
	/* HACK: "501 Not Implemented" if OPTIONS not permitted. */
	if (!strncmp("501 ", ne_get_error(u->sess), sizeof("501 ")-1)) {
	    u->allow |= RPMURL_SERVER_OPTIONSDONE;
	    rc = NE_OK;
	    break;
	}
	/* HACK: "301 Moved Permanently" on empty subdir. */
	if (!strncmp("301 ", ne_get_error(u->sess), sizeof("301 ")-1))
	    break;
	errno = EIO;		/* HACK: more precise errno. */
	goto bottom;
    case NE_LOOKUP:
	errno = ENOENT;		/* HACK: errno same as non-existent path. */
	goto bottom;
    case NE_CONNECT:		/* HACK: errno set already? */
    default:
bottom:
/*@-evalorderuncon@*/
if (_dav_debug)
fprintf(stderr, "*** Connect to %s:%d failed(%d):\n\t%s\n",
		   u->host, u->port, rc, ne_get_error(u->sess));
/*@=evalorderuncon@*/
	break;
    }

    /* HACK: sensitive to error returns? */
    u->httpVersion = (ne_version_pre_http11(u->sess) ? 0 : 1);

    return rc;
}

static int davInit(const char * url, urlinfo * uret)
	/*@globals internalState @*/
	/*@modifies *uret, internalState @*/
{
    urlinfo u = NULL;
    int rc = 0;

/*@-globs@*/	/* FIX: h_errno annoyance. */
    if (urlSplit(url, &u))
	return -1;	/* XXX error returns needed. */
/*@=globs@*/

    if (u->url != NULL && u->sess == NULL)
    switch (u->urltype) {
    default:
	assert(u->urltype != u->urltype);
	/*@notreached@*/ break;
    case URL_IS_HTTPS:
    case URL_IS_HTTP:
    case URL_IS_HKP:
      {	ne_server_capabilities * capabilities;

	/* HACK: oneshots should be done Somewhere Else Instead. */
/*@-noeffect@*/
	rc = ((_dav_debug < 0) ? NE_DBG_HTTP : 0);
	ne_debug_init(stderr, rc);		/* XXX oneshot? */
/*@=noeffect@*/
	rc = ne_sock_init();			/* XXX oneshot? */

	u->lockstore = ne_lockstore_create();	/* XXX oneshot? */

	u->capabilities = capabilities = xcalloc(1, sizeof(*capabilities));
	u->sess = ne_session_create(u->scheme, u->host, u->port);

	ne_lockstore_register(u->lockstore, u->sess);

	if (u->proxyh != NULL)
	    ne_session_proxy(u->sess, u->proxyh, u->proxyp);

#if 0
	{   const ne_inet_addr ** addrs;
	    unsigned int n;
	    ne_set_addrlist(u->sess, addrs, n);
	}
#endif

	ne_set_progress(u->sess, davProgress, u);
#if WITH_NEON_MIN_VERSION >= 0x002700
	ne_set_notifier(u->sess, davNotify, u);
#else
	ne_set_status(u->sess, davNotify, u);
#endif

	ne_set_persist(u->sess, 1);
	ne_set_read_timeout(u->sess, httpTimeoutSecs);
	ne_set_useragent(u->sess, PACKAGE "/" PACKAGE_VERSION);

	/* XXX check that neon is ssl enabled. */
	if (!strcasecmp(u->scheme, "https"))
	    ne_ssl_set_verify(u->sess, davVerifyCert, (char *)u->host);

	ne_set_session_private(u->sess, "urlinfo", u);

	ne_hook_destroy_session(u->sess, davDestroySession, u);

	ne_hook_create_request(u->sess, davCreateRequest, u);
	ne_hook_pre_send(u->sess, davPreSend, u);
	ne_hook_post_send(u->sess, davPostSend, u);
	ne_hook_destroy_request(u->sess, davDestroyRequest, u);

	/* HACK: where should server capabilities be read? */
	rc = davConnect(u);
	if (rc)
	    goto exit;
      }	break;
    }

exit:
    if (uret != NULL)
	*uret = urlLink(u, "davInit");
    u = urlFree(u, "urlSplit (davInit)");

    return rc;
}

/* =============================================================== */
enum fetch_rtype_e {
    resr_normal = 0,
    resr_collection,
    resr_reference,
    resr_error
};

struct fetch_resource_s {
/*@dependent@*/
    struct fetch_resource_s *next;
    char *uri;
/*@unused@*/
    char *displayname;
    enum fetch_rtype_e type;
    size_t size;
    time_t modtime;
    int is_executable;
    int is_vcr;    /* Is version resource. 0: no vcr, 1 checkin 2 checkout */
    char *error_reason; /* error string returned for this resource */
    int error_status; /* error status returned for this resource */
};

/*@null@*/
static void *fetch_destroy_item(/*@only@*/ struct fetch_resource_s *res)
	/*@modifies res @*/
{
    ne_free(res->uri);
    ne_free(res->error_reason);
    res = _free(res);
    return NULL;
}

#ifdef	NOTUSED
/*@null@*/
static void *fetch_destroy_list(/*@only@*/ struct fetch_resource_s *res)
	/*@modifies res @*/
{
    struct fetch_resource_s *next;
    for (; res != NULL; res = next) {
	next = res->next;
	res = fetch_destroy_item(res);
    }
    return NULL;
}
#endif

#if WITH_NEON_MIN_VERSION >= 0x002600
static void *fetch_create_item(/*@unused@*/ void *userdata, /*@unused@*/ const ne_uri *uri)
#else
static void *fetch_create_item(/*@unused@*/ void *userdata, /*@unused@*/ const char *uri)
#endif
        /*@*/
{
    struct fetch_resource_s * res = ne_calloc(sizeof(*res));
    return res;
}

/* =============================================================== */

/*@-readonlytrans@*/
/*@unchecked@*/ /*@observer@*/
static const ne_propname fetch_props[] = {
    { "DAV:", "getcontentlength" },
    { "DAV:", "getlastmodified" },
    { "http://apache.org/dav/props/", "executable" },
    { "DAV:", "resourcetype" },
    { "DAV:", "checked-in" },
    { "DAV:", "checked-out" },
    { NULL, NULL }
};
/*@=readonlytrans@*/

#define ELM_resourcetype (NE_PROPS_STATE_TOP + 1)
#define ELM_collection (NE_PROPS_STATE_TOP + 2)

/*@-readonlytrans@*/
/*@unchecked@*/ /*@observer@*/
static const struct ne_xml_idmap fetch_idmap[] = {
    { "DAV:", "resourcetype", ELM_resourcetype },
    { "DAV:", "collection", ELM_collection }
};
/*@=readonlytrans@*/

static int fetch_startelm(void *userdata, int parent,
		const char *nspace, const char *name,
		/*@unused@*/ const char **atts)
	/*@*/
{
    ne_propfind_handler *pfh = userdata;
    struct fetch_resource_s *r = ne_propfind_current_private(pfh);
/*@-sizeoftype@*/
    int state = ne_xml_mapid(fetch_idmap, NE_XML_MAPLEN(fetch_idmap),
                             nspace, name);
/*@=sizeoftype@*/

    if (r == NULL ||
        !((parent == NE_207_STATE_PROP && state == ELM_resourcetype) ||
          (parent == ELM_resourcetype && state == ELM_collection)))
        return NE_XML_DECLINE;

    if (state == ELM_collection) {
	r->type = resr_collection;
    }

    return state;
}

static int fetch_compare(const struct fetch_resource_s *r1,
			    const struct fetch_resource_s *r2)
	/*@*/
{
    /* Sort errors first, then collections, then alphabetically */
    if (r1->type == resr_error) {
	return -1;
    } else if (r2->type == resr_error) {
	return 1;
    } else if (r1->type == resr_collection) {
	if (r2->type != resr_collection) {
	    return -1;
	} else {
	    return strcmp(r1->uri, r2->uri);
	}
    } else {
	if (r2->type != resr_collection) {
	    return strcmp(r1->uri, r2->uri);
	} else {
	    return 1;
	}
    }
}

#if WITH_NEON_MIN_VERSION >= 0x002600
static void fetch_results(void *userdata, const ne_uri *uarg,
		    const ne_prop_result_set *set)
#else
static void fetch_results(void *userdata, void *uarg,
		    const ne_prop_result_set *set)
#endif
	/*@*/
{
    avContext ctx = userdata;
    struct fetch_resource_s *current, *previous, *newres;
    const char *clength, *modtime, *isexec;
    const char *checkin, *checkout;
    const ne_status *status = NULL;
    const char * path = NULL;

#if WITH_NEON_MIN_VERSION >= 0x002600
    const ne_uri * uri = uarg;
    (void) urlPath(uri->path, &path);
#else
    const char * uri = uarg;
    (void) urlPath(uri, &path);
#endif
    if (path == NULL)
	return;

    newres = ne_propset_private(set);

if (_dav_debug < 0)
fprintf(stderr, "==> %s in uri %s\n", path, ctx->uri);

    if (ne_path_compare(ctx->uri, path) == 0) {
	/* This is the target URI */
if (_dav_debug < 0)
fprintf(stderr, "==> %s skipping target resource.\n", path);
	/* Free the private structure. */
/*@-dependenttrans -exposetrans@*/
	free(newres);
/*@=dependenttrans =exposetrans@*/
	return;
    }

    newres->uri = ne_strdup(path);

    clength = ne_propset_value(set, &fetch_props[0]);
    modtime = ne_propset_value(set, &fetch_props[1]);
    isexec = ne_propset_value(set, &fetch_props[2]);
    checkin = ne_propset_value(set, &fetch_props[4]);
    checkout = ne_propset_value(set, &fetch_props[5]);

    if (clength == NULL)
	status = ne_propset_status(set, &fetch_props[0]);
    if (modtime == NULL)
	status = ne_propset_status(set, &fetch_props[1]);

    if (newres->type == resr_normal && status != NULL) {
	/* It's an error! */
	newres->error_status = status->code;

	/* Special hack for Apache 1.3/mod_dav */
	if (strcmp(status->reason_phrase, "status text goes here") == 0) {
	    const char *desc;
	    if (status->code == 401) {
		desc = _("Authorization Required");
	    } else if (status->klass == 3) {
		desc = _("Redirect");
	    } else if (status->klass == 5) {
		desc = _("Server Error");
	    } else {
		desc = _("Unknown Error");
	    }
	    newres->error_reason = ne_strdup(desc);
	} else {
	    newres->error_reason = ne_strdup(status->reason_phrase);
	}
	newres->type = resr_error;
    }

    if (isexec && strcasecmp(isexec, "T") == 0) {
	newres->is_executable = 1;
    } else {
	newres->is_executable = 0;
    }

    if (modtime)
	newres->modtime = ne_httpdate_parse(modtime);

    if (clength)
	newres->size = atoi(clength);

    /* is vcr */
    if (checkin) {
	newres->is_vcr = 1;
    } else if (checkout) {
	newres->is_vcr = 2;
    } else {
	newres->is_vcr = 0;
    }

    current = *(struct fetch_resource_s **)ctx->resrock;
    for (current = *ctx->resrock, previous = NULL; current != NULL;
	previous = current, current = current->next)
    {
	if (fetch_compare(current, newres) >= 0) {
	    break;
	}
    }
    if (previous) {
	previous->next = newres;
    } else {
/*@-dependenttrans @*/
        *(struct fetch_resource_s **)ctx->resrock = newres;
/*@=dependenttrans @*/
    }
    newres->next = current;
}

static int davFetch(const urlinfo u, avContext ctx)
	/*@globals internalState @*/
	/*@modifies ctx, internalState @*/
{
    const char * path = NULL;
    int depth = 1;					/* XXX passed arg? */
    struct fetch_resource_s * resitem = NULL;
    ne_propfind_handler *pfh;
    struct fetch_resource_s *current, *next;
    mode_t st_mode;
    int rc = 0;
    int xx;

    (void) urlPath(u->url, &path);
    pfh = ne_propfind_create(u->sess, ctx->uri, depth);

    /* HACK: need to set RPMURL_SERVER_HASRANGE in u->allow here. */

    ctx->resrock = (void **) &resitem;

    ne_xml_push_handler(ne_propfind_get_parser(pfh),
                        fetch_startelm, NULL, NULL, pfh);

    ne_propfind_set_private(pfh, fetch_create_item, NULL);

    rc = ne_propfind_named(pfh, fetch_props, fetch_results, ctx);

    ne_propfind_destroy(pfh);

    for (current = resitem; current != NULL; current = next) {
	const char *s, *se;
	char * val;

	next = current->next;

	/* Collections have trailing '/' that needs trim. */
	/* The top level collection is returned as well. */
	se = current->uri + strlen(current->uri);
	if (se[-1] == '/') {
	    if (strlen(current->uri) <= strlen(path)) {
		current = fetch_destroy_item(current);
		continue;
	    }
	    se--;
	}
	s = se;
	while (s > current->uri && s[-1] != '/')
	    s--;

	val = ne_strndup(s, (se - s));

/*@-nullpass@*/
	val = ne_path_unescape(val);
/*@=nullpass@*/

	switch (current->type) {
	case resr_normal:
	    st_mode = S_IFREG;
	    /*@switchbreak@*/ break;
	case resr_collection:
	    st_mode = S_IFDIR;
	    /*@switchbreak@*/ break;
	case resr_reference:
	case resr_error:
	default:
	    st_mode = 0;
	    /*@switchbreak@*/ break;
	}

	xx = avContextAdd(ctx, val, st_mode, current->size, current->modtime);
	ne_free(val);

	current = fetch_destroy_item(current);
    }
    ctx->resrock = NULL;	/* HACK: avoid leaving stack reference. */
    /* HACK realloc to truncate modes/sizes/mtimes */

    return rc;
}

/* HACK this should be rewritten to use davReq/davResp w callbacks. */
static int davHEAD(urlinfo u, struct stat *st) 
	/*@modifies *st @*/
{
    ne_request *req;
    const char *htag;
    const char *value = NULL;
    int rc;

    /* XXX HACK: URI's with pesky trailing '/' are directories. */
    {	size_t nb = strlen(u->url);
	st->st_mode = (u->url[nb-1] == '/' ? S_IFDIR : S_IFREG);
    }
    st->st_blksize = 4 * 1024;	/* HACK correct for linux ext */
    st->st_size = -1;
    st->st_atime = -1;
    st->st_mtime = -1;
    st->st_ctime = -1;

    req = ne_request_create(u->sess, "HEAD", u->url);

    rc = ne_request_dispatch(req);
    switch (rc) {
    default:
	goto exit;
	/*@notreached@*/ break;
    case NE_OK:
	if (ne_get_status(req)->klass != 2) {
	    rc = NE_ERROR;
	    goto exit;
	}
	break;
    }

#ifdef	NOTYET
    htag = "ETag";
    value = ne_get_response_header(req, htag); 
    if (value) {
	/* inode-size-mtime */
    }
#endif

    htag = "Content-Length";
#if defined(HAVE_NEON_NE_GET_RESPONSE_HEADER)
    value = ne_get_response_header(req, htag); 
#endif
    if (value) {
/*@-unrecog@*/	/* XXX LCLINT needs stdlib.h update. */
	st->st_size = strtoll(value, NULL, 10);
/*@=unrecog@*/
	st->st_blocks = (st->st_size + 511)/512;
    }

    htag = "Last-Modified";
#if defined(HAVE_NEON_NE_GET_RESPONSE_HEADER)
    value = ne_get_response_header(req, htag); 
#endif
    if (value) {
	st->st_mtime = ne_httpdate_parse(value);
	st->st_atime = st->st_ctime = st->st_mtime;	/* HACK */
    }

exit:
    ne_request_destroy(req);
    return rc;
}

static int davNLST(avContext ctx)
	/*@globals internalState @*/
	/*@modifies ctx, internalState @*/
{
    urlinfo u = NULL;
    int rc;
    int xx;

    rc = davInit(ctx->uri, &u);
    if (rc || u == NULL)
	goto exit;

/* HACK do PROPFIND through davFetch iff enabled, otherwise HEAD Content-length/ETag/Last-Modified */
    if (u->allow & RPMURL_SERVER_HASDAV)
	   rc = davFetch(u, ctx);	/* use PROPFIND to get contentLength */
    else {
/*@-nullpass@*/	/* XXX annotate ctx->st correctly */
	   rc = davHEAD(u, ctx->st);	/* use HEAD to get contentLength */
/*@=nullpass@*/
    }

    switch (rc) {
    case NE_OK:
        break;
    case NE_ERROR:
	/* HACK: "405 Method Not Allowed" for PROPFIND on non-DAV servers. */
	/* XXX #206066 OPTIONS is ok, but PROPFIND from Stat() fails. */
	/* rpm -qp --rpmiodebug --davdebug http://people.freedesktop.org/~sandmann/metacity-2.16.0-2.fc6/i386/metacity-2.16.0-2.fc6.i386.rpm */
	/* HACK: "301 Moved Permanently" on empty subdir. */
	if (!strncmp("301 ", ne_get_error(u->sess), sizeof("301 ")-1))
	    break;
	/*@fallthrough@*/
    default:
/*@-evalorderuncon@*/
if (_dav_debug)
fprintf(stderr, "*** Fetch from %s:%d failed:\n\t%s\n",
		   u->host, u->port, ne_get_error(u->sess));
/*@=evalorderuncon@*/
        break;
    }

exit:
    xx = davFree(u);
    return rc;
}

/* =============================================================== */
static int my_result(const char * msg, int ret, /*@null@*/ FILE * fp)
	/*@modifies *fp @*/
{
    /* HACK: don't print unless debugging. */
    if (_dav_debug >= 0)
	return ret;
    if (fp == NULL)
	fp = stderr;
    if (msg != NULL)
	fprintf(fp, "*** %s: ", msg);

    /* HACK FTPERR_NE_FOO == -NE_FOO error impedance match */
#ifdef	HACK
    fprintf(fp, "%s: %s\n", ftpStrerror(-ret), ne_get_error(sess));
#else
    fprintf(fp, "%s\n", ftpStrerror(-ret));
#endif
    return ret;
}

#ifdef	DYING
static void hexdump(const unsigned char * buf, ssize_t len)
	/*@*/
{
    int i;
    if (len <= 0)
	return;
    for (i = 0; i < len; i++) {
	if (i != 0 && (i%16) == 0)
	    fprintf(stderr, "\n");
	fprintf(stderr, " %02X", buf[i]);
    }
    fprintf(stderr, "\n");
}
#endif

/*@-mustmod@*/
static void davAcceptRanges(void * userdata, /*@null@*/ const char * value)
	/*@modifies userdata @*/
{
    urlinfo u = userdata;

    if (!(u != NULL && value != NULL)) return;
if (_dav_debug < 0)
fprintf(stderr, "*** u %p Accept-Ranges: %s\n", u, value);
    if (!strcmp(value, "bytes"))
	u->allow |= RPMURL_SERVER_HASRANGE;
    if (!strcmp(value, "none"))
	u->allow &= ~RPMURL_SERVER_HASRANGE;
}
/*@=mustmod@*/

#if !defined(HAVE_NEON_NE_GET_RESPONSE_HEADER)
static void davAllHeaders(void * userdata, const char * value)
{
    FD_t ctrl = userdata;

    if (!(ctrl != NULL && value != NULL)) return;
if (_dav_debug)
fprintf(stderr, "<- %s\n", value);
}
#endif

/*@-mustmod@*/
static void davContentLength(void * userdata, /*@null@*/ const char * value)
	/*@modifies userdata @*/
{
    FD_t ctrl = userdata;

    if (!(ctrl != NULL && value != NULL)) return;
if (_dav_debug < 0)
fprintf(stderr, "*** fd %p Content-Length: %s\n", ctrl, value);
/*@-unrecog@*/
   ctrl->contentLength = strtoll(value, NULL, 10);
/*@=unrecog@*/
}
/*@=mustmod@*/

/*@-mustmod@*/
static void davConnection(void * userdata, /*@null@*/ const char * value)
	/*@modifies userdata @*/
{
    FD_t ctrl = userdata;

    if (!(ctrl != NULL && value != NULL)) return;
if (_dav_debug < 0)
fprintf(stderr, "*** fd %p Connection: %s\n", ctrl, value);
    if (!strcasecmp(value, "close"))
	ctrl->persist = 0;
    else if (!strcasecmp(value, "Keep-Alive"))
	ctrl->persist = 1;
}
/*@=mustmod@*/

/*@-mustmod@*/ /* HACK: stash error in *str. */
int davResp(urlinfo u, FD_t ctrl, /*@unused@*/ char *const * str)
{
    int rc = 0;

    rc = ne_begin_request(ctrl->req);
    rc = my_result("ne_begin_req(ctrl->req)", rc, NULL);

if (_dav_debug < 0)
fprintf(stderr, "*** davResp(%p,%p,%p) sess %p req %p rc %d\n", u, ctrl, str, u->sess, ctrl->req, rc);

    /* HACK FTPERR_NE_FOO == -NE_FOO error impedance match */
/*@-observertrans@*/
    if (rc)
	fdSetSyserrno(ctrl, errno, ftpStrerror(-rc));
/*@=observertrans@*/

    return rc;
}
/*@=mustmod@*/

int davReq(FD_t ctrl, const char * httpCmd, const char * httpArg)
{
    urlinfo u;
    int rc = 0;

assert(ctrl != NULL);
    u = ctrl->url;
    URLSANE(u);

if (_dav_debug < 0)
fprintf(stderr, "*** davReq(%p,%s,\"%s\") entry sess %p req %p\n", ctrl, httpCmd, (httpArg ? httpArg : ""), u->sess, ctrl->req);

    ctrl->persist = (u->httpVersion > 0 ? 1 : 0);
    ctrl = fdLink(ctrl, "open ctrl (davReq)");

assert(u->sess != NULL);
assert(ctrl->req == NULL);
/*@-nullpass@*/
    ctrl->req = ne_request_create(u->sess, httpCmd, httpArg);
/*@=nullpass@*/
assert(ctrl->req != NULL);

    ne_set_request_private(ctrl->req, "fd", ctrl);

#if !defined(HAVE_NEON_NE_GET_RESPONSE_HEADER)
    ne_add_response_header_catcher(ctrl->req, davAllHeaders, ctrl);

    ne_add_response_header_handler(ctrl->req, "Content-Length",
		davContentLength, ctrl);
    ne_add_response_header_handler(ctrl->req, "Connection",
		davConnection, ctrl);
#endif

    if (!strcmp(httpCmd, "PUT")) {
#if defined(HAVE_NEON_NE_SEND_REQUEST_CHUNK)
	ctrl->wr_chunked = 1;
	ne_add_request_header(ctrl->req, "Transfer-Encoding", "chunked");
	ne_set_request_chunked(ctrl->req, 1);
	/* HACK: no retries if/when chunking. */
	rc = davResp(u, ctrl, NULL);
#else
	rc = FTPERR_SERVER_IO_ERROR;
#endif
    } else {
	/* HACK: possible Last-Modified: Tue, 02 Nov 2004 14:29:36 GMT */
	/* HACK: possible ETag: "inode-size-mtime" */
#if !defined(HAVE_NEON_NE_GET_RESPONSE_HEADER)
	ne_add_response_header_handler(ctrl->req, "Accept-Ranges",
			davAcceptRanges, u);
#endif
	/* HACK: possible Transfer-Encoding: on GET. */

	/* HACK: other errors may need retry too. */
	/* HACK: neon retries once, gud enuf. */
	/* HACK: retry counter? */
	do {
	    rc = davResp(u, ctrl, NULL);
	} while (rc == NE_RETRY);
    }
    if (rc)
	goto errxit;

if (_dav_debug < 0)
fprintf(stderr, "*** davReq(%p,%s,\"%s\") exit sess %p req %p rc %d\n", ctrl, httpCmd, (httpArg ? httpArg : ""), u->sess, ctrl->req, rc);

#if defined(HAVE_NEON_NE_GET_RESPONSE_HEADER)
    davContentLength(ctrl,
		ne_get_response_header(ctrl->req, "Content-Length"));
    davConnection(ctrl,
		ne_get_response_header(ctrl->req, "Connection"));
    if (strcmp(httpCmd, "PUT"))
	davAcceptRanges(u,
		ne_get_response_header(ctrl->req, "Accept-Ranges"));
#endif

    ctrl = fdLink(ctrl, "open data (davReq)");
    return 0;

errxit:
/*@-observertrans@*/
    fdSetSyserrno(ctrl, errno, ftpStrerror(rc));
/*@=observertrans@*/

    /* HACK balance fd refs. ne_session_destroy to tear down non-keepalive? */
    ctrl = fdLink(ctrl, "error data (davReq)");

    return rc;
}

FD_t davOpen(const char * url, /*@unused@*/ int flags,
		/*@unused@*/ mode_t mode, /*@out@*/ urlinfo * uret)
{
    const char * path = NULL;
    urltype urlType = urlPath(url, &path);
    urlinfo u = NULL;
    FD_t fd = NULL;
    int rc;

#if 0	/* XXX makeTempFile() heartburn */
    assert(!(flags & O_RDWR));
#endif

if (_dav_debug < 0)
fprintf(stderr, "*** davOpen(%s,0x%x,0%o,%p)\n", url, flags, (unsigned)mode, uret);
    rc = davInit(url, &u);
    if (rc || u == NULL || u->sess == NULL)
	goto exit;

    if (u->ctrl == NULL)
	u->ctrl = fdNew("persist ctrl (davOpen)");
    if (u->ctrl->nrefs > 2 && u->data == NULL)
	u->data = fdNew("persist data (davOpen)");

    if (u->ctrl->url == NULL)
	fd = fdLink(u->ctrl, "grab ctrl (davOpen persist ctrl)");
    else if (u->data->url == NULL)
	fd = fdLink(u->data, "grab ctrl (davOpen persist data)");
    else
	fd = fdNew("grab ctrl (davOpen)");

    if (fd) {
	fdSetOpen(fd, url, flags, mode);
	fdSetIo(fd, ufdio);

	fd->ftpFileDoneNeeded = 0;
	fd->rd_timeoutsecs = httpTimeoutSecs;
	fd->contentLength = fd->bytesRemain = -1;
	fd->url = urlLink(u, "url (davOpen)");
	fd = fdLink(fd, "grab data (davOpen)");
assert(urlType == URL_IS_HTTPS || urlType == URL_IS_HTTP || urlType == URL_IS_HKP);
	fd->urlType = urlType;
    }

exit:
    if (uret)
	*uret = u;
    /*@-refcounttrans@*/
    return fd;
    /*@=refcounttrans@*/
}

ssize_t davRead(void * cookie, /*@out@*/ char * buf, size_t count)
{
    FD_t fd = cookie;
    ssize_t rc;

#if 0
assert(count >= 128);	/* HACK: see ne_request.h comment */
#endif
    rc = ne_read_response_block(fd->req, buf, count);

if (_dav_debug < 0) {
fprintf(stderr, "*** davRead(%p,%p,0x%x) rc 0x%x\n", cookie, buf, (unsigned)count, (unsigned)rc);
#ifdef	DYING
hexdump(buf, rc);
#endif
    }

    return rc;
}

ssize_t davWrite(void * cookie, const char * buf, size_t count)
{
#if !defined(NEONBLOWSCHUNKS) || defined(HAVE_NEON_NE_SEND_REQUEST_CHUNK) || defined(__LCLINT__)
    FD_t fd = cookie;
#endif
    ssize_t rc;
    int xx = -1;

#if !defined(NEONBLOWSCHUNKS)
    ne_session * sess;

assert(fd->req != NULL);
    sess = ne_get_session(fd->req);
assert(sess != NULL);

    /* HACK: include ne_private.h to access sess->socket for now. */
    xx = ne_sock_fullwrite(sess->socket, buf, count);
#else
#if defined(HAVE_NEON_NE_SEND_REQUEST_CHUNK) || defined(__LCLINT__)
assert(fd->req != NULL);
/*@-unrecog@*/
    xx = ne_send_request_chunk(fd->req, buf, count);
/*@=unrecog@*/
#else
    errno = EIO;       /* HACK */
    return -1;
#endif
#endif

    /* HACK: stupid error impedence matching. */
    rc = (xx == 0 ? (ssize_t)count : -1);

if (_dav_debug < 0)
fprintf(stderr, "*** davWrite(%p,%p,0x%x) rc 0x%x\n", cookie, buf, (unsigned)count, (unsigned)rc);
#ifdef	DYING
if (count > 0)
hexdump(buf, count);
#endif

    return rc;
}

int davSeek(void * cookie, /*@unused@*/ _libio_pos_t pos, int whence)
{
if (_dav_debug < 0)
fprintf(stderr, "*** davSeek(%p,pos,%d)\n", cookie, whence);
    return -1;
}

/*@-mustmod@*/	/* HACK: fd->req is modified. */
int davClose(void * cookie)
{
/*@-onlytrans@*/
    FD_t fd = cookie;
/*@=onlytrans@*/
    int rc;

assert(fd->req != NULL);
    rc = ne_end_request(fd->req);
    rc = my_result("ne_end_request(req)", rc, NULL);

    ne_request_destroy(fd->req);
    fd->req = NULL;

if (_dav_debug < 0)
fprintf(stderr, "*** davClose(%p) rc %d\n", fd, rc);
    return rc;
}
/*@=mustmod@*/

/* =============================================================== */
int davMkdir(const char * path, mode_t mode)
{
    urlinfo u = NULL;
    const char * src = NULL;
    int rc;

    rc = davInit(path, &u);
    if (rc)
	goto exit;

    (void) urlPath(path, &src);

    rc = ne_mkcol(u->sess, path);

    if (rc) rc = -1;	/* XXX HACK: errno impedance match */

    /* XXX HACK: verify getrestype(remote) == resr_collection */

exit:
if (_dav_debug)
fprintf(stderr, "*** davMkdir(%s,0%o) rc %d\n", path, (unsigned)mode, rc);
    return rc;
}

int davRmdir(const char * path)
{
    urlinfo u = NULL;
    const char * src = NULL;
    int rc;

    rc = davInit(path, &u);
    if (rc)
	goto exit;

    (void) urlPath(path, &src);

    /* XXX HACK: only getrestype(remote) == resr_collection */

    rc = ne_delete(u->sess, path);

    if (rc) rc = -1;	/* XXX HACK: errno impedance match */

exit:
if (_dav_debug)
fprintf(stderr, "*** davRmdir(%s) rc %d\n", path, rc);
    return rc;
}

int davRename(const char * oldpath, const char * newpath)
{
    urlinfo u = NULL;
    const char * src = NULL;
    const char * dst = NULL;
    int overwrite = 1;		/* HACK: set this correctly. */
    int rc;

    rc = davInit(oldpath, &u);
    if (rc)
	goto exit;

    (void) urlPath(oldpath, &src);
    (void) urlPath(newpath, &dst);

    /* XXX HACK: only getrestype(remote) != resr_collection */

    rc = ne_move(u->sess, overwrite, src, dst);

    if (rc) rc = -1;	/* XXX HACK: errno impedance match */

exit:
if (_dav_debug)
fprintf(stderr, "*** davRename(%s,%s) rc %d\n", oldpath, newpath, rc);
    return rc;
}

int davUnlink(const char * path)
{
    urlinfo u = NULL;
    const char * src = NULL;
    int rc;

    rc = davInit(path, &u);
    if (rc)
	goto exit;

    (void) urlPath(path, &src);

    /* XXX HACK: only getrestype(remote) != resr_collection */

    rc = ne_delete(u->sess, src);

exit:
    if (rc) rc = -1;	/* XXX HACK: errno impedance match */

if (_dav_debug)
fprintf(stderr, "*** davUnlink(%s) rc %d\n", path, rc);
    return rc;
}

#ifdef	NOTYET
static int davChdir(const char * path)
	/*@globals h_errno, fileSystem, internalState @*/
	/*@modifies fileSystem, internalState @*/
{
    return davCommand("CWD", path, NULL);
}
#endif	/* NOTYET */

/* =============================================================== */

static const char * statstr(const struct stat * st,
		/*@returned@*/ /*@out@*/ char * buf)
	/*@modifies *buf @*/
{
    sprintf(buf,
	"*** dev %x ino %x mode %0o nlink %d uid %d gid %d rdev %x size %x\n",
	(unsigned)st->st_dev,
	(unsigned)st->st_ino,
	(unsigned)st->st_mode,
	(unsigned)st->st_nlink,
	(unsigned)st->st_uid,
	(unsigned)st->st_gid,
	(unsigned)st->st_rdev,
	(unsigned)st->st_size);
    return buf;
}

int davStat(const char * path, /*@out@*/ struct stat *st)
	/*@globals fileSystem, internalState @*/
	/*@modifies *st, fileSystem, internalState @*/
{
    avContext ctx = NULL;
    char buf[1024];
    int rc = -1;

    if (path == NULL || *path == '\0') {
	errno = ENOENT;
	goto exit;
    }
    ctx = avContextCreate(path, st);
    if (ctx == NULL) {
	errno = ENOENT;		/* Note: ctx is NULL iff urlSplit() fails. */
	goto exit;
    }
    rc = davNLST(ctx);
    if (rc) {
/* HACK: errno = ??? */
	goto exit;
    }

    if (st->st_mode == 0)
	st->st_mode = (ctx->ac > 1 ? S_IFDIR : S_IFREG);
    st->st_size = (ctx->sizes ? ctx->sizes[0] : st->st_size);
    st->st_mtime = (ctx->mtimes ? ctx->mtimes[0] : st->st_mtime);
    st->st_atime = st->st_ctime = st->st_mtime;	/* HACK */
    if (S_ISDIR(st->st_mode)) {
	st->st_nlink = 2;
	st->st_mode |= 0755;
    } else
    if (S_ISREG(st->st_mode)) {
	st->st_nlink = 1;
	st->st_mode |= 0644;
    }

    /* XXX Fts(3) needs/uses st_ino. */
    /* Hash the path to generate a st_ino analogue. */
    if (st->st_ino == 0)
	st->st_ino = hashFunctionString(0, path, 0);

exit:
if (_dav_debug < 0)
fprintf(stderr, "*** davStat(%s) rc %d\n%s", path, rc, statstr(st, buf));
    ctx = avContextDestroy(ctx);
    return rc;
}

int davLstat(const char * path, /*@out@*/ struct stat *st)
	/*@globals fileSystem, internalState @*/
	/*@modifies *st, fileSystem, internalState @*/
{
    avContext ctx = NULL;
    char buf[1024];
    int rc = -1;

    if (path == NULL || *path == '\0') {
	errno = ENOENT;
	goto exit;
    }
    ctx = avContextCreate(path, st);
    if (ctx == NULL) {
	errno = ENOENT;		/* Note: ctx is NULL iff urlSplit() fails. */
	goto exit;
    }
    rc = davNLST(ctx);
    if (rc) {
/* HACK: errno = ??? */
	goto exit;
    }

    if (st->st_mode == 0)
	st->st_mode = (ctx->ac > 1 ? S_IFDIR : S_IFREG);
    st->st_size = (ctx->sizes ? ctx->sizes[0] : st->st_size);
    st->st_mtime = (ctx->mtimes ? ctx->mtimes[0] : st->st_mtime);
    st->st_atime = st->st_ctime = st->st_mtime;	/* HACK */
    if (S_ISDIR(st->st_mode)) {
	st->st_nlink = 2;
	st->st_mode |= 0755;
    } else
    if (S_ISREG(st->st_mode)) {
	st->st_nlink = 1;
	st->st_mode |= 0644;
    }

    /* XXX fts(3) needs/uses st_ino. */
    /* Hash the path to generate a st_ino analogue. */
    if (st->st_ino == 0)
	st->st_ino = hashFunctionString(0, path, 0);

if (_dav_debug < 0)
fprintf(stderr, "*** davLstat(%s) rc %d\n%s\n", path, rc, statstr(st, buf));
exit:
    ctx = avContextDestroy(ctx);
    return rc;
}

#ifdef	NOTYET
static int davReadlink(const char * path, /*@out@*/ char * buf, size_t bufsiz)
	/*@globals h_errno, fileSystem, internalState @*/
	/*@modifies *buf, fileSystem, internalState @*/
{
    int rc;
    rc = davNLST(path, DO_FTP_READLINK, NULL, buf, bufsiz);
if (_dav_debug < 0)
fprintf(stderr, "*** davReadlink(%s) rc %d\n", path, rc);
    return rc;
}
#endif	/* NOTYET */

#endif /* WITH_NEON */

/* =============================================================== */
/*@unchecked@*/
int avmagicdir = 0x3607113;

#ifndef WITH_NEON
/*@-nullstate@*/        /* FIX: u->{ctrl,data}->url undef after XurlLink. */
FD_t httpOpen(const char * url, /*@unused@*/ int flags,
                /*@unused@*/ mode_t mode, /*@out@*/ urlinfo * uret)
        /*@globals internalState @*/
        /*@modifies *uret, internalState @*/
{
    urlinfo u = NULL;
    FD_t fd = NULL;

#if 0   /* XXX makeTempFile() heartburn */
    assert(!(flags & O_RDWR));
#endif
    if (urlSplit(url, &u))
        goto exit;

    if (u->ctrl == NULL)
        u->ctrl = fdNew("persist ctrl (httpOpen)");
    if (u->ctrl->nrefs > 2 && u->data == NULL)
        u->data = fdNew("persist data (httpOpen)");

    if (u->ctrl->url == NULL)
        fd = fdLink(u->ctrl, "grab ctrl (httpOpen persist ctrl)");
    else if (u->data->url == NULL)
        fd = fdLink(u->data, "grab ctrl (httpOpen persist data)");
    else
        fd = fdNew("grab ctrl (httpOpen)");

    if (fd) {
        fdSetIo(fd, ufdio);
        fd->ftpFileDoneNeeded = 0;
        fd->rd_timeoutsecs = httpTimeoutSecs;
        fd->contentLength = fd->bytesRemain = -1;
        fd->url = urlLink(u, "url (httpOpen)");
        fd = fdLink(fd, "grab data (httpOpen)");
        fd->urlType = URL_IS_HTTP;
    }

exit:
    if (uret)
        *uret = u;
    /*@-refcounttrans@*/
    return fd;
    /*@=refcounttrans@*/
}
/*@=nullstate@*/
#endif

#ifdef WITH_NEON
/* =============================================================== */
int davClosedir(/*@only@*/ DIR * dir)
{
    return avClosedir(dir);
}

struct dirent * davReaddir(DIR * dir)
{
    return avReaddir(dir);
}

DIR * davOpendir(const char * path)
{
    AVDIR avdir;
    avContext ctx;
    struct stat sb, *st = &sb; /* XXX HACK: davHEAD needs ctx->st. */
    int rc;

if (_dav_debug < 0)
fprintf(stderr, "*** davOpendir(%s)\n", path);

    /* Note: all URI's need pesky trailing '/' */
    if (path == NULL || *path == '\0' || path[strlen(path)-1] != '/') {
	errno = ENOENT;
	return NULL;
    }

    /* Load DAV collection into argv. */
    /* XXX HACK: davHEAD needs ctx->st. */
    ctx = avContextCreate(path, st);
    if (ctx == NULL) {
	errno = ENOENT;		/* Note: ctx is NULL iff urlSplit() fails. */
	return NULL;
    }
    rc = davNLST(ctx);
    if (rc) {
/* HACK: errno = ??? */
	return NULL;
    }

    avdir = (AVDIR) avOpendir(path, ctx->av, ctx->modes);

    ctx = avContextDestroy(ctx);

/*@-kepttrans@*/
    return (DIR *) avdir;
/*@=kepttrans@*/
}
/*@=modfilesys@*/

char * davRealpath(const char * path, char * resolved_path)
{
assert(resolved_path == NULL);	/* XXX no POSIXly broken realpath(3) here. */
    /* XXX TODO: handle redirects. For now, just dupe the path. */
    return xstrdup(path);
}

#endif /* WITH_NEON */
