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