r19138: add authentication capability; fix typos
[Samba/ekacnet.git] / jsonrpc / request.esp
blob1cd22a71a84af06bd456be4e8d728bee6ca190e8
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 /* Load the authentication script */
23 jsonrpc_include("json_auth.esp");
26 /* bring the string functions into the global frame */
27 string_init(global);
29 /* Bring the system functions into the global frame */
30 sys_init(global);
32 function printf()
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.
42  */
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
52  */
53 jsonrpc.Constant.ScriptTransport = new Object();
54 jsonrpc.Constant.ScriptTransport.NotInUse        = -1;
58  * JSON-RPC error origin constants
59  */
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
69  */
71 /**
72  * Error code, value 0: Unknown Error
73  *
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.
76  */
77 jsonrpc.Constant.ErrorCode.Unknown               = 0;
79 /**
80  * Error code, value 1: Illegal Service
81  *
82  * The service name contains illegal characters or is otherwise deemed
83  * unacceptable to the JSON-RPC server.
84  */
85 jsonrpc.Constant.ErrorCode.IllegalService        = 1;
87 /**
88  * Error code, value 2: Service Not Found
89  *
90  * The requested service does not exist at the JSON-RPC server.
91  */
92 jsonrpc.Constant.ErrorCode.ServiceNotFound       = 2;
94 /**
95  * Error code, value 3: Class Not Found
96  *
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)
101  */
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.
108  */
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.
117  */
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.
128  */
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.
136  */
137 jsonrpc.Constant.ErrorCode.UnexpectedOutput      = 7;
145 function sendReply(reply, scriptTransportId)
147     /* If not using ScriptTransport... */
148     if (scriptTransportId == jsonrpc.Constant.ScriptTransport.NotInUse)
149     {
150         /* ... then just output the reply. */
151         write(reply);
152     }
153     else
154     {
155         /* Otherwise, we need to add a call to a qooxdoo-specific function */
156         reply =
157             "qx.io.remote.ScriptTransport._requestFinished(" +
158             scriptTransportId + ", " + reply +
159             ");";
160         write(reply);
161     }
165 function _jsonValidRequest(req)
167     if (req == undefined)
168     {
169         return false;
170     }
172     if (typeof(req) != "object")
173     {
174         return false;
175     }
177     if (req["id"] == undefined)
178     {
179         return false;
180     }
182     if (req["service"] == undefined)
183     {
184         return false;
185     }
187     if (req["method"] == undefined)
188     {
189         return false;
190     }
192     if (req["params"] == undefined)
193     {
194         return false;
195     }
197     return true;
199 jsonrpc.validRequest = _jsonValidRequest;
200 _jsonValidRequest = null;
203  * class JsonRpcError
205  * This class allows service methods to easily provide error information for
206  * return via JSON-RPC.
207  */
208 function _JsonRpcError_create(origin, code, message)
210     var o = new Object();
212     o.data = new Object();
213     o.data.origin = origin;
214     o.data.code = code;
215     o.data.message = message;
216     o.scriptTransportId = jsonrpc.Constant.ScriptTransport.NotInUse;
217     o.__type = "_JsonRpcError";
219     function _origin(origin)
220     {
221         this.data.origin = origin;
222     }
223     o.setOrigin = _origin;
225     function _setError(code, message)
226     {
227         this.data.code = code;
228         this.data.message = message;
229     }
230     o.setError = _setError;
232     function _setId(id)
233     {
234         this.id = id;
235     }
236     o.setId = _setId;
238     function _setScriptTransportId(id)
239     {
240         this.scriptTransportId = id;
241     }
242     o.setScriptTransportId = _setScriptTransportId;
244     function _Send()
245     {
246         var error = this;
247         var id = this.id;
248         var ret = new Object();
249         ret.error = this.data;
250         ret.id = this.id;
251         sendReply(Json.encode(ret), this.scriptTransportId);
252     }
253     o.Send = _Send;
255     return o;
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
264  */
265 var input;
266 var jsonInput = null;
268 /* Allocate a generic error object */
269 error = jsonrpc.createError(jsonrpc.Constant.ErrorOrigin.Server,
270                             jsonrpc.Constant.ErrorCode.Unknown,
271                             "Unknown error");
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))
298     /*
299      * This request was not issued with JSON-RPC so echo the error rather than
300      * issuing a JsonRpcError response.
301      */
302     write("JSON-RPC request expected; service, method or params missing<br>");
303     return;
307  * Ok, it looks like JSON-RPC, so we'll return an Error object if we encounter
308  * errors from here on out.
309  */
310 error.setId(jsonInput.id);
312 /* Service and method names may contain these characters */
313 var nameChars =
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]
325  */
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.");
333     error.Send();
334     return;
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");
342     error.Send();
343     return;
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]
352  */
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.");
360     error.Send();
361     return;
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".
370  */
371 if (typeof(strstr(jsonInput.service, "..")) != "pointer")
373     error.setError(JsonRpcError.IllegalService,
374                    "Illegal use of two consecutive dots in service name");
375     error.Send();
376     return;
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)
386     {
387         error.setError(jsonrpc.Constant.ErrorCode.IllegalService,
388                        "A service name component does not begin with a letter");
389         error.Send();
390         return;
391     }
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.
398  */
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.");
407     error.Send();
408     return;
412  * Find the requested method.
414  * What we really want to do here, and could do in any reasonable language,
415  * is:
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.
429  */
430 var method = jsonrpc.method[jsonInput.method];
431 var valid = (method != undefined);
432 if (valid)
434     var type = typeof(method);
435     if (substr(type, 0, 1) != 'f' || substr(type, 1) != "unction")
436     {
437         valid = false;
438     }
441 if (! valid)
443     error.setError(jsonrpc.Constant.ErrorCode.MethodNotFound,
444                    "Method `" + method + "` not found.");
445     error.Send();
446     return;
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");
454     error.Send();
455     return;
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 */
468     retval.Send();
469     return;
472 /* Give 'em what they came for! */
473 var ret = new Object();
474 ret.result = retval;
475 ret.id = jsonInput.id;
476 sendReply(Json.encode(ret), scriptTransportId);
479  * Local Variables:
480  * mode: c
481  * End:
482  */