1/*
2 * Copyright (c) 2004 Apple Computer, Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24/*
25 * dotMacXmlRpc.cpp - perform authenticated XMLRPC invocation.
26 *					  Based upon example provided by Scott Ryder.
27 */
28
29#include <Availability.h>
30//#include <CoreServices/../Frameworks/CFNetwork.framework/Headers/CFHTTPMessage.h>
31#include <CFNetwork/CFHTTPMessage.h>
32#include <CoreServices/../Frameworks/OSServices.framework/Headers/WSMethodInvocation.h>
33#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
34
35#include <stdio.h>
36#include "dotMacXmlRpc.h"
37#include "dotMacTpMutils.h"
38
39#if		XML_DEBUG
40#define xmlDebug(args...)   printf(args)
41#else
42#define xmlDebug(args...)
43#endif
44
45/* dump contents of XMLRPC response dictionary */
46#if		DICTIONARY_DEBUG
47#define RESPONSE_DICTIONARY_DEBUG   0
48#else
49#define RESPONSE_DICTIONARY_DEBUG   0
50#endif
51static UInt32 getHTTPStatusCodeFromWSInvokationResponse(CFDictionaryRef response)
52{
53    CFHTTPMessageRef message =
54		(CFHTTPMessageRef)CFDictionaryGetValue(response, kWSHTTPResponseMessage);
55    UInt32 responseCode;
56
57    if (message != NULL) {
58        responseCode = CFHTTPMessageGetResponseStatusCode(message);
59        message = NULL;
60    } else {
61		xmlDebug("getHTTPStatusCode: no HTTP status\n");
62        responseCode = 500;
63    }
64    return responseCode;
65}
66
67static Boolean addAuthenticationToWSInvokation(
68	WSMethodInvocationRef wsRef,
69	CFStringRef username,
70	CFStringRef password,
71	CFDictionaryRef response)
72{
73    CFHTTPMessageRef message =
74		(CFHTTPMessageRef)CFDictionaryGetValue(response, kWSHTTPResponseMessage);
75    CFHTTPMessageRef outgoingMessage = NULL;
76
77    if (message != NULL) {
78        CFURLRef theURL = CFHTTPMessageCopyRequestURL(message);
79        if (theURL != NULL) {
80            //Move the stuff that counts into our new message
81            outgoingMessage = CFHTTPMessageCreateRequest(NULL, CFSTR("POST"),
82				theURL, kCFHTTPVersion1_1);
83            CFRelease(theURL);
84        }
85    }
86
87    Boolean successful = FALSE;
88
89    if ((message != NULL) && (outgoingMessage != NULL)) {
90        successful =  CFHTTPMessageAddAuthentication(outgoingMessage, message,
91			username, password, kCFHTTPAuthenticationSchemeDigest, FALSE);
92    }
93
94    if (successful) {
95		/* FIXME kWSHTTPMessage isn't exported by WebServicesCore
96		 * I can't find the source but there is a string in the binary that
97		 * looks like this, with the leading '/' */
98        WSMethodInvocationSetProperty(wsRef, CFSTR("/kWSHTTPMessage"), outgoingMessage);
99    }
100
101    if (outgoingMessage) {
102		CFRelease(outgoingMessage);
103	}
104    return successful;
105}
106
107
108OSStatus performAuthenticatedXMLRPC(
109	CFURLRef		theURL,
110	CFStringRef		theMethod,
111	CFDictionaryRef argDict,
112	CFArrayRef		argOrder,
113	CFStringRef		userName,
114	CFStringRef		password,
115	CFDictionaryRef *resultDict,	// RETURNED on success
116	uint32_t		*httpErrStatus)	// possibly RETURNED on error (403, 500, etc.)
117{
118    CFDictionaryRef result = NULL;
119	bool done = false;
120	OSStatus ortn = -1;		// must set before return
121	*httpErrStatus = 0;
122
123    /*
124	 * Create a WebServices Invocation
125	 */
126    WSMethodInvocationRef wsRef = WSMethodInvocationCreate(theURL, theMethod,
127		kWSXMLRPCProtocol);
128    WSMethodInvocationSetParameters(wsRef, argDict, argOrder);
129
130    for(unsigned attempt=0; attempt<2; attempt++) {
131		xmlDebug("***************************************************************************\n");
132		xmlDebug("performAuthenticatedXMLRPC: WSMethodInvocationInvoke (attempt %u)\n", attempt);
133		xmlDebug("***************************************************************************\n");
134        CFDictionaryRef response = WSMethodInvocationInvoke(wsRef);
135
136        /*
137		 * Since we can't reuse the Invocation dump it as we have our response
138		 */
139        if (wsRef) {
140            CFRelease(wsRef);
141            wsRef = NULL;
142        }
143
144        #if XML_DEBUG
145        logCFstr("performAuthenticatedXMLRPC: userName:", userName);
146        #endif
147
148        if (WSMethodResultIsFault(response)) {
149            CFStringRef errorMsg = (CFStringRef)CFDictionaryGetValue(
150				response, kWSFaultString);
151            if (errorMsg != NULL) {
152				#if XML_DEBUG
153				logCFstr("performAuthenticatedXMLRPC: errorMsg", errorMsg);
154				#endif
155                UInt32 HTTPResponseCode =
156					getHTTPStatusCodeFromWSInvokationResponse(response);
157				/* only try authentication once */
158				xmlDebug("performAuthenticatedXMLRPC: HTTP status %lu\n", HTTPResponseCode);
159                if ((HTTPResponseCode == 401) && (attempt == 0)) {
160                    wsRef = WSMethodInvocationCreate(theURL, theMethod, kWSXMLRPCProtocol);
161                    WSMethodInvocationSetParameters(wsRef, argDict, argOrder);
162                    Boolean successful =  addAuthenticationToWSInvokation(wsRef,
163						userName, password, response);
164                    if (!successful) {
165						xmlDebug("performAuthenticatedXMLRPC: unable to add authentication\n");
166						ortn = ioErr;
167						done = TRUE;
168                    }
169					xmlDebug("performAuthenticatedXMLRPC: retrying with auth\n");
170					/* else go one more time with authenticated invocation */
171                } else {
172					/* other fatal HTTP error */
173
174                    /*
175					 * From Scott Ryder:
176					 *
177					 * All other HTTP errs (eg 403 or 500) could / should be handled here
178                     * this could include adding proxy support
179					 *
180					 * FIXME does this mean that this code won't work through proxies?
181					 */
182					xmlDebug("performAuthenticatedXMLRPC: fatal RPC error\n");
183					*httpErrStatus = HTTPResponseCode;
184					ortn = dotMacHttpStatToOs(HTTPResponseCode);
185                    done = TRUE;
186                }
187            }
188			else {
189				xmlDebug("performAuthenticatedXMLRPC: fault with no fault string!\n");
190				ortn = ioErr;
191				done = TRUE;
192            }
193        }   /* fault */
194		else {
195			/* success */
196			xmlDebug("performAuthenticatedXMLRPC: success\n");
197            result = CFDictionaryCreateCopy(NULL,
198				(CFDictionaryRef)CFDictionaryGetValue(
199					response, kWSMethodInvocationResult));
200			ortn = noErr;
201			done = true;
202        }
203        if((response != NULL) /*&& done*/) {
204			#if RESPONSE_DICTIONARY_DEBUG
205			dumpDictionary("XMLRPC response", response);
206			#endif
207            CFRelease(response);
208		}
209		if(done) {
210			break;
211		}
212    }
213	if(result != NULL) {
214		*resultDict = result;
215	}
216    return ortn;
217}
218
219
220