From 7566f5535ffd9c7e4a50536907e0023da1bc3d78 Mon Sep 17 00:00:00 2001 From: Richard Frith-Macdonald Date: Fri, 4 Mar 2005 15:50:06 +0000 Subject: [PATCH] Integrated XMLRPC support. git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@20843 72102866-910b-0410-8b05-ffd578937521 --- ChangeLog | 5 + Headers/Additions/GNUstepBase/GSXML.h | 236 ++++++- Source/Additions/GSXML.m | 912 ++++++++++++++++++++++++++ 3 files changed, 1152 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index d094c1542..3fc7dc953 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2005-03-04 Richard Frith-Macdonald + + * Source/Additions/GSXML.m: GSXMLRPC integrated. + * Headers/Additions/GSUstepBase/GSXML.h: GSXMLRPC integrated. + 2005-03-03 Adam Fedor * Source/NSFileManager.m (-createFileAtPath:...): Define var at diff --git a/Headers/Additions/GNUstepBase/GSXML.h b/Headers/Additions/GNUstepBase/GSXML.h index 14df8e0e6..4da2f13fd 100644 --- a/Headers/Additions/GNUstepBase/GSXML.h +++ b/Headers/Additions/GNUstepBase/GSXML.h @@ -1,6 +1,6 @@ /** Interface for XML parsing classes - Copyright (C) 2000 Free Software Foundation, Inc. + Copyright (C) 2000-2005 Free Software Foundation, Inc. Written by: Michael Pakhantsov on behalf of Brainstorm computer solutions. @@ -403,6 +403,240 @@ - (GSXMLDocument*) xsltTransform: (GSXMLDocument*)xsltStylesheet; @end + + +#include + +@class NSArray; +@class NSDictionary; +@class NSTimer; +@class GSXMLNode; +@class GSXMLRPC; + +/** + *

The GSXMLRPC class provides methods for constructing and parsing + * XMLRPC method call and response documents ... so that calls may + * be constructed of standard objects. + *

+ *

The correspondence between XMLRPC values and Objective-C objects + * is as follows - + *

+ * + * i4 (or int) is an [NSNumber] other + * than a real/float or boolean. + * boolean is an [NSNumber] created as a BOOL. + * string is an [NSString] object. + * double is an [NSNumber] created as a float or + * double. + * dateTime.iso8601 is an [NSDate] object. + * base64 is an [NSData] object. + * array is an [NSArray] object. + * struct is an [NSDictionary] object. + * + *

If you attempt to use any other type of object in the construction + * of an XMLRPC document, the [NSObject-description] method of that + * object will be used to create a striong, and the resulting object + * will be encoded as an XMLRPC string element. + *

+ *

In particular, the names of members in a struct must be strings, + * so if you provide an [NSDictionary] object to represent a struct + * the keys of the dictionary will be converted to strings if necessary. + *

+ *

The class also provides a method for making a synchronous XMLRPC + * method call (with timeout), or an asynchronous call in which the + * call completion is handled by a delegate. + *

+ */ +@interface GSXMLRPC : NSObject +{ +@private + NSURLHandle *handle; + NSTimer *timer; + id result; + id delegate; // Not retained. +} + +/** + * Given a method name and an array of parameters, this method constructs + * the XML document for the corresponding XMLRPC call and returns the + * document as a string.
+ * The params array may be empty or nil if there are no parameters to be + * passed.
+ * The method returns nil if passed an invalid method name (a method name + * may contain any of the ascii alphanumeric characters and underscore, + * fullstop, colon, or slash).
+ * This method is used internally when sending an XMLRPC method call to + * a remote system, but you can also call it yourself. + */ +- (NSString*) buildMethodCall: (NSString*)method + params: (NSArray*)params; + +/** + * Constructs an XML document for an XMLRPC fault response with the + * specified code and string. The resulting document is returned + * as a string.
+ * This method is intended for use by applications acting as XMLRPC servers. + */ +- (NSString*) buildResponseWithFaultCode: (int)code andString: (NSString*)s; + +/** + * Builds an XMLRPC response with the specified array of parameters and + * returns the document as a string.
+ * The params array may be empty or nil if there are no parameters to be + * returned (an empty params element will be created).
+ * This method is intended for use by applications acting as XMLRPC servers. + */ +- (NSString*) buildResponseWithParams: (NSArray*)params; + +/** + * Returns the delegate previously set by the -setDelegate: method.
+ * The delegate handles completion of asynchronous method calls to the + * URL specified when the receiver was initialised (if any). + */ +- (id) delegate; + +/** + * Initialise the receiver to make XMLRPC calls to the specified URL.
+ * This method just calls -initWithURL:certificate:privateKey:password: + * with nil arguments for the SSL credentials. + */ +- (id) initWithURL: (NSString*)url; + +/** + * Initialise the receiver to make XMLRPC calls to the specified url + * and (optionally) with the specified SSL parameters.
+ * The url argument may be nil, in which case the receiver will be + * unable to make XMLRPC calls, but can be used to parse incoming + * requests and build responses.
+ * If the SSL credentials are non-nil, connections to the remote server + * will be authenticated using the supplied certificate so that the + * remote system knows who is contacting it. + */ +- (id) initWithURL: (NSString*)url + certificate: (NSString*)cert + privateKey: (NSString*)pKey + password: (NSString*)pwd; + +/** + * Calls -sendMethodCall:params:timeout: and waits for the response.
+ * Returns the response parameters (an array), + * the response fault (a dictionary), + * or a failure reason (a string). + */ +- (id) makeMethodCall: (NSString*)method + params: (NSArray*)params + timeout: (int)seconds; + +/** + * Parses XML data containing an XMLRPC method call.
+ * Returns the name of the method call.
+ * Empties, and then places the method parameters (if any) + * in the params argument.
+ * NB. Any containers (arrays or dictionaries) in the parsed parameters + * will be mutable, so you can modify this data structure as you like.
+ * Raises an exception if parsing fails.
+ * This method is intended for the use of XMLRPC server applications. + */ +- (NSString*) parseMethod: (NSData*)request + params: (NSMutableArray*)params; + +/** + * Parses XML data containing an XMLRPC method response.
+ * Returns nil for succes, the fault dictionary on failure.
+ * Places the response parameters (if any) in the params argument.
+ * NB. Any containers (arrays or dictionaries) in the parsed parameters + * will be mutable, so you can modify this data structure as you like.
+ * Raises an exception if parsing fails.
+ * Used internally when making a method call to a remote server. + */ +- (NSDictionary*) parseResponse: (NSData*)response + params: (NSMutableArray*)params; + +/** + * Returns the result of the last method call, or nil if there has been + * no method call or one is in progress.
+ * The result may be one of - + * + * A mutable array ... the parameters of a success response. + * A dictionary ... containing a fault response. + * A string ... describing a low-level failure (eg. timeout). + * + * NB. Any containers (arrays or dictionaries) in the parsed parameters + * of a success response will be mutable, so you can modify this data + * structure as you like. + */ +- (id) result; + +/** + * Send an asynchronous XMLRPC method call with the specified timeout.
+ * A delegate should have been set to handle the result of this call, + * but if one was not set the state of the asynchronous call may be polled + * by calling the -result method, which will return nil as long as the + * call has not completed.
+ * The call may be cancelled by calling the -timeout: method
+ * This method returns YES if the call was started, + * NO if it could not be started + * (eg because another call is in progress or because of bad arguments).
+ * NB. For the asynchronous operation to proceed, the current [NSRunLoop] + * must be run. + */ +- (BOOL) sendMethodCall: (NSString*)method + params: (NSArray*)params + timeout: (int)seconds; + +/** + * Specify whether to perform mdebug trace on I/O + */ +- (void) setDebug: (BOOL)flag; + +/** + * Sets the delegate object which will receive callbacks when an XMLRPC + * call completes.
+ * NB. this delegate is not retained, and should be removed + * before it is deallocated (call -setDelegate: again with a nil argument + * to remove the delegate). + */ +- (void) setDelegate: (id)aDelegate; + +/** + * Handles timeouts, passing information to delegate ... you don't need to + * call this method, but you may call it in order to cancel an + * asynchronous request as if it had timed out. + */ +- (void) timeout: (NSTimer*)t; + + +/** Allows GSXMLRPC to act as a client of NSURLHandle. Internal use only. */ +- (void) URLHandle: (NSURLHandle*)sender + resourceDataDidBecomeAvailable: (NSData*)newData; +/** Allows GSXMLRPC to act as a client of NSURLHandle. Internal use only. */ +- (void) URLHandle: (NSURLHandle*)sender + resourceDidFailLoadingWithReason: (NSString*)reason; +/** Allows GSXMLRPC to act as a client of NSURLHandle. Internal use only. */ +- (void) URLHandleResourceDidBeginLoading: (NSURLHandle*)sender; +/** Allows GSXMLRPC to act as a client of NSURLHandle. Internal use only. */ +- (void) URLHandleResourceDidCancelLoading: (NSURLHandle*)sender; +/** Allows GSXMLRPC to act as a client of NSURLHandle. Internal use only. */ +- (void) URLHandleResourceDidFinishLoading: (NSURLHandle*)sender; + +@end + +/** + * Delegates should implement this method in order to be informed of + * the success or failure of an XMLRPC method call which was initiated + * by the -sendMethodCall:params:timeout: method.
+ */ +@interface GSXMLRPC (Delegate) +/** + * Called by the sender when an XMLRPC method call completes (either success + * or failure). + * The delegate may then call the -result method to retrieve the result of + * the method call from the sender. + */ +- (void) completedXMLRPC: (GSXMLRPC*)sender; +@end + + #endif /* STRICT_MACOS_X */ #endif /* STRICT_OPENSTEP */ diff --git a/Source/Additions/GSXML.m b/Source/Additions/GSXML.m index 4feff9f3f..4fab4862d 100644 --- a/Source/Additions/GSXML.m +++ b/Source/Additions/GSXML.m @@ -4308,3 +4308,915 @@ static BOOL warned = NO; if (warned == NO) { warned = YES; NSLog(@"WARNING, use } @end + + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +/* + * Categories on other classes which are required for XMLRPC + */ +@interface NSArray (GSXMLRPC) +- (void) appendToXMLRPC: (NSMutableString*)str indent: (unsigned)indent; +@end + +@interface NSData (GSXMLRPC) +- (void) appendToXMLRPC: (NSMutableString*)str indent: (unsigned)indent; +@end + +@interface NSDate (GSXMLRPC) +- (void) appendToXMLRPC: (NSMutableString*)str indent: (unsigned)indent; +@end + +@interface NSDictionary (GSXMLRPC) +- (void) appendToXMLRPC: (NSMutableString*)str indent: (unsigned)indent; +@end + +@interface NSObject (GSXMLRPC) +- (void) appendToXMLRPC: (NSMutableString*)str indent: (unsigned)indent; +@end + +@interface NSNumber (GSXMLRPC) +- (void) appendToXMLRPC: (NSMutableString*)str indent: (unsigned)indent; +@end + +@interface NSString (GSXMLRPC) +- (void) appendToXMLRPC: (NSMutableString*)str indent: (unsigned)indent; +@end + + + + +/* + * A little code to handle indentation. + */ +static NSString *indentations[] = { + @" ", + @" ", + @" ", + @"\t", + @"\t ", + @"\t ", + @"\t ", + @"\t\t", + @"\t\t ", + @"\t\t ", + @"\t\t ", + @"\t\t\t", + @"\t\t\t ", + @"\t\t\t ", + @"\t\t\t ", + @"\t\t\t\t" +}; +static void indentation(unsigned level, NSMutableString *str) +{ + if (level > 0) + { + if (level >= sizeof(indentations)/sizeof(*indentations)) + { + level = sizeof(indentations)/sizeof(*indentations) - 1; + } + [str appendString: indentations[level]]; + } +} + + + +/* + * Implementation of categories to output objects for XMLRPC + */ + +@implementation NSArray (GSXMLRPC) +- (void) appendToXMLRPC: (NSMutableString*)str indent: (unsigned)indent +{ + unsigned i; + unsigned c = [self count]; + + indentation(indent++, str); + [str appendString: @"\n"]; + indentation(indent++, str); + [str appendString: @"\n"]; + for (i = 0; i < c; i++) + { + id value = [self objectAtIndex: i]; + + indentation(indent++, str); + [str appendString: @"\n"]; + [value appendToXMLRPC: str indent: indent]; + [str appendString: @"\n"]; + indentation(--indent, str); + [str appendString: @"\n"]; + } + indentation(--indent, str); + [str appendString: @"\n"]; + indentation(--indent, str); + [str appendString: @""]; +} +@end + +@implementation NSData (GSXMLRPC) +- (void) appendToXMLRPC: (NSMutableString*)str indent: (unsigned)indent +{ + NSData *d; + NSString *s; + + d = [GSMimeDocument encodeBase64: self]; + s = [[NSString alloc] initWithData: d encoding: NSASCIIStringEncoding]; + [str appendString: @""]; + [str appendString: s]; + [str appendString: @""]; + RELEASE(s); +} +@end + +@implementation NSDate (GSXMLRPC) +- (void) appendToXMLRPC: (NSMutableString*)str indent: (unsigned)indent +{ + static NSTimeZone *z = nil; + NSString *s; + + if (z == nil) + { + s = RETAIN([NSTimeZone timeZoneForSecondsFromGMT: 0]); + } + + s = [self descriptionWithCalendarFormat: @"%Y%m%dT%H:%M:%S" + timeZone: z + locale: nil]; + [str appendString: @""]; + [str appendString: s]; + [str appendString: @""]; +} +@end + +@implementation NSDictionary (GSXMLRPC) +- (void) appendToXMLRPC: (NSMutableString*)str indent: (unsigned)indent +{ + NSEnumerator *kEnum = [self keyEnumerator]; + NSString *key; + + indentation(indent++, str); + [str appendString: @"\n"]; + while ((key = [kEnum nextObject])) + { + id value = [self objectForKey: key]; + + indentation(indent++, str); + [str appendString: @"\n"]; + indentation(indent, str); + [str appendString: @""]; + [str appendString: [[key description] stringByEscapingXML]]; + [str appendString: @"\n"]; + indentation(indent++, str); + [str appendString: @"\n"]; + [value appendToXMLRPC: str indent: indent--]; + [str appendString: @"\n"]; + indentation(indent--, str); + [str appendString: @"\n"]; + indentation(indent, str); + [str appendString: @"\n"]; + } + indentation(--indent, str); + [str appendString: @""]; +} +@end + +@implementation NSNumber (GSXMLRPC) +- (void) appendToXMLRPC: (NSMutableString*)str indent: (unsigned)indent +{ + const char *t = [self objCType]; + + indentation(indent, str); + if (strchr("cCsSiIlL", *t) != 0) + { + long i = [self longValue]; + + if ((i == 0 || i == 1) && (*t == 'c' || *t == 'C')) + { + if (i == 0) + { + [str appendString: @"0"]; + } + else + { + [str appendString: @"1"]; + } + } + else + { + [str appendFormat: @"%d", i]; + } + } + else + { + [str appendFormat: @"%f", [self doubleValue]]; + } +} +@end + +@implementation NSObject (GSXMLRPC) +- (void) appendToXMLRPC: (NSMutableString*)str indent: (unsigned)indent +{ + [[self description] appendToXMLRPC: str indent: indent]; +} +@end + +@implementation NSString (GSXMLRPC) +- (void) appendToXMLRPC: (NSMutableString*)str indent: (unsigned)indent +{ + indentation(indent, str); + [str appendFormat: @"%@", [self stringByEscapingXML]]; +} +@end + + + +/* + * Convert incoming XMLRPC value to a normal Objective-C object. + */ +@interface GSXMLRPC (Private) +- (id) _parseValue: (GSXMLNode*)node; +@end + +@implementation GSXMLRPC (Private) +- (id) _parseValue: (GSXMLNode*)node +{ + NSString *name = [node name]; + NSString *str; + + if ([name isEqualToString: @"value"]) + { + GSXMLNode *type = [node firstChildElement]; + + /* + * A value with no type element is just a string. + */ + if (type == nil) + { + name = @"string"; + } + else + { + node = type; + name = [node name]; + } + } + + if ([name length] == 0) + { + return nil; + } + + if ([name isEqualToString: @"i4"] || [name isEqualToString: @"int"]) + { + str = [node content]; + if (str == nil) + { + [NSException raise: NSInvalidArgumentException + format: @"missing %@ value", name]; + } + return [NSNumber numberWithInt: [str intValue]]; + } + + if ([name isEqualToString: @"string"]) + { + str = [node content]; + if (str == nil) + { + str = @""; + } + return str; + } + + if ([name isEqualToString: @"boolean"]) + { + char c; + + str = [node content]; + if (str == nil) + { + [NSException raise: NSInvalidArgumentException + format: @"missing %@ value", name]; + } + c = [str intValue]; + return [NSNumber numberWithBool: c == 0 ? NO : YES]; + } + + if ([name isEqualToString: @"double"]) + { + str = [node content]; + if (str == nil) + { + [NSException raise: NSInvalidArgumentException + format: @"missing %@ value", name]; + } + return [NSNumber numberWithDouble: [str doubleValue]]; + } + + if ([name isEqualToString: @"data"]) + { + NSData *d; + + str = [node content]; + if (str == nil) + { + [NSException raise: NSInvalidArgumentException + format: @"missing %@ value", name]; + } + d = [str dataUsingEncoding: NSASCIIStringEncoding]; + return [GSMimeDocument decodeBase64: d]; + } + + if ([name isEqualToString: @"date"]) + { + str = [node content]; + if (str == nil) + { + [NSException raise: NSInvalidArgumentException + format: @"missing %@ value", name]; + } + return [NSCalendarDate dateWithString: str + calendarFormat: @"%Y%m%dT%H:%M:%S" + locale: nil]; + } + + if ([name isEqualToString: @"array"]) + { + NSMutableArray *arr = [NSMutableArray array]; + + node = [node firstChildElement]; + while (node != nil && [[node name] isEqualToString: @"data"] == NO) + { + node = [node nextElement]; + } + if ([[node name] isEqualToString: @"data"] == YES) + { + node = [node firstChildElement]; + while (node != nil) + { + if ([[node name] isEqualToString: @"value"] == YES) + { + id v; + + v = [self _parseValue: node]; + if (v != nil) + { + [arr addObject: v]; + } + } + node = [node nextElement]; + } + } + return arr; + } + + if ([name isEqualToString: @"struct"]) + { + NSMutableDictionary *dict = [NSMutableDictionary dictionary]; + + node = [node firstChildElement]; + while (node != nil) + { + if ([[node name] isEqualToString: @"member"] == YES) + { + GSXMLNode *member = [node firstChildElement]; + NSString *key = nil; + id val = nil; + + while (member != nil) + { + if ([[member name] isEqualToString: @"name"] == YES) + { + key = [member content]; + } + else if ([[member name] isEqualToString: @"value"] == YES) + { + val = [self _parseValue: member]; + } + if (key != nil && val != nil) + { + [dict setObject: val forKey: key]; + break; + } + member = [member nextElement]; + } + } + node = [node nextElement]; + } + return dict; + } + + [NSException raise: NSInvalidArgumentException + format: @"Unknown value type: %@", name]; + return nil; +} +@end + + + +/* + * And now, the actual GSXMLRPC class. + */ +@implementation GSXMLRPC + +- (NSString*) buildMethodCall: (NSString*)method + params: (NSArray*)params +{ + NSMutableString *str = [NSMutableString stringWithCapacity: 1024]; + unsigned c = [params count]; + unsigned i; + + if ([method length] == 0) + { + return nil; + } + else + { + static NSCharacterSet *illegal = nil; + NSRange r; + + if (illegal == nil) + { + NSMutableCharacterSet *tmp = [NSMutableCharacterSet new]; + + [tmp addCharactersInRange: NSMakeRange('0', 10)]; + [tmp addCharactersInRange: NSMakeRange('a', 26)]; + [tmp addCharactersInRange: NSMakeRange('A', 26)]; + [tmp addCharactersInString: @"_.:/"]; + [tmp invert]; + illegal = [tmp copy]; + RELEASE(tmp); + } + r = [method rangeOfCharacterFromSet: illegal]; + if (r.length > 0) + { + return nil; // Bad method name. + } + } + [str appendString: @"\n"]; + [str appendString: @"\n"]; + [str appendFormat: @" %@\n", + [method stringByEscapingXML]]; + if (c > 0) + { + [str appendString: @" \n"]; + for (i = 0; i < c; i++) + { + [str appendString: @" \n"]; + [str appendString: @" \n"]; + [[params objectAtIndex: i] appendToXMLRPC: str indent: 3]; + [str appendString: @"\n \n"]; + [str appendString: @" \n"]; + } + [str appendString: @" \n"]; + } + [str appendString: @"\n"]; + return str; +} + +- (NSString*) buildResponseWithFaultCode: (int)code andString: (NSString*)s +{ + NSMutableString *str = [NSMutableString stringWithCapacity: 1024]; + NSDictionary *fault; + + fault = [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt: code], @"faultCode", + [s stringByEscapingXML], @"faultString", + nil]; + + [str appendString: @"\n"]; + [str appendString: @"\n"]; + [str appendString: @" \n"]; + [str appendString: @" \n"]; + [fault appendToXMLRPC: str indent: 3]; + [str appendString: @"\n \n"]; + [str appendString: @" \n"]; + [str appendString: @"\n"]; + return str; +} + +- (NSString*) buildResponseWithParams: (NSArray*)params +{ + NSMutableString *str = [NSMutableString stringWithCapacity: 1024]; + unsigned c = [params count]; + unsigned i; + + [str appendString: @"\n"]; + [str appendString: @"\n"]; + [str appendString: @" \n"]; + for (i = 0; i < c; i++) + { + [str appendString: @" \n"]; + [str appendString: @" \n"]; + [[params objectAtIndex: i] appendToXMLRPC: str indent: 3]; + [str appendString: @"\n \n"]; + [str appendString: @" \n"]; + } + [str appendString: @" \n"]; + [str appendString: @"\n"]; + return str; +} + +- (void) dealloc +{ + if (timer != nil) + { + [self timeout: nil]; // Treat as immediate timeout. + } + [handle removeClient: self]; + DESTROY(result); + DESTROY(handle); + [super dealloc]; +} + +- (id) delegate +{ + return delegate; +} + +- (id) initWithURL: (NSString*)url +{ + return [self initWithURL: url certificate: nil privateKey: nil password: nil]; +} + +- (id) initWithURL: (NSString*)url + certificate: (NSString*)cert + privateKey: (NSString*)pKey + password: (NSString*)pwd +{ + if (url != nil) + { + NS_DURING + { + NSURL *u = [NSURL URLWithString: url]; + + handle = RETAIN([u URLHandleUsingCache: NO]); + if (cert != nil && pKey != nil && pwd != nil) + { + [handle writeProperty: cert + forKey: GSHTTPPropertyCertificateFileKey]; + [handle writeProperty: pKey forKey: GSHTTPPropertyKeyFileKey]; + [handle writeProperty: pwd forKey: GSHTTPPropertyPasswordKey]; + } + [handle addClient: self]; + } + NS_HANDLER + { + DESTROY(self); + } + NS_ENDHANDLER + } + return self; +} + +- (id) init +{ + return [self initWithURL: nil certificate: nil privateKey: nil password: nil]; +} + +- (id) makeMethodCall: (NSString*)method + params: (NSArray*)params + timeout: (int)seconds +{ + NS_DURING + { + if ([self sendMethodCall: method params: params timeout: seconds] == YES) + { + NSDate *when = AUTORELEASE(RETAIN([timer fireDate])); + + while (timer != nil) + { + [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode + beforeDate: when]; + } + } + } + NS_HANDLER + { + ASSIGN(result, [localException description]); + } + NS_ENDHANDLER + + return result; +} + +- (NSString*) parseMethod: (NSData*)request + params: (NSMutableArray*)params +{ + GSXPathContext *ctx = nil; + GSXPathNodeSet *ns = nil; + NSString *method; + + [params removeAllObjects]; + + NS_DURING + { + GSXMLParser *parser = [GSXMLParser parserWithData: request]; + GSXMLDocument *doc = nil; + + [parser substituteEntities: YES]; + [parser parse]; + doc = [parser document]; + ctx = AUTORELEASE([[GSXPathContext alloc] initWithDocument: doc]); + } + NS_HANDLER + { + ctx = nil; + } + NS_ENDHANDLER + if (ctx == nil) + { + [NSException raise: NSInvalidArgumentException + format: @"Bad Request: parse failed"]; + } + + ns = (GSXPathNodeSet*)[ctx evaluateExpression: @"//methodCall/methodName"]; + if ([ns count] != 1) + { + [NSException raise: NSInvalidArgumentException + format: @"Badly formatted methodCall"]; + } + method = [[ns nodeAtIndex: 0] content]; + + ns = (GSXPathNodeSet*)[ctx evaluateExpression: + @"//methodCall/params/param/value"]; + + NS_DURING + { + int i; + + for (i = 0; i < [ns count]; i++) + { + GSXMLNode *node = [ns nodeAtIndex: i]; + + if ([[node name] isEqualToString: @"value"] + && [node firstChildElement] != nil) + { + id value = [self _parseValue: [node firstChildElement]]; + + if (value != nil) + { + [params addObject: value]; + } + } + } + } + NS_HANDLER + { + [params removeAllObjects]; + [localException raise]; + } + NS_ENDHANDLER + + return method; +} + +- (NSDictionary*) parseResponse: (NSData*) response + params: (NSMutableArray*)params +{ + GSXPathContext *ctx = nil; + GSXPathNodeSet *ns = nil; + id fault = nil; + + [params removeAllObjects]; + + NS_DURING + { + GSXMLParser *parser = [GSXMLParser parserWithData: response]; + GSXMLDocument *doc = nil; + + [parser substituteEntities: YES]; + [parser parse]; + doc = [parser document]; + ctx = AUTORELEASE([[GSXPathContext alloc] initWithDocument: doc]); + } + NS_HANDLER + { + ctx = nil; + } + NS_ENDHANDLER + if (ctx == nil) + { + [NSException raise: NSInvalidArgumentException + format: @"Bad Response: parse failed"]; + } + + ns = (GSXPathNodeSet*)[ctx evaluateExpression: + @"//methodResponse/params/param/value"]; + + NS_DURING + { + int i; + + if ([ns count] > 0) + { + for (i = 0; i < [ns count]; i++) + { + GSXMLNode *node = [ns nodeAtIndex: i]; + + if ([[node name] isEqualToString: @"value"] + && [node firstChildElement] != nil) + { + id value = [self _parseValue: [node firstChildElement]]; + + if (value != nil) + { + [params addObject: value]; + } + } + } + } + else + { + ns = (GSXPathNodeSet*)[ctx evaluateExpression: + @"//methodResponse/fault/value/struct"]; + if ([ns count] > 0) + { + fault = [self _parseValue: [ns nodeAtIndex: 0]]; + } + } + } + NS_HANDLER + { + [params removeAllObjects]; + [localException raise]; + } + NS_ENDHANDLER + + return fault; +} + +- (id) result +{ + if (timer == nil) + { + return result; + } + else + { + return nil; + } +} + +- (BOOL) sendMethodCall: (NSString*)method + params: (NSArray*)params + timeout: (int)seconds +{ + NSString *xml; + NSData *data; + + ASSIGN(result, @"unable to send"); + + if (handle == nil) + { + return NO; // Not initialised to send. + } + if (timer != nil) + { + return NO; // Send already in progress. + } + xml = [self buildMethodCall: method params: params]; + if (xml == nil) + { + return NO; + } + data = [xml dataUsingEncoding: NSUTF8StringEncoding]; + + timer = [NSTimer scheduledTimerWithTimeInterval: seconds + target: self + selector: @selector(timeout:) + userInfo: nil + repeats: NO]; + + [handle writeProperty: @"POST" forKey: GSHTTPPropertyMethodKey]; + [handle writeProperty: @"GSXMLRPC/1.0.0" forKey: @"User-Agent"]; + [handle writeProperty: @"text/xml" forKey: @"Content-Type"]; + [handle writeData: data]; + [handle loadInBackground]; + return YES; +} + +- (void) setDebug: (BOOL)flag +{ + if ([handle respondsToSelector: _cmd] == YES) + { + [(id)handle setDebug: flag]; + } +} + +- (void) setDelegate: (id)aDelegate +{ + delegate = aDelegate; +} + +- (void) timeout: (NSTimer*)t +{ + [timer invalidate]; + timer = nil; + [handle cancelLoadInBackground]; +} + + +- (void) URLHandle: (NSURLHandle*)sender + resourceDataDidBecomeAvailable: (NSData*)newData +{ + // Not interesting +} + +- (void) URLHandle: (NSURLHandle*)sender + resourceDidFailLoadingWithReason: (NSString*)reason +{ + ASSIGN(result, reason); + [timer invalidate]; + timer = nil; + if ([delegate respondsToSelector: @selector(completedXMLRPC:)]) + { + [delegate completedXMLRPC: self]; + } +} + +- (void) URLHandleResourceDidBeginLoading: (NSURLHandle*)sender +{ + // Not interesting +} + +- (void) URLHandleResourceDidCancelLoading: (NSURLHandle*)sender +{ + ASSIGN(result, @"timeout"); + [timer invalidate]; + timer = nil; + if ([delegate respondsToSelector: @selector(completedXMLRPC:)]) + { + [delegate completedXMLRPC: self]; + } +} + +- (void) URLHandleResourceDidFinishLoading: (NSURLHandle*)sender +{ + NSMutableArray *params = [NSMutableArray array]; + id fault = nil; + int code; + + code = [[handle propertyForKey: NSHTTPPropertyStatusCodeKey] intValue]; + + if (code == 200) + { + NSData *response = [handle availableResourceData]; + + NS_DURING + { + fault = [self parseResponse: response params: params]; + } + NS_HANDLER + { + fault = [localException reason]; + } + NS_ENDHANDLER + } + else + { + fault = [NSString stringWithFormat: @"HTTP status %03d", code]; + } + if (fault == nil) + { + ASSIGN(result, params); + } + else + { + ASSIGN(result, fault); + } + + [timer invalidate]; + timer = nil; + + if ([delegate respondsToSelector: @selector(completedXMLRPC:)]) + { + [delegate completedXMLRPC: self]; + } +} + +@end + +@implementation GSXMLRPC (Delegate) +- (void) completedXMLRPC: (GSXMLRPC*)sender +{ +} +@end +