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 }