/* Copyright (C) 2000-2004 SKYRIX Software AG This file is part of OpenGroupware.org. OGo is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. OGo is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with OGo; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "SoWebDAVRenderer.h" #include "SoWebDAVValue.h" #include "SoObject+SoDAV.h" #include "EOFetchSpecification+SoDAV.h" #include "NSException+HTTP.h" #include #include #include #include #include #include #include "common.h" /* What HotMail uses for responses: Headers: Server: Microsoft-IIS/5.0 X-Timestamp: folders=1035823428, ACTIVE=1035813212 Client-Response-Num: 1 Client-Date: Expires: ... P3P: BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo */ #define XMLNS_INTTASK \ @"{http://schemas.microsoft.com/mapi/id/{00062003-0000-0000-C000-000000000046}/}" @interface SoWebDAVRenderer(Privates) - (BOOL)renderStatusResult:(id)_object withDefaultStatus:(int)_defStatus inContext:(WOContext *)_ctx; @end @implementation SoWebDAVRenderer static NSDictionary *predefinedNamespacePrefixes = nil; static NSTimeZone *gmt = nil; static BOOL debugOn = NO; static BOOL formatOutput = NO; + (void)initialize { NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; static BOOL didInit = NO; if (didInit) return; didInit = YES; if (gmt == nil) gmt = [[NSTimeZone timeZoneWithAbbreviation:@"GMT"] retain]; if (predefinedNamespacePrefixes == nil) { predefinedNamespacePrefixes = [[ud objectForKey:@"SoPreferredNamespacePrefixes"] copy]; } formatOutput = [ud boolForKey:@"SoWebDAVFormatOutput"]; if ((debugOn = [ud boolForKey:@"SoRendererDebugEnabled"])) NSLog(@"enabled debugging in SoWebDAVRenderer (SoRendererDebugEnabled)"); } + (id)sharedRenderer { static SoWebDAVRenderer *r = nil; // THREAD if (r == nil) r = [[SoWebDAVRenderer alloc] init]; return r; } - (NSString *)preferredPrefixForNamespace:(NSString *)_uri { return [predefinedNamespacePrefixes objectForKey:_uri]; } /* key render entry-point */ - (void)_fixupResponse:(WOResponse *)_r inContext:(WOContext *)_ctx { NSDate *now; NSString *nowHttpString; id tmp; if ((tmp = [_r headerForKey:@"server"]) == nil) { // TODO: add application name as primary name [_r setHeader:@"SOPE 4.2/WebDAV" forKey:@"server"]; } [_r setHeader:@"close" forKey:@"connection"]; [_r setHeader:@"DAV" forKey:@"Ms-Author-Via"]; // what program uses that header ? [_r setHeader:@"200 No error" forKey:@"X-Dav-Error"]; if ((tmp = [_r headerForKey:@"content-type"]) == nil) [_r setHeader:@"text/xml" forKey:@"content-type"]; now = [NSDate date]; nowHttpString = [now descriptionWithCalendarFormat: @"%a, %d %b %Y %H:%M:%S GMT" timeZone:gmt locale:nil]; if ((tmp = [_r headerForKey:@"date"]) == nil) [_r setHeader:nowHttpString forKey:@"date"]; #if 0 /* currently none of the clients allows zipping, retry later ... */ /* try zipping */ if ([_r shouldZipResponseToRequest:nil]) { [self logWithFormat:@"zipping DAV result ..."]; [_r zipResponse]; } #endif } - (NSString *)mimeTypeForData:(NSData *)_data inContext:(WOContext *)_ctx { /* should check extension for MIME type */ return @"application/octet-stream"; } - (NSString *)mimeTypeForString:(NSString *)_str inContext:(WOContext *)_ctx { /* should check extension for MIME type */ if ([_str hasPrefix:@"", [_prefixes objectForKey:@"DAV:"], _value]; } else if ([_prop isEqualToString:@"{DAV:}creationdate"]) datefmt = @"%Y-%m-%dT%H:%M:%S%zZ"; /* special processing for some properties */ // TODO: move this to user-level code ! // HH: what is that ? it does not do anything anyway ? if ([_prop hasPrefix:XMLNS_INTTASK]) { if ([_prop hasSuffix:@"}0x00008102"]) { } } /* special processing for some classes */ if ([_value isKindOfClass:[NSString class]]) return [_value stringByEscapingXMLString]; if ([_value isKindOfClass:[NSNumber class]]) return [_value stringValue]; if ([_value isKindOfClass:[NSDate class]]) { return [_value descriptionWithCalendarFormat:datefmt timeZone:gmt locale:nil]; } return [[_value stringValue] stringByEscapingXMLString]; } - (NSString *)baseURLForContext:(WOContext *)_ctx { /* Note: Evolution doesn't correctly transfer the "Host:" header, it misses the port argument :-( */ NSString *baseURL; WORequest *rq; NSString *hostport; id tmp; rq = [_ctx request]; if ((tmp = [rq headerForKey:@"x-webobjects-server-name"])) { hostport = tmp; if ((tmp = [rq headerForKey:@"x-webobjects-server-port"])) hostport = [NSString stringWithFormat:@"%@:%@", hostport, tmp]; } else if ((tmp = [rq headerForKey:@"host"])) hostport = tmp; else hostport = [[NSHost currentHost] name]; baseURL = [NSString stringWithFormat:@"http://%@%@", hostport, [rq uri]]; return baseURL; } - (NSString *)tidyHref:(id)href baseURL:(id)baseURL { href = [href stringValue]; if (href == nil) { if (debugOn) { [self logWithFormat: @"WARNING: using baseURL for href, " @"entry did not provide a URL: %@", baseURL]; } href = [baseURL stringValue]; } else if (![href isAbsoluteURL]) { // maybe only check for http[s]:// ? // TODO: use "real" URL processing href = [baseURL stringByAppendingPathComponent:href]; } return href; } - (id)tidyStatus:(id)stat { if (stat == nil) stat = @"HTTP/1.1 200 OK"; else if ([stat isKindOfClass:[NSException class]]) { int i; if ((i = [stat httpStatus]) > 0) stat = [NSString stringWithFormat:@"HTTP/1.1 %i %@", i, [stat reason]]; else { stat = [(NSException *)stat name]; stat = [@"HTTP/1.1 500 " stringByAppendingString:stat]; } } return stat; } - (void)renderSearchResultEntry:(id)entry inContext:(WOContext *)_ctx namesOnly:(BOOL)_namesOnly attributes:(NSArray *)_attrs propertyMap:(NSDictionary *)_propMap baseURL:(NSString *)baseURL tagToPrefix:(NSDictionary *)extNameCache nsToPrefix:(NSDictionary *)nsToPrefix { /* Note: the entry is an NSArray in case _namesOnly is requested! */ // TODO: use -valueForKey: to improve NSNull handling ? WOResponse *r; NSEnumerator *keys; NSString *key; id href = nil; id stat = nil; BOOL isBrief; r = [_ctx response]; isBrief = [[[_ctx request] headerForKey:@"brief"] hasPrefix:@"t"] ? YES : NO; if (debugOn) { [self debugWithFormat:@" render entry: 0x%08X<%@>%s%s", entry, NSStringFromClass([entry class]), isBrief ? " brief" : "", _namesOnly ? " names-only" : ""]; } /* we do not map these DAV properties because they are very special */ if (!_namesOnly) { if ((href = [entry valueForKey:@"{DAV:}href"]) == nil) { if ((key = [_propMap objectForKey:@"{DAV:}href"])) { if ((href = [entry valueForKey:key]) == nil) { if (debugOn) { [self debugWithFormat: @"WARNING: no value for {DAV:}href key '%@': %@", key, entry]; } } } else if (debugOn) { [self debugWithFormat: @"WARNING: no key for {DAV:}href in property map !"]; } } if ((stat = [entry valueForKey:@"{DAV:}status"]) == nil) { if ((key = [_propMap objectForKey:@"{DAV:}status"])) stat = [entry valueForKey:key]; } /* tidy href */ href = [self tidyHref:href baseURL:baseURL]; /* tidy status */ stat = [self tidyStatus:stat]; } else { /* propnames only */ href = [baseURL stringValue]; stat = @"HTTP/1.1 200 OK"; } if (debugOn) { [self debugWithFormat:@" status: %@", stat]; [self debugWithFormat:@" href: %@", href]; } /* generate */ [r appendContentString:@""]; if (formatOutput) [r appendContentCharacter:'\n']; if (href) { [r appendContentString:@""]; /* TODO: need to find out what is appropriate! While Cadaver and ZideLook (both Neon+Expat) seem to be fine with this, OSX reports invalid characters (displayed as '#') for umlauts. It might be that we are supposed to use *URL* escaping in any case! (notably entering a directory with an umlaut doesn't seem to work in Cadaver either because of a URL mismatch!) Note: we cannot apply URL encoding in this place, because it will encode all URL special chars ... where are URLs escaped? Note: we always need to apply XML escaping (even though higher-level characters might be already encoded)! */ [r appendContentXMLString:[href stringValue]]; [r appendContentString:@""]; if (formatOutput) [r appendContentCharacter:'\n']; } [r appendContentString:@""]; if (stat) { [r appendContentString:@""]; [r appendContentXMLString:[stat stringValue]]; [r appendContentString:@""]; } [r appendContentString:@""]; if (formatOutput) [r appendContentCharacter:'\n']; /* now the properties */ keys = [_attrs objectEnumerator] ; while ((key = [keys nextObject])) { NSString *extName; NSString *okey; id value; #if 0 /* this filter probably doesn't make sense ? */ /* filter out predefined props */ if ([key isEqualToString:@"{DAV:}href"]) continue; if ([key isEqualToString:@"{DAV:}status"]) continue; #endif extName = [extNameCache objectForKey:key]; if (_namesOnly) { [r appendContentCharacter:'<']; [r appendContentString:extName]; [r appendContentString:@"/>"]; if (formatOutput) [r appendContentCharacter:'\n']; continue; } // TODO: we should support property status (eg encode 404 on NSNull) if ((okey = [_propMap objectForKey:key]) == nil) okey = key; if ([key isEqualToString:@"{DAV:}href"]) value = href; else value = [entry valueForKey:okey]; if ([value isNotNull]) { NSString *s; if ([value isKindOfClass:[SoWebDAVValue class]]) { s = [value stringForTag:key rawName:extName inContext:_ctx prefixes:nsToPrefix]; [r appendContentString:s]; } else { [r appendContentCharacter:'<']; [r appendContentString:extName]; [r appendContentCharacter:'>']; s = [self stringForValue:value ofProperty:key prefixes:nsToPrefix]; [r appendContentString:s]; [r appendContentString:@""]; if (formatOutput) [r appendContentCharacter:'\n']; } continue; } if (!isBrief) { /* Not sure whether this is correct, do we need to encode null attrs? Seems like Evo gets confused on that. TODO: probably add a 404 property status for that! */ [r appendContentCharacter:'<']; [r appendContentString:extName]; [r appendContentString:@"/>"]; if (formatOutput) [r appendContentCharacter:'\n']; } } [r appendContentString:@""]; if (formatOutput) [r appendContentCharacter:'\n']; } - (void)buildPrefixMapForAttributes:(NSArray *)_attrs tagToExtName:(NSMutableDictionary *)_tagToExtName nsToPrefix:(NSMutableDictionary *)_nsToPrefix { unichar autoPrefix[2] = { ('a' - 1), 0 }; NSEnumerator *e; NSString *fqn; e = [_attrs objectEnumerator]; while ((fqn = [e nextObject])) { NSString *ns, *localName, *prefix, *extName; if ([_tagToExtName objectForKey:fqn]) continue; if (![fqn xmlIsFQN]) { /* hm, no namespace given :-(, using DAV */ ns = @"DAV:"; localName = fqn; } else { ns = [fqn xmlNamespaceURI]; localName = [fqn xmlLocalName]; } if ((prefix = [_nsToPrefix objectForKey:ns]) == nil) { if ((prefix = [self preferredPrefixForNamespace:ns]) == nil) { (autoPrefix[0])++; prefix = [NSString stringWithCharacters:&(autoPrefix[0]) length:1]; } [_nsToPrefix setObject:prefix forKey:ns]; } extName = [NSString stringWithFormat:@"%@:%@", prefix, localName]; [_tagToExtName setObject:extName forKey:fqn]; } } - (NSString *)nsDeclsForMap:(NSDictionary *)_nsToPrefix { NSMutableString *ms; NSEnumerator *nse; NSString *ns; ms = [NSMutableString stringWithCapacity:256]; nse = [_nsToPrefix keyEnumerator]; while ((ns = [nse nextObject])) { [ms appendString:@" xmlns:"]; [ms appendString:[_nsToPrefix objectForKey:ns]]; [ms appendString:@"=\""]; [ms appendString:ns]; [ms appendString:@"\""]; } return ms; } - (void)renderSearchResult:(id)_entries inContext:(WOContext *)_ctx namesOnly:(BOOL)_namesOnly attributes:(NSArray *)_attrs propertyMap:(NSDictionary *)_propMap { NSMutableDictionary *extNameCache = nil; NSMutableDictionary *nsToPrefix = nil; NSAutoreleasePool *pool; WOResponse *r; unsigned entryCount; pool = [[NSAutoreleasePool alloc] init]; r = [_ctx response]; if (![_entries isKindOfClass:[NSEnumerator class]]) { if ([_entries isKindOfClass:[NSArray class]]) { [self debugWithFormat:@" render %i entries", [_entries count]]; _entries = [_entries objectEnumerator]; } else { [self debugWithFormat:@" render a single object ..."]; _entries = [[NSArray arrayWithObject:_entries] objectEnumerator]; } } /* collect used namespaces */ nsToPrefix = [NSMutableDictionary dictionaryWithCapacity:16]; [nsToPrefix setObject:@"D" forKey:XMLNS_WEBDAV]; /* the extNameCache is used to map fully qualified tag names to their prefixed external representation */ extNameCache = [NSMutableDictionary dictionaryWithCapacity:32]; // TODO: only walk attrs, if available /* Walk all attributes of all entries to collect names. We might be able to take a look at just the first record if it is guaranteed, that all records have all properties (even if the value is NSNull) ? */ [self buildPrefixMapForAttributes:_attrs tagToExtName:extNameCache nsToPrefix:nsToPrefix]; /* generate multistatus */ [r setStatus:207 /* multistatus */]; [r setHeader:@"no-cache" forKey:@"pragma"]; [r setHeader:@"no-cache" forKey:@"cache-control"]; [r appendContentString:@"\n"]; [r appendContentString:@""]; if (formatOutput) [r appendContentCharacter:'\n']; { NSString *baseURL; NSString *range; id entry; baseURL = [self baseURLForContext:_ctx]; [self debugWithFormat:@" baseURL: %@", baseURL]; entryCount = 0; /* Note: this will clash with streamed output later */ while ((entry = [_entries nextObject])) { [self renderSearchResultEntry:entry inContext:_ctx namesOnly:_namesOnly attributes:_attrs propertyMap:_propMap baseURL:baseURL tagToPrefix:extNameCache nsToPrefix:nsToPrefix]; entryCount++; } [self debugWithFormat:@" rendered %i entries", entryCount]; /* If we got a "rows" range header, we report back the actual rows delivered. Since we do not really support ranges in the moment, we just report all rows ... TODO: support for row ranges. */ if ((range = [[[_ctx request] headerForKey:@"range"] stringValue])) { /* sample: "Content-Range: rows 0-143; total=144" */ NSString *v; v = [[NSString alloc] initWithFormat:@"rows 0-%i; total=%i", entryCount>0?(entryCount - 1):0, entryCount]; [r setHeader:v forKey:@"content-range"]; [v release]; } } [r appendContentString:@""]; if (formatOutput) [r appendContentCharacter:'\n']; [pool release]; } - (BOOL)renderSearchResult:(id)_object inContext:(WOContext *)_ctx { EOFetchSpecification *fs; NSDictionary *propMap; if ((fs = [_ctx objectForKey:@"DAVFetchSpecification"]) == nil) return NO; if ((propMap = [_ctx objectForKey:@"DAVPropertyMap"]) == nil) propMap = [_object davAttributeMapInContext:_ctx]; if (debugOn) { [self debugWithFormat:@"render search result 0x%08X<%@>", _object, NSStringFromClass([_object class])]; } [self renderSearchResult:_object inContext:_ctx namesOnly:[fs queryWebDAVPropertyNamesOnly] attributes:[fs selectedWebDAVPropertyNames] propertyMap:propMap]; if (debugOn) [self debugWithFormat:@"finished rendering."]; return YES; } - (BOOL)renderLockToken:(id)_object inContext:(WOContext *)_ctx { /* TODO: this is a fake ! */ WOResponse *r; if (_object == nil) return NO; r = [_ctx response]; [r setStatus:200]; [r setContentEncoding:NSUTF8StringEncoding]; [r setHeader:@"text/xml; charset=\"utf-8\"" forKey:@"content-type"]; [r setHeader:[_object stringValue] forKey:@"lock-token"]; [r appendContentString:@"\n"]; [r appendContentString:@""]; [r appendContentString:@""]; [r appendContentString:@""]; if (formatOutput) [r appendContentCharacter:'\n']; /* this is the href of the lock, not of the locked resource */ [r appendContentString:@""]; [r appendContentString:[_object stringValue]]; [r appendContentString:@""]; if (formatOutput) [r appendContentCharacter:'\n']; // TODO: locktype, eg // TODO: lockscope, eg // TODO: depth, eg Infinitiy // TODO: owner, eg ... // TODO: timeout, eg Second-604800 [r appendContentString:@""]; [r appendContentString:@""]; [r appendContentString:@""]; if (formatOutput) [r appendContentCharacter:'\n']; return YES; } - (BOOL)renderOptions:(id)_object inContext:(WOContext *)_ctx { WOResponse *r = [_ctx response]; [r setStatus:200]; [r setHeader:@"1,2" forKey:@"DAV"]; // TODO: select protocol level //[r setHeader:@"" forKey:@"Etag"]; [r setHeader:[_object componentsJoinedByString:@", "] forKey:@"allow"]; return YES; } - (BOOL)renderSubscription:(id)_object inContext:(WOContext *)_ctx { // TODO: this is fake, mirrors request WOResponse *r = [_ctx response]; WORequest *rq; NSString *callback; NSString *notificationType; NSString *lifetime; rq = [_ctx request]; callback = [rq headerForKey:@"call-back"]; notificationType = [rq headerForKey:@"notification-type"]; lifetime = [rq headerForKey:@"subscription-lifetime"]; [r setStatus:200]; if (notificationType) [r setHeader:notificationType forKey:@"notification-type"]; if (lifetime) [r setHeader:lifetime forKey:@"subscription-lifetime"]; if (callback) [r setHeader:callback forKey:@"callback"]; [r setHeader:[self baseURLForContext:_ctx] forKey:@"content-location"]; [r setHeader:_object forKey:@"subscription-id"]; return YES; } - (BOOL)renderPropPatchResult:(id)_object inContext:(WOContext *)_ctx { NSMutableDictionary *extNameCache = nil; NSMutableDictionary *nsToPrefix = nil; WOResponse *r = [_ctx response]; if (_object == nil) return NO; nsToPrefix = [NSMutableDictionary dictionaryWithCapacity:16]; [nsToPrefix setObject:@"D" forKey:XMLNS_WEBDAV]; extNameCache = [NSMutableDictionary dictionaryWithCapacity:32]; [self buildPrefixMapForAttributes:_object tagToExtName:extNameCache nsToPrefix:nsToPrefix]; [r setStatus:207 /* multistatus */]; [r appendContentString:@"\n"]; [r appendContentString:@""]; if (formatOutput) [r appendContentCharacter:'\n']; [r appendContentString:@""]; if (formatOutput) [r appendContentCharacter:'\n']; [r appendContentString:@""]; [r appendContentString:[[_ctx request] uri]]; [r appendContentString:@""]; if (formatOutput) [r appendContentCharacter:'\n']; [r appendContentString:@"HTTP/1.1 200 OK"]; if (formatOutput) [r appendContentCharacter:'\n']; [r appendContentString:@""]; if (formatOutput) [r appendContentCharacter:'\n']; /* encode properties */ { NSEnumerator *e; NSString *tag; e = [_object objectEnumerator]; while ((tag = [e nextObject])) { NSString *extName; extName = [extNameCache objectForKey:tag]; [r appendContentCharacter:'<']; [r appendContentString:extName]; [r appendContentString:@"/>"]; if (formatOutput) [r appendContentCharacter:'\n']; } } [r appendContentString:@""]; if (formatOutput) [r appendContentCharacter:'\n']; [r appendContentString:@""]; if (formatOutput) [r appendContentCharacter:'\n']; return YES; } - (BOOL)renderDeleteResult:(id)_object inContext:(WOContext *)_ctx { WOResponse *r = [_ctx response]; if (_object == nil || [_object boolValue]) { [r setStatus:204 /* no content */]; //[r appendContentString:@"object was deleted."]; return YES; } if ([_object isKindOfClass:[NSNumber class]]) { [r setStatus:[_object intValue]]; if ([r status] != 204 /* No Content */) [r appendContentString:@"object could not be deleted."]; } else { [r setStatus:500 /* server error */]; [r appendContentString:@"object could not be deleted. reason: "]; [r appendContentHTMLString:[_object stringValue]]; } return YES; } - (BOOL)renderStatusResult:(id)_object withDefaultStatus:(int)_defStatus inContext:(WOContext *)_ctx { WOResponse *r = [_ctx response]; if (_object == nil) { [r setStatus:_defStatus /* no content */]; return YES; } if ([_object isKindOfClass:[NSNumber class]]) { if ([_object intValue] < 100) { [r setStatus:_defStatus /* no content */]; return YES; } else { [r setStatus:[_object intValue]]; } } else { [r setStatus:_defStatus /* no content */]; } return YES; } - (BOOL)renderUploadResult:(id)_object inContext:(WOContext *)_ctx { WOResponse *r = [_ctx response]; if (_object == nil) { [r setStatus:204 /* no content */]; return YES; } if ([_object isKindOfClass:[NSNumber class]]) { if ([_object intValue] < 100) { [r setStatus:204 /* no content */]; return YES; } [r setStatus:[_object intValue]]; if ([_object intValue] >= 300) { [r setHeader:@"text/html" forKey:@"content-type"]; [r appendContentString:@"object could not be stored."]; } return YES; } [r setStatus:204 /* no content */]; return YES; } - (void)renderPollList:(NSArray *)_sids code:(int)_code inContext:(WOContext *)_ctx { WOResponse *r = [_ctx response]; NSEnumerator *e; NSString *sid; NSString *href; if ([_sids count] == 0) return; href = [self baseURLForContext:_ctx]; [r appendContentString:@""]; if (formatOutput) [r appendContentCharacter:'\n']; [r appendContentString:@""]; [r appendContentString:href]; [r appendContentString:@""]; if (formatOutput) [r appendContentCharacter:'\n']; [r appendContentString:@"HTTP/1.1 "]; if (_code == 200) [r appendContentString:@"200 OK"]; else if (_code == 204) [r appendContentString:@"204 No Content"]; else { NSString *s; s = [NSString stringWithFormat:@"%i code%i"]; [r appendContentString:s]; } [r appendContentString:@""]; if (formatOutput) [r appendContentCharacter:'\n']; [r appendContentString:@""]; if (formatOutput) [r appendContentCharacter:'\n']; e = [_sids objectEnumerator]; while ((sid = [e nextObject])) { if (formatOutput) [r appendContentString:@" "]; [r appendContentString:@"
  • "]; [r appendContentString:sid]; [r appendContentString:@"
  • "]; if (formatOutput) [r appendContentCharacter:'\n']; } [r appendContentString:@"
    "]; if (formatOutput) [r appendContentCharacter:'\n']; [r appendContentString:@"
    "]; if (formatOutput) [r appendContentCharacter:'\n']; } - (BOOL)renderPollResult:(id)_object inContext:(WOContext *)_ctx { WOResponse *r = [_ctx response]; if (_object == nil) { [r setStatus:204 /* no content */]; return YES; } if ([_object isKindOfClass:[NSDictionary class]]) { NSArray *pending, *inactive; pending = [_object objectForKey:@"pending"]; inactive = [_object objectForKey:@"inactive"]; [r setStatus:207 /* Multi-Status */]; [r setHeader:@"text/xml" forKey:@"content-type"]; [r appendContentString:@"\n"]; [r appendContentString:@""]; if (formatOutput) [r appendContentCharacter:'\n']; [self renderPollList:pending code:200 inContext:_ctx]; [self renderPollList:inactive code:204 inContext:_ctx]; [r appendContentString:@""]; if (formatOutput) [r appendContentCharacter:'\n']; } else if ([_object isKindOfClass:[NSArray class]]) { [r setStatus:207 /* Multi-Status */]; [r setHeader:@"text/xml" forKey:@"content-type"]; [r appendContentString:@"\n"]; [r appendContentString:@""]; if (formatOutput) [r appendContentCharacter:'\n']; [self renderPollList:_object code:200 inContext:_ctx]; [r appendContentString:@""]; if (formatOutput) [r appendContentCharacter:'\n']; } else { [r setStatus:204 /* no content */]; //[r appendContentString:@"object was stored."]; } return YES; } - (BOOL)renderMkColResult:(id)_object inContext:(WOContext *)_ctx { WOResponse *r = [_ctx response]; if (_object == nil || [_object boolValue]) { [r setStatus:201 /* Created */]; return YES; } if ([_object isKindOfClass:[NSNumber class]]) { [r setStatus:[_object intValue]]; [r appendContentString:@"object could not be created."]; } else { [r setStatus:500 /* server error */]; [r appendContentString:@"object could not be deleted. reason: "]; [r appendContentHTMLString:[_object stringValue]]; } return YES; } /* debugging */ - (BOOL)isDebuggingEnabled { return debugOn; } @end /* SoWebDAVRenderer */