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 }