Monday, November 08, 2010 at 12:02 AM.

system.verbs.builtins.betty.rpc.server

on server (xmlText, adrParamTable) { //called from the RPC2 responder on an incoming request
	<<Change notes:
		<<5/24/06; 8:15:45 AM by DW
			<<Handle multiple domains as described here...
				<<http://geeks.opml.org/2006/05/24#a865
		<<01/31/02; 2:13:15 PM by JES
			<<Factored the security check by IP address to betty.rpc.checkClient, so the check can be used for both XML-RPC and SOAP.
		<<04/27/01; 7:58:15 PM by JES
			<<If a request doesn't have a <params> element, don't fault. Instead treat it as a call with no input parameters.
		<<Thu, 02 Dec 1999 at 5:17:37 PM by AR
			<<Disabled fldebug flag again.
		<<Wed, 04 Nov 1999 at 10:33:45 PM by AR
			<<If user.betty.prefs.flAllowByIpOnly is true, block XML-RPC requests from anyone except those IP addresses listed in user.betty.prefs.allowedIpAddresses
		<<Mon, 25 Oct 1999 at 11:07:19 PM by AR
			<<If user.betty.prefs.flKeepServerLog is true, we log all incoming requests to the rpcServer section of the daily log GDB.
		<<Mon, 14 Jun 1999 15:46:57 GMT by AR
			<<When walking the XML structure to extract the parameters, we now handle the case correctly when a parameter is a table and the next parameter is a scalar.
			<<If an RPC fault occurs, left angle brackets and ampersands in the error string are now properly encoded to ensure that the XML-RPC response is well-formed.
		<<Friday, July 17, 1998 at 12:57:38 PM by PBS
			<<XML header is now lowercase to conform to the XML spec.
			<<Turned off debugging; use a local table rather than a global table so the server is thread-safe.
	
	local (fldebug = false, flFault = false, securitylist = {});
	
	betty.init ();
	if not defined (user.betty.prefs.flServerDomains) {
		user.betty.prefs.flServerDomains = false};
	
	local (adrhandlerstable = @user.betty.rpcHandlers);
	if user.betty.prefs.flServerDomains { //5/24/06 by DW
		if defined (user.betty.domains) {
			if defined (adrparamtable^.host) {
				local (host = adrparamtable^.host);
				if host contains ":" { //remove the port designator
					host = string.nthfield (host, ":", 1)};
				local (adrtable = @user.betty.domains.[host]);
				if defined (adrtable^) {
					adrhandlerstable = adrtable}}}};
	
	local (flLog = user.betty.prefs.flKeepServerLog);
	
	local (xmlResponse = "", indentlevel = 1);
	on add (s) {
		xmlResponse = xmlResponse + string.filledString ("\t", indentlevel) + s + "\r\n"};
	
	on addToRpcLog (errorNum = nil) { //AR 10/25/1999
		local (adrHitTable = log.addToGuestDatabase ("rpcServer", adrParamTable^.client, true));
		adrHitTable^.time = clock.now ();
		adrHitTable^.threads = Frontier.countThreads ();
		adrHitTable^.sizeIn = sizeof (adrParamTable^.requestBody);
		adrHitTable^.sizeOut = sizeof (xmlResponse);
		if errorNum != nil {
			adrHitTable^.faultCode= errorNum};
		if procedureName != nil {
			adrHitTable^.methodName= procedureName};
		if defined (adrParamTable^.host) {
			adrHitTable^.host = adrParamTable^.host};
		if defined (adrParamTable^.requestHeaders.["User-Agent"]) {
			adrHitTable^.agent = adrParamTable^.requestHeaders.["User-Agent"]};
		adrHitTable^.ticks = clock.ticks () - adrParamTable^.stats.requestProcessingStarted};
	on rpcFault (errorstring, errornum) {
		flFault = true;
		add ("<fault>"); indentlevel++;
		add ("<value>"); indentlevel++;
		add ("<struct>"); indentlevel++;
		
		add ("<member>"); indentlevel++;
		add ("<name>faultCode</name>");
		add ("<value>"); indentlevel++;
		add ("<int>" + errornum + "</int>");
		add ("</value>"); indentlevel--;
		add ("</member>"); indentlevel--;
		
		bundle { //AR 6/14/1999: make sure errorstring is encoded
			errorstring = string.replaceAll (errorstring, "&", "&");
			errorstring = string.replaceAll (errorstring, "<", "<")};
		
		add ("<member>"); indentlevel++;
		add ("<name>faultString</name>");
		add ("<value>"); indentlevel++;
		add ("<string>" + errorstring + "</string>");
		add ("</value>"); indentlevel--;
		add ("</member>"); indentlevel--;
		
		add ("</struct>"); indentlevel--;
		add ("</value>"); indentlevel++;
		add ("</fault>"); indentlevel--;
		
		if flLog {
			try {
				addToRpcLog (errornum)}}};
	
	local (procedureName, paramlist);
	
	bundle { //AR 11/04/1999: check for rpc request blocking
		if not betty.rpc.checkClient (adrParamTable) { //1/31/02 JES: factored for use for both XML-RPC and SOAP
			rpcFault ("Access not allowed from " + adrParamTable^.client + ".", betty.rpc.errorCodes.securityFault)}};
		<<if defined (user.betty.prefs.flAllowByIpOnly)
			<<if user.betty.prefs.flAllowByIpOnly
				<<local (flBlocked = true)
				<<local (okIpList = user.betty.prefs.allowedIpAddresses)
				<<if typeOf (okIpList) == stringType
					<<It's a comma-delimited string: create a local list from that string.
					<<local (s = okIpList)
					<<okIpList = {}
					<<local (i)
					<<for i = 1 to string.countFields (s, ',')
						<<local (oneIpAddress = string.nthField (s, ',', i))
						<<oneIpAddress = string.trimWhiteSpace (oneIpAddress)
						<<if oneIpAddress != ""
							<<okIpList = okIpList + oneIpAddress
				<<local (oneIp)
				<<for oneIp in okIpList
					<<if string.trimWhiteSpace (oneIp) != ""
						<<if adrParamTable^.client == oneIp
							<<flblocked = false
							<<break
				<<if flblocked
					<<rpcFault ("Access not allowed from " + adrParamTable^.client + ".", betty.rpc.errorCodes.securityFault)
	
	if (not flFault) { //get the procedure name and the paramlist
		on parseXML () {
			local (xtable);
			try { //parse the XML into a table
				xml.compile (xmlText, @xtable)}
			else {
				rpcFault (tryError, betty.rpc.errorcodes.xmlFormatError);
				return};
			
			if fldebug {
				scratchpad.rpcTable = xtable};
			
			try { //set the procedure name and fill the params list by walking the XML structure
				paramlist = {};
				adrTable = @xtable; //PBS 7/17/98
				local (adrcall = xml.getAddress (adrtable, "methodCall"));
				procedureName = xml.getValue (adrcall, "methodName");
				local (adrparams, flParams = true);
				try { //04/27/2001 JES: if there's no <params> element, getting its address will fail
					adrparams = xml.getAddress (adrcall, "params")}
				else { //no params
					flParams = false};
				if flParams {
					local (xmlparamlist = xml.getAddressList (adrparams, "param"));
					local (param, name, adrvalue, val);
					for param in xmlparamlist {
						adrvalue = xml.getAddress (param, "value");
						if typeof (adrvalue^) == tabletype {
							xml.coercions.structToFrontierValue (@adrvalue^ [1], @val)}
						else {
							table.assign (@val, adrvalue^)}; //AR 6/14/1999
						paramlist = paramlist + {val}}}}
			else {
				rpcFault (tryError, betty.rpc.errorcodes.illegalCallStructureError);
				return};
			if fldebug {
				try {delete (@scratchpad.rpcParams)};
				scratchpad.rpcParams = paramlist}};
		parseXML ()};
	if (not flFault) { //call the script, put result in xmlResponse
		on callrpcScript () {
			local (nomad, adrscript);
			try { //locate the script
				local (s = procedureName, name);
				nomad = adrhandlerstable;
				loop {
					if typeOf (nomad^) == tabletype {
						local (adr = @nomad^.["#security"]);
						if defined (adr^) {
							securitylist = securitylist + {adr}}};
					if sizeof (s) == 0 {
						break};
					name = string.nthField (s, '.', 1);
					nomad = @nomad^.[name];
					if typeof (nomad^) == addresstype {
						nomad = nomad^};
					s = string.delete (s, 1, sizeof (name) + 1)};
				adrscript = nomad}
			else {
				rpcFault (tryError, betty.rpc.errorcodes.scriptAddressError);
				return};
			
			try { //check in with the #security scripts
				local (adr);
				for adr in securitylist {
					callScript (string (adr), {}, adrParamTable)}}
			else {
				rpcFault (tryError, betty.rpc.errorcodes.securityFault);
				return};
			
			local (scriptResponse);
			try { //call the script
				scriptResponse = callScript (string (adrscript), paramlist, adrParamTable)}
			else {
				rpcFault (tryError, betty.rpc.errorcodes.scriptError);
				return};
			
			local (encodedString);
			try { //encode the response
				encodedString = xml.coercions.frontierValueToTaggedText (@scriptResponse, 1)}
			else {
				rpcFault (tryError, betty.rpc.errorcodes.encodingFailure);
				return};
			
			try { //add the encoded string to the returned XML text
				add ("<params>"); indentlevel++;
				add ("<param>"); indentlevel++;
				add ("<value>" + encodedString + "</value>");
				add ("</param>"); indentlevel--;
				add ("</params>"); indentlevel--}
			else {
				rpcFault (tryError, betty.rpc.errorcodes.addReturnedValueError);
				return}};
		callrpcScript ()};
	
	local (pre = "<?xml version=\"1.0\"?>\r\n<methodResponse>\r\n");
	local (post = "\t</methodResponse>\r\n");
	xmlResponse = pre + xmlResponse + post;
	if fldebug {
		table.assign (@scratchpad.xmlResponse, xmlResponse)};
	if flLog and not flFault { //AR 10/25/1999: don't log twice
		try {
			addToRpcLog ()}};
	
	return (xmlResponse)}



This listing is for code that runs in the OPML Editor environment. I created these listings because I wanted the search engines to index it, so that when I want to look up something in my codebase I don't have to use the much slower search functionality in my object database. Dave Winer.