1 /**
2  * Simple XML-RPC serializer.
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <vladimir@thecybershadow.net>
12  */
13 
14 module ae.utils.xmlrpc;
15 
16 import std.string;
17 import std.conv;
18 import std.exception;
19 
20 import ae.utils.xml;
21 
22 XmlNode valueToXml(T)(T v)
23 {
24 	static if (is(T : string))
25 		return (new XmlNode(XmlNodeType.Node, "value"))
26 			.addChild((new XmlNode(XmlNodeType.Node, "string"))
27 				.addChild(new XmlNode(XmlNodeType.Text, v))
28 			);
29 	else
30 	static if (is(T : long))
31 		return (new XmlNode(XmlNodeType.Node, "value"))
32 			.addChild((new XmlNode(XmlNodeType.Node, "integer"))
33 				.addChild(new XmlNode(XmlNodeType.Text, to!string(v)))
34 			);
35 	else
36 	static if (is(T U : U[]))
37 	{
38 		XmlNode data = new XmlNode(XmlNodeType.Node, "data");
39 		foreach (item; v)
40 			data.addChild(valueToXml(item));
41 		return (new XmlNode(XmlNodeType.Node, "value"))
42 			.addChild((new XmlNode(XmlNodeType.Node, "array"))
43 				.addChild(data)
44 			);
45 	}
46 	else
47 	static if (is(T==struct))
48 	{
49 		XmlNode s = new XmlNode(XmlNodeType.Node, "struct");
50 		foreach (i, field; v.tupleof)
51 			s.addChild((new XmlNode(XmlNodeType.Node, "member"))
52 				.addChild((new XmlNode(XmlNodeType.Node, "name"))
53 					.addChild(new XmlNode(XmlNodeType.Text, v.tupleof[i].stringof[2..$]))
54 				)
55 				.addChild(valueToXml(field))
56 			);
57 		return (new XmlNode(XmlNodeType.Node, "value"))
58 			.addChild(s);
59 	}
60 	else
61 	static if (is(typeof(T.keys)) && is(typeof(T.values)))
62 	{
63 		XmlNode s = new XmlNode(XmlNodeType.Node, "struct");
64 		foreach (key, value; v)
65 			s.addChild((new XmlNode(XmlNodeType.Node, "member"))
66 				.addChild((new XmlNode(XmlNodeType.Node, "name"))
67 					.addChild(new XmlNode(XmlNodeType.Text, key))
68 				)
69 				.addChild(valueToXml(value))
70 			);
71 		return (new XmlNode(XmlNodeType.Node, "value"))
72 			.addChild(s);
73 	}
74 	else
75 	static if (is(typeof(*v)))
76 		return valueToXml(*v);
77 	else
78 		static assert(0, "Can't encode " ~ T.stringof ~ " to XML-RPC");
79 }
80 
81 XmlDocument formatXmlRpcCall(T...)(string methodName, T params)
82 {
83 	auto paramsNode = new XmlNode(XmlNodeType.Node, "params");
84 
85 	foreach (param; params)
86 		paramsNode.addChild((new XmlNode(XmlNodeType.Node, "param"))
87 			.addChild(valueToXml(param))
88 		);
89 
90 	auto doc =
91 		(new XmlDocument())
92 		.addChild((new XmlNode(XmlNodeType.Meta, "xml"))
93 			.addAttribute("version", "1.0")
94 		)
95 		.addChild((new XmlNode(XmlNodeType.Node, "methodCall"))
96 			.addChild((new XmlNode(XmlNodeType.Node, "methodName"))
97 				.addChild(new XmlNode(XmlNodeType.Text, methodName))
98 			)
99 			.addChild(paramsNode)
100 		);
101 	return cast(XmlDocument)doc;
102 }
103 
104 T parseXmlValue(T)(XmlNode value)
105 {
106 	enforce(value.type==XmlNodeType.Node && value.tag == "value", "Expected <value> node");
107 	enforce(value.children.length==1, "Expected one <value> child");
108 	XmlNode typeNode = value[0];
109 	enforce(typeNode.type==XmlNodeType.Node, "Expected <value> child to be XML node");
110 	string valueType = typeNode.tag;
111 
112 	static if (is(T : string))
113 	{
114 		enforce(valueType == "string", "Expected <string>");
115 		enforce(typeNode.children.length==1, "Expected one <string> child");
116 		XmlNode contentNode = typeNode[0];
117 		enforce(contentNode.type==XmlNodeType.Text, "Expected <string> child to be text node");
118 		return contentNode.tag;
119 	}
120 	else
121 	static if (is(T == bool))
122 	{
123 		enforce(valueType == "boolean", "Expected <boolean>");
124 		enforce(typeNode.children.length==1, "Expected one <boolean> child");
125 		XmlNode contentNode = typeNode[0];
126 		enforce(contentNode.type==XmlNodeType.Text, "Expected <boolean> child to be text node");
127 		enforce(contentNode.tag == "0" || contentNode.tag == "1", "Expected <boolean> child to be 0 or 1");
128 		return contentNode.tag == "1";
129 	}
130 	else
131 	static if (is(T : long))
132 	{
133 		enforce(valueType == "integer" || valueType == "int" || valueType == "i4", "Expected <integer> or <int> or <i4>");
134 		enforce(typeNode.children.length==1, "Expected one <integer> child");
135 		XmlNode contentNode = typeNode[0];
136 		enforce(contentNode.type==XmlNodeType.Text, "Expected <integer> child to be text node");
137 		string s = contentNode.tag;
138 		static if (is(T==byte))
139 			return to!byte(s);
140 		else
141 		static if (is(T==ubyte))
142 			return to!ubyte(s);
143 		else
144 		static if (is(T==short))
145 			return to!short(s);
146 		else
147 		static if (is(T==ushort))
148 			return to!ushort(s);
149 		else
150 		static if (is(T==int))
151 			return to!int(s);
152 		else
153 		static if (is(T==uint))
154 			return to!uint(s);
155 		else
156 		static if (is(T==long))
157 			return to!long(s);
158 		else
159 		static if (is(T==ulong))
160 			return to!ulong(s);
161 		else
162 			static assert(0, "Don't know how to parse numerical type " ~ T.stringof);
163 	}
164 	else
165 	static if (is(T == double))
166 	{
167 		enforce(valueType == "double", "Expected <double>");
168 		enforce(typeNode.children.length==1, "Expected one <double> child");
169 		XmlNode contentNode = typeNode[0];
170 		enforce(contentNode.type==XmlNodeType.Text, "Expected <double> child to be text node");
171 		string s = contentNode.tag;
172 		return to!double(s);
173 	}
174 	else
175 	static if (is(T U : U[]))
176 	{
177 		enforce(valueType == "array", "Expected <array>");
178 		enforce(typeNode.children.length==1, "Expected one <array> child");
179 		XmlNode dataNode = typeNode[0];
180 		enforce(dataNode.type==XmlNodeType.Node && dataNode.tag == "data", "Expected <data>");
181 		T result = new U[dataNode.children.length];
182 		foreach (i, child; dataNode.children)
183 			result[i] = parseXmlValue!(U)(child);
184 		return result;
185 	}
186 	else
187 	static if (is(T==struct))
188 	{
189 		enforce(valueType == "struct", "Expected <struct>");
190 		T v;
191 		foreach (memberNode; typeNode.children)
192 		{
193 			enforce(memberNode.type==XmlNodeType.Node && memberNode.tag == "member", "Expected <member>");
194 			enforce(memberNode.children.length == 2, "Expected 2 <member> children");
195 			auto nameNode = memberNode[0];
196 			enforce(nameNode.type==XmlNodeType.Node && nameNode.tag == "name", "Expected <name>");
197 			enforce(nameNode.children.length == 1, "Expected one <name> child");
198 			XmlNode contentNode = nameNode[0];
199 			enforce(contentNode.type==XmlNodeType.Text, "Expected <name> child to be text node");
200 			string memberName = contentNode.tag;
201 
202 			bool found;
203 			foreach (i, field; v.tupleof)
204 				if (v.tupleof[i].stringof[2..$] == memberName)
205 				{
206 					v.tupleof[i] = parseXmlValue!(typeof(v.tupleof[i]))(memberNode[1]);
207 					found = true;
208 					break;
209 				}
210 			enforce(found, "Unknown field " ~ memberName);
211 		}
212 		return v;
213 	}
214 	else
215 		static assert(0, "Can't decode " ~ T.stringof ~ " from XML-RPC");
216 }
217 
218 class XmlRpcException : Exception
219 {
220 	int faultCode;
221 	string faultString;
222 
223 	this(int faultCode, string faultString)
224 	{
225 		this.faultCode = faultCode;
226 		this.faultString = faultString;
227 		super(format("XML-RPC error %d (%s)", faultCode, faultString));
228 	}
229 }
230 
231 T parseXmlRpcResponse(T)(XmlDocument doc)
232 {
233 	auto methodResponse = doc["methodResponse"];
234 	auto fault = methodResponse.findChild("fault");
235 	if (fault)
236 	{
237 		struct Fault
238 		{
239 			int faultCode;
240 			string faultString;
241 		}
242 		auto details = parseXmlValue!(Fault)(fault["value"]);
243 		throw new XmlRpcException(details.faultCode, details.faultString);
244 	}
245 
246 	auto params = methodResponse.findChild("params");
247 	enforce(params.children.length==1, "Only one response parameter supported");
248 	return parseXmlValue!(T)(params["param"][0]);
249 }