a8080d9dc717d5ea1fe9218419ed9913ed900906
[jelmer/samba4-debian.git] / jsonrpc / request.esp
1 <%
2
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  */
11
12 /*
13  * This is a simple JSON-RPC server.
14  */
15
16 /* Bring in the date class */
17 jsonrpc_include("jsondate.esp");
18
19 /* bring the string functions into the global frame */
20 string_init(global);
21
22 /* Bring the system functions into the global frame */
23 sys_init(global);
24
25 function printf()
26 {
27         print(vsprintf(arguments));
28 }
29
30
31 /* KLUDGE... */
32 form = new Array();
33 server = new Array();
34 request = new Array();
35 /* ...KLUDGE */
36
37 /*
38  * All of our manipulation of JSON RPC methods will be through this object.
39  * Each class of methods will assign to here, and all of the constants will
40  * also be in this object.
41  */
42 jsonrpc = new Object();
43 jsonrpc.Constant = new Object();
44 jsonrpc.Constant.ErrorOrigin = new Object(); /* error origins */
45 jsonrpc.Constant.ErrorCode = new Object();   /* server-generated error codes */
46 jsonrpc.method = new Object();       /* methods available in requested class */
47
48
49 /*
50  * ScriptTransport constants
51  */
52 jsonrpc.Constant.ScriptTransport = new Object();
53 jsonrpc.Constant.ScriptTransport.NotInUse        = -1;
54
55
56 /*
57  * JSON-RPC error origin constants
58  */
59 jsonrpc.Constant.ErrorOrigin.Server              = 1;
60 jsonrpc.Constant.ErrorOrigin.Application         = 2;
61 jsonrpc.Constant.ErrorOrigin.Transport           = 3;
62 jsonrpc.Constant.ErrorOrigin.Client              = 4;
63
64
65
66 /*
67  * JSON-RPC server-generated error code constants
68  */
69
70 /**
71  * Error code, value 0: Unknown Error
72  *
73  * The default error code, used only when no specific error code is passed to
74  * the JsonRpcError constructor.  This code should generally not be used.
75  */
76 jsonrpc.Constant.ErrorCode.Unknown               = 0;
77
78 /**
79  * Error code, value 1: Illegal Service
80  *
81  * The service name contains illegal characters or is otherwise deemed
82  * unacceptable to the JSON-RPC server.
83  */
84 jsonrpc.Constant.ErrorCode.IllegalService        = 1;
85
86 /**
87  * Error code, value 2: Service Not Found
88  *
89  * The requested service does not exist at the JSON-RPC server.
90  */
91 jsonrpc.Constant.ErrorCode.ServiceNotFound       = 2;
92
93 /**
94  * Error code, value 3: Class Not Found
95  *
96  * If the JSON-RPC server divides service methods into subsets (classes), this
97  * indicates that the specified class was not found.  This is slightly more
98  * detailed than "Method Not Found", but that error would always also be legal
99  * (and true) whenever this one is returned. (Not used in this implementation)
100  */
101 jsonrpc.Constant.ErrorCode.ClassNotFound         = 3; // not used in this implementation
102
103 /**
104  * Error code, value 4: Method Not Found
105  *
106  * The method specified in the request is not found in the requested service.
107  */
108 jsonrpc.Constant.ErrorCode.MethodNotFound        = 4;
109
110 /**
111  * Error code, value 5: Parameter Mismatch
112  *
113  * If a method discovers that the parameters (arguments) provided to it do not
114  * match the requisite types for the method's parameters, it should return
115  * this error code to indicate so to the caller.
116  */
117 jsonrpc.Constant.ErrorCode.PaameterMismatch      = 5;
118
119 /**
120  * Error code, value 6: Permission Denied
121  *
122  * A JSON-RPC service provider can require authentication, and that
123  * authentication can be implemented such the method takes authentication
124  * parameters, or such that a method or class of methods requires prior
125  * authentication.  If the caller has not properly authenticated to use the
126  * requested method, this error code is returned.
127  */
128 jsonrpc.Constant.ErrorCode.PermissionDenied      = 6;
129
130 /*
131  * Error code, value 7: Unexpected Output
132  *
133  * The called method illegally generated output to the browser, which would
134  * have preceeded the JSON-RPC data.
135  */
136 jsonrpc.Constant.ErrorCode.UnexpectedOutput      = 7;
137
138
139
140
141
142
143
144 function sendReply(reply, scriptTransportId)
145 {
146     /* If not using ScriptTransport... */
147     if (scriptTransportId == jsonrpc.Constant.ScriptTransport.NotInUse)
148     {
149         /* ... then just output the reply. */
150         printf(reply);
151     }
152     else
153     {
154         /* Otherwise, we need to add a call to a qooxdoo-specific function */
155         reply =
156             "qx.io.remote.ScriptTransport._requestFinished(" +
157             scriptTransportId + ", " + reply +
158             ");";
159         printf(reply);
160     }
161 }
162
163
164 /*
165  * class Json
166  *
167  * This class provides the JSON encoder and decoder, and some utility
168  * functions.
169  */
170 Json = new Object();
171
172 /* KLUDGE... */
173 function _jsonDecode(s)
174 {
175     var o = new Object();
176     o.id = 23;
177     o.service = "qooxdoo.test";
178     o.method = "echo";
179     o.params = new Array(1);
180     o.params[0] = "hello world";
181     return o;
182 }
183 /* ...KLUDGE */
184
185 Json.decode = _jsonDecode;
186
187 /* KLUDGE... */
188 function _jsonEncode(o)
189 {
190     return "{ result: \"hello world\" }"
191 }
192 /* ...KLUDGE */
193
194 Json.encode = _jsonEncode;
195
196 function _jsonValidRequest(req)
197 {
198     if (req == undefined)
199     {
200         return false;
201     }
202
203     if (req.id == undefined)
204     {
205         return false;
206     }
207
208     if (req.service == undefined)
209     {
210         return false;
211     }
212
213     if (req.method == undefined)
214     {
215         return false;
216     }
217
218     if (req.params == undefined)
219     {
220         return false;
221     }
222
223     return true;
224 }
225 jsonrpc.validRequest = _jsonValidRequest;
226 _jsonValidRequest = null;
227
228 /*
229  * class JsonRpcError
230  *
231  * This class allows service methods to easily provide error information for
232  * return via JSON-RPC.
233  */
234 function _JsonRpcError_create(origin, code, message)
235 {
236     var o = new Object();
237
238     o.data = new Object();
239     o.data.origin = origin;
240     o.data.code = code;
241     o.data.message = message;
242     o.scriptTransportId = jsonrpc.Constant.ScriptTransport.NotInUse;
243     o.__type = "_JsonRpcError";
244
245     function _origin(origin)
246     {
247         this.origin = origin;
248     }
249     o.setOrigin = _origin;
250
251     function _setError(code, message)
252     {
253         this.code = code;
254         this.message = message;
255     }
256     o.setError = _setError;
257
258     function _setId(id)
259     {
260         this.id = id;
261     }
262     o.setId = _setId;
263
264     function _setScriptTransportId(id)
265     {
266         this.scriptTransportId = id;
267     }
268     o.setScriptTransportId = _setScriptTransportId;
269
270     function _Send()
271     {
272         var error = this;
273         var id = this.id;
274         var ret = new Array(2);
275         ret.error = this.data;
276         ret.id = this.id;
277         sendReply(Json.encode(ret), this.scriptTransportId);
278     }
279     o.Send = _Send;
280
281     return o;
282 }
283
284 jsonrpc.createError = _JsonRpcError_create;
285 _JsonRpcError_create = null;
286
287 /*
288  * 'input' is the user-provided json-encoded request
289  * 'jsonInput' is that request, decoded into its object form
290  */
291 var input;
292 var jsonInput = null;
293
294 /* Allocate a generic error object */
295 error = jsonrpc.createError(jsonrpc.Constant.ErrorOrigin.Server,
296                             jsonrpc.Constant.ErrorCode.Unknown,
297                             "Unknown error");
298
299 /* Assume (default) we're not using ScriptTransport */
300 scriptTransportId = jsonrpc.Constant.ScriptTransport.NotInUse;
301
302 /* What type of request did we receive? */
303 if (server["REQUEST_METHOD"] == "POST" &&
304     server["CONENT_TYPE"] == "text/json")
305 {
306     /* We found literal POSTed json-rpc data (we hope) */
307     input = request["POST_DATA"];
308     jsonInput = Json.decode(input);
309 }
310 else if (server["REQUEST_METHOD"] == "GET" &&
311          form["_ScriptTransport_id"] != undefined &&
312          form["_ScriptTransport_data"] != undefined)
313 {
314     /* We have what looks like a valid ScriptTransport request */
315     scriptTransportId = form["_ScriptTransport_id"];
316     error.setScriptTransportId(scriptTransportId);
317     input = form["_ScriptTransport_data"];
318     jsonInput = Json.decode(input);
319 }
320
321 /* KLUDGE... */
322 jsonInput = Json.decode(input);
323 /* ...KLUDGE */
324
325 /* Ensure that this was a JSON-RPC service request */
326 if (! jsonrpc.validRequest(jsonInput))
327 {
328     /*
329      * This request was not issued with JSON-RPC so echo the error rather than
330      * issuing a JsonRpcError response.
331      */
332     printf("JSON-RPC request expected; service, method or params missing<br>");
333     return;
334 }
335
336 /*
337  * Ok, it looks like JSON-RPC, so we'll return an Error object if we encounter
338  * errors from here on out.
339  */
340 error.setId(jsonInput.id);
341
342 /* Service and method names may contain these characters */
343 var nameChars =
344     "_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
345
346 /* The first letter of service and method names must be a letter */
347 var nameFirstLetter =
348     "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
349
350 /*
351  * Ensure the method name is kosher.  A meethod name should be:
352  *
353  *   - first character is in [a-zA-Z] 
354  *   - other characters are in [_a-zA-Z0-9]
355  */
356
357 /* First check for legal characters */
358 if (strspn(jsonInput.method, nameChars) != strlen(jsonInput.method))
359 {
360     /* There's some illegal character in the service name */
361     error.setError(JsonRpcError.MethodNotFound,
362                    "Illegal character found in method name.");
363     error.Send();
364     return;
365 }
366
367 /* Now ensure that it begins with a letter */
368 if (strspn(substr(jsonInput.method, 0, 1), nameFirstLetter) != 1)
369 {
370     error.setError(jsonrpc.Constant.ErrorCode.MethodNotFound,
371                    "The method name does not begin with a letter");
372     error.Send();
373     return;
374 }
375
376 /*
377  * Ensure the requested service name is kosher.  A service name should be:
378  *
379  *   - a dot-separated sequences of strings; no adjacent dots
380  *   - first character of each string is in [a-zA-Z] 
381  *   - other characters are in [_a-zA-Z0-9]
382  */
383
384 /* First check for legal characters */
385 if (strspn(jsonInput.service, "." + nameChars) != strlen(jsonInput.service))
386 {
387     /* There's some illegal character in the service name */
388     error.setError(JsonRpcError.IllegalService,
389                    "Illegal character found in service name.");
390     error.Send();
391     return;
392 }
393
394 /*
395  * Now ensure there are no double dots.
396  *
397  * Frustration with ejs.  Result must be NULL, but we can't use the ===
398  * operator: strstr() === null so we have to use typeof.  If the result isn't
399  * null, then it'll be a number and therefore not type "pointer".
400  */
401 if (typeof(strstr(jsonInput.service, "..")) != "pointer")
402 {
403     error.setError(JsonRpcError.IllegalService,
404                    "Illegal use of two consecutive dots in service name");
405     error.Send();
406     return;
407 }
408
409 /* Explode the service name into its dot-separated parts */
410 var serviceComponents = split(".", jsonInput.service);
411
412 /* Ensure that each component begins with a letter */
413 for (var i = 0; i < serviceComponents.length; i++)
414 {
415     if (strspn(substr(serviceComponents[i], 0, 1), nameFirstLetter) != 1)
416     {
417         error.setError(jsonrpc.Constant.ErrorCode.IllegalService,
418                        "A service name component does not begin with a letter");
419         error.Send();
420         return;
421     }
422 }
423
424 /*
425  * Now replace all dots with slashes so we can locate the service script.  We
426  * also retain the split components of the path, as the class name of the
427  * service is the last component of the path.
428  */
429 var servicePath = join("/", serviceComponents) + ".esp";
430
431 /* Load the requested class */
432 if (jsonrpc_include(servicePath))
433 {
434     /* Couldn't find the requested service */
435     error.setError(jsonrpc.Constant.ErrorCode.ServiceNotFound,
436                    "Service class `" + servicePath + "` does not exist.");
437     error.Send();
438     return;
439 }
440
441 /*
442  * Find the requested method.
443  *
444  * What we really want to do here, and could do in any reasonable language,
445  * is:
446  *
447  *   method = jsonrpc.method[jsonInput.method];
448  *   if (method && typeof(method) == "function") ...
449  *
450  * The following completely unreasonable sequence of commands is because:
451  *
452  *  (a) ejs evaluates all OR'ed expressions even if an early one is false, and
453  *      bars on the typeof(method) call if method is undefined
454  *
455  *  (b) ejs does not allow comparing against the string "function"!!!  What
456  *      the hell is special about that particular string???
457  *
458  * E-gad.  What a mess.
459  */
460 var method = jsonrpc.method[jsonInput.method];
461 var valid = (method != undefined);
462 if (valid)
463 {
464     var type = typeof(method);
465     if (substr(type, 0, 1) != 'f' || substr(type, 1) != "unction")
466     {
467         valid = false;
468     }
469 }
470
471 if (! valid)
472 {
473     error.setError(jsonrpc.Constant.ErrorCode.MethodNotFound,
474                    "Method `" + method + "` not found.");
475     error.Send();
476     return;
477 }
478
479 /* Most errors from here on out will be Application-generated */
480 error.setOrigin(jsonrpc.Constant.ErrorOrigin.Application);
481
482 /* Call the requested method passing it the provided params */
483 var retval = method(jsonInput.params, error);
484
485 /* See if the result of the function was actually an error object */
486 var wasError = (retval["__type"] != undefined);
487 if (wasError)
488 {
489     wasError = retval.__type == "_JsonRpcError";
490 }
491 if (wasError)
492 {
493     /* Yup, it was.  Return the error */
494     retval.Send();
495     return;
496 }
497
498 /* Give 'em what they came for! */
499 var ret = new Object();
500 ret.result = retval;
501 ret.id = jsonInput.id;
502 sendReply(Json.encode(ret), scriptTransportId);
503 %>