2 Copyright (C) 2004-2005 SKYRIX Software AG
4 This file is part of SOPE.
6 SOPE is free software; you can redistribute it and/or modify it under
7 the terms of the GNU Lesser General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
11 SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14 License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with SOPE; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
22 #include "SoObjectSOAPDispatcher.h"
24 #include "NSException+HTTP.h"
25 #include "WOContext+SoObjects.h"
26 #include "SoDefaultRenderer.h"
27 #include <NGObjWeb/WOActionResults.h>
28 #include <NGObjWeb/WOContext.h>
29 #include <NGObjWeb/WOResponse.h>
30 #include <NGObjWeb/WORequest.h>
33 #include <SaxObjC/XMLNamespaces.h>
36 TODO: is it required by SOAP that the HTTP method is POST?
39 Servers also set a SOAPAction HTTP header.
42 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
44 xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
45 xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
46 xmlns:xsd="http://www.w3.org/1999/XMLSchema"
47 xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
50 xmlns:types="http://schemas.novell.com/2003/10/NCSP/types.xsd"
51 SOAP-ENV:encodingStyle=""
54 <types:auth xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
55 xsi:type="types:PlainText"
57 <types:username>dummy</types:username>
58 <types:password>user</types:password>
64 Another (http://novell.com/simias/domain/GetDomainID):
65 <?xml version="1.0" encoding="utf-8"?>
66 <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
67 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
68 xmlns:xsd="http://www.w3.org/2001/XMLSchema">
70 <GetDomainID xmlns="http://novell.com/simias/domain" />
75 @interface SoSOAPRenderer : SoDefaultRenderer
78 @implementation SoObjectSOAPDispatcher
80 static BOOL debugOn = NO;
81 static BOOL debugParsing = NO;
84 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
86 debugOn = [ud boolForKey:@"SoObjectSOAPDispatcherDebugEnabled"];
87 if (debugOn) NSLog(@"Note: SOPE SOAP dispatcher debugging turned on.");
89 debugParsing = [ud boolForKey:@"SoObjectSOAPDispatcherParserDebugEnabled"];
90 if (debugParsing) NSLog(@"Note: SOPE SOAP parsing debugging turned on.");
95 - (id)performSOAPAction:(NSString *)_actionName
96 header:(id<DOMElement>)_header body:(id<DOMElement>)_body
97 inContext:(WOContext *)_ctx
104 [self debugWithFormat:@"calling SOAP method: '%@'", _actionName];
106 /* find client object */
108 if ((clientObject = [_ctx clientObject]) != nil) {
110 [self debugWithFormat:@" client object from ctx: %@", clientObject];
112 else if ((clientObject = [self->object clientObject])) {
114 [self debugWithFormat:@" setting client object: %@", clientObject];
115 [_ctx setClientObject:clientObject];
118 /* find callable (method) object */
120 // TODO: should we allow acquisition?
121 methodObject = [clientObject lookupName:_actionName inContext:_ctx
123 if (methodObject == nil) {
124 /* check for common names like "GetFolderRequest" => "GetFolder" */
125 if ([_actionName hasSuffix:@"Request"]) {
128 an = [_actionName substringToIndex:([_actionName length] - 7)];
129 if (debugOn) [self debugWithFormat:@" try special name: %@", an];
130 methodObject = [clientObject lookupName:an inContext:_ctx acquire:NO];
131 if (methodObject != nil) _actionName = an;
134 if (methodObject == nil) {
135 /* check for names like "http://novell.com/domain/GetID" => "GetID" */
138 r = [_actionName rangeOfString:@"/" options:NSBackwardsSearch];
142 an = [_actionName substringFromIndex:(r.location + r.length)];
143 if (debugOn) [self debugWithFormat:@" try special name: %@", an];
145 methodObject = [clientObject lookupName:an inContext:_ctx acquire:NO];
146 if (methodObject != nil) _actionName = an;
150 if (methodObject == nil) {
151 [self warnWithFormat:@"could not locate SOAP method: %@",
153 return [NSException exceptionWithHTTPStatus:501 /* not implemented */
154 reason:@"did not find the specified SOAP method"];
156 else if (![methodObject isCallable]) {
157 [self warnWithFormat:
158 @"object found for SOAP method '%@' is not callable: "
159 @"%@", _actionName, methodObject];
160 return [NSException exceptionWithHTTPStatus:501 /* not implemented */
161 reason:@"did not find the specified SOAP method"];
163 if (debugOn) [self debugWithFormat:@" method: %@", methodObject];
165 /* apply arguments */
167 // TODO: use some query syntax in product.plist to retrieve parameters
170 // TODO: somehow apply SOPE header/body?
171 if (_header) [_ctx setObject:_header forKey:@"SOAPHeader"];
172 if (_body) [_ctx setObject:_body forKey:@"SOAPBody"];
174 if ([methodObject respondsToSelector:
175 @selector(takeValuesFromRequest:inContext:)]) {
177 [self debugWithFormat:@" applying values from request ..."];
178 [methodObject takeValuesFromRequest:[_ctx request] inContext:_ctx];
183 resultObject = [methodObject callOnObject:[_ctx clientObject]
185 if (debugOn) [self debugWithFormat:@"got SOAP result: %@", resultObject];
189 - (id)performSOAPAction:(NSString *)_actionName document:(id)_dom
190 inContext:(WOContext *)_ctx
192 id<DOMElement> envelope;
193 id<DOMElement> header;
195 id<DOMNodeList> list;
199 envelope = [_dom documentElement];
200 if (![[envelope tagName] isEqualToString:@"Envelope"] ||
201 ![[envelope namespaceURI] isEqualToString:XMLNS_SOAP_ENVELOPE]) {
202 [self debugWithFormat:@"Note: missing SOAP envelope at document root."];
203 return [NSException exceptionWithHTTPStatus:400 /* bad request */
204 reason:@"could not parse SOAP content of request"];
206 if (debugParsing) [self debugWithFormat:@"envelope: %@", envelope];
208 [_ctx setObject:envelope forKey:@"SOAPEnvelope"];
212 list = [envelope getElementsByTagName:@"Header"];
213 // TODO: not yet supported by DOMElement: namespaceURI:XMLNS_SOAP_ENVELOPE];
214 if ([list length] > 1) {
215 [self warnWithFormat:@"multiple SOAP headers in request?! (using first)"];
217 header = [list length] > 0 ? [list objectAtIndex:0] : nil;
218 if (debugParsing) [self debugWithFormat:@"header: %@", header];
222 list = [envelope getElementsByTagName:@"Body"];
223 // TODO: not yet supported by DOMElement: namespaceURI:XMLNS_SOAP_ENVELOPE];
224 if ([list length] == 0) {
225 [self debugWithFormat:@"Note: missing SOAP body."];
226 return [NSException exceptionWithHTTPStatus:400 /* bad request */
227 reason:@"could not parse SOAP body of request"];
229 else if ([list length] > 1) {
230 [self warnWithFormat:@"multiple SOAP bodies in request?! (using first)"];
232 body = [list objectAtIndex:0];
233 if (debugParsing) [self debugWithFormat:@"body: %@", body];
237 return [self performSOAPAction:_actionName
238 header:header body:body inContext:_ctx];
241 /* main dispatcher */
243 - (id)dispatchInContext:(WOContext *)_ctx {
244 NSAutoreleasePool *pool;
246 NSString *SOAPAction;
250 pool = [[NSAutoreleasePool alloc] init];
252 if ((rq = [_ctx request]) == nil) {
253 [self errorWithFormat:@"missing request in context!"];
258 Note: the SOAPAction is also contained in the body which is probably
259 considered the authority? We currently prefer the header when
262 SOAPAction = [rq headerForKey:@"soapaction"];
263 if ([SOAPAction length] > 1) {
265 if ([SOAPAction characterAtIndex:0] == '"' &&
266 [SOAPAction characterAtIndex:([SOAPAction length] - 1)] == '"') {
267 /* a quoted header, like "http://novell.com/simias/domain/GetDomainID" */
271 r.length = [SOAPAction length] - 2;
272 SOAPAction = [SOAPAction substringWithRange:r];
275 if (![SOAPAction isNotEmpty]) {
276 [self errorWithFormat:@"missing SOAPAction HTTP header!"];
282 if ((dom = [rq contentAsDOMDocument]) == nil) {
283 [self debugWithFormat:@"Note: could not parse XML content of request"];
284 return [NSException exceptionWithHTTPStatus:400 /* bad request */
285 reason:@"could not parse XML content of request"];
289 [[self performSOAPAction:SOAPAction document:dom inContext:_ctx]retain];
292 return [resultObject autorelease];
297 - (NSString *)loggingPrefix {
298 return @"[obj-soap-dispatch]";
300 - (BOOL)isDebuggingEnabled {
304 @end /* SoObjectSOAPDispatcher */
306 @implementation SoSOAPRenderer
308 // TODO: render exceptions as SOAP faults
309 // TODO: maybe support rendering of DOM trees? (should be supported by default)
310 // TODO: maybe some "schema" driven rendering
312 @end /* SoSOAPRenderer */