1 /**
2  * Authors: Szabo Bogdan <szabobogdan@yahoo.com>
3  * Date: 3 29, 2015
4  * License: Subject to the	 terms of the MIT license, as written in the included LICENSE.txt file.
5  * Copyright: Public Domain
6  */
7 module vibedav.davresource;
8 
9 import vibedav.prop;
10 
11 import vibe.core.log;
12 import vibe.core.file;
13 import vibe.inet.mimetypes;
14 import vibe.inet.message;
15 import vibe.http.server;
16 import vibe.http.fileserver;
17 import vibe.http.router : URLRouter;
18 import vibe.stream.operations;
19 import vibe.internal.meta.uda;
20 
21 import std.stdio;
22 import std.datetime;
23 import std..string;
24 import std.file;
25 import std.path;
26 import std.conv;
27 
28 struct ResourcePropertyValue {
29 	enum Mode {
30 		none,
31 		attribute,
32 		tagName,
33 		tagAttributes,
34 		tag,
35 		levelTag
36 	}
37 
38 	string name;
39 	string name2;
40 	string ns;
41 	string attr;
42 	Mode mode;
43 
44 	DavProp create(string val) {
45 		if(mode == Mode.attribute)
46 			return createAttribute(val);
47 
48 		if(mode == Mode.tagName)
49 			return createTagName(val);
50 
51 		if(mode == Mode.tag)
52 			return createTagText(val);
53 
54 		if(mode == Mode.levelTag)
55 			return createLevelTag(val);
56 
57 		return new DavProp(val);
58 	}
59 
60 	DavProp create(string[string] val) {
61 		if(mode == Mode.tagAttributes)
62 			return createTagList(val);
63 
64 		throw new DavException(HTTPStatus.internalServerError, "Can't parse value.");
65 	}
66 
67 	DavProp createAttribute(string val) {
68 		DavProp p = new DavProp(ns, name);
69 		p.attribute[attr] = val;
70 
71 		return p;
72 	}
73 
74 	DavProp createTagName(string val) {
75 		return DavProp.FromKey(val, "");
76 	}
77 
78 	DavProp createTagList(string[string] val) {
79 		DavProp p = new DavProp(ns, name);
80 
81 		foreach(k, v; val)
82 			p.attribute[k] = v;
83 
84 		return p;
85 	}
86 
87 	DavProp createLevelTag(string val) {
88 		DavProp level1 = new DavProp(ns, name);
89 		DavProp level2 = new DavProp(ns, name2);
90 
91 		level2.addChild(DavProp.FromKey(val, ""));
92 		level1.addChild(level2);
93 
94 		return level1;
95 	}
96 
97 	DavProp createTagText(string val) {
98 		return new DavProp(ns, name, val);
99 	}
100 }
101 
102 /// Make the returned value to be rendered like this: <[name] xmlns="[ns]" [attr]=[value]/>
103 ResourcePropertyValue ResourcePropertyValueAttr(string name, string ns, string attr) {
104 	ResourcePropertyValue v;
105 	v.name = name;
106 	v.ns = ns;
107 	v.attr = attr;
108 	v.mode = ResourcePropertyValue.Mode.attribute;
109 
110 	return v;
111 }
112 
113 /// Make the returned value to be a <collection> tag or not
114 ResourcePropertyValue ResourcePropertyTagName() {
115 	ResourcePropertyValue v;
116 	v.mode = ResourcePropertyValue.Mode.tagName;
117 
118 	return v;
119 }
120 
121 /// Make the returned value to be <[name] xmlns=[ns] [attribute list]/>
122 ResourcePropertyValue ResourcePropertyTagAttributes(string name, string ns) {
123 	ResourcePropertyValue v;
124 	v.name = name;
125 	v.ns = ns;
126 	v.mode = ResourcePropertyValue.Mode.tagAttributes;
127 
128 	return v;
129 }
130 
131 /// Make the returned value to be: <[name] xmlns=[ns]>[value]</[name]>
132 ResourcePropertyValue ResourcePropertyTagText(string name, string ns) {
133 	ResourcePropertyValue v;
134 	v.name = name;
135 	v.ns = ns;
136 	v.mode = ResourcePropertyValue.Mode.tag;
137 
138 	return v;
139 }
140 
141 /// Make the returned value to be: <[level1Name] xmlns=[ns]><[level2Name] xmlns=[ns]><value></[level2Name]></[level1Name]>
142 ResourcePropertyValue ResourcePropertyLevelTagText(string level1Name, string level2Name, string ns) {
143 	ResourcePropertyValue v;
144 	v.name  = level1Name;
145 	v.name2 = level2Name;
146 	v.ns = ns;
147 	v.mode = ResourcePropertyValue.Mode.levelTag;
148 
149 	return v;
150 }
151 
152 struct ResourceProperty {
153 	string name;
154 	string ns;
155 }
156 
157 ResourceProperty getResourceProperty(T...)() {
158 	static if(T.length == 0)
159 		static assert(false, "There is no `@ResourceProperty` attribute.");
160 	else static if( is(typeof(T[0]) == ResourceProperty) )
161 		return T[0];
162 	else
163 		return getResourceProperty!(T[1..$]);
164 }
165 
166 ResourcePropertyValue getResourceTagProperty(T...)() {
167 	static if(T.length == 0) {
168 		ResourcePropertyValue v;
169 		return v;
170 	}
171 	else static if( is(typeof(T[0]) == ResourcePropertyValue) )
172 		return T[0];
173 	else
174 		return getResourceTagProperty!(T[1..$]);
175 }
176 
177 pure bool hasDavInterfaceProperty(I)(string key) {
178 	bool result = false;
179 
180 	void keyExist(T...)() {
181 		static if(T.length > 0) {
182 			enum val = getResourceProperty!(__traits(getAttributes, __traits(getMember, I, T[0])));
183 			enum staticKey = val.name ~ ":" ~ val.ns;
184 
185 			if(staticKey == key)
186 				result = true;
187 
188 			keyExist!(T[1..$])();
189 		}
190 	}
191 
192 	keyExist!(__traits(allMembers, I))();
193 
194 	return result;
195 }
196 
197 DavProp propFrom(T, U)(string name, string ns, T value, U tagVal) {
198 	string v;
199 
200 	auto p = new DavProp(ns, name);
201 
202 	static if( is(T == SysTime) )
203 	{
204 		auto elm = tagVal.create(toRFC822DateTimeString(value));
205 		p.addChild(elm);
206 	}
207 	else static if( is(T == string[]) )
208 	{
209 		foreach(item; value) {
210 			auto tag = tagVal.create(item);
211 
212 			try
213 				p.addChild(tag);
214 			catch(Exception e)
215 				writeln(e);
216 		}
217 	}
218 	else static if( is(T == string[][string]) )
219 	{
220 		foreach(item; value) {
221 			auto tag = tagVal.create(item);
222 			p.addChild(tag);
223 		}
224 	}
225 	else
226 	{
227 		auto elm = tagVal.create(value.to!string);
228 		p.addChild(elm);
229 	}
230 
231 	return p;
232 }
233 
234 DavProp getDavInterfaceProperty(I, T)(string key, T plugin, DavResource resource) {
235 	DavProp result;
236 
237 	void getProp(T...)() {
238 		static if(T.length > 0) {
239 			enum val = getResourceProperty!(__traits(getAttributes, __traits(getMember, I, T[0])));
240 			enum tagVal = getResourceTagProperty!(__traits(getAttributes, __traits(getMember, I, T[0])));
241 			enum staticKey = val.name ~ ":" ~ val.ns;
242 
243 
244 			if(staticKey == key) {
245 				auto value = __traits(getMember, plugin, T[0])(resource);
246 				result = propFrom(val.name, val.ns, value, tagVal);
247 			}
248 
249 			getProp!(T[1..$])();
250 		}
251 	}
252 
253 	getProp!(__traits(allMembers, I))();
254 
255 	return result;
256 }
257 
258 enum DavDepth : int {
259 	zero = 0,
260 	one = 1,
261 	infinity = 99
262 };
263 
264 interface IDavBindingProperties {
265 
266 	@property {
267 
268 		@ResourceProperty("resource-id", "DAV:")
269 		string resourceId(DavResource resource);
270 	}
271 }
272 
273 interface IDavReportSetProperties {
274 
275 	@property {
276 
277 		@ResourcePropertyLevelTagText("supported-report", "report", "DAV:")
278 		@ResourceProperty("supported-report-set", "DAV:")
279 		string[] supportedReportSet(DavResource resource);
280 	}
281 }
282 
283 interface IDavResourceProperties {
284 	@ResourceProperty("creationdate", "DAV:")
285 	SysTime creationDate(DavResource resource);
286 
287 	@ResourceProperty("getlastmodified", "DAV:")
288 	SysTime lastModified(DavResource resource);
289 
290 	@ResourceProperty("getetag", "DAV:")
291 	string eTag(DavResource resource);
292 
293 	@ResourceProperty("getcontenttype", "DAV:")
294 	string contentType(DavResource resource);
295 
296 	@ResourceProperty("getcontentlength", "DAV:")
297 	ulong contentLength(DavResource resource);
298 
299 	@ResourceProperty("resourcetype", "DAV:")
300 	@ResourcePropertyTagName()
301 	string[] resourceType(DavResource resource);
302 
303 	@ResourceProperty("displayname", "DAV:")
304 	string displayName(DavResource resource);
305 }
306 
307 interface IDavResourceExtendedProperties {
308 
309 	@ResourceProperty("add-member", "DAV:")
310 	@ResourcePropertyTagText("href", "DAV:")
311 	string[] addMember();
312 
313 	@ResourceProperty("owner", "DAV:")
314 	@ResourcePropertyTagText("href", "DAV:")
315 	string owner();
316 }
317 
318 interface IDavResourcePlugin {
319 
320 	bool canSetContent(DavResource resource);
321 	bool canGetStream(DavResource resource);
322 	bool canGetProperty(DavResource resource, string name);
323 	bool canSetProperty(DavResource resource, string name);
324 	bool canRemoveProperty(DavResource resource, string name);
325 
326 	void setContent(DavResource resource, const ubyte[] content);
327 	void setContent(DavResource resource, InputStream content, ulong size);
328 	InputStream stream(DavResource resource);
329 
330 	void copyPropertiesTo(URL source, URL destination);
331 	DavProp property(DavResource resource, string name);
332 	HTTPStatus setProperty(DavResource resource, string name, DavProp prop);
333 	HTTPStatus removeProperty(DavResource resource, string name);
334 
335 	@property {
336 		string name();
337 	}
338 }
339 
340 abstract class BaseDavResourcePlugin : IDavResourcePlugin {
341 
342 	bool canSetContent(DavResource resource) {
343 		return false;
344 	}
345 
346 	bool canGetStream(DavResource resource) {
347 		return false;
348 	}
349 
350 	bool canSetProperty(DavResource resource, string name) {
351 		return false;
352 	}
353 
354 	bool canRemoveProperty(DavResource resource, string name) {
355 		return false;
356 	}
357 
358 	bool canGetProperty(DavResource resource, string name) {
359 		return false;
360 	}
361 
362 	void setContent(DavResource resource, const ubyte[] content) {
363 		throw new DavException(HTTPStatus.internalServerError, "Can't set content.");
364 	}
365 
366 	void setContent(DavResource resource, InputStream content, ulong size) {
367 		throw new DavException(HTTPStatus.internalServerError, "Can't set content.");
368 	}
369 
370 	InputStream stream(DavResource resource) {
371 		throw new DavException(HTTPStatus.internalServerError, "Can't get stream.");
372 	}
373 
374 	void copyPropertiesTo(URL source, URL destination) { }
375 
376 	DavProp property(DavResource resource, string name) {
377 		throw new DavException(HTTPStatus.internalServerError, "Can't get property.");
378 	}
379 
380 	HTTPStatus setProperty(DavResource resource, string name, DavProp prop) {
381 		throw new DavException(HTTPStatus.internalServerError, "Can't set property.");
382 	}
383 
384 	HTTPStatus removeProperty(DavResource resource, string name) {
385 		throw new DavException(HTTPStatus.internalServerError, "Can't remove property.");
386 	}
387 }
388 
389 interface IDavResourcePluginHub {
390 	void registerPlugin(IDavResourcePlugin plugin);
391 	bool hasPlugin(string name);
392 }
393 
394 class ResourceBasicProperties : BaseDavResourcePlugin, IDavResourceProperties {
395 
396 	SysTime creationDate(DavResource resource) {
397 		return resource.creationDate;
398 	}
399 
400 	SysTime lastModified(DavResource resource) {
401 		return resource.lastModified;
402 	}
403 
404 	string eTag(DavResource resource) {
405 		return resource.eTag;
406 	}
407 
408 	string contentType(DavResource resource) {
409 		return resource.contentType;
410 	}
411 
412 	ulong contentLength(DavResource resource) {
413 		return resource.contentLength;
414 	}
415 
416 	string[] resourceType(DavResource resource) {
417 		return resource.resourceType;
418 	}
419 
420 	string displayName(DavResource resource) {
421 		return resource.name;
422 	}
423 
424 	override {
425 		bool canGetProperty(DavResource resource, string name) {
426 			if(hasDavInterfaceProperty!IDavResourceProperties(name))
427 				return true;
428 
429 			return false;
430 		}
431 
432 		DavProp property(DavResource resource, string name) {
433 
434 			if(canGetProperty(resource, name))
435 				return getDavInterfaceProperty!IDavResourceProperties(name, this, resource);
436 
437 			throw new DavException(HTTPStatus.internalServerError, "Can't get property.");
438 		}
439 	}
440 
441 	@property {
442 		string name() {
443 			return "ResourceBasicProperties";
444 		}
445 	}
446 }
447 
448 class ResourceCustomProperties : BaseDavResourcePlugin {
449 
450 	private static DavProp[string][string] properties;
451 
452 	override {
453 		bool canGetProperty(DavResource resource, string name) {
454 			string u = resource.url.toString;
455 
456 			if(u !in properties)
457 				return false;
458 
459 			if(name !in properties[u])
460 				return false;
461 
462 			return true;
463 		}
464 
465 		bool canSetProperty(DavResource resource, string name) {
466 			return true;
467 		}
468 
469 		bool canRemoveProperty(DavResource resource, string name) {
470 			return canGetProperty(resource, name);
471 		}
472 
473 		void copyPropertiesTo(URL source, URL destination) {
474 			if(source.toString in properties)
475 				properties[destination.toString] = properties[source.toString];
476 		}
477 
478 		DavProp property(DavResource resource, string name) {
479 			if(canGetProperty(resource, name))
480 				return properties[resource.url.toString][name];
481 
482 			throw new DavException(HTTPStatus.internalServerError, "Can't get property.");
483 		}
484 
485 		HTTPStatus setProperty(DavResource resource, string name, DavProp prop) {
486 			if(resource.url.toString !in properties) {
487 				DavProp[string] list;
488 				properties[resource.url.toString] = list;
489 			}
490 
491 			properties[resource.url.toString][name] = prop;
492 
493 			return HTTPStatus.ok;
494 		}
495 
496 		HTTPStatus removeProperty(DavResource resource, string name) {
497 			if(canGetProperty(resource, name))
498 				properties[resource.url.toString].remove(name);
499 
500 			return HTTPStatus.notFound;
501 		}
502 	}
503 
504 	@property {
505 		string name() {
506 			return "ResourceCustomProperties";
507 		}
508 	}
509 }
510 
511 /// Represents a DAV resource
512 class DavResource : IDavResourcePluginHub {
513 	string href;
514 	URL url;
515 	string username;
516 
517 	SysTime creationDate;
518 	SysTime lastModified;
519 	string eTag;
520 	string contentType;
521 	ulong contentLength;
522 	string[] resourceType;
523 	string name;
524 
525 	protected {
526 		IDavResourcePlugin[] plugins;
527 		IDav dav;
528 	}
529 
530 	this(IDav dav, URL url) {
531 		this.dav = dav;
532 		this.url = url;
533 
534 		resourceType = [];
535 
536 		creationDate = Clock.currTime;
537 		lastModified = Clock.currTime;
538 	}
539 
540 	private {
541 		HTTPStatus removeProperty(string key) {
542 			HTTPStatus result = HTTPStatus.notFound;
543 
544 			foreach_reverse(plugin; plugins)
545 				if(plugin.canRemoveProperty(this, key))
546 					try plugin.removeProperty(this, key);
547 						catch (DavException e)
548 							result = e.status;
549 
550 			foreach_reverse(plugin; plugins)
551 				if(plugin.canGetProperty(this, key))
552 					result = HTTPStatus.ok;
553 
554 			return result;
555 		}
556 
557 		HTTPStatus setProperty(string key, DavProp prop) {
558 			HTTPStatus result = HTTPStatus.notFound;
559 
560 			foreach_reverse(plugin; plugins)
561 				if(plugin.canSetProperty(this, key))
562 					try plugin.setProperty(this, key, prop);
563 						catch (DavException e)
564 							result = e.status;
565 
566 			foreach_reverse(plugin; plugins)
567 				if(plugin.canGetProperty(this, key))
568 					result = HTTPStatus.ok;
569 
570 			return result;
571 		}
572 	}
573 
574 	void registerPlugin(IDavResourcePlugin plugin) {
575 		assert(!hasPlugin(plugin.name), plugin.name ~ " already added.");
576 		plugins ~= plugin;
577 	}
578 
579 	bool hasPlugin(string name) {
580 
581 		foreach_reverse(plugin; plugins)
582 			if(plugin.name == name)
583 				return true;
584 
585 		return false;
586 	}
587 
588 	@property {
589 		string fullURL() {
590 			return url.toString;
591 		}
592 
593 		string rootURL() {
594 			return dav.rootUrl.toString;
595 		}
596 
597 		Path path() {
598 			return dav.path(url);
599 		}
600 
601 		nothrow pure string type() {
602 			return "DavResource";
603 		}
604 	}
605 
606 	DavProp property(string key) {
607 		DavProp p;
608 
609 		foreach_reverse(plugin; plugins) {
610 			if(plugin.canGetProperty(this, key)) {
611 
612 				auto t = plugin.property(this, key);
613 
614 				if(p is null)
615 					p = t;
616 				else if(p.isGroup && t.isGroup)
617 					foreach(string k, DavProp child; t)
618 						p.addChild(child);
619 			}
620 		}
621 
622 		if(p !is null)
623 			return p;
624 
625 		throw new DavException(HTTPStatus.notFound, "Not Found");
626 	}
627 
628 	void filterProps(DavProp parent, bool[string] props) {
629 		DavProp item = new DavProp;
630 		item.parent = parent;
631 		item.name = "d:response";
632 
633 		DavProp[][int] result;
634 
635 		item[`d:href`] = urlToString(url);
636 
637 		foreach_reverse(key; props.keys) {
638 			DavProp p;
639 			auto splitPos = key.indexOf(":");
640 			auto tagName = key[0..splitPos];
641 			auto tagNameSpace = key[splitPos+1..$];
642 
643 			try {
644 				p = property(key);
645 				result[200] ~= p;
646 			} catch (DavException e) {
647 				p = new DavProp;
648 				p.name = tagName;
649 				p.namespaceAttr = tagNameSpace;
650 				result[e.status] ~= p;
651 			}
652 		}
653 
654 		/// Add the properties by status
655 		foreach_reverse(code; result.keys) {
656 			auto propStat = new DavProp;
657 			propStat.parent = item;
658 			propStat.name = "d:propstat";
659 			propStat["d:prop"] = "";
660 
661 			foreach(p; result[code]) {
662 				propStat["d:prop"].addChild(p);
663 			}
664 
665 			propStat["d:status"] = `HTTP/1.1 ` ~ code.to!string ~ ` ` ~ httpStatusText(code);
666 			item.addChild(propStat);
667 		}
668 
669 		item["d:status"] = `HTTP/1.1 200 OK`;
670 
671 		parent.addChild(item);
672 	}
673 
674 	string propPatch(DavProp document) {
675 		string description;
676 		string result = `<?xml version="1.0" encoding="utf-8" ?><d:multistatus xmlns:d="DAV:"><d:response>`;
677 		result ~= `<d:href>` ~ urlToString(url) ~ `</d:href>`;
678 
679 		auto updateList = [document].getTagChilds("propertyupdate");
680 
681 		foreach(string key, item; updateList[0]) {
682 			if(item.tagName == "remove") {
683 				//remove properties
684 				auto removeList = [item].getTagChilds("prop");
685 
686 				foreach(prop; removeList)
687 					foreach(string key, p; prop) {
688 						auto status = removeProperty(key);
689 						result ~= `<d:propstat><d:prop>` ~ p.toString ~ `</d:prop>`;
690 						result ~= `<d:status>HTTP/1.1 ` ~ status.to!int.to!string ~ ` ` ~ status.to!string ~ `</d:status></d:propstat>`;
691 					}
692 			}
693 			else if(item.tagName == "set") {
694 				//set properties
695 				auto setList = [item].getTagChilds("prop");
696 
697 				foreach(prop; setList) {
698 					foreach(string key, p; prop) {
699 						auto status = setProperty(key, p);
700 						result ~= `<d:propstat><d:prop>` ~ p.toString ~ `</d:prop>`;
701 						result ~= `<d:status>HTTP/1.1 ` ~ status.to!int.to!string ~ ` ` ~ status.to!string ~ `</d:status></d:propstat>`;
702 					}
703 				}
704 			}
705 		}
706 
707 		if(description != "")
708 			result ~= `<d:responsedescription>` ~ description ~ `</d:responsedescription>`;
709 
710 		result ~= `</d:response></d:multistatus>`;
711 
712 		return result;
713 	}
714 
715 	void copyPropertiesTo(URL destination) {
716 		foreach_reverse(plugin; plugins)
717 			plugin.copyPropertiesTo(url, destination);
718 	}
719 
720 	void setContent(const ubyte[] content) {
721 		foreach_reverse(plugin; plugins)
722 			if(plugin.canSetContent(this)) {
723 				plugin.setContent(this, content);
724 
725 				return;
726 			}
727 
728 		throw new DavException(HTTPStatus.methodNotAllowed, "setContent No plugin support.");
729 	}
730 
731 	void setContent(InputStream content, ulong size) {
732 
733 		foreach_reverse(plugin; plugins)
734 			if(plugin.canSetContent(this)) {
735 				plugin.setContent(this, content, size);
736 
737 				return;
738 			}
739 
740 		throw new DavException(HTTPStatus.methodNotAllowed, "setContent No plugin support.");
741 	}
742 
743 	@property {
744 		InputStream stream() {
745 			foreach_reverse(plugin; plugins)
746 			if(plugin.canGetStream(this))
747 				return plugin.stream(this);
748 
749 			throw new DavException(HTTPStatus.methodNotAllowed, "stream No plugin support.");
750 		}
751 
752 		pure nothrow bool isCollection() {
753 			foreach_reverse(type; resourceType)
754 				if(type == "collection:DAV:")
755 					return true;
756 
757 			return false;
758 		}
759 	}
760 }