mirror of
https://github.com/clearlinux/uwsgi.git
synced 2026-08-19 12:06:36 +00:00
afedff7589
Reported by Coverity as CID #1308292
1428 lines
50 KiB
C
1428 lines
50 KiB
C
#include <uwsgi.h>
|
|
#include <libxml/parser.h>
|
|
#include <libxml/tree.h>
|
|
|
|
#if defined(__linux__)
|
|
#include <attr/xattr.h>
|
|
#elif defined(__APPLE__)
|
|
#include <sys/xattr.h>
|
|
#endif
|
|
|
|
/*
|
|
|
|
rfc4918 implementation (WebDAV)
|
|
|
|
requires libxml2
|
|
|
|
--webdav-mount [mountpoint=]<dir>
|
|
|
|
or
|
|
|
|
--webdav-use-docroot[=VAR]
|
|
|
|
steps to build a path:
|
|
|
|
1) get the mountpoint
|
|
|
|
2) concat the base with the path_info
|
|
|
|
3) realpath() on it
|
|
|
|
step 3 could be a non-existent file (for example on MKCOL or PUT). In such a case:
|
|
|
|
4) find the last / in the path_info, and try realpath() on it, if success the resource can be created
|
|
|
|
ALL MUST BE BOTH THREAD SAFE AND ASYNC SAFE !!!
|
|
|
|
Locking requires a cache (local or remote)
|
|
|
|
when a lock request is made, an item is added to the cache (directly using cache_set to avoid duplicates). The
|
|
item key is the full url of the request (host + path_info, in such a way we have virtualhosting for locks). The value is
|
|
a uuid.
|
|
|
|
if a lock_token is passed the url is checked in the cache and uuid compared
|
|
|
|
- Resource properties are stored as filesystem xattr (warning, not all operating system support them) -
|
|
|
|
*/
|
|
|
|
extern struct uwsgi_server uwsgi;
|
|
struct uwsgi_plugin webdav_plugin;
|
|
|
|
struct uwsgi_webdav {
|
|
struct uwsgi_string_list *mountpoints;
|
|
struct uwsgi_string_list *css;
|
|
struct uwsgi_string_list *javascript;
|
|
char *class_directory;
|
|
char *div;
|
|
|
|
char *lock_cache;
|
|
char *principal_base;
|
|
|
|
struct uwsgi_string_list *add_option;
|
|
|
|
struct uwsgi_string_list *add_prop;
|
|
struct uwsgi_string_list *add_collection_prop;
|
|
struct uwsgi_string_list *add_object_prop;
|
|
|
|
struct uwsgi_string_list *add_prop_href;
|
|
struct uwsgi_string_list *add_collection_prop_href;
|
|
struct uwsgi_string_list *add_object_prop_href;
|
|
|
|
struct uwsgi_string_list *add_prop_comp;
|
|
struct uwsgi_string_list *add_collection_prop_comp;
|
|
struct uwsgi_string_list *add_object_prop_comp;
|
|
|
|
struct uwsgi_string_list *add_rtype_prop;
|
|
struct uwsgi_string_list *add_rtype_collection_prop;
|
|
struct uwsgi_string_list *add_rtype_object_prop;
|
|
|
|
struct uwsgi_string_list *skip_prop;
|
|
|
|
} udav;
|
|
|
|
struct uwsgi_option uwsgi_webdav_options[] = {
|
|
{ "webdav-mount", required_argument, 0, "map a filesystem directory as a webdav store", uwsgi_opt_add_string_list, &udav.mountpoints, UWSGI_OPT_MIME},
|
|
{ "webdav-css", required_argument, 0, "add a css url for automatic webdav directory listing", uwsgi_opt_add_string_list, &udav.css, UWSGI_OPT_MIME},
|
|
{ "webdav-javascript", required_argument, 0, "add a javascript url for automatic webdav directory listing", uwsgi_opt_add_string_list, &udav.javascript, UWSGI_OPT_MIME},
|
|
{ "webdav-js", required_argument, 0, "add a javascript url for automatic webdav directory listing", uwsgi_opt_add_string_list, &udav.javascript, UWSGI_OPT_MIME},
|
|
{ "webdav-class-directory", required_argument, 0, "set the css directory class for automatic webdav directory listing", uwsgi_opt_set_str, &udav.class_directory, UWSGI_OPT_MIME},
|
|
{ "webdav-div", required_argument, 0, "set the div id for automatic webdav directory listing", uwsgi_opt_set_str, &udav.div, UWSGI_OPT_MIME},
|
|
{ "webdav-lock-cache", required_argument, 0, "set the cache to use for webdav locking", uwsgi_opt_set_str, &udav.lock_cache, UWSGI_OPT_MIME},
|
|
{ "webdav-principal-base", required_argument, 0, "enable WebDAV Current Principal Extension using the specified base", uwsgi_opt_set_str, &udav.principal_base, UWSGI_OPT_MIME},
|
|
{ "webdav-add-option", required_argument, 0, "add a WebDAV standard to the OPTIONS response", uwsgi_opt_add_string_list, &udav.add_option, UWSGI_OPT_MIME},
|
|
|
|
{ "webdav-add-prop", required_argument, 0, "add a WebDAV property to all resources", uwsgi_opt_add_string_list, &udav.add_prop, UWSGI_OPT_MIME},
|
|
{ "webdav-add-collection-prop", required_argument, 0, "add a WebDAV property to all collections", uwsgi_opt_add_string_list, &udav.add_collection_prop, UWSGI_OPT_MIME},
|
|
{ "webdav-add-object-prop", required_argument, 0, "add a WebDAV property to all objects", uwsgi_opt_add_string_list, &udav.add_object_prop, UWSGI_OPT_MIME},
|
|
|
|
{ "webdav-add-prop-href", required_argument, 0, "add a WebDAV property to all resources (href value)", uwsgi_opt_add_string_list, &udav.add_prop_href, UWSGI_OPT_MIME},
|
|
{ "webdav-add-collection-prop-href", required_argument, 0, "add a WebDAV property to all collections (href value)", uwsgi_opt_add_string_list, &udav.add_collection_prop_href, UWSGI_OPT_MIME},
|
|
{ "webdav-add-object-prop-href", required_argument, 0, "add a WebDAV property to all objects (href value)", uwsgi_opt_add_string_list, &udav.add_object_prop_href, UWSGI_OPT_MIME},
|
|
|
|
{ "webdav-add-prop-comp", required_argument, 0, "add a WebDAV property to all resources (xml value)", uwsgi_opt_add_string_list, &udav.add_prop_comp, UWSGI_OPT_MIME},
|
|
{ "webdav-add-collection-prop-comp", required_argument, 0, "add a WebDAV property to all collections (xml value)", uwsgi_opt_add_string_list, &udav.add_collection_prop_comp, UWSGI_OPT_MIME},
|
|
{ "webdav-add-object-prop-comp", required_argument, 0, "add a WebDAV property to all objects (xml value)", uwsgi_opt_add_string_list, &udav.add_object_prop_comp, UWSGI_OPT_MIME},
|
|
|
|
{ "webdav-add-rtype-prop", required_argument, 0, "add a WebDAV resourcetype property to all resources", uwsgi_opt_add_string_list, &udav.add_rtype_prop, UWSGI_OPT_MIME},
|
|
{ "webdav-add-rtype-collection-prop", required_argument, 0, "add a WebDAV resourcetype property to all collections", uwsgi_opt_add_string_list, &udav.add_rtype_collection_prop, UWSGI_OPT_MIME},
|
|
{ "webdav-add-rtype-object-prop", required_argument, 0, "add a WebDAV resourcetype property to all objects", uwsgi_opt_add_string_list, &udav.add_rtype_object_prop, UWSGI_OPT_MIME},
|
|
|
|
{ "webdav-skip-prop", required_argument, 0, "do not add the specified prop if available in resource xattr", uwsgi_opt_add_string_list, &udav.skip_prop, UWSGI_OPT_MIME},
|
|
|
|
{ 0, 0, 0, 0, 0, 0, 0 },
|
|
};
|
|
|
|
static int uwsgi_webdav_prop_requested(xmlNode *req_prop, char *ns, char *name) {
|
|
if (!req_prop) return 1;
|
|
xmlNode *node;
|
|
for (node = req_prop->children; node; node = node->next) {
|
|
if (node->type == XML_ELEMENT_NODE) {
|
|
if (ns) {
|
|
if (node->ns && !strcmp((char *) node->ns->href, ns)) {
|
|
if (!strcmp((char *) node->name, name)) return 1;
|
|
}
|
|
}
|
|
else {
|
|
if (!strcmp((char *) node->name, name)) return 1;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void uwsgi_webdav_add_a_prop(xmlNode *node, char *opt, xmlNode *req_prop, int type, char *force_name) {
|
|
char *first_space = strchr(opt, ' ');
|
|
if (!first_space) return;
|
|
*first_space = 0;
|
|
char *second_space = strchr(first_space + 1, ' ');
|
|
xmlNode *new_node = NULL;
|
|
char *ns = opt;
|
|
if (!force_name) force_name = first_space + 1;
|
|
else {
|
|
ns = "DAV:";
|
|
}
|
|
if (second_space) {
|
|
*second_space = 0;
|
|
if (!uwsgi_webdav_prop_requested(req_prop, ns, force_name)) {
|
|
*first_space = ' ';
|
|
*second_space = ' ';
|
|
return;
|
|
}
|
|
// href
|
|
if (type == 1) {
|
|
new_node = xmlNewChild(node, NULL, BAD_CAST first_space + 1, NULL);
|
|
xmlNewTextChild(new_node, NULL, BAD_CAST "href", BAD_CAST second_space + 1);
|
|
}
|
|
// comp
|
|
else if (type == 2) {
|
|
new_node = xmlNewChild(node, NULL, BAD_CAST first_space + 1, NULL);
|
|
char *comps = uwsgi_str(second_space + 1);
|
|
char *p, *ctx = NULL;
|
|
uwsgi_foreach_token(comps, ",", p, ctx) {
|
|
xmlNode *comp = xmlNewChild(new_node, NULL, BAD_CAST "comp", NULL);
|
|
xmlNewProp(comp, BAD_CAST "name", BAD_CAST p);
|
|
}
|
|
free(comps);
|
|
}
|
|
else {
|
|
if (!uwsgi_webdav_prop_requested(req_prop, ns, first_space + 1)) {
|
|
*first_space = ' ';
|
|
*second_space = ' ';
|
|
return;
|
|
}
|
|
new_node = xmlNewTextChild(node, NULL, BAD_CAST first_space + 1, BAD_CAST second_space + 1);
|
|
}
|
|
*second_space = ' ';
|
|
}
|
|
else {
|
|
if (!uwsgi_webdav_prop_requested(req_prop, ns, force_name)) {
|
|
*first_space = ' ';
|
|
return;
|
|
}
|
|
new_node = xmlNewChild(node, NULL, BAD_CAST first_space + 1, NULL);
|
|
}
|
|
xmlNsPtr x_ns = xmlNewNs(new_node, BAD_CAST opt, NULL);
|
|
xmlSetNs(new_node, x_ns);
|
|
*first_space = ' ';
|
|
}
|
|
|
|
static void uwsgi_webdav_foreach_prop(struct uwsgi_string_list *usl, xmlNode *req_prop, xmlNode *node, int type, char *force_name) {
|
|
if (!usl) return;
|
|
while(usl) {
|
|
uwsgi_webdav_add_a_prop(node, usl->value, req_prop, type, force_name);
|
|
usl = usl->next;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
OPTIONS: if it is a valid webdav resource add Dav: to the response header
|
|
*/
|
|
static int uwsgi_wevdav_manage_options(struct wsgi_request *wsgi_req) {
|
|
uwsgi_response_prepare_headers(wsgi_req, "200 OK", 6);
|
|
if (udav.add_option) {
|
|
struct uwsgi_buffer *ub = uwsgi_buffer_new(uwsgi.page_size);
|
|
if (uwsgi_buffer_append(ub, "1, 2, 3", 7)) goto end;
|
|
struct uwsgi_string_list *usl = udav.add_option;
|
|
while(usl) {
|
|
if (uwsgi_buffer_append(ub, ", ", 2)) goto end;
|
|
if (uwsgi_buffer_append(ub, usl->value, usl->len)) goto end;
|
|
usl = usl->next;
|
|
}
|
|
uwsgi_response_add_header(wsgi_req, "Dav", 3, ub->buf, ub->pos);
|
|
end:
|
|
uwsgi_buffer_destroy(ub);
|
|
}
|
|
else {
|
|
uwsgi_response_add_header(wsgi_req, "Dav", 3, "1, 2, 3", 7);
|
|
}
|
|
return UWSGI_OK;
|
|
}
|
|
|
|
static char *uwsgi_webdav_new_date(uint64_t t) {
|
|
// 30+1
|
|
char d[31];
|
|
int len = uwsgi_http_date((time_t) t, d);
|
|
if (!len) {
|
|
return NULL;
|
|
}
|
|
return uwsgi_concat2n(d, len, "", 0);
|
|
}
|
|
|
|
static int uwsgi_webdav_add_props(struct wsgi_request *wsgi_req, xmlNode *req_prop, xmlNode * multistatus, xmlNsPtr dav_ns, char *uri, char *filename, int with_values) {
|
|
struct stat st;
|
|
if (stat(filename, &st)) {
|
|
uwsgi_error("uwsgi_webdav_add_props()/stat()");
|
|
return -1;
|
|
}
|
|
|
|
int is_collection = 0;
|
|
|
|
xmlNode *response = xmlNewChild(multistatus, dav_ns, BAD_CAST "response", NULL);
|
|
uint16_t uri_len = strlen(uri) ;
|
|
char *encoded_uri = uwsgi_malloc( (uri_len * 3) + 1);
|
|
http_url_encode(uri, &uri_len, encoded_uri);
|
|
encoded_uri[uri_len] = 0;
|
|
xmlNewChild(response, dav_ns, BAD_CAST "href", BAD_CAST encoded_uri);
|
|
free(encoded_uri);
|
|
xmlNode *r_propstat = xmlNewChild(response, dav_ns, BAD_CAST "propstat", NULL);
|
|
char *r_status = uwsgi_concat2n(wsgi_req->protocol, wsgi_req->protocol_len, " 200 OK", 7);
|
|
xmlNewChild(r_propstat, dav_ns, BAD_CAST "status", BAD_CAST r_status);
|
|
free(r_status);
|
|
|
|
xmlNode *r_prop = xmlNewChild(r_propstat, dav_ns, BAD_CAST "prop", NULL);
|
|
|
|
if (with_values) {
|
|
if (uwsgi_webdav_prop_requested(req_prop, "DAV:", "displayname")) {
|
|
char *base_uri = uwsgi_get_last_char(uri, '/');
|
|
if (base_uri) {
|
|
xmlNewChild(r_prop, dav_ns, BAD_CAST "displayname", BAD_CAST base_uri+1);
|
|
}
|
|
else {
|
|
xmlNewChild(r_prop, dav_ns, BAD_CAST "displayname", BAD_CAST uri);
|
|
}
|
|
|
|
}
|
|
|
|
if (S_ISDIR(st.st_mode)) is_collection = 1;
|
|
|
|
xmlNode *r_type = NULL;
|
|
|
|
if (uwsgi_webdav_prop_requested(req_prop, "DAV:", "resourcetype")) {
|
|
r_type = xmlNewChild(r_prop, dav_ns, BAD_CAST "resourcetype", NULL);
|
|
if (is_collection) {
|
|
xmlNewChild(r_type, dav_ns, BAD_CAST "collection", NULL);
|
|
is_collection = 1;
|
|
}
|
|
}
|
|
|
|
|
|
if (!is_collection) {
|
|
if (uwsgi_webdav_prop_requested(req_prop, "DAV:", "getcontentlength")) {
|
|
char *r_contentlength = uwsgi_num2str(st.st_size);
|
|
xmlNewChild(r_prop, dav_ns, BAD_CAST "getcontentlength", BAD_CAST r_contentlength);
|
|
free(r_contentlength);
|
|
}
|
|
if (uwsgi_webdav_prop_requested(req_prop, "DAV:", "getcontenttype")) {
|
|
size_t mime_type_len = 0;
|
|
char *mime_type = uwsgi_get_mime_type(filename, strlen(filename), &mime_type_len);
|
|
if (mime_type) {
|
|
char *r_ctype = uwsgi_concat2n(mime_type, mime_type_len, "", 0);
|
|
xmlNewTextChild(r_prop, dav_ns, BAD_CAST "getcontenttype", BAD_CAST r_ctype);
|
|
free(r_ctype);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (uwsgi_webdav_prop_requested(req_prop, "DAV:", "creationdate")) {
|
|
// there is no creation date on UNIX/POSIX, ctime is the nearest thing...
|
|
char *cdate = uwsgi_webdav_new_date(st.st_ctime);
|
|
if (cdate) {
|
|
xmlNewTextChild(r_prop, dav_ns, BAD_CAST "creationdate", BAD_CAST cdate);
|
|
free(cdate);
|
|
}
|
|
}
|
|
|
|
if (uwsgi_webdav_prop_requested(req_prop, "DAV:", "getlastmodified")) {
|
|
char *mdate = uwsgi_webdav_new_date(st.st_mtime);
|
|
if (mdate) {
|
|
xmlNewTextChild(r_prop, dav_ns, BAD_CAST "getlastmodified", BAD_CAST mdate);
|
|
free(mdate);
|
|
}
|
|
}
|
|
|
|
if (uwsgi_webdav_prop_requested(req_prop, "DAV:", "getetag")) {
|
|
char *etag = uwsgi_num2str(st.st_mtime);
|
|
xmlNewTextChild(r_prop, dav_ns, BAD_CAST "getetag", BAD_CAST etag);
|
|
free(etag);
|
|
}
|
|
|
|
if (uwsgi_webdav_prop_requested(req_prop, "DAV:", "executable")) {
|
|
xmlNewChild(r_prop, dav_ns, BAD_CAST "executable", NULL);
|
|
}
|
|
|
|
if (uwsgi_webdav_prop_requested(req_prop, "DAV:", "owner")) {
|
|
xmlNewTextChild(r_prop, dav_ns, BAD_CAST "owner", NULL);
|
|
}
|
|
|
|
if (wsgi_req->remote_user_len > 0) {
|
|
|
|
if (udav.principal_base) {
|
|
if (uwsgi_webdav_prop_requested(req_prop, "DAV:", "current-user-principal")) {
|
|
char *current_user_principal = uwsgi_concat2n(udav.principal_base, strlen(udav.principal_base), wsgi_req->remote_user, wsgi_req->remote_user_len);
|
|
xmlNode *cup = xmlNewChild(r_prop, dav_ns, BAD_CAST "current-user-principal", NULL);
|
|
xmlNewTextChild(cup, dav_ns, BAD_CAST "href", BAD_CAST current_user_principal);
|
|
if (uwsgi_webdav_prop_requested(req_prop, "DAV:", "resourcetype")) {
|
|
if (!strcmp(current_user_principal, uri)) {
|
|
xmlNewChild(r_type, dav_ns, BAD_CAST "principal", NULL);
|
|
}
|
|
}
|
|
free(current_user_principal);
|
|
}
|
|
}
|
|
|
|
if (uwsgi_webdav_prop_requested(req_prop, "DAV:", "current-user-privilege-set")) {
|
|
xmlNode *cups = xmlNewChild(r_prop, dav_ns, BAD_CAST "current-user-privilege-set", NULL);
|
|
xmlNode *privilege = xmlNewChild(cups, dav_ns, BAD_CAST "privilege", NULL);
|
|
xmlNewChild(privilege, dav_ns, BAD_CAST "all", NULL);
|
|
xmlNewChild(privilege, dav_ns, BAD_CAST "read", NULL);
|
|
xmlNewChild(privilege, dav_ns, BAD_CAST "write", NULL);
|
|
xmlNewChild(privilege, dav_ns, BAD_CAST "write-content", NULL);
|
|
xmlNewChild(privilege, dav_ns, BAD_CAST "write-properties", NULL);
|
|
}
|
|
}
|
|
|
|
if (uwsgi_webdav_prop_requested(req_prop, "DAV:", "supported-report-set")) {
|
|
xmlNode *report_set = xmlNewChild(r_prop, dav_ns, BAD_CAST "supported-report-set", NULL);
|
|
xmlNode *supported_report = xmlNewChild(report_set, dav_ns, BAD_CAST "supported-report", NULL);
|
|
xmlNewChild(supported_report, dav_ns, BAD_CAST "report", BAD_CAST "principal-property-search");
|
|
supported_report = xmlNewChild(report_set, dav_ns, BAD_CAST "supported-report", NULL);
|
|
xmlNewChild(supported_report, dav_ns, BAD_CAST "report", BAD_CAST "sync-collection");
|
|
supported_report = xmlNewChild(report_set, dav_ns, BAD_CAST "supported-report", NULL);
|
|
xmlNewChild(supported_report, dav_ns, BAD_CAST "report", BAD_CAST "expand-property");
|
|
supported_report = xmlNewChild(report_set, dav_ns, BAD_CAST "supported-report", NULL);
|
|
xmlNewChild(supported_report, dav_ns, BAD_CAST "report", BAD_CAST "principal-search-property-set");
|
|
}
|
|
|
|
uwsgi_webdav_foreach_prop(udav.add_prop, req_prop, r_prop, 0, NULL );
|
|
uwsgi_webdav_foreach_prop(udav.add_prop_href, req_prop, r_prop, 1, NULL);
|
|
uwsgi_webdav_foreach_prop(udav.add_prop_comp,req_prop, r_prop, 2 , NULL);
|
|
|
|
uwsgi_webdav_foreach_prop(udav.add_rtype_prop,req_prop, r_type, 0, "resourcetype");
|
|
|
|
if (is_collection) {
|
|
uwsgi_webdav_foreach_prop(udav.add_rtype_collection_prop,req_prop, r_type, 0, "resourcetype");
|
|
uwsgi_webdav_foreach_prop(udav.add_collection_prop,req_prop, r_prop, 0, NULL);
|
|
uwsgi_webdav_foreach_prop(udav.add_collection_prop_href,req_prop, r_prop, 1, NULL);
|
|
uwsgi_webdav_foreach_prop(udav.add_collection_prop_comp,req_prop, r_prop, 2, NULL);
|
|
}
|
|
else {
|
|
uwsgi_webdav_foreach_prop(udav.add_rtype_object_prop,req_prop, r_type, 0, "resourcetype");
|
|
uwsgi_webdav_foreach_prop(udav.add_object_prop,req_prop, r_prop, 0, NULL);
|
|
uwsgi_webdav_foreach_prop(udav.add_object_prop_href,req_prop, r_prop, 1, NULL);
|
|
uwsgi_webdav_foreach_prop(udav.add_object_prop_comp,req_prop, r_prop, 2, NULL);
|
|
}
|
|
}
|
|
else {
|
|
xmlNewChild(r_prop, dav_ns, BAD_CAST "displayname", NULL);
|
|
xmlNewChild(r_prop, dav_ns, BAD_CAST "resourcetype", NULL);
|
|
if (!S_ISDIR(st.st_mode)) {
|
|
xmlNewChild(r_prop, dav_ns, BAD_CAST "getcontentlength", NULL);
|
|
xmlNewChild(r_prop, dav_ns, BAD_CAST "getcontenttype", NULL);
|
|
}
|
|
xmlNewChild(r_prop, dav_ns, BAD_CAST "creationdate", NULL);
|
|
xmlNewChild(r_prop, dav_ns, BAD_CAST "getlastmodified", NULL);
|
|
xmlNewChild(r_prop, dav_ns, BAD_CAST "supported-report-set", NULL);
|
|
if (wsgi_req->remote_user_len > 0) {
|
|
xmlNewChild(r_prop, dav_ns, BAD_CAST "current-user-privilege-set", NULL);
|
|
if (udav.principal_base) {
|
|
xmlNewChild(r_prop, dav_ns, BAD_CAST "current-user-principal", NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if defined(__linux__) || defined(__APPLE__)
|
|
// get xattr for user.uwsgi.webdav.
|
|
#if defined(__linux__)
|
|
ssize_t rlen = listxattr(filename, NULL, 0);
|
|
#elif defined(__APPLE__)
|
|
ssize_t rlen = listxattr(filename, NULL, 0, 0);
|
|
#endif
|
|
// do not return -1 as the previous xml is valid !!!
|
|
if (rlen <= 0) return 0;
|
|
// use calloc to avoid races
|
|
char *xattrs = uwsgi_calloc(rlen);
|
|
#if defined(__linux__)
|
|
if (listxattr(filename, xattrs, rlen) <= 0) {
|
|
#elif defined(__APPLE__)
|
|
if (listxattr(filename, xattrs, rlen, 0) <= 0) {
|
|
#endif
|
|
free(xattrs);
|
|
return 0;
|
|
}
|
|
// parse the name list
|
|
ssize_t i;
|
|
char *key = NULL;
|
|
for(i=0;i<rlen;i++) {
|
|
// check for wrong condition
|
|
if (xattrs[i] == 0 && key == NULL) break;
|
|
if (key && xattrs[i] == 0) {
|
|
if (!uwsgi_starts_with(key, strlen(key), "user.uwsgi.webdav.", 18)) {
|
|
if (uwsgi_string_list_has_item(udav.skip_prop, key + 18, strlen(key + 18))) continue;
|
|
xmlNsPtr xattr_ns = NULL;
|
|
// does it has a namespace ?
|
|
char *separator = strchr(key + 18, '|');
|
|
char *xattr_key = key + 18;
|
|
if (separator) {
|
|
xattr_key = separator + 1;
|
|
*separator = 0;
|
|
if (!uwsgi_webdav_prop_requested(req_prop, key + 18, xattr_key)) continue;
|
|
}
|
|
else {
|
|
if (!uwsgi_webdav_prop_requested(req_prop, NULL, xattr_key)) continue;
|
|
}
|
|
xmlNode *xattr_item = NULL;
|
|
if (with_values) {
|
|
#if defined(__linux__)
|
|
ssize_t rlen2 = getxattr(filename, key, NULL, 0);
|
|
#elif defined(__APPLE__)
|
|
ssize_t rlen2 = getxattr(filename, key, NULL, 0, 0, 0);
|
|
#endif
|
|
if (rlen2 > 0) {
|
|
// leave space for final 0
|
|
char *xvalue = uwsgi_calloc(rlen2 + 1);
|
|
#if defined(__linux__)
|
|
if (getxattr(filename, key, xvalue, rlen2) > 0) {
|
|
#elif defined(__APPLE__)
|
|
if (getxattr(filename, key, xvalue, rlen2, 0 ,0) > 0) {
|
|
#endif
|
|
xattr_item = xmlNewTextChild(r_prop, NULL, BAD_CAST xattr_key, BAD_CAST xvalue);
|
|
}
|
|
free(xvalue);
|
|
}
|
|
else if (rlen2 == 0) {
|
|
xattr_item = xmlNewTextChild(r_prop, NULL, BAD_CAST xattr_key, NULL);
|
|
}
|
|
}
|
|
else {
|
|
xattr_item = xmlNewTextChild(r_prop, NULL, BAD_CAST xattr_key, NULL);
|
|
}
|
|
if (separator && xattr_item) {
|
|
xattr_ns = xmlNewNs(xattr_item, BAD_CAST (key + 18), NULL);
|
|
*separator = '|';
|
|
xmlSetNs(xattr_item, xattr_ns);
|
|
}
|
|
}
|
|
key = NULL;
|
|
}
|
|
else if (key == NULL) {
|
|
key = &xattrs[i];
|
|
}
|
|
}
|
|
free(xattrs);
|
|
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static size_t uwsgi_webdav_expand_path(struct wsgi_request *wsgi_req, char *item, uint16_t item_len, char *filename) {
|
|
struct uwsgi_app *ua = &uwsgi_apps[wsgi_req->app_id];
|
|
char *docroot = ua->interpreter;
|
|
size_t docroot_len = strlen(docroot);
|
|
|
|
// merge docroot with path_info
|
|
char *tmp_filename = uwsgi_concat3n(docroot, docroot_len, "/", 1, item, item_len);
|
|
// try expanding the path
|
|
if (!realpath(tmp_filename, filename)) {
|
|
free(tmp_filename);
|
|
return 0;
|
|
}
|
|
free(tmp_filename);
|
|
return strlen(filename);
|
|
}
|
|
|
|
static size_t uwsgi_webdav_expand_fake_path(struct wsgi_request *wsgi_req, char *item, uint16_t item_len, char *filename) {
|
|
char *last_slash = uwsgi_get_last_charn(item, item_len, '/');
|
|
if (!last_slash) return 0;
|
|
size_t filename_len = uwsgi_webdav_expand_path(wsgi_req, item, last_slash - item, filename);
|
|
if (!filename_len) return 0;
|
|
// check for overflow
|
|
if (filename_len + (item_len - (last_slash - item)) >= PATH_MAX) return 0;
|
|
memcpy(filename + filename_len, last_slash, (item_len - (last_slash - item)));
|
|
filename_len += (item_len - (last_slash - item));
|
|
filename[(int)filename_len] = 0;
|
|
return filename_len;
|
|
}
|
|
|
|
static xmlDoc *uwsgi_webdav_manage_prop(struct wsgi_request *wsgi_req, xmlNode *req_prop, char *filename, size_t filename_len, int with_values) {
|
|
// default 1 depth
|
|
int depth = 1;
|
|
uint16_t http_depth_len = 0;
|
|
char *http_depth = uwsgi_get_var(wsgi_req, "HTTP_DEPTH", 10, &http_depth_len);
|
|
if (http_depth) {
|
|
depth = uwsgi_str_num(http_depth, http_depth_len);
|
|
}
|
|
|
|
xmlDoc *rdoc = xmlNewDoc(BAD_CAST "1.0");
|
|
xmlNode *multistatus = xmlNewNode(NULL, BAD_CAST "multistatus");
|
|
xmlDocSetRootElement(rdoc, multistatus);
|
|
xmlNsPtr dav_ns = xmlNewNs(multistatus, BAD_CAST "DAV:", BAD_CAST "D");
|
|
xmlSetNs(multistatus, dav_ns);
|
|
|
|
if (depth == 0) {
|
|
char *uri = uwsgi_concat2n(wsgi_req->path_info, wsgi_req->path_info_len, "", 0);
|
|
uwsgi_webdav_add_props(wsgi_req, req_prop, multistatus, dav_ns, uri, filename, with_values);
|
|
free(uri);
|
|
}
|
|
else {
|
|
DIR *collection = opendir(filename);
|
|
struct dirent de;
|
|
for (;;) {
|
|
struct dirent *de_r = NULL;
|
|
if (readdir_r(collection, &de, &de_r)) {
|
|
uwsgi_error("uwsgi_wevdav_manage_propfind()/readdir_r()");
|
|
break;
|
|
}
|
|
if (de_r == NULL) {
|
|
break;
|
|
}
|
|
char *uri = NULL;
|
|
char *direntry = NULL;
|
|
if (!strcmp(de.d_name, "..")) {
|
|
// skip ..
|
|
continue;
|
|
}
|
|
else if (!strcmp(de.d_name, ".")) {
|
|
uri = uwsgi_concat2n(wsgi_req->path_info, wsgi_req->path_info_len, "", 0);
|
|
direntry = uwsgi_concat2n(filename, filename_len, "", 0);
|
|
}
|
|
else if (wsgi_req->path_info[wsgi_req->path_info_len - 1] == '/') {
|
|
uri = uwsgi_concat2n(wsgi_req->path_info, wsgi_req->path_info_len, de.d_name, strlen(de.d_name));
|
|
direntry = uwsgi_concat3n(filename, filename_len, "/", 1, de.d_name, strlen(de.d_name));
|
|
}
|
|
else {
|
|
uri = uwsgi_concat3n(wsgi_req->path_info, wsgi_req->path_info_len, "/", 1, de.d_name, strlen(de.d_name));
|
|
direntry = uwsgi_concat3n(filename, filename_len, "/", 1, de.d_name, strlen(de.d_name));
|
|
}
|
|
uwsgi_webdav_add_props(wsgi_req, req_prop, multistatus, dav_ns, uri, direntry, with_values);
|
|
free(uri);
|
|
free(direntry);
|
|
}
|
|
closedir(collection);
|
|
}
|
|
|
|
return rdoc;
|
|
|
|
}
|
|
|
|
static int uwsgi_wevdav_manage_propfind(struct wsgi_request *wsgi_req, xmlDoc * doc) {
|
|
char filename[PATH_MAX];
|
|
size_t filename_len = uwsgi_webdav_expand_path(wsgi_req, wsgi_req->path_info, wsgi_req->path_info_len, filename);
|
|
if (filename_len == 0) {
|
|
uwsgi_404(wsgi_req);
|
|
return UWSGI_OK;
|
|
}
|
|
xmlDoc *rdoc = NULL;
|
|
xmlNode *element = NULL;
|
|
|
|
if (doc) {
|
|
element = xmlDocGetRootElement(doc);
|
|
if (!element) return -1;
|
|
|
|
if (!element || strcmp((char *) element->name, "propfind")) return -1;
|
|
}
|
|
|
|
if (uwsgi_response_prepare_headers(wsgi_req, "207 Multi-Status", 16))
|
|
return -1;
|
|
if (uwsgi_response_add_content_type(wsgi_req, "application/xml; charset=\"utf-8\"", 32))
|
|
return -1;
|
|
|
|
if (doc) {
|
|
// propfind must have a child (scan them until you find a valid one)
|
|
xmlNode *node;
|
|
for (node = element->children; node; node = node->next) {
|
|
if (node->type == XML_ELEMENT_NODE) {
|
|
if (node->ns && !strcmp((char *) node->ns->href, "DAV:")) {
|
|
if (!strcmp((char *) node->name, "prop")) {
|
|
rdoc = uwsgi_webdav_manage_prop(wsgi_req, node, filename, filename_len, 1);
|
|
break;
|
|
}
|
|
if (!strcmp((char *) node->name, "allprop")) {
|
|
rdoc = uwsgi_webdav_manage_prop(wsgi_req, NULL, filename, filename_len, 1);
|
|
break;
|
|
}
|
|
if (!strcmp((char *) node->name, "propname")) {
|
|
rdoc = uwsgi_webdav_manage_prop(wsgi_req, node, filename, filename_len, 0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
rdoc = uwsgi_webdav_manage_prop(wsgi_req, NULL, filename, filename_len, 1);
|
|
}
|
|
|
|
if (!rdoc) return UWSGI_OK;
|
|
|
|
xmlChar *xmlbuf;
|
|
int xlen = 0;
|
|
xmlDocDumpFormatMemory(rdoc, &xmlbuf, &xlen, 1);
|
|
uwsgi_response_add_content_length(wsgi_req, xlen);
|
|
uwsgi_response_write_body_do(wsgi_req, (char *) xmlbuf, xlen);
|
|
#ifdef UWSGI_DEBUG
|
|
uwsgi_log("\n%.*s\n", xlen, xmlbuf);
|
|
#endif
|
|
xmlFreeDoc(rdoc);
|
|
xmlFree(xmlbuf);
|
|
return UWSGI_OK;
|
|
}
|
|
|
|
static int uwsgi_webdav_prop_set(char *filename, char *attr, char *ns, char *body) {
|
|
int ret = 0;
|
|
#if defined(__linux__) || defined(__APPLE__)
|
|
char *xattr_name = NULL;
|
|
if (ns) {
|
|
xattr_name = uwsgi_concat4("user.uwsgi.webdav.", ns, "|", attr);
|
|
}
|
|
else {
|
|
xattr_name = uwsgi_concat2("user.uwsgi.webdav.", attr);
|
|
}
|
|
#if defined(__linux__)
|
|
ret = setxattr(filename, xattr_name, body, strlen(body), 0);
|
|
#elif defined(__APPLE__)
|
|
ret = setxattr(filename, xattr_name, body, strlen(body), 0, 0);
|
|
#endif
|
|
free(xattr_name);
|
|
#endif
|
|
return ret;
|
|
}
|
|
|
|
static int uwsgi_webdav_prop_del(char *filename, char *attr, char *ns) {
|
|
int ret = 0;
|
|
#if defined(__linux__) || defined(__APPLE__)
|
|
char *xattr_name = NULL;
|
|
if (ns) {
|
|
xattr_name = uwsgi_concat4("user.uwsgi.webdav.", ns, "|", attr);
|
|
}
|
|
else {
|
|
xattr_name = uwsgi_concat2("user.uwsgi.webdav.", attr);
|
|
}
|
|
#if defined(__linux__)
|
|
ret = removexattr(filename, xattr_name);
|
|
#elif defined(__APPLE__)
|
|
ret = removexattr(filename, xattr_name, 0);
|
|
#endif
|
|
free(xattr_name);
|
|
#endif
|
|
return ret;
|
|
}
|
|
|
|
static void uwsgi_webdav_do_prop_update(struct wsgi_request *wsgi_req, xmlNode *prop, xmlNode *response, char *filename, uint8_t action) {
|
|
xmlNode *node;
|
|
// search for "prop"
|
|
for (node = prop->children; node; node = node->next) {
|
|
if (node->type == XML_ELEMENT_NODE) {
|
|
xmlNode *propstat = xmlNewChild(response, NULL, BAD_CAST "propstat", NULL);
|
|
xmlNode *r_prop = xmlNewChild(propstat, NULL, BAD_CAST "prop" , NULL);
|
|
xmlNode *new_prop = xmlNewChild(r_prop, NULL, node->name, NULL);
|
|
if (node->ns) {
|
|
xmlNsPtr xattr_ns = xmlNewNs(new_prop, node->ns->href, NULL);
|
|
xmlSetNs(new_prop, xattr_ns);
|
|
}
|
|
if (action == 0) {
|
|
if (uwsgi_webdav_prop_set(filename, (char *) node->name, node->ns ? (char *) node->ns->href : NULL, node->children ? (char *) node->children->content : "")) {
|
|
char *r_status = uwsgi_concat2n(wsgi_req->protocol, wsgi_req->protocol_len, " 403 Forbidden", 14);
|
|
xmlNewChild(r_prop, NULL, BAD_CAST "status", BAD_CAST r_status);
|
|
free(r_status);
|
|
}
|
|
else {
|
|
char *r_status = uwsgi_concat2n(wsgi_req->protocol, wsgi_req->protocol_len, " 200 OK", 7);
|
|
xmlNewChild(r_prop, NULL, BAD_CAST "status", BAD_CAST r_status);
|
|
free(r_status);
|
|
}
|
|
}
|
|
else if (action == 1) {
|
|
if (uwsgi_webdav_prop_del(filename, (char *) node->name, node->ns ? (char *) node->ns->href : NULL)) {
|
|
char *r_status = uwsgi_concat2n(wsgi_req->protocol, wsgi_req->protocol_len, " 403 Forbidden", 14);
|
|
xmlNewChild(r_prop, NULL, BAD_CAST "status", BAD_CAST r_status);
|
|
free(r_status);
|
|
}
|
|
else {
|
|
char *r_status = uwsgi_concat2n(wsgi_req->protocol, wsgi_req->protocol_len, " 200 OK", 7);
|
|
xmlNewChild(r_prop, NULL, BAD_CAST "status", BAD_CAST r_status);
|
|
free(r_status);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// action 0 is set, 1 is remove
|
|
static void uwsgi_webdav_manage_prop_update(struct wsgi_request *wsgi_req, xmlNode *parent, xmlNode *response, char *filename, uint8_t action) {
|
|
xmlNode *node;
|
|
// search for "prop"
|
|
for (node = parent->children; node; node = node->next) {
|
|
if (node->type == XML_ELEMENT_NODE) {
|
|
if (node->ns && !strcmp((char *) node->ns->href, "DAV:")) {
|
|
if (!strcmp((char *) node->name, "prop")) {
|
|
uwsgi_webdav_do_prop_update(wsgi_req, node, response, filename, action);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int uwsgi_wevdav_manage_proppatch(struct wsgi_request *wsgi_req, xmlDoc * doc) {
|
|
char filename[PATH_MAX];
|
|
size_t filename_len = uwsgi_webdav_expand_path(wsgi_req, wsgi_req->path_info, wsgi_req->path_info_len, filename);
|
|
if (filename_len == 0) {
|
|
uwsgi_404(wsgi_req);
|
|
return UWSGI_OK;
|
|
}
|
|
|
|
xmlNode *element = xmlDocGetRootElement(doc);
|
|
if (!element) return -1;
|
|
|
|
if (!element || (strcmp((char *) element->name, "propertyupdate"))) return -1;
|
|
|
|
if (uwsgi_response_prepare_headers(wsgi_req, "207 Multi-Status", 16))
|
|
return -1;
|
|
if (uwsgi_response_add_content_type(wsgi_req, "application/xml; charset=\"utf-8\"", 32))
|
|
return -1;
|
|
|
|
xmlDoc *rdoc = xmlNewDoc(BAD_CAST "1.0");
|
|
xmlNode *multistatus = xmlNewNode(NULL, BAD_CAST "multistatus");
|
|
xmlDocSetRootElement(rdoc, multistatus);
|
|
xmlNsPtr dav_ns = xmlNewNs(multistatus, BAD_CAST "DAV:", BAD_CAST "D");
|
|
xmlSetNs(multistatus, dav_ns);
|
|
xmlNode *response = xmlNewChild(multistatus, dav_ns, BAD_CAST "response", NULL);
|
|
|
|
char *uri = uwsgi_concat2n(wsgi_req->path_info, wsgi_req->path_info_len, "", 0);
|
|
uint16_t uri_len = strlen(uri) ;
|
|
char *encoded_uri = uwsgi_malloc( (uri_len * 3) + 1);
|
|
http_url_encode(uri, &uri_len, encoded_uri);
|
|
encoded_uri[uri_len] = 0;
|
|
xmlNewChild(response, dav_ns, BAD_CAST "href", BAD_CAST encoded_uri);
|
|
free(encoded_uri);
|
|
free(uri);
|
|
|
|
// propfind can be "set" or "remove"
|
|
xmlNode *node;
|
|
for (node = element->children; node; node = node->next) {
|
|
if (node->type == XML_ELEMENT_NODE) {
|
|
if (node->ns && !strcmp((char *) node->ns->href, "DAV:")) {
|
|
if (!strcmp((char *) node->name, "set")) {
|
|
uwsgi_webdav_manage_prop_update(wsgi_req, node, response, filename, 0);
|
|
}
|
|
else if (!strcmp((char *) node->name, "remove")) {
|
|
uwsgi_webdav_manage_prop_update(wsgi_req, node, response, filename, 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!rdoc) return UWSGI_OK;
|
|
|
|
xmlChar *xmlbuf;
|
|
int xlen = 0;
|
|
xmlDocDumpFormatMemory(rdoc, &xmlbuf, &xlen, 1);
|
|
uwsgi_response_add_content_length(wsgi_req, xlen);
|
|
uwsgi_response_write_body_do(wsgi_req, (char *) xmlbuf, xlen);
|
|
#ifdef UWSGI_DEBUG
|
|
uwsgi_log("\n%.*s\n", xlen, xmlbuf);
|
|
#endif
|
|
xmlFreeDoc(rdoc);
|
|
xmlFree(xmlbuf);
|
|
return UWSGI_OK;
|
|
}
|
|
|
|
|
|
static int uwsgi_wevdav_manage_put(struct wsgi_request *wsgi_req) {
|
|
char filename[PATH_MAX];
|
|
size_t filename_len = uwsgi_webdav_expand_path(wsgi_req, wsgi_req->path_info, wsgi_req->path_info_len, filename);
|
|
// the collection does not exist, search for the last /
|
|
if (!filename_len) {
|
|
filename_len = uwsgi_webdav_expand_fake_path(wsgi_req, wsgi_req->path_info, wsgi_req->path_info_len, filename);
|
|
if (!filename_len) {
|
|
uwsgi_response_prepare_headers(wsgi_req, "409 Conflict", 12);
|
|
return UWSGI_OK;
|
|
}
|
|
}
|
|
|
|
int fd = open(filename, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
|
if (fd < 0) {
|
|
uwsgi_403(wsgi_req);
|
|
return UWSGI_OK;
|
|
}
|
|
|
|
if (uwsgi_response_prepare_headers(wsgi_req, "201 Created", 11)) goto end;
|
|
|
|
size_t remains = wsgi_req->post_cl;
|
|
while(remains > 0) {
|
|
ssize_t body_len = 0;
|
|
char *body = uwsgi_request_body_read(wsgi_req, UMIN(remains, 32768) , &body_len);
|
|
if (!body || body == uwsgi.empty) break;
|
|
if (write(fd, body, body_len) != body_len) goto end;
|
|
}
|
|
|
|
end:
|
|
close(fd);
|
|
return UWSGI_OK;
|
|
}
|
|
|
|
static int uwsgi_webdav_massive_delete(char *dir) {
|
|
int ret = 0;
|
|
DIR *d = opendir(dir);
|
|
for (;;) {
|
|
struct dirent *de_r = NULL;
|
|
struct dirent de;
|
|
if (readdir_r(d, &de, &de_r)) {
|
|
ret = -1;
|
|
goto end;
|
|
}
|
|
if (de_r == NULL) break;
|
|
// skip myself and parent
|
|
if (!strcmp(de.d_name, ".") || !strcmp(de.d_name, "..")) continue;
|
|
char *item = uwsgi_concat3(dir, "/", de.d_name);
|
|
if (de.d_type == DT_DIR) {
|
|
if (uwsgi_webdav_massive_delete(item)) {
|
|
free(item);
|
|
ret = -1;
|
|
goto end;
|
|
}
|
|
}
|
|
else {
|
|
if (unlink(item)) {
|
|
free(item);
|
|
ret = -1;
|
|
goto end;
|
|
}
|
|
}
|
|
free(item);
|
|
}
|
|
if (rmdir(dir)) ret = -1;
|
|
end:
|
|
closedir(d);
|
|
return ret;
|
|
}
|
|
|
|
static int uwsgi_wevdav_manage_delete(struct wsgi_request *wsgi_req) {
|
|
char filename[PATH_MAX];
|
|
size_t filename_len = uwsgi_webdav_expand_path(wsgi_req, wsgi_req->path_info, wsgi_req->path_info_len, filename);
|
|
// the collection does not exists
|
|
if (!filename_len) {
|
|
uwsgi_404(wsgi_req);
|
|
return UWSGI_OK;
|
|
}
|
|
|
|
if (uwsgi_is_dir(filename)) {
|
|
int ret = rmdir(filename);
|
|
if (ret < 0) {
|
|
if (errno == ENOTEMPTY) {
|
|
if (uwsgi_webdav_massive_delete(filename)) {
|
|
uwsgi_403(wsgi_req);
|
|
return UWSGI_OK;
|
|
}
|
|
}
|
|
else {
|
|
uwsgi_403(wsgi_req);
|
|
return UWSGI_OK;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (unlink(filename)) {
|
|
uwsgi_403(wsgi_req);
|
|
return UWSGI_OK;
|
|
}
|
|
}
|
|
|
|
uwsgi_response_prepare_headers(wsgi_req, "200 OK", 6);
|
|
return UWSGI_OK;
|
|
}
|
|
|
|
static int uwsgi_webdav_dirlist_add_item(struct uwsgi_buffer *ub, char *item, size_t item_len, uint8_t is_dir) {
|
|
if (is_dir) {
|
|
if (udav.class_directory) {
|
|
if (uwsgi_buffer_append(ub, "<li class=\"", 11)) return -1;
|
|
if (uwsgi_buffer_append(ub, udav.class_directory, strlen(udav.class_directory))) return -1;
|
|
if (uwsgi_buffer_append(ub, "\"><a href=\"", 11)) return -1;
|
|
}
|
|
else {
|
|
if (uwsgi_buffer_append(ub, "<li class=\"directory\"><a href=\"", 31)) return -1;
|
|
}
|
|
}
|
|
else {
|
|
if (uwsgi_buffer_append(ub, "<li><a href=\"", 13)) return -1;
|
|
}
|
|
if (uwsgi_buffer_append(ub, item, item_len)) return -1;
|
|
if (is_dir) {
|
|
if (uwsgi_buffer_append(ub, "/\">", 3)) return -1;
|
|
if (uwsgi_buffer_append(ub, item, item_len)) return -1;
|
|
if (uwsgi_buffer_append(ub, "/", 1)) return -1;
|
|
}
|
|
else {
|
|
if (uwsgi_buffer_append(ub, "\">", 2)) return -1;
|
|
if (uwsgi_buffer_append(ub, item, item_len)) return -1;
|
|
}
|
|
if (uwsgi_buffer_append(ub, "</a></li>", 9)) return -1;
|
|
return 0;
|
|
}
|
|
|
|
static void uwsgi_webdav_dirlist(struct wsgi_request *wsgi_req, char *dir) {
|
|
struct uwsgi_buffer *ub = uwsgi_buffer_new(uwsgi.page_size);
|
|
if (uwsgi_buffer_append(ub, "<html><head><title>", 19)) goto end;
|
|
if (uwsgi_buffer_append(ub, dir, strlen(dir))) goto end;
|
|
if (uwsgi_buffer_append(ub, "</title>", 8)) goto end;
|
|
|
|
struct uwsgi_string_list *usl = udav.css;
|
|
while(usl) {
|
|
if (uwsgi_buffer_append(ub, "<link rel=\"stylesheet\" href=\"", 29)) goto end;
|
|
if (uwsgi_buffer_append(ub, usl->value, usl->len)) goto end;
|
|
if (uwsgi_buffer_append(ub, "\" type=\"text/css\">", 18)) goto end;
|
|
usl = usl->next;
|
|
}
|
|
|
|
|
|
usl = udav.javascript;
|
|
while(usl) {
|
|
if (uwsgi_buffer_append(ub, "<script src=\"", 13)) goto end;
|
|
if (uwsgi_buffer_append(ub, usl->value, usl->len)) goto end;
|
|
if (uwsgi_buffer_append(ub, "\"></script>", 11)) goto end;
|
|
usl = usl->next;
|
|
}
|
|
|
|
if (uwsgi_buffer_append(ub, "</head><body>", 13)) goto end;
|
|
|
|
if (udav.div) {
|
|
if (uwsgi_buffer_append(ub, "<div id=\"", 9)) goto end;
|
|
if (uwsgi_buffer_append(ub, udav.div, strlen(udav.div))) goto end;
|
|
if (uwsgi_buffer_append(ub, "\">", 2)) goto end;
|
|
}
|
|
else {
|
|
if (uwsgi_buffer_append(ub, "<div>", 5)) goto end;
|
|
}
|
|
if (uwsgi_webdav_dirlist_add_item(ub, "..", 2, 1)) goto end;
|
|
|
|
#ifdef __linux__
|
|
struct dirent **tasklist;
|
|
int n = scandir(dir, &tasklist, 0, versionsort);
|
|
if (n < 0) goto end;
|
|
int i;
|
|
for(i=0;i<n;i++) {
|
|
if (tasklist[i]->d_name[0] == '.') goto next;
|
|
if (uwsgi_webdav_dirlist_add_item(ub, tasklist[i]->d_name, strlen(tasklist[i]->d_name), tasklist[i]->d_type == DT_DIR ? 1 : 0)) {
|
|
free(tasklist[i]);
|
|
free(tasklist);
|
|
goto end;
|
|
}
|
|
next:
|
|
free(tasklist[i]);
|
|
}
|
|
|
|
free(tasklist);
|
|
#else
|
|
DIR *d = opendir(dir);
|
|
for (;;) {
|
|
struct dirent *de_r = NULL;
|
|
struct dirent de;
|
|
if (readdir_r(d, &de, &de_r)) goto end;
|
|
if (de_r == NULL) break;
|
|
// skip items startign with a dot
|
|
if (de.d_name[0] == '.') continue;
|
|
if (uwsgi_webdav_dirlist_add_item(ub, de.d_name, strlen(de.d_name), de.d_type == DT_DIR ? 1 : 0)) goto end;
|
|
}
|
|
|
|
closedir(d);
|
|
#endif
|
|
|
|
if (uwsgi_buffer_append(ub, "</ul></div></body></html>", 25)) goto end;
|
|
|
|
if (uwsgi_response_add_content_type(wsgi_req, "text/html", 9)) goto end;
|
|
if (uwsgi_response_add_content_length(wsgi_req, ub->pos)) goto end;
|
|
|
|
uwsgi_response_write_body_do(wsgi_req, ub->buf, ub->pos);
|
|
end:
|
|
uwsgi_buffer_destroy(ub);
|
|
}
|
|
|
|
static int uwsgi_wevdav_manage_get(struct wsgi_request *wsgi_req, int send_body) {
|
|
char filename[PATH_MAX];
|
|
size_t filename_len = uwsgi_webdav_expand_path(wsgi_req, wsgi_req->path_info, wsgi_req->path_info_len, filename);
|
|
if (!filename_len) {
|
|
uwsgi_404(wsgi_req);
|
|
return UWSGI_OK;
|
|
}
|
|
|
|
if (uwsgi_is_dir(filename)) {
|
|
uwsgi_response_prepare_headers(wsgi_req, "200 OK", 6);
|
|
if (send_body) {
|
|
uwsgi_webdav_dirlist(wsgi_req, filename);
|
|
}
|
|
return UWSGI_OK;
|
|
}
|
|
|
|
int fd = open(filename, O_RDONLY);
|
|
if (fd < 0) {
|
|
uwsgi_403(wsgi_req);
|
|
return UWSGI_OK;
|
|
}
|
|
struct stat st;
|
|
if (fstat(fd, &st)) {
|
|
close(fd);
|
|
uwsgi_403(wsgi_req);
|
|
return UWSGI_OK;
|
|
}
|
|
|
|
if (uwsgi_response_prepare_headers(wsgi_req, "200 OK", 6))
|
|
goto end;
|
|
// add content_length
|
|
if (uwsgi_response_add_content_length(wsgi_req, st.st_size))
|
|
goto end;
|
|
// add last-modified
|
|
if (uwsgi_response_add_last_modified(wsgi_req, st.st_mtime))
|
|
goto end;
|
|
// add mime_type
|
|
size_t mime_type_len = 0;
|
|
char *mime_type = uwsgi_get_mime_type(filename, filename_len, &mime_type_len);
|
|
if (mime_type) {
|
|
if (uwsgi_response_add_content_type(wsgi_req, mime_type, mime_type_len))
|
|
goto end;
|
|
}
|
|
// add ETag (based on file mtime, not rock-solid, but good enough)
|
|
char *etag = uwsgi_num2str(st.st_mtime);
|
|
if (uwsgi_response_add_header(wsgi_req, "ETag", 4, etag, strlen(etag))) {
|
|
free(etag);
|
|
goto end;
|
|
}
|
|
free(etag);
|
|
// start sending the file (note: we do not use sendfile() api, for being able to use caching and transformations)
|
|
if (!send_body)
|
|
goto end;
|
|
// use a pretty big buffer (for performance reasons)
|
|
char buf[32768];
|
|
size_t remains = st.st_size;
|
|
while (remains > 0) {
|
|
ssize_t rlen = read(fd, buf, UMIN(32768, remains));
|
|
if (rlen <= 0) {
|
|
uwsgi_error("uwsgi_wevdav_manage_get/read()");
|
|
break;
|
|
}
|
|
remains -= rlen;
|
|
if (uwsgi_response_write_body_do(wsgi_req, buf, rlen)) {
|
|
break;
|
|
}
|
|
}
|
|
end:
|
|
close(fd);
|
|
return UWSGI_OK;
|
|
}
|
|
|
|
static int uwsgi_wevdav_manage_copy(struct wsgi_request *wsgi_req) {
|
|
uint16_t destination_len = 0;
|
|
char *destination = uwsgi_get_var(wsgi_req, "HTTP_DESTINATION", 16, &destination_len);
|
|
uwsgi_log("Destination: %.*s\n", destination_len, destination);
|
|
return -1;
|
|
}
|
|
|
|
static int uwsgi_wevdav_manage_move(struct wsgi_request *wsgi_req) {
|
|
char filename[PATH_MAX];
|
|
char d_filename[PATH_MAX];
|
|
size_t filename_len = uwsgi_webdav_expand_path(wsgi_req, wsgi_req->path_info, wsgi_req->path_info_len, filename);
|
|
if (filename_len == 0) {
|
|
uwsgi_404(wsgi_req);
|
|
return UWSGI_OK;
|
|
}
|
|
|
|
uint16_t destination_len = 0;
|
|
char *destination = uwsgi_get_var(wsgi_req, "HTTP_DESTINATION", 16, &destination_len);
|
|
if (destination_len == 0) {
|
|
uwsgi_403(wsgi_req);
|
|
return UWSGI_OK;
|
|
}
|
|
|
|
uint16_t overwrite_len = 0;
|
|
int can_overwrite = 1;
|
|
char *overwrite = uwsgi_get_var(wsgi_req, "HTTP_OVERWRITE", 14, &overwrite_len);
|
|
if (overwrite) {
|
|
if (overwrite[0] == 'F') {
|
|
can_overwrite = 0;
|
|
}
|
|
}
|
|
|
|
|
|
uint16_t scheme_len = wsgi_req->scheme_len;
|
|
if (wsgi_req->scheme_len == 0) {
|
|
// http
|
|
scheme_len = 4;
|
|
}
|
|
uint16_t skip = scheme_len + 3 + wsgi_req->host_len;
|
|
int already_exists = 0;
|
|
size_t d_filename_len = uwsgi_webdav_expand_path(wsgi_req, destination + skip, destination_len - skip, d_filename);
|
|
if (d_filename_len > 0) {
|
|
already_exists = 1;
|
|
if (!can_overwrite) {
|
|
uwsgi_response_prepare_headers(wsgi_req, "412 Precondition Failed", 23);
|
|
return UWSGI_OK;
|
|
}
|
|
}
|
|
else {
|
|
d_filename_len = uwsgi_webdav_expand_fake_path(wsgi_req, destination + skip, destination_len - skip, d_filename);
|
|
}
|
|
|
|
if (d_filename_len == 0) {
|
|
uwsgi_response_prepare_headers(wsgi_req, "409 Conflict", 12);
|
|
return UWSGI_OK;
|
|
}
|
|
|
|
if (rename(filename, d_filename)) {
|
|
uwsgi_403(wsgi_req);
|
|
return UWSGI_OK;
|
|
}
|
|
|
|
if (already_exists) {
|
|
uwsgi_response_prepare_headers(wsgi_req, "204 No Content", 14);
|
|
}
|
|
else {
|
|
uwsgi_response_prepare_headers(wsgi_req, "201 Created", 11);
|
|
}
|
|
|
|
return UWSGI_OK;
|
|
}
|
|
|
|
static int uwsgi_wevdav_manage_mkcol(struct wsgi_request *wsgi_req) {
|
|
if (wsgi_req->post_cl > 0) {
|
|
uwsgi_response_prepare_headers(wsgi_req, "415 Unsupported Media Type", 26);
|
|
return UWSGI_OK;
|
|
}
|
|
char filename[PATH_MAX];
|
|
size_t filename_len = uwsgi_webdav_expand_path(wsgi_req, wsgi_req->path_info, wsgi_req->path_info_len, filename);
|
|
// the collection already exists
|
|
if (filename_len > 0) {
|
|
uwsgi_response_prepare_headers(wsgi_req, "405 Method Not Allowed", 22);
|
|
return UWSGI_OK;
|
|
}
|
|
|
|
// remove the last slash (if needed)
|
|
if (wsgi_req->path_info_len > 1 && wsgi_req->path_info[wsgi_req->path_info_len-1] == '/') {
|
|
wsgi_req->path_info_len--;
|
|
}
|
|
|
|
filename_len = uwsgi_webdav_expand_fake_path(wsgi_req, wsgi_req->path_info, wsgi_req->path_info_len, filename);
|
|
if (!filename_len) {
|
|
uwsgi_response_prepare_headers(wsgi_req, "409 Conflict", 12);
|
|
return UWSGI_OK;
|
|
}
|
|
// mkdir, if it fails, return a 409 (Conflict)
|
|
if (mkdir(filename, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)) {
|
|
uwsgi_response_prepare_headers(wsgi_req, "409 Conflict", 12);
|
|
}
|
|
uwsgi_response_prepare_headers(wsgi_req, "201 Created", 11);
|
|
return UWSGI_OK;
|
|
}
|
|
|
|
static int uwsgi_wevdav_manage_mkcalendar(struct wsgi_request *wsgi_req, xmlDoc *doc) {
|
|
char filename[PATH_MAX];
|
|
size_t filename_len = uwsgi_webdav_expand_path(wsgi_req, wsgi_req->path_info, wsgi_req->path_info_len, filename);
|
|
// the collection already exists
|
|
if (filename_len > 0) {
|
|
uwsgi_response_prepare_headers(wsgi_req, "405 Method Not Allowed", 22);
|
|
return UWSGI_OK;
|
|
}
|
|
|
|
// remove the last slash (if needed)
|
|
if (wsgi_req->path_info_len > 1 && wsgi_req->path_info[wsgi_req->path_info_len-1] == '/') {
|
|
wsgi_req->path_info_len--;
|
|
}
|
|
|
|
filename_len = uwsgi_webdav_expand_fake_path(wsgi_req, wsgi_req->path_info, wsgi_req->path_info_len, filename);
|
|
if (!filename_len) {
|
|
uwsgi_response_prepare_headers(wsgi_req, "409 Conflict", 12);
|
|
return UWSGI_OK;
|
|
}
|
|
// mkdir, if it fails, return a 409 (Conflict)
|
|
if (mkdir(filename, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)) {
|
|
uwsgi_response_prepare_headers(wsgi_req, "409 Conflict", 12);
|
|
return UWSGI_OK;
|
|
}
|
|
|
|
xmlNode *element = xmlDocGetRootElement(doc);
|
|
if (!element) return -1;
|
|
|
|
if (!element || (strcmp((char *) element->name, "mkcalendar"))) return -1;
|
|
|
|
xmlDoc *rdoc = xmlNewDoc(BAD_CAST "1.0");
|
|
xmlNode *foobar = xmlNewNode(NULL, BAD_CAST "foobar");
|
|
xmlDocSetRootElement(rdoc, foobar);
|
|
|
|
// propfind can be "set" or "remove"
|
|
xmlNode *node;
|
|
for (node = element->children; node; node = node->next) {
|
|
if (node->type == XML_ELEMENT_NODE) {
|
|
if (node->ns && !strcmp((char *) node->ns->href, "DAV:")) {
|
|
if (!strcmp((char *) node->name, "set")) {
|
|
uwsgi_webdav_manage_prop_update(wsgi_req, node, foobar, filename, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
uwsgi_response_prepare_headers(wsgi_req, "201 Created", 11);
|
|
xmlFreeDoc(rdoc);
|
|
return UWSGI_OK;
|
|
}
|
|
|
|
|
|
static int uwsgi_wevdav_manage_lock(struct wsgi_request *wsgi_req) {
|
|
uwsgi_response_prepare_headers(wsgi_req, "201 Created", 11);
|
|
return UWSGI_OK;
|
|
}
|
|
|
|
static int uwsgi_webdav_request(struct wsgi_request *wsgi_req) {
|
|
|
|
if (!udav.mountpoints) {
|
|
uwsgi_500(wsgi_req);
|
|
return -1;
|
|
}
|
|
|
|
if (uwsgi_parse_vars(wsgi_req)) {
|
|
return -1;
|
|
}
|
|
|
|
if (wsgi_req->path_info_len == 0) {
|
|
uwsgi_403(wsgi_req);
|
|
return UWSGI_OK;
|
|
}
|
|
|
|
wsgi_req->app_id = uwsgi_get_app_id(wsgi_req, wsgi_req->appid, wsgi_req->appid_len, webdav_plugin.modifier1);
|
|
if (wsgi_req->app_id == -1) {
|
|
uwsgi_403(wsgi_req);
|
|
return UWSGI_OK;
|
|
}
|
|
|
|
// non lockables methods...
|
|
|
|
if (!uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "OPTIONS", 7)) {
|
|
return uwsgi_wevdav_manage_options(wsgi_req);
|
|
}
|
|
|
|
if (!uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "GET", 3)) {
|
|
return uwsgi_wevdav_manage_get(wsgi_req, 1);
|
|
}
|
|
|
|
if (!uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "HEAD", 4)) {
|
|
return uwsgi_wevdav_manage_get(wsgi_req, 0);
|
|
}
|
|
|
|
if (!uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "PROPFIND", 8)) {
|
|
if (wsgi_req->post_cl > 0) {
|
|
ssize_t body_len = 0;
|
|
char *body = uwsgi_request_body_read(wsgi_req, wsgi_req->post_cl, &body_len);
|
|
#ifdef UWSGI_DEBUG
|
|
uwsgi_log("%.*s\n", body_len, body);
|
|
#endif
|
|
xmlDoc *doc = xmlReadMemory(body, body_len, NULL, NULL, 0);
|
|
if (!doc) goto end;
|
|
uwsgi_wevdav_manage_propfind(wsgi_req, doc);
|
|
xmlFreeDoc(doc);
|
|
}
|
|
else {
|
|
uwsgi_wevdav_manage_propfind(wsgi_req, NULL);
|
|
}
|
|
}
|
|
|
|
if (!uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "REPORT", 6)) {
|
|
if (wsgi_req->post_cl > 0) {
|
|
ssize_t body_len = 0;
|
|
char *body = uwsgi_request_body_read(wsgi_req, wsgi_req->post_cl, &body_len);
|
|
#ifdef UWSGI_DEBUG
|
|
uwsgi_log("%.*s\n", body_len, body);
|
|
#endif
|
|
xmlDoc *doc = xmlReadMemory(body, body_len, NULL, NULL, 0);
|
|
if (!doc) goto end;
|
|
xmlFreeDoc(doc);
|
|
}
|
|
}
|
|
|
|
|
|
// lockable methods ...
|
|
// check for locking
|
|
|
|
if (!uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "PUT", 3)) {
|
|
return uwsgi_wevdav_manage_put(wsgi_req);
|
|
}
|
|
|
|
if (!uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "DELETE", 6)) {
|
|
return uwsgi_wevdav_manage_delete(wsgi_req);
|
|
}
|
|
|
|
if (!uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "MKCOL", 5)) {
|
|
return uwsgi_wevdav_manage_mkcol(wsgi_req);
|
|
}
|
|
|
|
if (!uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "MKCALENDAR", 10)) {
|
|
if (wsgi_req->post_cl == 0)
|
|
goto end;
|
|
ssize_t body_len = 0;
|
|
char *body = uwsgi_request_body_read(wsgi_req, wsgi_req->post_cl, &body_len);
|
|
#ifdef UWSGI_DEBUG
|
|
uwsgi_log("%.*s\n", body_len, body);
|
|
#endif
|
|
xmlDoc *doc = xmlReadMemory(body, body_len, NULL, NULL, 0);
|
|
if (!doc) goto end;
|
|
uwsgi_wevdav_manage_mkcalendar(wsgi_req, doc);
|
|
xmlFreeDoc(doc);
|
|
}
|
|
|
|
if (!uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "COPY", 4)) {
|
|
return uwsgi_wevdav_manage_copy(wsgi_req);
|
|
}
|
|
|
|
if (!uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "MOVE", 4)) {
|
|
return uwsgi_wevdav_manage_move(wsgi_req);
|
|
}
|
|
|
|
if (!uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "LOCK", 4)) {
|
|
if (wsgi_req->post_cl > 0) {
|
|
ssize_t body_len = 0;
|
|
char *body = uwsgi_request_body_read(wsgi_req, wsgi_req->post_cl, &body_len);
|
|
#ifdef UWSGI_DEBUG
|
|
uwsgi_log("%.*s\n", body_len, body);
|
|
#endif
|
|
xmlDoc *doc = xmlReadMemory(body, body_len, NULL, NULL, 0);
|
|
if (!doc) goto end;
|
|
xmlFreeDoc(doc);
|
|
}
|
|
return uwsgi_wevdav_manage_lock(wsgi_req);
|
|
}
|
|
|
|
if (!uwsgi_strncmp(wsgi_req->method, wsgi_req->method_len, "PROPPATCH", 9)) {
|
|
if (wsgi_req->post_cl == 0)
|
|
goto end;
|
|
ssize_t body_len = 0;
|
|
char *body = uwsgi_request_body_read(wsgi_req, wsgi_req->post_cl, &body_len);
|
|
#ifdef UWSGI_DEBUG
|
|
uwsgi_log("%.*s\n", body_len, body);
|
|
#endif
|
|
xmlDoc *doc = xmlReadMemory(body, body_len, NULL, NULL, 0);
|
|
if (!doc) goto end;
|
|
uwsgi_wevdav_manage_proppatch(wsgi_req, doc);
|
|
xmlFreeDoc(doc);
|
|
}
|
|
|
|
end:
|
|
return UWSGI_OK;
|
|
}
|
|
|
|
static void uwsgi_webdav_mount() {
|
|
struct uwsgi_string_list *usl = udav.mountpoints;
|
|
while(usl) {
|
|
if (uwsgi_apps_cnt >= uwsgi.max_apps) {
|
|
uwsgi_log("ERROR: you cannot load more than %d apps in a worker\n", uwsgi.max_apps);
|
|
exit(1);
|
|
}
|
|
int id = uwsgi_apps_cnt;
|
|
char *mountpoint = "";
|
|
int mountpoint_len = 0;
|
|
char *docroot = usl->value;
|
|
|
|
char *equal = strchr(usl->value, '=');
|
|
|
|
if (equal) {
|
|
*equal = 0;
|
|
docroot = equal+1;
|
|
mountpoint = usl->value;
|
|
mountpoint_len = strlen(mountpoint);
|
|
}
|
|
|
|
char *wd_docroot = uwsgi_calloc(PATH_MAX);
|
|
if (!realpath(docroot, wd_docroot)) {
|
|
uwsgi_error("uwsgi_webdav_mount()/realpath()");
|
|
exit(1);
|
|
}
|
|
if (equal) {
|
|
*equal = '=';
|
|
}
|
|
struct uwsgi_app *ua = uwsgi_add_app(id, webdav_plugin.modifier1, mountpoint, mountpoint_len, wd_docroot, wd_docroot);
|
|
uwsgi_emulate_cow_for_apps(id);
|
|
uwsgi_log("WebDAV mountpoint \"%.*s\" (%d) added: docroot=%s\n", ua->mountpoint_len, ua->mountpoint, id, wd_docroot );
|
|
usl = usl->next;
|
|
}
|
|
}
|
|
|
|
static void uwsgi_webdav_after_request(struct wsgi_request *wsgi_req) {
|
|
if (!udav.mountpoints) return;
|
|
log_request(wsgi_req);
|
|
}
|
|
|
|
struct uwsgi_plugin webdav_plugin = {
|
|
.modifier1 = 35,
|
|
.name = "webdav",
|
|
.options = uwsgi_webdav_options,
|
|
.init_apps = uwsgi_webdav_mount,
|
|
.request = uwsgi_webdav_request,
|
|
.after_request = uwsgi_webdav_after_request,
|
|
};
|