r19052: clarify what actually does what
[Samba.git] / jsonrpc / request.esp
blob984ee4663e044264a9bbcc455a08abe0aac0f36d
1 <%
3 /*
4  * Copyright:
5  *   (C) 2006 by Derrell Lipman
6  *       All rights reserved
7  *
8  * License:
9  *   LGPL 2.1: http://creativecommons.org/licenses/LGPL/2.1/
10  */
13  * This is a simple JSON-RPC server.
14  */
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 /* bring the string functions into the global frame */
23 string_init(global);
25 /* Bring the system functions into the global frame */
26 sys_init(global);
28 function printf()
30         print(vsprintf(arguments));
35  * All of our manipulation of JSON RPC methods will be through this object.
36  * Each class of methods will assign to here, and all of the constants will
37  * also be in this object.
38  */
39 jsonrpc = new Object();
40 jsonrpc.Constant = new Object();
41 jsonrpc.Constant.ErrorOrigin = new Object(); /* error origins */
42 jsonrpc.Constant.ErrorCode = new Object();   /* server-generated error codes */
43 jsonrpc.method = new Object();       /* methods available in requested class */
47  * ScriptTransport constants
48  */
49 jsonrpc.Constant.ScriptTransport = new Object();
50 jsonrpc.Constant.ScriptTransport.NotInUse        = -1;
54  * JSON-RPC error origin constants
55  */
56 jsonrpc.Constant.ErrorOrigin.Server              = 1;
57 jsonrpc.Constant.ErrorOrigin.Application         = 2;
58 jsonrpc.Constant.ErrorOrigin.Transport           = 3;
59 jsonrpc.Constant.ErrorOrigin.Client              = 4;
64  * JSON-RPC server-generated error code constants
65  */
67 /**
68  * Error code, value 0: Unknown Error
69  *
70  * The default error code, used only when no specific error code is passed to
71  * the JsonRpcError constructor.  This code should generally not be used.
72  */
73 jsonrpc.Constant.ErrorCode.Unknown               = 0;
75 /**
76  * Error code, value 1: Illegal Service
77  *
78  * The service name contains illegal characters or is otherwise deemed
79  * unacceptable to the JSON-RPC server.
80  */
81 jsonrpc.Constant.ErrorCode.IllegalService        = 1;
83 /**
84  * Error code, value 2: Service Not Found
85  *
86  * The requested service does not exist at the JSON-RPC server.
87  */
88 jsonrpc.Constant.ErrorCode.ServiceNotFound       = 2;
90 /**
91  * Error code, value 3: Class Not Found
92  *
93  * If the JSON-RPC server divides service methods into subsets (classes), this
94  * indicates that the specified class was not found.  This is slightly more
95  * detailed than "Method Not Found", but that error would always also be legal
96  * (and true) whenever this one is returned. (Not used in this implementation)
97  */
98 jsonrpc.Constant.ErrorCode.ClassNotFound         = 3; // not used in this implementation
101  * Error code, value 4: Method Not Found
103  * The method specified in the request is not found in the requested service.
104  */
105 jsonrpc.Constant.ErrorCode.MethodNotFound        = 4;
108  * Error code, value 5: Parameter Mismatch
110  * If a method discovers that the parameters (arguments) provided to it do not
111  * match the requisite types for the method's parameters, it should return
112  * this error code to indicate so to the caller.
113  */
114 jsonrpc.Constant.ErrorCode.PaameterMismatch      = 5;
117  * Error code, value 6: Permission Denied
119  * A JSON-RPC service provider can require authentication, and that
120  * authentication can be implemented such the method takes authentication
121  * parameters, or such that a method or class of methods requires prior
122  * authentication.  If the caller has not properly authenticated to use the
123  * requested method, this error code is returned.
124  */
125 jsonrpc.Constant.ErrorCode.PermissionDenied      = 6;
128  * Error code, value 7: Unexpected Output
130  * The called method illegally generated output to the browser, which would
131  * have preceeded the JSON-RPC data.
132  */
133 jsonrpc.Constant.ErrorCode.UnexpectedOutput      = 7;
141 function sendReply(reply, scriptTransportId)
143     /* If not using ScriptTransport... */
144     if (scriptTransportId == jsonrpc.Constant.ScriptTransport.NotInUse)
145     {
146         /* ... then just output the reply. */
147         write(reply);
148     }
149     else
150     {
151         /* Otherwise, we need to add a call to a qooxdoo-specific function */
152         reply =
153             "qx.io.remote.ScriptTransport._requestFinished(" +
154             scriptTransportId + ", " + reply +
155             ");";
156         write(reply);
157     }
161 function _jsonValidRequest(req)
163     if (req == undefined)
164     {
165         return false;
166     }
168     if (req.id == undefined)
169     {
170         return false;
171     }
173     if (req.service == undefined)
174     {
175         return false;
176     }
178     if (req.method == undefined)
179     {
180         return false;
181     }
183     if (req.params == undefined)
184     {
185         return false;
186     }
188     return true;
190 jsonrpc.validRequest = _jsonValidRequest;
191 _jsonValidRequest = null;
194  * class JsonRpcError
196  * This class allows service methods to easily provide error information for
197  * return via JSON-RPC.
198  */
199 function _JsonRpcError_create(origin, code, message)
201     var o = new Object();
203     o.data = new Object();
204     o.data.origin = origin;
205     o.data.code = code;
206     o.data.message = message;
207     o.scriptTransportId = jsonrpc.Constant.ScriptTransport.NotInUse;
208     o.__type = "_JsonRpcError";
210     function _origin(origin)
211     {
212         this.origin = origin;
213     }
214     o.setOrigin = _origin;
216     function _setError(code, message)
217     {
218         this.code = code;
219         this.message = message;
220     }
221     o.setError = _setError;
223     function _setId(id)
224     {
225         this.id = id;
226     }
227     o.setId = _setId;
229     function _setScriptTransportId(id)
230     {
231         this.scriptTransportId = id;
232     }
233     o.setScriptTransportId = _setScriptTransportId;
235     function _Send()
236     {
237         var error = this;
238         var id = this.id;
239         var ret = new Array(2);
240         ret.error = this.data;
241         ret.id = this.id;
242         sendReply(Json.encode(ret), this.scriptTransportId);
243     }
244     o.Send = _Send;
246     return o;
249 jsonrpc.createError = _JsonRpcError_create;
250 _JsonRpcError_create = null;
253  * 'input' is the user-provided json-encoded request
254  * 'jsonInput' is that request, decoded into its object form
255  */
256 var input;
257 var jsonInput = null;
259 /* Allocate a generic error object */
260 error = jsonrpc.createError(jsonrpc.Constant.ErrorOrigin.Server,
261                             jsonrpc.Constant.ErrorCode.Unknown,
262                             "Unknown error");
264 /* Assume (default) we're not using ScriptTransport */
265 scriptTransportId = jsonrpc.Constant.ScriptTransport.NotInUse;
267 /* What type of request did we receive? */
268 if (request["REQUEST_METHOD"] == "POST" &&
269     request["CONTENT_TYPE"] == "text/json")
271     /* We found literal POSTed json-rpc data (we hope) */
272     input = request["POST_DATA"];
273     jsonInput = Json.decode(input);
275 else if (request["REQUEST_METHOD"] == "GET" &&
276          form["_ScriptTransport_id"] != undefined &&
277          form["_ScriptTransport_data"] != undefined)
279     /* We have what looks like a valid ScriptTransport request */
280     scriptTransportId = form["_ScriptTransport_id"];
281     error.setScriptTransportId(scriptTransportId);
282     input = form["_ScriptTransport_data"];
283     jsonInput = Json.decode(input);
286 /* Ensure that this was a JSON-RPC service request */
287 if (! jsonrpc.validRequest(jsonInput))
289     /*
290      * This request was not issued with JSON-RPC so echo the error rather than
291      * issuing a JsonRpcError response.
292      */
293     write("JSON-RPC request expected; service, method or params missing<br>");
294     return;
298  * Ok, it looks like JSON-RPC, so we'll return an Error object if we encounter
299  * errors from here on out.
300  */
301 error.setId(jsonInput.id);
303 /* Service and method names may contain these characters */
304 var nameChars =
305     "_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
307 /* The first letter of service and method names must be a letter */
308 var nameFirstLetter =
309     "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
312  * Ensure the method name is kosher.  A meethod name should be:
314  *   - first character is in [a-zA-Z] 
315  *   - other characters are in [_a-zA-Z0-9]
316  */
318 /* First check for legal characters */
319 if (strspn(jsonInput.method, nameChars) != strlen(jsonInput.method))
321     /* There's some illegal character in the service name */
322     error.setError(JsonRpcError.MethodNotFound,
323                    "Illegal character found in method name.");
324     error.Send();
325     return;
328 /* Now ensure that it begins with a letter */
329 if (strspn(substr(jsonInput.method, 0, 1), nameFirstLetter) != 1)
331     error.setError(jsonrpc.Constant.ErrorCode.MethodNotFound,
332                    "The method name does not begin with a letter");
333     error.Send();
334     return;
338  * Ensure the requested service name is kosher.  A service name should be:
340  *   - a dot-separated sequences of strings; no adjacent dots
341  *   - first character of each string is in [a-zA-Z] 
342  *   - other characters are in [_a-zA-Z0-9]
343  */
345 /* First check for legal characters */
346 if (strspn(jsonInput.service, "." + nameChars) != strlen(jsonInput.service))
348     /* There's some illegal character in the service name */
349     error.setError(JsonRpcError.IllegalService,
350                    "Illegal character found in service name.");
351     error.Send();
352     return;
356  * Now ensure there are no double dots.
358  * Frustration with ejs.  Result must be NULL, but we can't use the ===
359  * operator: strstr() === null so we have to use typeof.  If the result isn't
360  * null, then it'll be a number and therefore not type "pointer".
361  */
362 if (typeof(strstr(jsonInput.service, "..")) != "pointer")
364     error.setError(JsonRpcError.IllegalService,
365                    "Illegal use of two consecutive dots in service name");
366     error.Send();
367     return;
370 /* Explode the service name into its dot-separated parts */
371 var serviceComponents = split(".", jsonInput.service);
373 /* Ensure that each component begins with a letter */
374 for (var i = 0; i < serviceComponents.length; i++)
376     if (strspn(substr(serviceComponents[i], 0, 1), nameFirstLetter) != 1)
377     {
378         error.setError(jsonrpc.Constant.ErrorCode.IllegalService,
379                        "A service name component does not begin with a letter");
380         error.Send();
381         return;
382     }
386  * Now replace all dots with slashes so we can locate the service script.  We
387  * also retain the split components of the path, as the class name of the
388  * service is the last component of the path.
389  */
390 var servicePath = join("/", serviceComponents) + ".esp";
392 /* Load the requested class */
393 if (jsonrpc_include(servicePath))
395     /* Couldn't find the requested service */
396     error.setError(jsonrpc.Constant.ErrorCode.ServiceNotFound,
397                    "Service class `" + servicePath + "` does not exist.");
398     error.Send();
399     return;
403  * Find the requested method.
405  * What we really want to do here, and could do in any reasonable language,
406  * is:
408  *   method = jsonrpc.method[jsonInput.method];
409  *   if (method && typeof(method) == "function") ...
411  * The following completely unreasonable sequence of commands is because:
413  *  (a) ejs evaluates all OR'ed expressions even if an early one is false, and
414  *      bars on the typeof(method) call if method is undefined
416  *  (b) ejs does not allow comparing against the string "function"!!!  What
417  *      the hell is special about that particular string???
419  * E-gad.  What a mess.
420  */
421 var method = jsonrpc.method[jsonInput.method];
422 var valid = (method != undefined);
423 if (valid)
425     var type = typeof(method);
426     if (substr(type, 0, 1) != 'f' || substr(type, 1) != "unction")
427     {
428         valid = false;
429     }
432 if (! valid)
434     error.setError(jsonrpc.Constant.ErrorCode.MethodNotFound,
435                    "Method `" + method + "` not found.");
436     error.Send();
437     return;
440 /* Most errors from here on out will be Application-generated */
441 error.setOrigin(jsonrpc.Constant.ErrorOrigin.Application);
443 /* Call the requested method passing it the provided params */
444 var retval = method(jsonInput.params, error);
446 /* See if the result of the function was actually an error object */
447 var wasError = (retval["__type"] != undefined);
448 if (wasError)
450     wasError = retval.__type == "_JsonRpcError";
452 if (wasError)
454     /* Yup, it was.  Return the error */
455     retval.Send();
456     return;
459 /* Give 'em what they came for! */
460 var ret = new Object();
461 ret.result = retval;
462 ret.id = jsonInput.id;
463 sendReply(Json.encode(ret), scriptTransportId);