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.
17 /* Bring in the json format/parse functions */
18 jsonrpc_include("json.esp");
20 /* Bring in the date class */
21 jsonrpc_include("jsondate.esp");
23 /* Load the authentication script */
24 jsonrpc_include("json_auth.esp");
27 /* bring the string functions into the global frame */
30 /* Bring the system functions into the global frame */
35 print(vsprintf(arguments));
40 * All of our manipulation of JSON RPC methods will be through this object.
41 * Each class of methods will assign to here, and all of the constants will
42 * also be in this object.
44 jsonrpc = new Object();
45 jsonrpc.Constant = new Object();
46 jsonrpc.Constant.ErrorOrigin = new Object(); /* error origins */
47 jsonrpc.Constant.ErrorCode = new Object(); /* server-generated error codes */
48 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 * This error is also used to indicate an illegal parameter value, in server
121 jsonrpc.Constant.ErrorCode.ParameterMismatch = 5;
124 * Error code, value 6: Permission Denied
126 * A JSON-RPC service provider can require authentication, and that
127 * authentication can be implemented such the method takes authentication
128 * parameters, or such that a method or class of methods requires prior
129 * authentication. If the caller has not properly authenticated to use the
130 * requested method, this error code is returned.
132 jsonrpc.Constant.ErrorCode.PermissionDenied = 6;
135 * Error code, value 7: Unexpected Output
137 * The called method illegally generated output to the browser, which would
138 * have preceeded the JSON-RPC data.
140 jsonrpc.Constant.ErrorCode.UnexpectedOutput = 7;
143 * Error code, value 8: Resource Error
145 * Too many resources were requested, a system limitation on the total number
146 * of resources has been reached, or a resource or resource id was misused.
148 jsonrpc.Constant.ErrorCode.ResourceError = 8;
154 function sendReply(reply, scriptTransportId)
156 /* If not using ScriptTransport... */
157 if (scriptTransportId == jsonrpc.Constant.ScriptTransport.NotInUse)
159 /* ... then just output the reply. */
164 /* Otherwise, we need to add a call to a qooxdoo-specific function */
166 "qx.io.remote.ScriptTransport._requestFinished(" +
167 scriptTransportId + ", " + reply +
174 function _jsonValidRequest(req)
176 if (req == undefined)
181 if (typeof(req) != "object")
186 if (req["id"] == undefined)
191 if (req["service"] == undefined)
196 if (req["method"] == undefined)
201 if (req["params"] == undefined)
208 jsonrpc.validRequest = _jsonValidRequest;
209 _jsonValidRequest = null;
214 * This class allows service methods to easily provide error information for
215 * return via JSON-RPC.
217 function _JsonRpcError_create(origin, code, message)
219 var o = new Object();
221 o.data = new Object();
222 o.data.origin = origin;
224 o.data.message = message;
225 o.scriptTransportId = jsonrpc.Constant.ScriptTransport.NotInUse;
226 o.__type = "_JsonRpcError";
228 function _origin(origin)
230 this.data.origin = origin;
232 o.setOrigin = _origin;
234 function _setError(code, message)
236 this.data.code = code;
237 this.data.message = message;
239 o.setError = _setError;
247 function _setScriptTransportId(id)
249 this.scriptTransportId = id;
251 o.setScriptTransportId = _setScriptTransportId;
257 var ret = new Object();
258 ret.error = this.data;
260 sendReply(Json.encode(ret), this.scriptTransportId);
267 jsonrpc.createError = _JsonRpcError_create;
268 _JsonRpcError_create = null;
271 * 'input' is the user-provided json-encoded request
272 * 'jsonInput' is that request, decoded into its object form
275 var jsonInput = null;
277 /* Allocate a generic error object */
278 error = jsonrpc.createError(jsonrpc.Constant.ErrorOrigin.Server,
279 jsonrpc.Constant.ErrorCode.Unknown,
282 /* Assume (default) we're not using ScriptTransport */
283 scriptTransportId = jsonrpc.Constant.ScriptTransport.NotInUse;
285 /* What type of request did we receive? */
286 if (request["REQUEST_METHOD"] == "POST" &&
287 request["CONTENT_TYPE"] == "text/json")
289 /* We found literal POSTed json-rpc data (we hope) */
290 input = request["POST_DATA"];
291 jsonInput = Json.decode(input);
293 else if (request["REQUEST_METHOD"] == "GET" &&
294 form["_ScriptTransport_id"] != undefined &&
295 form["_ScriptTransport_data"] != undefined)
297 /* We have what looks like a valid ScriptTransport request */
298 scriptTransportId = form["_ScriptTransport_id"];
299 error.setScriptTransportId(scriptTransportId);
300 input = form["_ScriptTransport_data"];
301 jsonInput = Json.decode(input);
304 /* Ensure that this was a JSON-RPC service request */
305 if (! jsonrpc.validRequest(jsonInput))
308 * This request was not issued with JSON-RPC so echo the error rather than
309 * issuing a JsonRpcError response.
311 write("JSON-RPC request expected; service, method or params missing<br>");
316 * Ok, it looks like JSON-RPC, so we'll return an Error object if we encounter
317 * errors from here on out.
319 error.setId(jsonInput.id);
321 /* Service and method names may contain these characters */
323 "_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
325 /* The first letter of service and method names must be a letter */
326 var nameFirstLetter =
327 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
330 * Ensure the method name is kosher. A meethod name should be:
332 * - first character is in [a-zA-Z]
333 * - other characters are in [_a-zA-Z0-9]
336 /* First check for legal characters */
337 if (strspn(jsonInput.method, nameChars) != strlen(jsonInput.method))
339 /* There's some illegal character in the service name */
340 error.setError(jsonrpc.Constant.ErrorCode.MethodNotFound,
341 "Illegal character found in method name.");
346 /* Now ensure that it begins with a letter */
347 if (strspn(substr(jsonInput.method, 0, 1), nameFirstLetter) != 1)
349 error.setError(jsonrpc.Constant.ErrorCode.MethodNotFound,
350 "The method name does not begin with a letter");
356 * Ensure the requested service name is kosher. A service name should be:
358 * - a dot-separated sequences of strings; no adjacent dots
359 * - first character of each string is in [a-zA-Z]
360 * - other characters are in [_a-zA-Z0-9]
363 /* First check for legal characters */
364 if (strspn(jsonInput.service, "." + nameChars) != strlen(jsonInput.service))
366 /* There's some illegal character in the service name */
367 error.setError(jsonrpc.Constant.ErrorCode.IllegalService,
368 "Illegal character found in service name.");
374 * Now ensure there are no double dots.
376 * Frustration with ejs. Result must be NULL, but we can't use the ===
377 * operator: strstr() === null so we have to use typeof. If the result isn't
378 * null, then it'll be a number and therefore not type "pointer".
380 if (typeof(strstr(jsonInput.service, "..")) != "pointer")
382 error.setError(jsonrpc.Constant.ErrorCode.IllegalService,
383 "Illegal use of two consecutive dots in service name");
388 /* Explode the service name into its dot-separated parts */
389 var serviceComponents = split(".", jsonInput.service);
391 /* Ensure that each component begins with a letter */
392 for (var i = 0; i < serviceComponents.length; i++)
394 if (strspn(substr(serviceComponents[i], 0, 1), nameFirstLetter) != 1)
396 error.setError(jsonrpc.Constant.ErrorCode.IllegalService,
397 "A service name component does not begin with a letter");
404 * Now replace all dots with slashes so we can locate the service script. We
405 * also retain the split components of the path, as the class name of the
406 * service is the last component of the path.
408 var servicePath = join("/", serviceComponents) + ".esp";
410 /* Load the requested class */
411 if (jsonrpc_include(servicePath))
413 /* Couldn't find the requested service */
414 error.setError(jsonrpc.Constant.ErrorCode.ServiceNotFound,
415 "Service class `" + servicePath + "` does not exist.");
421 * Find the requested method.
423 * What we really want to do here, and could do in any reasonable language,
426 * method = jsonrpc.method[jsonInput.method];
427 * if (method && typeof(method) == "function") ...
429 * The following completely unreasonable sequence of commands is because:
431 * (a) ejs evaluates all OR'ed expressions even if an early one is false, and
432 * barfs on the typeof(method) call if method is undefined
434 * (b) ejs does not allow comparing against the string "function"!!! What
435 * the hell is special about that particular string???
437 * E-gad. What a mess.
439 var method = jsonrpc.method[jsonInput.method];
440 var valid = (method != undefined);
443 var type = typeof(method);
444 if (substr(type, 0, 1) != 'f' || substr(type, 1) != "unction")
452 error.setError(jsonrpc.Constant.ErrorCode.MethodNotFound,
453 "Method `" + method + "` not found.");
458 /* Ensure the logged-in user is allowed to issue the requested method */
459 if (! json_authenticate(serviceComponents, jsonInput.method))
461 error.setError(jsonrpc.Constant.ErrorCode.PermissionDenied,
462 "Permission denied");
467 /* Most errors from here on out will be Application-generated */
468 error.setOrigin(jsonrpc.Constant.ErrorOrigin.Application);
470 /* Call the requested method passing it the provided params */
471 var retval = method(jsonInput.params, error);
473 /* See if the result of the function was actually an error object */
474 if (retval["__type"] == "_JsonRpcError")
476 /* Yup, it was. Return the error */
481 /* Give 'em what they came for! */
482 var ret = new Object();
484 ret.id = jsonInput.id;
485 sendReply(Json.encode(ret), scriptTransportId);