EXPORTED int check_precond(struct transaction_t *txn, const void *data,
		  const char *etag, time_t lastmod)
{
    const char *lock_token = NULL;
    unsigned locked = 0;
    hdrcache_t hdrcache = txn->req_hdrs;
    const char **hdr;
    time_t since;

#ifdef WITH_DAV
    struct dav_data *ddata = (struct dav_data *) data;

    /* Check for a write-lock on the source */
    if (ddata && ddata->lock_expire > time(NULL)) {
	lock_token = ddata->lock_token;

	switch (txn->meth) {
	case METH_DELETE:
	case METH_LOCK:
	case METH_MOVE:
	case METH_POST:
	case METH_PUT:
	    /* State-changing method: Only the lock owner can execute
	       and MUST provide the correct lock-token in an If header */
	    if (strcmp(ddata->lock_ownerid, httpd_userid)) return HTTP_LOCKED;

	    locked = 1;
	    break;

	case METH_UNLOCK:
	    /* State-changing method: Authorized in meth_unlock() */
	    break;

	case METH_ACL:
	case METH_MKCALENDAR:
	case METH_MKCOL:
	case METH_PROPPATCH:
	    /* State-changing method: Locks on collections unsupported */
	    break;

	default:
	    /* Non-state-changing method: Always allowed */
	    break;
	}
    }
#else
    assert(!data);
#endif /* WITH_DAV */

    /* Per RFC 4918, If is similar to If-Match, but with lock-token submission.
       Per Section 5 of HTTPbis, Part 4, LOCK errors supercede preconditions */
    if ((hdr = spool_getheader(hdrcache, "If"))) {
	/* State tokens (sync-token, lock-token) and Etags */
	if (!eval_if(hdr[0], etag, lock_token, &locked))
	    return HTTP_PRECOND_FAILED;
    }

    if (locked) {
	/* Correct lock-token was not provided in If header */
	return HTTP_LOCKED;
    }

    /* Evaluate other precondition headers per Section 5 of HTTPbis, Part 4 */

    /* Step 1 */
    if ((hdr = spool_getheader(hdrcache, "If-Match"))) {
	if (!etag_match(hdr, etag)) return HTTP_PRECOND_FAILED;

	/* Continue to step 3 */
    }

    /* Step 2 */
    else if ((hdr = spool_getheader(hdrcache, "If-Unmodified-Since"))) {
	if (time_from_rfc822(hdr[0], &since))
	    return HTTP_BAD_REQUEST;

	if (since && (lastmod > since)) return HTTP_PRECOND_FAILED;

	/* Continue to step 3 */
    }

    /* Step 3 */
    if ((hdr = spool_getheader(hdrcache, "If-None-Match"))) {
	if (etag_match(hdr, etag)) {
	    if (txn->meth == METH_GET || txn->meth == METH_HEAD)
		return HTTP_NOT_MODIFIED;
	    else
		return HTTP_PRECOND_FAILED;
	}

	/* Continue to step 5 */
    }

    /* Step 4 */
    else if ((txn->meth == METH_GET || txn->meth == METH_HEAD) &&
	     (hdr = spool_getheader(hdrcache, "If-Modified-Since"))) {
	if (time_from_rfc822(hdr[0], &since))
	    return HTTP_BAD_REQUEST;

	if (lastmod <= since) return HTTP_NOT_MODIFIED;

	/* Continue to step 5 */
    }

    /* Step 5 */
    if (txn->flags.ranges &&  /* Only if we support Range requests */
	txn->meth == METH_GET && (hdr = spool_getheader(hdrcache, "Range"))) {

	if ((hdr = spool_getheader(hdrcache, "If-Range"))) {
	    time_from_rfc822(hdr[0], &since); /* error OK here, could be an etag */
	}

	/* Only process Range if If-Range isn't present or validator matches */
	if (!hdr || (since && (lastmod <= since)) || !etagcmp(hdr[0], etag))
	    return HTTP_PARTIAL;
    }

    /* Step 6 */
    return HTTP_OK;
}