static int caldav_put(struct transaction_t *txn,
		      struct mime_type_t *mime,
		      struct mailbox *mailbox,
		      struct caldav_db *davdb,
		      unsigned flags)
{
    int ret = 0;
    icalcomponent *ical = NULL, *comp, *nextcomp;
    icalcomponent_kind kind;
    icalproperty_kind recip_kind;
    icalproperty *prop;
    const char *uid, *organizer = NULL;

    /* Parse and validate the iCal data */
    ical = mime->from_string(buf_cstring(&txn->req_body.payload));
    if (!ical || (icalcomponent_isa(ical) != ICAL_VCALENDAR_COMPONENT)) {
	txn->error.precond = CALDAV_VALID_DATA;
	ret = HTTP_FORBIDDEN;
	goto done;
    }

    icalrestriction_check(ical);
    if ((txn->error.desc = get_icalcomponent_errstr(ical))) {
	assert(!buf_len(&txn->buf));
	buf_setcstr(&txn->buf, txn->error.desc);
	txn->error.desc = buf_cstring(&txn->buf);
	txn->error.precond = CALDAV_VALID_DATA;
	ret = HTTP_FORBIDDEN;
	goto done;
    }

    comp = icalcomponent_get_first_real_component(ical);

#ifdef HAVE_RSCALE
    /* Make sure we support the provided RSCALE in an RRULE */
    prop = icalcomponent_get_first_property(comp, ICAL_RRULE_PROPERTY);
    if (prop && rscale_calendars) {
	struct icalrecurrencetype rt = icalproperty_get_rrule(prop);

	if (rt.rscale) {
	    /* Perform binary search on sorted icalarray */
	    unsigned found = 0, start = 0, end = rscale_calendars->num_elements;

	    ucase(rt.rscale);
	    while (!found && start < end) {
		unsigned mid = start + (end - start) / 2;
		const char **rscale = icalarray_element_at(rscale_calendars, mid);
		int r = strcmp(rt.rscale, *rscale);

		if (r == 0) found = 1;
		else if (r < 0) end = mid;
		else start = mid + 1;
	    }

	    if (!found) {
		txn->error.precond = CALDAV_SUPP_RSCALE;
		ret = HTTP_FORBIDDEN;
		goto done;
	    }
	}
    }
#endif /* HAVE_RSCALE */

    /* Make sure iCal UIDs [and ORGANIZERs] in all components are the same */
    kind = icalcomponent_isa(comp);
    uid = icalcomponent_get_uid(comp);
    prop = icalcomponent_get_first_property(comp, ICAL_ORGANIZER_PROPERTY);
    if (prop) organizer = icalproperty_get_organizer(prop);
    while ((nextcomp =
	    icalcomponent_get_next_component(ical, kind))) {
	const char *nextuid = icalcomponent_get_uid(nextcomp);

	if (!nextuid || strcmp(uid, nextuid)) {
	    txn->error.precond = CALDAV_VALID_OBJECT;
	    ret = HTTP_FORBIDDEN;
	    goto done;
	}

	const char *nextorg = NULL;

	prop = icalcomponent_get_first_property(nextcomp,
						ICAL_ORGANIZER_PROPERTY);
	if (prop) nextorg = icalproperty_get_organizer(prop);
	if ( (!organizer && nextorg)
	     || (organizer && (!nextorg || strcmp(organizer, nextorg)))) {
	    txn->error.precond = CALDAV_SAME_ORGANIZER;
	    ret = HTTP_FORBIDDEN;
	    goto done;
	}
    }

    switch (kind) {
    case ICAL_VEVENT_COMPONENT:
    case ICAL_VTODO_COMPONENT:
    case ICAL_VPOLL_COMPONENT:
	recip_kind = (kind == ICAL_VPOLL_COMPONENT) ?
	    ICAL_VOTER_PROPERTY : ICAL_ATTENDEE_PROPERTY;

	if ((namespace_calendar.allow & ALLOW_CAL_SCHED) && organizer
	    /* XXX  Hack for Outlook */
	    && icalcomponent_get_first_property(comp, recip_kind)) {
	    /* Scheduling object resource */
	    const char *userid;
	    struct caldav_data *cdata;
	    struct caldav_sched_param sparam;
	    icalcomponent *oldical = NULL;
	    int r;

	    /* Construct userid corresponding to mailbox */
	    userid = mboxname_to_userid(txn->req_tgt.mboxname);

	    /* Make sure iCal UID is unique for this user */
	    caldav_lookup_uid(davdb, uid, 0, &cdata);
	    /* XXX  Check errors */

	    if (cdata->dav.mailbox &&
		(strcmp(cdata->dav.mailbox, txn->req_tgt.mboxname) ||
		 strcmp(cdata->dav.resource, txn->req_tgt.resource))) {

		buf_printf(&txn->buf, "%s/user/%s/%s/%s",
			   namespace_calendar.prefix,
			   userid, strrchr(cdata->dav.mailbox, '.')+1,
			   cdata->dav.resource);
		txn->error.resource = buf_cstring(&txn->buf);
		ret = HTTP_FORBIDDEN;
		goto done;
	    }

	    /* Lookup the organizer */
	    r = caladdress_lookup(organizer, &sparam);
	    if (r == HTTP_NOT_FOUND)
		break;  /* not a local organiser?  Just skip it */
	    if (r) {
		syslog(LOG_ERR,
		       "meth_put: failed to process scheduling message in %s"
		       " (org=%s)",
		       txn->req_tgt.mboxname, organizer);
		txn->error.desc = "Failed to lookup organizer address\r\n";
		ret = HTTP_SERVER_ERROR;
		goto done;
	    }

	    if (cdata->dav.imap_uid) {
		/* Update existing object */
		struct index_record record;

		/* Load message containing the resource and parse iCal data */
		r = mailbox_find_index_record(mailbox, cdata->dav.imap_uid, &record);
		if (r) {
		    txn->error.desc = "Failed to read record \r\n";
		    ret = HTTP_SERVER_ERROR;
		    goto done;
		}
		oldical = record_to_ical(mailbox, &record);
	    }

	    if (cdata->organizer) {
		/* Don't allow ORGANIZER to be changed */
		const char *p = organizer;

		if (!strncasecmp(p, "mailto:", 7)) p += 7;
		if (strcmp(cdata->organizer, p)) {
		    txn->error.desc = "Can not change organizer address";
		    ret = HTTP_FORBIDDEN;
		}
	    }

	    if (!strcmpsafe(sparam.userid, userid)) {
		/* Organizer scheduling object resource */
		if (ret) {
		    txn->error.precond = CALDAV_ALLOWED_ORG_CHANGE;
		    goto done;
		}
		sched_request(organizer, &sparam, oldical, ical, 0);
	    }
	    else {
		/* Attendee scheduling object resource */
		if (ret) {
		    txn->error.precond = CALDAV_ALLOWED_ATT_CHANGE;
		    goto done;
		}
#if 0
		if (!oldical) {
		    /* Can't reply to a non-existent invitation */
		    /* XXX  But what about invites over iMIP? */
		    txn->error.desc = "Can not reply to non-existent resource";
		    ret = HTTP_FORBIDDEN;
		    goto done;
		}
#endif
		sched_reply(userid, oldical, ical);
	    }

	    if (oldical) icalcomponent_free(oldical);

	    flags |= NEW_STAG;
	}
	break;

    case ICAL_VJOURNAL_COMPONENT:
    case ICAL_VFREEBUSY_COMPONENT:
    case ICAL_VAVAILABILITY_COMPONENT:
	/* Nothing else to do */
	break;

    default:
	txn->error.precond = CALDAV_SUPP_COMP;
	return HTTP_FORBIDDEN;
	break;
    }

    /* Store resource at target */
    ret = store_resource(txn, ical, mailbox, txn->req_tgt.resource,
			 davdb, OVERWRITE_CHECK, flags);

    if (flags & PREFER_REP) {
	struct resp_body_t *resp_body = &txn->resp_body;
	const char **hdr;
	char *data;

	if ((hdr = spool_getheader(txn->req_hdrs, "Accept")))
	    mime = get_accept_type(hdr, caldav_mime_types);
	if (!mime) goto done;

	switch (ret) {
	case HTTP_NO_CONTENT:
	    ret = HTTP_OK;

	case HTTP_CREATED:
	    /* Convert into requested MIME type */
	    data = mime->to_string(ical);

	    /* Fill in Content-Type, Content-Length */
	    resp_body->type = mime->content_type;
	    resp_body->len = strlen(data);

	    /* Fill in Content-Location */
	    resp_body->loc = txn->req_tgt.path;

	    /* Fill in Expires and Cache-Control */
	    resp_body->maxage = 3600;	/* 1 hr */
	    txn->flags.cc = CC_MAXAGE
		| CC_REVALIDATE		/* don't use stale data */
		| CC_NOTRANSFORM;	/* don't alter iCal data */

	    /* Output current representation */
	    write_body(ret, txn, data, resp_body->len);

	    free(data);
	    ret = 0;
	    break;

	default:
	    /* failure - do nothing */
	    break;
	}
    }

  done:
    if (ical) icalcomponent_free(ical);

    return ret;
}