1 /**
2  * Authors: Szabo Bogdan <szabobogdan@yahoo.com>
3  * Date: 3 10, 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.locks;
8 
9 import vibedav.prop;
10 import vibedav.ifheader;
11 
12 import std.datetime;
13 import std.uuid;
14 import std..string;
15 import std.conv;
16 
17 import vibe.core.log;
18 import vibe.core.file;
19 import vibe.inet.mimetypes;
20 import vibe.inet.message;
21 import vibe.http.server;
22 import vibe.http.fileserver;
23 import vibe.http.router : URLRouter;
24 import vibe.stream.operations;
25 import vibe.utils.dictionarylist;
26 
27 //412 (Precondition Failed)
28 
29 class DavLockInfo {
30 
31 	enum Scope {
32 		exclusiveLock,
33 		sharedLock
34 	};
35 
36 	private SysTime _timeout;
37 
38 	string rootURL;
39 
40 	Scope scopeLock;
41 	bool isWrite;
42 	string owner;
43 	string uuid;
44 	DavDepth depth;
45 
46 	this() {
47 		auto id = randomUUID();
48 		uuid = "urn:uuid:" ~ id.toString();
49 	}
50 
51 	this(string uuid) {
52 		this.uuid = uuid;
53 	}
54 
55 	static DavLockInfo fromXML(DavProp node) {
56 		auto lock = new DavLockInfo;
57 
58 		if("lockinfo" !in node || "lockscope" !in node["lockinfo"])
59 			throw new DavException(HTTPStatus.preconditionFailed, "Lockinfo is missing.");
60 
61 		if("shared" in node["lockinfo"]["lockscope"])
62 			lock.scopeLock = Scope.sharedLock;
63 
64 		if("locktype" in node["lockinfo"] && "write" in node["lockinfo"]["locktype"])
65 			lock.isWrite = true;
66 
67 		lock.owner = node["lockinfo"]["owner"].value;
68 
69 		return lock;
70 	}
71 
72 	static DavLockInfo fromXML(DavProp node, DavResource resource) {
73 		auto lock = DavLockInfo.fromXML(node);
74 		lock.rootURL = resource.fullURL;
75 
76 		return lock;
77 	}
78 
79 	@property {
80 		/// Set the timeout based on the request header
81 		void timeout(Duration val) {
82 			_timeout = Clock.currTime + val;
83 		}
84 	}
85 
86 	override string toString() {
87 		string a = `<d:activelock>`;
88 
89 		if(isWrite)
90 			a ~= `<d:locktype><d:write/></d:locktype>`;
91 
92 		if(scopeLock == Scope.exclusiveLock)
93 			a ~= `<d:lockscope><d:exclusive/></d:lockscope>`;
94 		else if(scopeLock == Scope.sharedLock)
95 			a ~= `<d:lockscope><d:shared/></d:lockscope>`;
96 
97 		if(depth == DavDepth.zero)
98 			a ~= `<d:depth>0</d:depth>`;
99 		else if(depth == DavDepth.infinity)
100 			a ~= `<d:depth>infinity</d:depth>`;
101 
102 		if(owner != "")
103 			a ~= `<d:owner><d:href>`~owner~`</d:href></d:owner>`;
104 
105 		if(_timeout == SysTime.max)
106 			a ~= `<d:timeout>Infinite</d:timeout>`;
107 		else {
108 			long seconds = (_timeout - Clock.currTime).total!"seconds";
109 			a ~= `<d:timeout>Second-` ~ seconds.to!string ~ `</d:timeout>`;
110 		}
111 
112 		a ~= `<d:locktoken><d:href>`~uuid~`</d:href></d:locktoken>`;
113 		a ~= `<d:lockroot><d:href>`~URL(rootURL).path.toNativeString~`</d:href></d:lockroot>`;
114 		a ~= `</d:activelock>`;
115 
116 		return a;
117 	}
118 }
119 
120 class DavLockList {
121 	protected {
122 		DavLockInfo[string][string] locks;
123 		string[string] etags;
124 	}
125 
126 	bool hasEtag(string url, string etag) {
127 		return url in etags && etags[url] == etag;
128 	}
129 
130 	bool existLock(string url, string uuid) {
131 		bool result;
132 		if(url !in locks || uuid !in locks[url])
133 			result = false;
134 		else
135 			result = true;
136 
137 		if(url == "DAV:no-lock")
138 			return !result;
139 
140 		return result;
141 	}
142 
143 	bool checkCondition(URL url, IfHeader header) {
144 		bool partialResult;
145 		string strUrl = url.toString;
146 
147 		if(header.isEmpty)
148 			return true;
149 
150 		//check the if header
151 		foreach(string conditionUrl, ifConditionList; header.list) {
152 			if(conditionUrl == "") conditionUrl = strUrl;
153 
154 			foreach(ifCondition; ifConditionList) {
155 				bool conditionResult;
156 
157 				//check for conditions
158 				bool result = existLock(conditionUrl, ifCondition.condition);
159 				if(ifCondition.isNot)
160 					result = !result;
161 
162 				if(result) {
163 					//check for etag
164 					if(ifCondition.etag != "")
165 						conditionResult = hasEtag(conditionUrl, ifCondition.etag);
166 					else
167 						conditionResult = true;
168 				}
169 
170 				//compute the partial result
171 				partialResult = partialResult || conditionResult;
172 			}
173 		}
174 
175 		return partialResult;
176 	}
177 
178 	bool canResolve(string[] list, bool[string] headerLocks) {
179 
180 		foreach(i; 0..list.length)
181 			if(list[i] !in headerLocks)
182 				return false;
183 
184 		return true;
185 	}
186 
187 	bool check(URL url, IfHeader header = IfHeader()) {
188 		if(!checkCondition(url, header))
189 			throw new DavException(HTTPStatus.preconditionFailed, "Precondition failes.");
190 
191 		auto mustResolve = lockedParentResource(url);
192 
193 		if(!canResolve(mustResolve, header.getLocks(url.toString)))
194 			throw new DavException(HTTPStatus.locked, "Locked.");
195 
196 		return true;
197 	}
198 
199 	string[] lockedParentResource(URL url, long depth = -1) {
200 		string[] list;
201 		string path = url.path.toString;
202 		string strUrl = url.toString;
203 
204 		if(strUrl in locks)
205 			foreach(uuid, lock; locks[strUrl])
206 				if(depth <= lock.depth)
207 					list ~= uuid;
208 
209 		if(path == "/")
210 			return list;
211 		else
212 			return list ~ lockedParentResource(url.parentURL, depth + 1);
213 	}
214 
215 	void add(DavLockInfo lockInfo) {
216 		locks[lockInfo.rootURL][lockInfo.uuid] = lockInfo;
217 	}
218 
219 	void remove(DavResource resource, string token) {
220 		string strUrl = resource.fullURL;
221 		ulong index;
222 
223 		if(strUrl !in locks)
224 			throw new DavException(HTTPStatus.conflict ,"The resource is already unlocked.");
225 
226 		if(token !in locks[strUrl])
227 			throw new DavException(HTTPStatus.conflict ,"Invalid lock uuid.");
228 
229 		locks[resource.fullURL].remove(token);
230 
231 		if(locks[resource.fullURL].keys.length == 0)
232 			locks.remove(resource.fullURL);
233 	}
234 
235 	bool hasLock(string url) {
236 		if(url !in locks)
237 			return false;
238 
239 		if(locks[url].keys.length > 0)
240 			return false;
241 
242 		return true;
243 	}
244 
245 	bool hasExclusiveLock(string url) {
246 		if(url !in locks)
247 			return false;
248 
249 		foreach(string uuid, lock; locks[url])
250 			if(lock.scopeLock == DavLockInfo.Scope.exclusiveLock)
251 				return true;
252 
253 		return false;
254 	}
255 
256 	DavLockInfo opIndex(string path, string uuid) {
257 		if(path !in locks || uuid !in locks[path])
258 			return null;
259 
260 		return locks[path][uuid];
261 	}
262 
263 	DavLockInfo[string] opIndex(string path) {
264 		if(path !in locks)
265 			return null;
266 
267 		return locks[path];
268 	}
269 
270 	void setETag(URL url, string etag) {
271 		etags[url.toString] = etag;
272 	}
273 }