| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372 | #import <libxml/parser.h>#import "DAVResponse.h"#import "HTTPLogging.h"// WebDAV specifications: http://webdav.org/specs/rfc4918.htmltypedef enum {  kDAVProperty_ResourceType = (1 << 0),  kDAVProperty_CreationDate = (1 << 1),  kDAVProperty_LastModified = (1 << 2),  kDAVProperty_ContentLength = (1 << 3),  kDAVAllProperties = kDAVProperty_ResourceType | kDAVProperty_CreationDate | kDAVProperty_LastModified | kDAVProperty_ContentLength} DAVProperties;#define kXMLParseOptions (XML_PARSE_NONET | XML_PARSE_RECOVER | XML_PARSE_NOBLANKS | XML_PARSE_COMPACT | XML_PARSE_NOWARNING | XML_PARSE_NOERROR)static const int httpLogLevel = HTTP_LOG_LEVEL_WARN;@implementation DAVResponsestatic void _AddPropertyResponse(NSString* itemPath, NSString* resourcePath, DAVProperties properties, NSMutableString* xmlString) {  CFStringRef escapedPath = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (__bridge CFStringRef)resourcePath, NULL,                                                                    CFSTR("<&>?+"), kCFStringEncodingUTF8);  if (escapedPath) {    NSDictionary* attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:itemPath error:NULL];    BOOL isDirectory = [[attributes fileType] isEqualToString:NSFileTypeDirectory];    [xmlString appendString:@"<D:response>"];      [xmlString appendFormat:@"<D:href>%@</D:href>", escapedPath];      [xmlString appendString:@"<D:propstat>"];        [xmlString appendString:@"<D:prop>"];                  if (properties & kDAVProperty_ResourceType) {            if (isDirectory) {              [xmlString appendString:@"<D:resourcetype><D:collection/></D:resourcetype>"];            } else {              [xmlString appendString:@"<D:resourcetype/>"];            }          }                    if ((properties & kDAVProperty_CreationDate) && [attributes objectForKey:NSFileCreationDate]) {            NSDateFormatter* formatter = [[NSDateFormatter alloc] init];            formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];            formatter.timeZone = [NSTimeZone timeZoneWithName:@"GMT"];            formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss'+00:00'";            [xmlString appendFormat:@"<D:creationdate>%@</D:creationdate>", [formatter stringFromDate:[attributes fileCreationDate]]];          }                    if ((properties & kDAVProperty_LastModified) && [attributes objectForKey:NSFileModificationDate]) {            NSDateFormatter* formatter = [[NSDateFormatter alloc] init];            formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];            formatter.timeZone = [NSTimeZone timeZoneWithName:@"GMT"];            formatter.dateFormat = @"EEE', 'd' 'MMM' 'yyyy' 'HH:mm:ss' GMT'";            [xmlString appendFormat:@"<D:getlastmodified>%@</D:getlastmodified>", [formatter stringFromDate:[attributes fileModificationDate]]];          }                    if ((properties & kDAVProperty_ContentLength) && !isDirectory && [attributes objectForKey:NSFileSize]) {            [xmlString appendFormat:@"<D:getcontentlength>%qu</D:getcontentlength>", [attributes fileSize]];          }                [xmlString appendString:@"</D:prop>"];        [xmlString appendString:@"<D:status>HTTP/1.1 200 OK</D:status>"];      [xmlString appendString:@"</D:propstat>"];    [xmlString appendString:@"</D:response>\n"];    CFRelease(escapedPath);  }}static xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name) {  while (child) {    if ((child->type == XML_ELEMENT_NODE) && !xmlStrcmp(child->name, name)) {      return child;    }    child = child->next;  }  return NULL;}- (id) initWithMethod:(NSString*)method headers:(NSDictionary*)headers bodyData:(NSData*)body resourcePath:(NSString*)resourcePath rootPath:(NSString*)rootPath {  if ((self = [super init])) {    _status = 200;    _headers = [[NSMutableDictionary alloc] init];        // 10.1 DAV Header    if ([method isEqualToString:@"OPTIONS"]) {      if ([[headers objectForKey:@"User-Agent"] hasPrefix:@"WebDAVFS/"]) {  // Mac OS X WebDAV support        [_headers setObject:@"1, 2" forKey:@"DAV"];      } else {        [_headers setObject:@"1" forKey:@"DAV"];      }    }        // 9.1 PROPFIND Method    if ([method isEqualToString:@"PROPFIND"]) {      NSInteger depth;      NSString* depthHeader = [headers objectForKey:@"Depth"];      if ([depthHeader isEqualToString:@"0"]) {        depth = 0;      } else if ([depthHeader isEqualToString:@"1"]) {        depth = 1;      } else {        HTTPLogError(@"Unsupported DAV depth \"%@\"", depthHeader);        return nil;      }            DAVProperties properties = 0;      xmlDocPtr document = xmlReadMemory(body.bytes, (int)body.length, NULL, NULL, kXMLParseOptions);      if (document) {        xmlNodePtr node = _XMLChildWithName(document->children, (const xmlChar*)"propfind");        if (node) {          node = _XMLChildWithName(node->children, (const xmlChar*)"prop");        }        if (node) {          node = node->children;          while (node) {            if (!xmlStrcmp(node->name, (const xmlChar*)"resourcetype")) {              properties |= kDAVProperty_ResourceType;            } else if (!xmlStrcmp(node->name, (const xmlChar*)"creationdate")) {              properties |= kDAVProperty_CreationDate;            } else if (!xmlStrcmp(node->name, (const xmlChar*)"getlastmodified")) {              properties |= kDAVProperty_LastModified;            } else if (!xmlStrcmp(node->name, (const xmlChar*)"getcontentlength")) {              properties |= kDAVProperty_ContentLength;            } else {              HTTPLogWarn(@"Unknown DAV property requested \"%s\"", node->name);            }            node = node->next;          }        } else {          HTTPLogWarn(@"HTTP Server: Invalid DAV properties\n%@", [[NSString alloc] initWithData:body encoding:NSUTF8StringEncoding]);        }        xmlFreeDoc(document);      }      if (!properties) {        properties = kDAVAllProperties;      }            NSString* basePath = [rootPath stringByAppendingPathComponent:resourcePath];      if (![basePath hasPrefix:rootPath] || ![[NSFileManager defaultManager] fileExistsAtPath:basePath]) {        return nil;      }            NSMutableString* xmlString = [NSMutableString stringWithString:@"<?xml version=\"1.0\" encoding=\"utf-8\" ?>"];      [xmlString appendString:@"<D:multistatus xmlns:D=\"DAV:\">\n"];      if (![resourcePath hasPrefix:@"/"]) {        resourcePath = [@"/" stringByAppendingString:resourcePath];      }      _AddPropertyResponse(basePath, resourcePath, properties, xmlString);      if (depth == 1) {        if (![resourcePath hasSuffix:@"/"]) {          resourcePath = [resourcePath stringByAppendingString:@"/"];        }        NSDirectoryEnumerator* enumerator = [[NSFileManager defaultManager] enumeratorAtPath:basePath];        NSString* path;        while ((path = [enumerator nextObject])) {          _AddPropertyResponse([basePath stringByAppendingPathComponent:path], [resourcePath stringByAppendingString:path], properties, xmlString);          [enumerator skipDescendents];        }      }      [xmlString appendString:@"</D:multistatus>"];            [_headers setObject:@"application/xml; charset=\"utf-8\"" forKey:@"Content-Type"];      _data = [xmlString dataUsingEncoding:NSUTF8StringEncoding];      _status = 207;    }        // 9.3 MKCOL Method    if ([method isEqualToString:@"MKCOL"]) {      NSString* path = [rootPath stringByAppendingPathComponent:resourcePath];      if (![path hasPrefix:rootPath]) {        return nil;      }            if (![[NSFileManager defaultManager] fileExistsAtPath:[path stringByDeletingLastPathComponent]]) {        HTTPLogError(@"Missing intermediate collection(s) at \"%@\"", path);        _status = 409;      } else if (![[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:NO attributes:nil error:NULL]) {        HTTPLogError(@"Failed creating collection at \"%@\"", path);        _status = 405;      }    }        // 9.8 COPY Method    // 9.9 MOVE Method    if ([method isEqualToString:@"MOVE"] || [method isEqualToString:@"COPY"]) {      if ([method isEqualToString:@"COPY"] && ![[headers objectForKey:@"Depth"] isEqualToString:@"infinity"]) {        HTTPLogError(@"Unsupported DAV depth \"%@\"", [headers objectForKey:@"Depth"]);        return nil;      }            NSString* sourcePath = [rootPath stringByAppendingPathComponent:resourcePath];      if (![sourcePath hasPrefix:rootPath] || ![[NSFileManager defaultManager] fileExistsAtPath:sourcePath]) {        return nil;      }            NSString* destination = [headers objectForKey:@"Destination"];      NSRange range = [destination rangeOfString:[headers objectForKey:@"Host"]];      if (range.location == NSNotFound) {        return nil;      }      NSString* destinationPath = [rootPath stringByAppendingPathComponent:        [[destination substringFromIndex:(range.location + range.length)] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];      if (![destinationPath hasPrefix:rootPath] || [[NSFileManager defaultManager] fileExistsAtPath:destinationPath]) {        return nil;      }            BOOL isDirectory;      if (![[NSFileManager defaultManager] fileExistsAtPath:[destinationPath stringByDeletingLastPathComponent] isDirectory:&isDirectory] || !isDirectory) {        HTTPLogError(@"Invalid destination path \"%@\"", destinationPath);        _status = 409;      } else {        BOOL existing = [[NSFileManager defaultManager] fileExistsAtPath:destinationPath];        if (existing && [[headers objectForKey:@"Overwrite"] isEqualToString:@"F"]) {          HTTPLogError(@"Pre-existing destination path \"%@\"", destinationPath);          _status = 412;        } else {          if ([method isEqualToString:@"COPY"]) {            if ([[NSFileManager defaultManager] copyItemAtPath:sourcePath toPath:destinationPath error:NULL]) {              _status = existing ? 204 : 201;            } else {              HTTPLogError(@"Failed copying \"%@\" to \"%@\"", sourcePath, destinationPath);              _status = 403;            }          } else {            if ([[NSFileManager defaultManager] moveItemAtPath:sourcePath toPath:destinationPath error:NULL]) {              _status = existing ? 204 : 201;            } else {              HTTPLogError(@"Failed moving \"%@\" to \"%@\"", sourcePath, destinationPath);              _status = 403;            }          }        }      }    }        // 9.10 LOCK Method - TODO: Actually lock the resource    if ([method isEqualToString:@"LOCK"]) {      NSString* path = [rootPath stringByAppendingPathComponent:resourcePath];      if (![path hasPrefix:rootPath]) {        return nil;      }            NSString* depth = [headers objectForKey:@"Depth"];      NSString* scope = nil;      NSString* type = nil;      NSString* owner = nil;      NSString* token = nil;      xmlDocPtr document = xmlReadMemory(body.bytes, (int)body.length, NULL, NULL, kXMLParseOptions);      if (document) {        xmlNodePtr node = _XMLChildWithName(document->children, (const xmlChar*)"lockinfo");        if (node) {          xmlNodePtr scopeNode = _XMLChildWithName(node->children, (const xmlChar*)"lockscope");          if (scopeNode && scopeNode->children && scopeNode->children->name) {            scope = [NSString stringWithUTF8String:(const char*)scopeNode->children->name];          }          xmlNodePtr typeNode = _XMLChildWithName(node->children, (const xmlChar*)"locktype");          if (typeNode && typeNode->children && typeNode->children->name) {            type = [NSString stringWithUTF8String:(const char*)typeNode->children->name];          }          xmlNodePtr ownerNode = _XMLChildWithName(node->children, (const xmlChar*)"owner");          if (ownerNode) {            ownerNode = _XMLChildWithName(ownerNode->children, (const xmlChar*)"href");            if (ownerNode && ownerNode->children && ownerNode->children->content) {              owner = [NSString stringWithUTF8String:(const char*)ownerNode->children->content];            }          }        } else {          HTTPLogWarn(@"HTTP Server: Invalid DAV properties\n%@", [[NSString alloc] initWithData:body encoding:NSUTF8StringEncoding]);        }        xmlFreeDoc(document);      } else {		  // No body, see if they're trying to refresh an existing lock.  If so, then just fake up the scope, type and depth so we fall		  // into the lock create case.		  NSString* lockToken;		  if ((lockToken = [headers objectForKey:@"If"]) != nil) {			  scope = @"exclusive";			  type = @"write";			  depth = @"0";			  token = [lockToken stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"(<>)"]];		  }	  }      if ([scope isEqualToString:@"exclusive"] && [type isEqualToString:@"write"] && [depth isEqualToString:@"0"] &&        ([[NSFileManager defaultManager] fileExistsAtPath:path] || [[NSData data] writeToFile:path atomically:YES])) {        NSString* timeout = [headers objectForKey:@"Timeout"];		if (!token) {          CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);          NSString *uuidStr = (__bridge_transfer NSString *)CFUUIDCreateString(kCFAllocatorDefault, uuid);          token = [NSString stringWithFormat:@"urn:uuid:%@", uuidStr];          CFRelease(uuid);		}                NSMutableString* xmlString = [NSMutableString stringWithString:@"<?xml version=\"1.0\" encoding=\"utf-8\" ?>"];        [xmlString appendString:@"<D:prop xmlns:D=\"DAV:\">\n"];        [xmlString appendString:@"<D:lockdiscovery>\n<D:activelock>\n"];        [xmlString appendFormat:@"<D:locktype><D:%@/></D:locktype>\n", type];        [xmlString appendFormat:@"<D:lockscope><D:%@/></D:lockscope>\n", scope];        [xmlString appendFormat:@"<D:depth>%@</D:depth>\n", depth];        if (owner) {          [xmlString appendFormat:@"<D:owner><D:href>%@</D:href></D:owner>\n", owner];        }        if (timeout) {          [xmlString appendFormat:@"<D:timeout>%@</D:timeout>\n", timeout];        }        [xmlString appendFormat:@"<D:locktoken><D:href>%@</D:href></D:locktoken>\n", token];		NSString* lockroot = [@"http://" stringByAppendingString:[[headers objectForKey:@"Host"] stringByAppendingString:[@"/" stringByAppendingString:resourcePath]]];        [xmlString appendFormat:@"<D:lockroot><D:href>%@</D:href></D:lockroot>\n", lockroot];        [xmlString appendString:@"</D:activelock>\n</D:lockdiscovery>\n"];        [xmlString appendString:@"</D:prop>"];                [_headers setObject:@"application/xml; charset=\"utf-8\"" forKey:@"Content-Type"];        _data = [xmlString dataUsingEncoding:NSUTF8StringEncoding];        _status = 200;        HTTPLogVerbose(@"Pretending to lock \"%@\"", resourcePath);      } else {        HTTPLogError(@"Locking request \"%@/%@/%@\" for \"%@\" is not allowed", scope, type, depth, resourcePath);        _status = 403;      }    }        // 9.11 UNLOCK Method - TODO: Actually unlock the resource    if ([method isEqualToString:@"UNLOCK"]) {      NSString* path = [rootPath stringByAppendingPathComponent:resourcePath];      if (![path hasPrefix:rootPath] || ![[NSFileManager defaultManager] fileExistsAtPath:path]) {        return nil;      }            NSString* token = [headers objectForKey:@"Lock-Token"];      _status = token ? 204 : 400;      HTTPLogVerbose(@"Pretending to unlock \"%@\"", resourcePath);    }      }  return self;}- (UInt64) contentLength {  return _data ? _data.length : 0;}- (UInt64) offset {  return _offset;}- (void) setOffset:(UInt64)offset {  _offset = offset;}- (NSData*) readDataOfLength:(NSUInteger)lengthParameter {  if (_data) {    NSUInteger remaining = _data.length - (NSUInteger)_offset;    NSUInteger length = lengthParameter < remaining ? lengthParameter : remaining;    void* bytes = (void*)(_data.bytes + _offset);    _offset += length;    return [NSData dataWithBytesNoCopy:bytes length:length freeWhenDone:NO];  }  return nil;}- (BOOL) isDone {  return _data ? _offset == _data.length : YES;}- (NSInteger) status {  return _status;}- (NSDictionary*) httpHeaders {  return _headers;}@end
 |