5 * (C) 2006 by Derrell Lipman
9 * LGPL 2.1: http://creativecommons.org/licenses/LGPL/2.1/
13 * This is a simple JSON-RPC server.
16 /* Bring in the json format/parse functions */
17 jsonrpc_include("json.esp");
19 /* Bring in the date class */
20 jsonrpc_include("jsondate.esp");
22 /* Load the authentication script */
23 jsonrpc_include("json_auth.esp");
26 /* bring the string functions into the global frame */
29 /* Bring the system functions into the global frame */
34 print(vsprintf(arguments));
39 * All of our manipulation of JSON RPC methods will be through this object.
40 * Each class of methods will assign to here, and all of the constants will
41 * also be in this object.
43 jsonrpc = new Object();
44 jsonrpc.Constant = new Object();
45 jsonrpc.Constant.ErrorOrigin = new Object(); /* error origins */
46 jsonrpc.Constant.ErrorCode = new Object(); /* server-generated error codes */
47 jsonrpc.method = new Object(); /* methods available in requested class */
51 * ScriptTransport constants
53 jsonrpc.Constant.ScriptTransport = new Object();
54 jsonrpc.Constant.ScriptTransport.NotInUse = -1;
58 * JSON-RPC error origin constants
60 jsonrpc.Constant.ErrorOrigin.Server = 1;
61 jsonrpc.Constant.ErrorOrigin.Application = 2;
62 jsonrpc.Constant.ErrorOrigin.Transport = 3;
63 jsonrpc.Constant.ErrorOrigin.Client = 4;
68 * JSON-RPC server-generated error code constants
72 * Error code, value 0: Unknown Error
74 * The default error code, used only when no specific error code is passed to
75 * the JsonRpcError constructor. This code should generally not be used.
77 jsonrpc.Constant.ErrorCode.Unknown = 0;
80 * Error code, value 1: Illegal Service
82 * The service name contains illegal characters or is otherwise deemed
83 * unacceptable to the JSON-RPC server.
85 jsonrpc.Constant.ErrorCode.IllegalService = 1;
88 * Error code, value 2: Service Not Found
90 * The requested service does not exist at the JSON-RPC server.
92 jsonrpc.Constant.ErrorCode.ServiceNotFound = 2;
95 * Error code, value 3: Class Not Found
97 * If the JSON-RPC server divides service methods into subsets (classes), this
98 * indicates that the specified class was not found. This is slightly more
99 * detailed than "Method Not Found", but that error would always also be legal
100 * (and true) whenever this one is returned. (Not used in this implementation)
102 jsonrpc.Constant.ErrorCode.ClassNotFound = 3; // not used in this implementation
105 * Error code, value 4: Method Not Found
107 * The method specified in the request is not found in the requested service.
109 jsonrpc.Constant.ErrorCode.MethodNotFound = 4;
112 * Error code, value 5: Parameter Mismatch
114 * If a method discovers that the parameters (arguments) provided to it do not
115 * match the requisite types for the method's parameters, it should return
116 * this error code to indicate so to the caller.
118 jsonrpc.Constant.ErrorCode.PaameterMismatch = 5;
121 * Error code, value 6: Permission Denied
123 * A JSON-RPC service provider can require authentication, and that
124 * authentication can be implemented such the method takes authentication
125 * parameters, or such that a method or class of methods requires prior
126 * authentication. If the caller has not properly authenticated to use the
127 * requested method, this error code is returned.
129 jsonrpc.Constant.ErrorCode.PermissionDenied = 6;
132 * Error code, value 7: Unexpected Output
134 * The called method illegally generated output to the browser, which would
135 * have preceeded the JSON-RPC data.
137 jsonrpc.Constant.ErrorCode.UnexpectedOutput = 7;
145 function sendReply(reply, scriptTransportId)
147 /* If not using ScriptTransport... */
148 if (scriptTransportId == jsonrpc.Constant.ScriptTransport.NotInUse)
150 /* ... then just output the reply. */
155 /* Otherwise, we need to add a call to a qooxdoo-specific function */
157 "qx.io.remote.ScriptTransport._requestFinished(" +
158 scriptTransportId + ", " + reply +
165 function _jsonValidRequest(req)
167 if (req == undefined)
172 if (typeof(req) != "object")
177 if (req["id"] == undefined)
182 if (req["service"] == undefined)
187 if (req["method"] == undefined)
192 if (req["params"] == undefined)
199 jsonrpc.validRequest = _jsonValidRequest;
200 _jsonValidRequest = null;
205 * This class allows service methods to easily provide error information for
206 * return via JSON-RPC.
208 function _JsonRpcError_create(origin, code, message)
210 var o = new Object();
212 o.data = new Object();
213 o.data.origin = origin;
215 o.data.message = message;
216 o.scriptTransportId = jsonrpc.Constant.ScriptTransport.NotInUse;
217 o.__type = "_JsonRpcError";
219 function _origin(origin)
221 this.data.origin = origin;
223 o.setOrigin = _origin;
225 function _setError(code, message)
227 this.data.code = code;
228 this.data.message = message;
230 o.setError = _setError;
238 function _setScriptTransportId(id)
240 this.scriptTransportId = id;
242 o.setScriptTransportId = _setScriptTransportId;
248 var ret = new Object();
249 ret.error = this.data;
251 sendReply(Json.encode(ret), this.scriptTransportId);
258 jsonrpc.createError = _JsonRpcError_create;
259 _JsonRpcError_create = null;
262 * 'input' is the user-provided json-encoded request
263 * 'jsonInput' is that request, decoded into its object form
266 var jsonInput = null;
268 /* Allocate a generic error object */
269 error = jsonrpc.createError(jsonrpc.Constant.ErrorOrigin.Server,
270 jsonrpc.Constant.ErrorCode.Unknown,
273 /* Assume (default) we're not using ScriptTransport */
274 scriptTransportId = jsonrpc.Constant.ScriptTransport.NotInUse;
276 /* What type of request did we receive? */
277 if (request["REQUEST_METHOD"] == "POST" &&
278 request["CONTENT_TYPE"] == "text/json")
280 /* We found literal POSTed json-rpc data (we hope) */
281 input = request["POST_DATA"];
282 jsonInput = Json.decode(input);
284 else if (request["REQUEST_METHOD"] == "GET" &&
285 form["_ScriptTransport_id"] != undefined &&
286 form["_ScriptTransport_data"] != undefined)
288 /* We have what looks like a valid ScriptTransport request */
289 scriptTransportId = form["_ScriptTransport_id"];
290 error.setScriptTransportId(scriptTransportId);
291 input = form["_ScriptTransport_data"];
292 jsonInput = Json.decode(input);
295 /* Ensure that this was a JSON-RPC service request */
296 if (! jsonrpc.validRequest(jsonInput))
299 * This request was not issued with JSON-RPC so echo the error rather than
300 * issuing a JsonRpcError response.
302 write("JSON-RPC request expected; service, method or params missing<br>");
307 * Ok, it looks like JSON-RPC, so we'll return an Error object if we encounter
308 * errors from here on out.
310 error.setId(jsonInput.id);
312 /* Service and method names may contain these characters */
314 "_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
316 /* The first letter of service and method names must be a letter */
317 var nameFirstLetter =
318 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
321 * Ensure the method name is kosher. A meethod name should be:
323 * - first character is in [a-zA-Z]
324 * - other characters are in [_a-zA-Z0-9]
327 /* First check for legal characters */
328 if (strspn(jsonInput.method, nameChars) != strlen(jsonInput.method))
330 /* There's some illegal character in the service name */
331 error.setError(JsonRpcError.MethodNotFound,
332 "Illegal character found in method name.");
337 /* Now ensure that it begins with a letter */
338 if (strspn(substr(jsonInput.method, 0, 1), nameFirstLetter) != 1)
340 error.setError(jsonrpc.Constant.ErrorCode.MethodNotFound,
341 "The method name does not begin with a letter");
347 * Ensure the requested service name is kosher. A service name should be:
349 * - a dot-separated sequences of strings; no adjacent dots
350 * - first character of each string is in [a-zA-Z]
351 * - other characters are in [_a-zA-Z0-9]
354 /* First check for legal characters */
355 if (strspn(jsonInput.service, "." + nameChars) != strlen(jsonInput.service))
357 /* There's some illegal character in the service name */
358 error.setError(JsonRpcError.IllegalService,
359 "Illegal character found in service name.");
365 * Now ensure there are no double dots.
367 * Frustration with ejs. Result must be NULL, but we can't use the ===
368 * operator: strstr() === null so we have to use typeof. If the result isn't
369 * null, then it'll be a number and therefore not type "pointer".
371 if (typeof(strstr(jsonInput.service, "..")) != "pointer")
373 error.setError(JsonRpcError.IllegalService,
374 "Illegal use of two consecutive dots in service name");
379 /* Explode the service name into its dot-separated parts */
380 var serviceComponents = split(".", jsonInput.service);
382 /* Ensure that each component begins with a letter */
383 for (var i = 0; i < serviceComponents.length; i++)
385 if (strspn(substr(serviceComponents[i], 0, 1), nameFirstLetter) != 1)
387 error.setError(jsonrpc.Constant.ErrorCode.IllegalService,
388 "A service name component does not begin with a letter");
395 * Now replace all dots with slashes so we can locate the service script. We
396 * also retain the split components of the path, as the class name of the
397 * service is the last component of the path.
399 var servicePath = join("/", serviceComponents) + ".esp";
401 /* Load the requested class */
402 if (jsonrpc_include(servicePath))
404 /* Couldn't find the requested service */
405 error.setError(jsonrpc.Constant.ErrorCode.ServiceNotFound,
406 "Service class `" + servicePath + "` does not exist.");
412 * Find the requested method.
414 * What we really want to do here, and could do in any reasonable language,
417 * method = jsonrpc.method[jsonInput.method];
418 * if (method && typeof(method) == "function") ...
420 * The following completely unreasonable sequence of commands is because:
422 * (a) ejs evaluates all OR'ed expressions even if an early one is false, and
423 * barfs on the typeof(method) call if method is undefined
425 * (b) ejs does not allow comparing against the string "function"!!! What
426 * the hell is special about that particular string???
428 * E-gad. What a mess.
430 var method = jsonrpc.method[jsonInput.method];
431 var valid = (method != undefined);
434 var type = typeof(method);
435 if (substr(type, 0, 1) != 'f' || substr(type, 1) != "unction")
443 error.setError(jsonrpc.Constant.ErrorCode.MethodNotFound,
444 "Method `" + method + "` not found.");
449 /* Ensure the logged-in user is allowed to issue the requested method */
450 if (! json_authenticate(serviceComponents, method))
452 error.setError(jsonrpc.Constant.ErrorCode.PermissionDenied,
453 "Permission denied");
458 /* Most errors from here on out will be Application-generated */
459 error.setOrigin(jsonrpc.Constant.ErrorOrigin.Application);
461 /* Call the requested method passing it the provided params */
462 var retval = method(jsonInput.params, error);
464 /* See if the result of the function was actually an error object */
465 if (retval["__type"] == "_JsonRpcError")
467 /* Yup, it was. Return the error */
472 /* Give 'em what they came for! */
473 var ret = new Object();
475 ret.id = jsonInput.id;
476 sendReply(Json.encode(ret), scriptTransportId);