// // FMWebDAVRequest.m // davtest // // Created by August Mueller on 8/7/08. // Copyright 2008 Flying Meat Inc. All rights reserved. // #import "FMWebDAVRequest.h" #import "FMFileDAVRequest.h" #import "ISO8601DateFormatter.h" @implementation FMWebDAVRequest @synthesize connection=_connection; @synthesize responseData=_responseData; @synthesize url=_url; @synthesize delegate=_delegate; @synthesize contextInfo=_contextInfo; @synthesize endSelector=_endSelector; @synthesize responseStatusCode=_responseStatusCode; @synthesize error=_error; + (id) requestToURL:(NSURL*)url { FMWebDAVRequest *request = [url isFileURL] ? [[FMFileDAVRequest alloc] init] : [[FMWebDAVRequest alloc] init];; [request setUrl:url]; return request; } + (id) requestToURL:(NSURL*)url delegate:(id)del endSelector:(SEL)anEndSelector contextInfo:(id)context { FMWebDAVRequest *request = [url isFileURL] ? [[FMFileDAVRequest alloc] init] : [[FMWebDAVRequest alloc] init];; [request setUrl:url]; [request setDelegate:del]; [request setContextInfo:context]; [request setEndSelector:anEndSelector]; return request; } - (void)dealloc { // _delegate isn't retained. [_connection release]; [_responseData release]; [_url release]; [_contextInfo release]; [_xmlChars release]; [_directoryBucket release]; [super dealloc]; } - (FMWebDAVRequest*) synchronous { _synchronous = YES; return [self autorelease]; } - (void) sendRequest:(NSMutableURLRequest *)req { // defaults write com.flyingmeat.VoodooPad_Pro FMWebDAVRequestDebug 1 // defaults delete com.flyingmeat.VoodooPad_Pro FMWebDAVRequestDebug if ([[NSUserDefaults standardUserDefaults] boolForKey:@"FMWebDAVRequestDebug"]) { NSData *d = [req HTTPBody]; if (d) { NSString *junk = [[[NSString alloc] initWithData:d encoding:NSUTF8StringEncoding] autorelease]; NSLog(@"%@", junk); } } if (_synchronous) { NSURLResponse *response = 0x00; self.responseData = (NSMutableData*)[NSURLConnection sendSynchronousRequest:req returningResponse:&response error:&_error]; NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response; if (![httpResponse isKindOfClass:[NSHTTPURLResponse class]]) { NSLog(@"%s:%d", __FUNCTION__, __LINE__); NSLog(@"FMWebDAVRequest Unknown response type: %@", httpResponse); } else { _responseStatusCode = [httpResponse statusCode]; } } else { [NSURLConnection connectionWithRequest:req delegate:self]; } } - (FMWebDAVRequest*) createDirectory { if (!_endSelector) { _endSelector = @selector(requestDidCreateDirectory:); } NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:_url]; [req setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData]; [req setTimeoutInterval:60 * 5]; [req setHTTPMethod:@"MKCOL"]; // defaults write com.flyingmeat.VoodooPad_Pro skipMKCOLContentType 1 // defaults delete com.flyingmeat.VoodooPad_Pro skipMKCOLContentType if (![[NSUserDefaults standardUserDefaults] boolForKey:@"skipMKCOLContentType"]) { [req setValue:@"application/xml" forHTTPHeaderField:@"Content-Type"]; } [self sendRequest:req]; return self; } - (FMWebDAVRequest*) delete { if (!_endSelector) { _endSelector = @selector(requestDidDelete:); } NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:_url]; [req setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData]; [req setTimeoutInterval:60 * 5]; [req setHTTPMethod:@"DELETE"]; [req setValue:@"application/xml" forHTTPHeaderField:@"Content-Type"]; [self sendRequest:req]; return self; } - (FMWebDAVRequest*) putData:(NSData*)data { if (!_endSelector) { _endSelector = @selector(requestDidPutData:); } NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:_url]; [req setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData]; [req setTimeoutInterval:60 * 5]; [req setHTTPMethod:@"PUT"]; [req setValue:@"application/octet-stream" forHTTPHeaderField:@"Content-Type"]; // this actually speeds things up for some reason. [req setValue:[NSString stringWithFormat:@"%d", [data length]] forHTTPHeaderField:@"Content-Length"]; [req setHTTPBody:data]; [self sendRequest:req]; return self; } - (FMWebDAVRequest*) get { if (!_endSelector) { _endSelector = @selector(requestDidGet:); } NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:_url]; [req setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData]; [req setTimeoutInterval:60 * 5]; [self sendRequest:req]; return self; } - (FMWebDAVRequest*) copyToDestinationURL:(NSURL*)dest { if (!_endSelector) { _endSelector = @selector(requestDidCopy:); } NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:_url]; [req setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData]; [req setTimeoutInterval:60 * 5]; [req setHTTPMethod:@"COPY"]; [req setValue:[dest absoluteString] forHTTPHeaderField:@"Destination"]; [self sendRequest:req]; return self; } - (FMWebDAVRequest*) moveToDestinationURL:(NSURL*)dest { if (!_endSelector) { _endSelector = @selector(requestDidCopy:); } NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:_url]; [req setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData]; [req setTimeoutInterval:60 * 5]; [req setHTTPMethod:@"MOVE"]; [req setValue:[dest absoluteString] forHTTPHeaderField:@"Destination"]; [self sendRequest:req]; return self; } - (FMWebDAVRequest*) head { if (!_endSelector) { _endSelector = @selector(requestDidHead:); } NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:_url]; [req setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData]; [req setTimeoutInterval:60 * 5]; [req setHTTPMethod:@"HEAD"]; [self sendRequest:req]; return self; } - (FMWebDAVRequest*) propfind { if (!_endSelector) { _endSelector = @selector(requestDidPropfind:); } return [self fetchDirectoryListingWithDepth:0]; } - (FMWebDAVRequest*) fetchDirectoryListing { return [self fetchDirectoryListingWithDepth:1]; } - (FMWebDAVRequest*) fetchDirectoryListingWithDepth:(NSUInteger)depth { return [self fetchDirectoryListingWithDepth:depth extraToPropfind:@""]; } // - (FMWebDAVRequest*) fetchDirectoryListingWithDepth:(NSUInteger)depth extraToPropfind:(NSString*)extra { if (!_endSelector) { _endSelector = @selector(requestDidFetchDirectoryListing:); } if (!extra) { extra = @""; } NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:_url]; [req setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData]; [req setTimeoutInterval:60 * 5]; // the trailing / always gets stripped off for some reason... _uriLength = [[_url path] length] + 1; [req setHTTPMethod:@"PROPFIND"]; NSString *xml = [NSString stringWithFormat:@"\n%@", extra]; if (depth > 1) { // http://tools.ietf.org/html/rfc2518#section-9.2 [req setValue:@"infinity" forHTTPHeaderField:@"Depth"]; } else { [req setValue:[NSString stringWithFormat:@"%d", depth] forHTTPHeaderField:@"Depth"]; } [req setValue:@"application/xml" forHTTPHeaderField:@"Content-Type"]; [req setHTTPBody:[xml dataUsingEncoding:NSUTF8StringEncoding]]; [self sendRequest:req]; return self; } - (NSArray*) directoryListing { NSMutableArray *ret = [NSMutableArray array]; for (NSDictionary *dict in [self directoryListingWithAttributes]) { if ([dict objectForKey:@"href"]) { [ret addObject:[dict objectForKey:@"href"]]; } } return ret; } - (NSArray*) directoryListingWithAttributes { if (!_responseData) { return nil; } _parseState = FMWebDAVDirectoryListing; _directoryBucket = [[NSMutableArray array] retain]; NSXMLParser *parser = [[[NSXMLParser alloc] initWithData:_responseData] autorelease]; [parser setDelegate:self]; [parser parse]; return _directoryBucket; } - (NSString*) responseString { return [[[NSString alloc] initWithData:_responseData encoding:NSUTF8StringEncoding] autorelease]; } - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { if (!_responseData) { [self setResponseData:[NSMutableData data]]; } [_responseData appendData:data]; } - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { if (self.delegate && [self.delegate respondsToSelector:@selector(request:didReceiveAuthenticationChallenge:)]) { [self.delegate request:self didReceiveAuthenticationChallenge:challenge]; } } - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)URLresponse { [_responseData setLength:0]; NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)URLresponse; if (![httpResponse isKindOfClass:[NSHTTPURLResponse class]]) { NSLog(@"%s:%d", __FUNCTION__, __LINE__); NSLog(@"Unknown response type: %@", URLresponse); return; } _responseStatusCode = [httpResponse statusCode]; if (_responseStatusCode >= 400) { if (self.delegate && [self.delegate respondsToSelector:@selector(request:hadStatusCodeErrorWithResponse:)]) { [self.delegate request:self hadStatusCodeErrorWithResponse:httpResponse]; } } } - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { // hrm... do we really want to do this? if (self.delegate && [self.delegate respondsToSelector:@selector(connection:didFailWithError:)]) { [self.delegate connection:connection didFailWithError:error]; } // what about this? if (self.delegate && [self.delegate respondsToSelector:_endSelector]) { [self.delegate performSelector:_endSelector withObject:self]; } [self autorelease]; } -(void)connectionDidFinishLoading:(NSURLConnection *)connection { if (self.delegate && [self.delegate respondsToSelector:_endSelector]) { [self.delegate performSelector:_endSelector withObject:self]; } [self autorelease]; } - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict { //debug(@"start: %@", elementName); if (!_xmlChars) { _xmlChars = [[NSMutableString string] retain]; } [_xmlChars setString:@""]; if (_parseState == FMWebDAVDirectoryListing) { if ([elementName isEqualToString:@"D:response"]) { _xmlBucket = [[NSMutableDictionary dictionary] retain]; } // ... } } + (NSDate*) parseDateString:(NSString*)dateString { ISO8601DateFormatter *formatter = [[[ISO8601DateFormatter alloc] init] autorelease]; NSDate *date = [formatter dateFromString:dateString]; if (!date) { NSLog(@"Could not parse %@", dateString); } return date; /* NSLog(@"dateString: %@", dateString); static NSDateFormatter* formatterA = nil; if (!formatterA) { formatterA = [[NSDateFormatter alloc] init]; [formatterA setTimeStyle:NSDateFormatterFullStyle]; [formatterA setDateFormat:@"yyyy-MM-dd'T'HH:mm:ssZZZ"]; // NOTE: problem! (but I'm not sure what that problem would be) } static NSDateFormatter* formatterB = nil; if (!formatterB) { formatterB = [[NSDateFormatter alloc] init]; [formatterB setTimeStyle:NSDateFormatterFullStyle]; [formatterB setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'Z'"]; // NOTE: problem! (but I'm not sure what that problem would be) } NSArray *formatters = [NSArray arrayWithObjects:formatterA, formatterB, nil]; for (NSDateFormatter *formatter in formatters) { NSString *stringToWorkOn = dateString; if ([stringToWorkOn hasSuffix:@"Z"]) { NSLog(@"stripping the z"); stringToWorkOn = [[stringToWorkOn substringToIndex:(dateString.length-1)] stringByAppendingString:@"GMT"]; } NSDate *d = [formatter dateFromString:stringToWorkOn]; if (!d) { // 2009-06-30T02:46:53GM NSLog(@"Initial parse failed"); } if (!d && ![stringToWorkOn hasSuffix:@"GMT"]) { stringToWorkOn = [stringToWorkOn stringByAppendingString:@"GMT"]; d = [formatter dateFromString:stringToWorkOn]; } if (d) { return d; } } NSLog(@"Could not parse %@", dateString); return nil; */ } - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { //debug(@"end: %@, '%@'", elementName, _xmlChars); if (_parseState == FMWebDAVDirectoryListing) { if ([elementName isEqualToString:@"D:href"]) { if ([_xmlChars length] < _uriLength) { // whoa, problemo. return; } if ([_xmlChars hasPrefix:@"http"]) { // aakkkk! NSURL *junk = [NSURL URLWithString:_xmlChars]; [_xmlChars setString:[[junk path] stringByAppendingString:@"/"]]; } NSString *lastBit = [_xmlChars substringFromIndex:_uriLength]; if ([lastBit length]) { [_xmlBucket setObject:lastBit forKey:@"href"]; } } else if ([elementName hasSuffix:@":creationdate"] || [elementName hasSuffix:@":modificationdate"]) { // 2009-06-30T02:46:53GMT // '2008-10-30T02:52:47Z' // 1997-12-01T17:42:21-08:00 // date-time = full-date "T" full-time, aka ISO-8601 // stolen from http://www.cocoabuilder.com/archive/message/cocoa/2008/3/18/201578 NSDate *d = [[self class] parseDateString:_xmlChars]; if (d) { int colIdx = [elementName rangeOfString:@":"].location; [_xmlBucket setObject:d forKey:[elementName substringFromIndex:colIdx + 1]]; } else { NSLog(@"Could not parse date string '%@' for '%@'", _xmlChars, elementName); } } else if ([elementName hasSuffix:@":getlastmodified"]) { // 'Thu, 30 Oct 2008 02:52:47 GMT' // Monday, 12-Jan-98 09:25:56 GMT // Value: HTTP-date ; defined in section 3.3.1 of RFC2068 // of course it's fucking different than creationdate. // // That makes complete sense. // // I thought for a while, that WebDAV was pretty sane. then I saw this. // ok ok ok, it's not _that_ bad... but, really? // // obviously there's no code here to deal with it. // // I'll take a patch. kthx, bai now. } else if ([elementName isEqualToString:@"D:response"] && [_xmlBucket objectForKey:@"href"]) { [_directoryBucket addObject:_xmlBucket]; [_xmlBucket release]; _xmlBucket = nil; } } } - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { [_xmlChars appendString:string]; } @end