#include <stdio.h>
#include <stdlib.h>
#include "../daemon/session.h"
#include "../daemon/protocol.h"
#include "../util/platform.h"
#include <cx/string.h>
#include <cx/hash_map.h>
#include "multistatus.h"
#include "operation.h"
#include "xml.h"
#define MULTISTATUS_BUFFER_LENGTH 2048
Multistatus* multistatus_response(Session *sn, Request *rq) {
Multistatus *ms = pool_malloc(sn->pool,
sizeof(Multistatus));
if(!ms) {
return NULL;
}
ZERO(ms,
sizeof(Multistatus));
ms->response.addresource = multistatus_addresource;
ms->sn = sn;
ms->rq = rq;
ms->namespaces = cxHashMapCreate(pool_allocator(sn->pool),
CX_STORE_POINTERS,
8);
ms->proppatch =
FALSE;
if(!ms->namespaces) {
return NULL;
}
if(cxMapPut(ms->namespaces, cx_hash_key_str(
"D"), webdav_dav_namespace())) {
return NULL;
}
return ms;
}
static int send_xml_root(Multistatus *ms, Writer *out) {
writer_put_lit(out,
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
"<D:multistatus");
CxIterator i = cxMapIterator(ms->namespaces);
cx_foreach(CxMapEntry*, entry, i) {
WSNamespace *ns = entry->value;
writer_put_lit(out,
" xmlns:");
writer_put (out, entry->key->data, entry->key->len);
writer_put_lit(out,
"=\"");
writer_put_str(out, (
char*)ns->href);
writer_put_lit(out,
"\"");
}
writer_put_lit(out,
">\n");
return out->error;
}
static void send_nsdef(WSNamespace *ns, Writer *out) {
writer_put_lit(out,
" xmlns:");
writer_put_str(out, (
char*)ns->prefix);
writer_put_lit(out,
"=\"");
writer_put_str(out, (
char*)ns->href);
writer_putc (out,
'\"');
}
static const char hex_ch[] =
"0123456789ABCDEF";
static void send_string_escaped(Writer *out, cxmutstr str) {
char *begin = str.ptr;
char *end = begin;
char escape[
4];
escape[
0] =
'%';
for(
size_t i=
0;i<str.length;i++) {
unsigned char c = (
unsigned char)str.ptr[i];
end = str.ptr + i;
if( (c >=
'A' && c <=
'Z') ||
(c >=
'a' && c <=
'z') ||
(c >=
'/' && c <=
'9') ||
(strchr(
"_.\\-~", c) !=
NULL))
{
continue;
}
escape[
1] = hex_ch[(c >>
4) & 0x0F];
escape[
2] = hex_ch[c & 0x0F];
ptrdiff_t len = end - begin;
if(len >
0) {
writer_put(out, begin, len);
}
writer_put(out, escape,
3);
begin = end +
1;
}
ptrdiff_t len = str.ptr + str.length - begin;
if(len >
0) {
writer_put(out, begin, len);
begin = end +
1;
}
}
static int send_property(
Multistatus *ms,
WebdavProperty *property,
WebdavNSList *nsdef,
WSBool writeContent,
Writer *out)
{
writer_putc (out,
'<');
writer_put_str(out, (
char*)property->namespace->prefix);
writer_putc (out,
':');
writer_put_str(out, (
char*)property->name);
WebdavNSList *def = nsdef;
while(def) {
send_nsdef(def->namespace, out);
def = def->next;
}
if(property->lang) {
writer_put_lit(out,
" xml:lang=\"");
writer_put_str(out, (
char*)property->lang);
writer_putc (out,
'\"');
}
if(writeContent) {
writer_putc(out,
'>');
switch(property->vtype) {
case WS_VALUE_NO_TYPE:
break;
case WS_VALUE_XML_NODE: {
wsxml_write_nodes_without_nsdef(
ms->sn->pool,
out,
property->value.node);
break;
}
case WS_VALUE_XML_DATA: {
writer_put(
out,
property->value.data.data,
property->value.data.length);
break;
}
case WS_VALUE_TEXT: {
writer_put(
out,
property->value.text.str,
property->value.text.length);
break;
}
}
writer_put_lit(out,
"</");
writer_put_str(out, (
char*)property->namespace->prefix);
writer_putc (out,
':');
writer_put_str(out, (
char*)property->name);
writer_putc (out,
'>');
}
else {
writer_put_lit(out,
"/>");
}
return out->error;
}
static int send_response_tag(Multistatus *ms, MSResponse *rp, Writer *out) {
writer_put_lit(out,
" <D:response>\n"
" <D:href>");
send_string_escaped(out, cx_mutstr(rp->resource.href));
writer_put_lit(out,
"</D:href>\n");
WSBool writeContent = ms->proppatch ?
FALSE :
TRUE;
if(rp->plist_begin) {
writer_put_lit(out,
" <D:propstat>\n"
" <D:prop>\n");
PropertyOkList *p = rp->plist_begin;
while(p) {
writer_put_lit(out,
" ");
if(send_property(ms, p->property, p->nsdef, writeContent, out)) {
return out->error;
}
writer_put_lit(out,
"\n");
p = p->next;
}
writer_put_lit(out,
" </D:prop>\n"
" <D:status>HTTP/1.1 200 OK</D:status>\n"
" </D:propstat>\n");
}
PropertyErrorList *error = rp->errors;
while(error) {
writer_put_lit(out,
" <D:propstat>\n"
" <D:prop>\n");
WebdavPList *errprop = error->begin;
while(errprop) {
writer_put_lit(out,
" ");
if(send_property(ms, errprop->property,
NULL,
FALSE, out)) {
return out->error;
}
writer_putc(out,
'\n');
errprop = errprop->next;
}
char statuscode[
8];
int sclen = snprintf(statuscode,
8,
"%d ", error->status);
if(sclen >
4) {
statuscode[
0] =
'5';
statuscode[
1] =
'0';
statuscode[
2] =
'0';
statuscode[
3] =
' ';
sclen =
4;
}
writer_put_lit(out,
" </D:prop>\n"
" <D:status>HTTP/1.1 ");
writer_put(out, statuscode, sclen);
const char *status_msg = protocol_status_message(error->status);
if(status_msg) {
writer_put(out, status_msg, strlen(status_msg));
}
else {
writer_put_lit(out,
"Server Error");
}
writer_put_lit(out,
"</D:status>\n"
" </D:propstat>\n");
error = error->next;
}
writer_put_lit(out,
" </D:response>\n");
return out->error;
}
int multistatus_send(Multistatus *ms,
SYS_NETFD net) {
if(ms->current && !ms->current->resource.isclosed) {
if(msresponse_close((WebdavResource*)ms->current)) {
return 1;
}
}
protocol_status(ms->sn, ms->rq,
207,
NULL);
if(protocol_start_response(ms->sn, ms->rq)) {
return 1;
}
char buffer[
MULTISTATUS_BUFFER_LENGTH];
Writer writer;
Writer *out = &writer;
writer_init(out, net, buffer,
MULTISTATUS_BUFFER_LENGTH);
if(send_xml_root(ms, out)) {
return 1;
}
MSResponse *response = ms->first;
while(response) {
if(send_response_tag(ms, response, out)) {
return 1;
}
response = response->next;
}
writer_put_lit(out,
"</D:multistatus>\n");
writer_flush(out);
return 0;
}
WebdavResource * multistatus_addresource(
WebdavResponse *response,
const char *path)
{
Multistatus *ms = (Multistatus*)response;
MSResponse *res = pool_malloc(ms->sn->pool,
sizeof(MSResponse));
if(!res) {
return NULL;
}
ZERO(res,
sizeof(MSResponse));
res->resource.href = pool_strdup(ms->sn->pool, path);
if(!res->resource.href) {
return NULL;
}
res->resource.err =
0;
res->resource.addproperty = msresponse_addproperty;
res->resource.close = msresponse_close;
res->properties = cxHashMapCreate(pool_allocator(ms->sn->pool),
CX_STORE_POINTERS,
32);
if(!res->properties) {
return NULL;
}
res->multistatus = ms;
res->errors =
NULL;
res->resource.isclosed =
0;
res->closing =
0;
if(ms->current) {
if(!ms->current->resource.isclosed) {
msresponse_close((WebdavResource*)ms->current);
}
ms->current->next = res;
}
else {
ms->first = res;
}
ms->current = res;
return (WebdavResource*)res;
}
static int oklist_add(
pool_handle_t *pool,
PropertyOkList **begin,
PropertyOkList **end,
WebdavProperty *property,
WebdavNSList *nsdef)
{
PropertyOkList *newelm = pool_malloc(pool,
sizeof(PropertyOkList));
if(!newelm) {
return 1;
}
newelm->property = property;
newelm->nsdef = nsdef;
newelm->next =
NULL;
if(*end) {
(*end)->next = newelm;
}
else {
*begin = newelm;
}
*end = newelm;
return 0;
}
static int msresponse_addproperror(
MSResponse *response,
WebdavProperty *property,
int statuscode)
{
pool_handle_t *pool = response->multistatus->sn->pool;
response->resource.err++;
PropertyErrorList *errlist =
NULL;
PropertyErrorList *list = response->errors;
PropertyErrorList *last =
NULL;
while(list) {
if(list->status == statuscode) {
errlist = list;
break;
}
last = list;
list = list->next;
}
if(!errlist) {
PropertyErrorList *newelm = pool_malloc(pool,
sizeof(PropertyErrorList));
if(!newelm) {
return 1;
}
newelm->begin =
NULL;
newelm->end =
NULL;
newelm->next =
NULL;
newelm->status = statuscode;
if(last) {
last->next = newelm;
}
else {
response->errors = newelm;
}
errlist = newelm;
}
if(webdav_plist_add(pool, &errlist->begin, &errlist->end, property)) {
return 1;
}
return 0;
}
static CxHashKey ms_property_key(
CxAllocator *a,
const xmlChar *href,
const char *property_name)
{
cxmutstr key_data = cx_strcat_a(a,
3, cx_str((
const char*)href), (cxstring){
"\0",
1 }, cx_str(property_name));
return cx_hash_key_bytes((
unsigned char*)key_data.ptr, key_data.length);
}
int msresponse_addproperty(
WebdavResource *res,
WebdavProperty *property,
int status)
{
MSResponse *response = (MSResponse*)res;
Session *sn = response->multistatus->sn;
if(response->resource.isclosed) {
log_ereport(
LOG_WARN,
"%s",
"webdav: cannot add property to closed response tag");
return 0;
}
if(!property->namespace || !property->namespace->href) {
log_ereport(
LOG_FAILURE,
"%s",
"webdav: property ''%s'' has no namespace",
property->name);
return 1;
}
CxAllocator *a = pool_allocator(sn->pool);
CxHashKey key = ms_property_key(a, property->namespace->href, property->name);
if(cxMapGet(response->properties, key)) {
cxFree(a, (
void*)key.data);
return 0;
}
if(cxMapPut(response->properties, key, property)) {
return 1;
}
cxFree(a, (
void*)key.data);
WebdavNSList *nsdef_begin =
NULL;
WebdavNSList *nsdef_end =
NULL;
if(property->namespace->prefix) {
WSNamespace *ns = cxMapGet(
response->multistatus->namespaces,
cx_hash_key_str((
const char*)property->namespace->prefix));
if(!ns) {
int err = cxMapPut(
response->multistatus->namespaces,
cx_hash_key_str((
const char*)property->namespace->prefix),
property->namespace);
if(err) {
return 1;
}
}
else if(
strcmp((
const char*)property->namespace->href,
(
const char*)ns->href))
{
if(webdav_nslist_add(
sn->pool,
&nsdef_begin,
&nsdef_end,
property->namespace))
{
return 1;
}
}
}
if(response->multistatus->proppatch && response->errors) {
status =
424;
}
if(status !=
200) {
return msresponse_addproperror(response, property, status);
}
WebdavNSList *nslist =
NULL;
if(property->vtype ==
WS_VALUE_XML_NODE) {
int err =
0;
nslist = wsxml_get_required_namespaces(
response->multistatus->sn->pool,
property->value.node,
&err);
if(err) {
return 1;
}
}
else if(property->vtype ==
WS_VALUE_XML_DATA) {
nslist = property->value.data.namespaces;
}
while(nslist) {
if(strcmp(
(
const char*)nslist->namespace->prefix,
(
const char*)property->namespace->prefix))
{
if(webdav_nslist_add(
sn->pool,
&nsdef_begin,
&nsdef_end,
nslist->namespace))
{
return 1;
}
}
nslist = nslist->next;
}
if(oklist_add(
sn->pool,
&response->plist_begin,
&response->plist_end,
property,
nsdef_begin))
{
return 1;
}
return 0;
}
int msresponse_close(WebdavResource *res) {
MSResponse *response = (MSResponse*)res;
if(response->closing) {
return 0;
}
response->closing =
TRUE;
Multistatus *ms = response->multistatus;
int ret =
REQ_PROCEED;
WebdavOperation *op = ms->response.op;
if(op->response_close(op, res)) {
ret =
REQ_ABORTED;
}
CxAllocator *a = pool_allocator(ms->sn->pool);
WebdavPList *pl = ms->response.op->reqprops;
while(pl) {
CxHashKey key = ms_property_key(a, pl->property->namespace->href, pl->property->name);
if(!cxMapGet(response->properties, key)) {
if(ms->proppatch) {
if(msresponse_addproperty(res, pl->property,
424)) {
ret =
REQ_ABORTED;
break;
}
}
else {
if(msresponse_addproperty(res, pl->property,
404)) {
ret =
REQ_ABORTED;
break;
}
}
}
pl = pl->next;
}
if(ms->proppatch && response->errors) {
PropertyOkList *elm = response->plist_begin;
PropertyOkList *nextelm;
while(elm) {
if(msresponse_addproperty(res, elm->property,
424)) {
return 1;
}
nextelm = elm->next;
pool_free(response->multistatus->sn->pool, elm);
elm = nextelm;
}
response->plist_begin =
NULL;
response->plist_end =
NULL;
}
cxMapDestroy(response->properties);
response->resource.isclosed =
TRUE;
return ret;
}