1 /*
2  * The contents of this file are subject to the Mozilla Public
3  * License Version 1.1 (the "License"); you may not use this file
4  * except in compliance with the License. You may obtain a copy of
5  * the License at http://www.mozilla.org/MPL/
6  *
7  * Software distributed under the License is distributed on an "AS
8  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
9  * implied. See the License for the specific language governing
10  * rights and limitations under the License.
11  *
12  * The Original Code is the Sablotron XSLT Processor.
13  *
14  * The Initial Developer of the Original Code is Ginger Alliance Ltd.
15  * Portions created by Ginger Alliance are Copyright (C) 2000-2002
16  * Ginger Alliance Ltd. All Rights Reserved.
17  *
18  * Contributor(s): Han Qi
19  *
20  * Alternatively, the contents of this file may be used under the
21  * terms of the GNU General Public License Version 2 or later (the
22  * "GPL"), in which case the provisions of the GPL are applicable
23  * instead of those above.  If you wish to allow use of your
24  * version of this file only under the terms of the GPL and not to
25  * allow others to use your version of this file under the MPL,
26  * indicate your decision by deleting the provisions above and
27  * replace them with the notice and other provisions required by
28  * the GPL.  If you do not delete the provisions above, a recipient
29  * may use your version of this file under either the MPL or the
30  * GPL.
31  */
32 
33 #include "jsext.h"
34 
35 #ifdef ENABLE_JS
36 
37 #include "expr.h"
38 #include "error.h"
39 #include "context.h"
40 #include "guard.h"
41 #include "domprovider.h"
42 #include "jsdom.h"
43 
44 /************* extern constants ******************/
45 const char* theArrayProtoRoot = "_array_prototype_root_";
46 
47 /*************** JS error reporter ******************/
48 
SJSErrorReporter(JSContext * cx,const char * message,JSErrorReport * report)49 void SJSErrorReporter(JSContext *cx, const char *message,
50 		      JSErrorReport *report)
51 {
52   JSContextItem *item = (JSContextItem*)JS_GetContextPrivate(cx);
53   if (item) {
54     if (item -> errInfo.message) delete item -> errInfo.message;
55     if (item -> errInfo.token) delete item -> errInfo.token;
56     item -> errInfo.message = NULL;
57     item -> errInfo.token = NULL;
58     if (message) {
59       item -> errInfo.message = new char[strlen(message) + 1];
60       strcpy(item -> errInfo.message, message);
61     }
62     if (report -> tokenptr) {
63       item -> errInfo.token = new char[strlen(report -> tokenptr) + 1];
64       strcpy(item -> errInfo.token, report -> tokenptr);
65     }
66     item -> errInfo.line = report -> lineno;
67     item -> errInfo.errNumber = report -> errorNumber;
68   }
69 }
70 
71 /************************ MANAGER *******************/
72 
73 JSRuntime_Sab* gJSRuntime;
74 
getRuntime()75 JSRuntime_Sab* JSManager::getRuntime()
76 {
77   if (! gJSRuntime ) {
78     gJSRuntime = JS_NewRuntime(JS_RUNTIME_SIZE);
79   }
80   return gJSRuntime;
81 }
82 
createContext(int size)83 JSContext_Sab* JSManager::createContext(int size /* =JS_CONTEXT_SIZE */)
84 {
85   return JS_NewContext(getRuntime(), size);
86 }
87 
88 
finalize()89 void JSManager::finalize()
90 {
91   if ( gJSRuntime ) JS_DestroyRuntime(gJSRuntime);
92 }
93 
94 /******************************delegates etc. ******************/
95 
JS_METHOD(jsglobalLog)96 JS_METHOD(jsglobalLog) {
97   JSContextItem *item = (JSContextItem*)JS_GetContextPrivate(cx);
98   Situation *sit = item -> proc -> recallSituation();
99   JSString *str = JS_ValueToString(cx, argv[0]);
100   char *msg = JS_GetStringBytes(str);
101   sit -> message(MT_LOG, L_JS_LOG, (const char*) msg, (const char*)NULL);
102   return TRUE;
103 }
104 
105 /****************************************************************
106 
107 JSContexts
108 
109 ****************************************************************/
110 
JSContextItem(JSContext_Sab * cx_,Str & uri_,Processor * proc_)111 JSContextItem::JSContextItem(JSContext_Sab *cx_, Str &uri_, Processor *proc_)
112   : cx(cx_), uri(uri_), proc(proc_)
113 {
114   //cls = NULL;
115   errInfo.message = NULL;
116   errInfo.token = NULL;
117   node = NULL;
118   domex = NULL;
119   domimpl = NULL;
120   nlclass = NULL;
121   array_proto = NULL;
122 };
123 
~JSContextItem()124 JSContextItem::~JSContextItem()
125 {
126   names.freeall(FALSE);
127   if (errInfo.message) delete errInfo.message;
128   if (errInfo.token) delete errInfo.token;
129   if (cx)
130     {
131 #ifdef ENABLE_JS_THREADS
132       JS_ResumeRequest(cx);
133 #endif
134       if (array_proto) JS_RemoveRoot(cx, &array_proto);
135 #ifdef ENABLE_JS_THREADS
136       JS_EndRequest(cx);
137 #endif
138       JS_GC(cx);
139       JS_DestroyContext(cx);
140     }
141   //if (cls) delete cls;
142 }
143 
144 //js class for global object
145 JSClass sabGlobalClass = {
146   "global",
147   JSCLASS_HAS_PRIVATE,
148   JS_PropertyStub, JS_PropertyStub,
149   JS_PropertyStub,
150   JS_PropertyStub,
151   JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub,
152   JS_FinalizeStub, 0, 0, NULL, NULL, NULL, NULL, 0, 0
153 };
154 
find(Str uri,Bool canCreate)155 JSContextItem* JSContextList::find(Str uri, Bool canCreate /*TRUE*/)
156 {
157   JSContextItem *item = NULL;
158   for (int i = 0; i < number(); i++)
159     {
160       if ((*this)[i] -> uri == uri) item = (*this)[i];
161     }
162   if (!item && canCreate)
163     {
164       JSContext_Sab *cx;
165       cx = JSManager::createContext();
166 #ifdef ENABLE_JS_THREADS
167       JS_BeginRequest(cx);
168 #endif
169       item = new JSContextItem(cx, uri, proc);
170       append(item);
171       //initalize context
172       //JSClass *cls = SJSGetGlobalClass();
173       //item -> cls = cls;
174       JSObject *global = JS_NewObject(cx, &sabGlobalClass, NULL, NULL);
175       JS_InitStandardClasses(cx, global);
176       //set debugging globals
177       JS_DefineFunction(cx, global, "log", jsglobalLog, 1, 0);
178       //private data and error handling
179       JS_SetContextPrivate(cx, (void*)item);
180       JS_SetErrorReporter(cx, SJSErrorReporter);
181       //create persistent objects
182       jsdom_delegateDOM(cx);
183 #ifdef ENABLE_JS_THREADS
184       JS_Suspendrequest(cx);
185 #endif
186     }
187   return item;
188 }
189 
190 /************************************************************/
191 // JSExternalPrivate
192 /************************************************************/
193 
JSExternalPrivate(void * p_,void * v_)194 JSExternalPrivate::JSExternalPrivate(void *p_, void *v_)
195 : priv(p_), value(v_), refcnt(1)
196 {
197   if (value)
198     {
199       JS_AddRoot((JSContext*)priv, &value);
200     }
201 };
202 
~JSExternalPrivate()203 JSExternalPrivate::~JSExternalPrivate()
204 {
205   if (value)
206     {
207       JS_RemoveRoot((JSContext*)priv, &value);
208     }
209 }
210 
211 /**************** ordinary functions */
212 
sjs_instanceOf(JSContext_Sab * cx,JSObject_Sab * obj,JSClass_Sab * cls)213 Bool sjs_instanceOf(JSContext_Sab *cx, JSObject_Sab *obj, JSClass_Sab *cls)
214 {
215   JSObject *o = obj;
216   while (o)
217     {
218       JSClass *c = JS_GET_CLASS(cx, o);
219       if (c && c == cls) return TRUE;
220       o = JS_GetPrototype(cx, o);
221     }
222   return FALSE;
223 }
224 
225 //  const char* gClassName = "global";
226 
227 //  JSClass* SJSGetGlobalClass()
228 //  {
229 //  #ifdef HAVE_JSAPI_H
230 //    JSClass *ret = new JSClass();
231 //    ret -> name = gClassName;
232 //    ret -> flags = 0;
233 //    ret -> addProperty = JS_PropertyStub;
234 //    ret -> delProperty = JS_PropertyStub;
235 //    ret -> getProperty = JS_PropertyStub;
236 //    ret -> setProperty = JS_PropertyStub;
237 //    ret -> enumerate = JS_EnumerateStub;
238 //    ret -> resolve = JS_ResolveStub;
239 //    ret -> convert = JS_ConvertStub;
240 //    ret -> finalize = JS_FinalizeStub;
241 
242 //    return ret;
243 //  #else
244 //    return NULL;
245 //  #endif
246 //  }
247 
SJSEvaluate(JSContextItem & item,DStr & script)248 Bool SJSEvaluate(JSContextItem &item, DStr &script)
249 {
250   jsval rval;
251   JSBool status;
252   JSContext *cx = item.cx;
253 
254 #ifdef ENABLE_JS_THREADS
255   JS_ResumeRequest(cx);
256 #endif
257 
258   /*
259     int len = utf8StrLength((const char*) script);
260     wchar_t *ucscript = new wchar_t[len + 1];
261     utf8ToUtf16(ucscript, (const char*) script);
262 
263     status = JS_EvaluateUCScript(cx, JS_GetGlobalObject(cx),
264     (jschar*)ucscript, len,
265     "stylesheet", 0, &rval);
266     delete ucscript;
267   */
268 
269   char *scr = (char *) script;
270   status = JS_EvaluateScript(cx, JS_GetGlobalObject(cx),
271 			     scr, strlen(scr),
272 			     "stylesheet", 0, &rval);
273   if ( JS_IsExceptionPending(cx) )
274     {
275       JS_ClearPendingException(cx);
276     }
277 
278   if (status) {
279     //read all functions
280     JSIdArray *arr = JS_Enumerate(cx, JS_GetGlobalObject(cx));
281     item.names.freeall(FALSE);
282     if (arr) {
283       for (int i = 0; i < arr -> length; i++) {
284 	jsval propname;
285 	jsid id = arr -> vector[i];
286 	JS_IdToValue(cx, id, &propname);
287 
288 	JSString *str = JS_ValueToString(cx, propname);
289 	jsval prop;
290 	JS_GetProperty(cx, JS_GetGlobalObject(cx),
291 		       JS_GetStringBytes(str), &prop);
292 
293 	JSFunction *func = JS_ValueToFunction(cx, prop);
294 	if (func) {
295 	  const char* fname = JS_GetFunctionName(func);
296 	  item.names.append(new Str(fname));
297 	}
298       }
299       JS_DestroyIdArray(cx, arr);
300     }
301   }
302   else { //error occured
303 
304   }
305 
306 #ifdef ENABLE_JS_THREADS
307   JS_SuspendRequest(cx);
308 #endif
309 
310   return (Bool)status;
311 }
312 
313 /************** XSLTContext *******************/
314 
315 struct XSLTContextPrivate {
316   Context *ctx;
317   Situation *situa;
318 };
319 
ctxFinalize(JSContext * cx,JSObject * obj)320 void ctxFinalize(JSContext *cx, JSObject *obj)
321 {
322   XSLTContextPrivate *priv = (XSLTContextPrivate*)JS_GetPrivate(cx, obj);
323   if (priv) delete priv;
324 }
325 
JS_PROP(ctxGetPosition)326 JS_PROP(ctxGetPosition)
327 {
328   XSLTContextPrivate *priv = (XSLTContextPrivate*)JS_GetPrivate(cx, obj);
329   if (priv) {
330     *rval = INT_TO_JSVAL(priv -> ctx -> getPosition() + 1);
331     return TRUE;
332   }
333   else {
334     return FALSE;
335   };
336 }
337 
JS_PROP(ctxGetSize)338 JS_PROP(ctxGetSize)
339 {
340   XSLTContextPrivate *priv = (XSLTContextPrivate*)JS_GetPrivate(cx, obj);
341   if (priv) {
342     *rval = INT_TO_JSVAL(priv -> ctx -> getSize());
343     return TRUE;
344   }
345   else {
346     return FALSE;
347   };
348 }
349 
JS_PROP(ctxGetContextNode)350 JS_PROP(ctxGetContextNode)
351 {
352   XSLTContextPrivate *priv = (XSLTContextPrivate*)JS_GetPrivate(cx, obj);
353   if (priv) {
354     JSObject *obj = jsdom_wrapNode(*(priv->situa), cx, priv->ctx->current());
355     *rval = OBJECT_TO_JSVAL(obj);
356     return TRUE;
357   } else {
358     return FALSE;
359   }
360 }
361 
JS_PROP(ctxGetCurrentNode)362 JS_PROP(ctxGetCurrentNode)
363 {
364   XSLTContextPrivate *priv = (XSLTContextPrivate*)JS_GetPrivate(cx, obj);
365   if (priv) {
366     JSObject *obj = jsdom_wrapNode(*(priv->situa), cx,
367 				   priv->ctx->getCurrentNode());
368     *rval = OBJECT_TO_JSVAL(obj);
369     return TRUE;
370   } else {
371     return FALSE;
372   }
373 }
374 
JS_PROP(ctxGetOwnerDocument)375 JS_PROP(ctxGetOwnerDocument)
376 {
377   XSLTContextPrivate *priv = (XSLTContextPrivate*)JS_GetPrivate(cx, obj);
378   if (priv) {
379     Tree &tree = toV(priv->ctx->current())->getOwner();
380     JSObject *obj = jsdom_wrapNode(*(priv -> situa), cx,
381 				   &(tree.getRoot()));
382     *rval = OBJECT_TO_JSVAL(obj);
383     return TRUE;
384   } else {
385     return FALSE;
386   }
387 }
388 
JS_METHOD(ctxSystemProperty)389 JS_METHOD(ctxSystemProperty)
390 {
391   JSString *str =  JS_NewStringCopyN(cx, "not supported", 13);
392   *rval = STRING_TO_JSVAL(str);
393   return TRUE;
394 }
395 
JS_METHOD(ctxStringValue)396 JS_METHOD(ctxStringValue)
397 {
398 //    if (argc > 0 && JSVAL_IS_OBJECT(argv[0])
399 //        && sjs_instanceOf(cx, JSVAL_TO_OBJECT(argv[0]), &nodeClass))
400 //      {
401 //        JSObject *node = JSVAL_TO_OBJECT(argv[0]);
402 //        NodePrivate *np = (NodePrivate*)JS_GetPrivate(cx, obj);
403 //        sabassert(np);
404 //        DStr val;
405 //        np->situa->dom().constructStringValue(np->node, val);
406 //        JSString *str = JS_NewStringCopyZ(cx, (char*)val);
407 //        *rval = STRING_TO_JSVAL(str);
408 //        return TRUE;
409 //      } else {
410 //        return FALSE;
411 //      }
412   DOM_EX( 9 );
413   return TRUE;
414 }
415 
416 JSClass ctxClass = {
417   "XSLTContext",
418   JSCLASS_HAS_PRIVATE,
419   JS_PropertyStub, JS_PropertyStub,
420   JS_PropertyStub, JS_PropertyStub,
421   JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub,
422   ctxFinalize, 0, 0, NULL, NULL, NULL, NULL, 0, 0
423 };
424 
425 JSPropertySpec ctxProps[] =
426 {
427   {"contextPosition", 0, PROP_OPT, ctxGetPosition, NULL},
428   {"contextSize", 0, PROP_OPT, ctxGetSize, NULL},
429   {"contextNode", 0, PROP_OPT, ctxGetContextNode, NULL},
430   {"currentNode", 0, PROP_OPT, ctxGetCurrentNode, NULL},
431   {"ownerDocument", 0, PROP_OPT, ctxGetOwnerDocument, NULL},
432   {NULL, 0, 0, 0, 0}
433 };
434 
435 JSFunctionSpec ctxFunctions[] =
436 {
437   {"systemProperty", ctxSystemProperty, 0, 0, 0},
438   {"stringValue", ctxStringValue, 0, 0, 0},
439   {NULL, 0, 0, 0, 0}
440 };
441 
sjs_PublishContext(Sit S,JSContext_Sab * cx,Context * c)442 void sjs_PublishContext(Sit S, JSContext_Sab *cx, Context *c)
443 {
444   JSObject *obj = JS_DefineObject(cx, JS_GetGlobalObject(cx), "XSLTContext",
445 				  &ctxClass, NULL,
446 				  JSPROP_ENUMERATE |
447 				  JSPROP_READONLY);
448 
449   XSLTContextPrivate *priv = new XSLTContextPrivate;
450   priv -> ctx = c;
451   priv -> situa = &S;
452   JS_SetPrivate(cx, obj, priv);
453   JS_DefineProperties(cx, obj, ctxProps);
454   JS_DefineFunctions(cx, obj, ctxFunctions);
455 }
456 
457 /************* function call ******************/
SJSCallFunction(Sit S,Context * c,JSContextItem & item,Str & name,ExprList & atoms,Expression & retxpr)458 Bool SJSCallFunction(Sit S, Context *c, JSContextItem &item, Str &name,
459 		      ExprList &atoms, Expression &retxpr)
460 {
461   JSContext *cx = item.cx;
462   sabassert(cx);
463 
464 #ifdef ENABLE_JS_THREADS
465   JS_ResumeRequest(cx);
466 #endif
467 
468   sjs_PublishContext(S, cx, c);
469 
470   //get args
471   int argc = atoms.number();
472   jsval *args = new jsval[argc];
473   for (int i = 0; i < argc; i++) {
474     switch (atoms[i] -> type) {
475     case EX_NUMBER:
476       {
477 	jsdouble d = (double)(atoms[i]->tonumber(S));
478 	JS_NewDoubleValue(cx, d, &args[i]);
479       }; break;
480     case EX_BOOLEAN:
481       {
482 	args[i] = BOOLEAN_TO_JSVAL(atoms[i]->tobool());
483       }; break;
484     case EX_STRING:
485       {
486 	Str str;
487 	atoms[i]->tostring(S, str);
488 	char *p = (char*)str;
489 	JSString *s = JS_NewStringCopyN(cx, p, strlen(p));
490 	args[i] = STRING_TO_JSVAL(s);
491       }; break;
492     case EX_NODESET:
493       {
494 	const Context &ctx = atoms[i]->tonodesetRef();
495 	int num = ctx.getSize();
496 	JSObject *arr = jsdom_createNodeList(cx, num);
497 	args[i] = OBJECT_TO_JSVAL(arr);
498 	//iterate
499 	for (int j = 0; j < num; j++) {
500 	  NodeHandle node = ctx[j];
501 	  jsval val;// = new jsval;
502 	  val = OBJECT_TO_JSVAL(jsdom_wrapNode(S, cx, node));
503 	  JS_SetElement(cx, arr, j, &val);
504 	}
505       }; break;
506     case EX_EXTERNAL:
507       {
508 	External e;
509 	e.assign(atoms[i]->toexternal(S));
510 	JSObject *o = (JSObject*)e.getValue();
511 	if ( o )
512 	  {
513 	    args[i] = OBJECT_TO_JSVAL(o);
514 	  }
515 	else
516 	  {
517 	    args[i] = JSVAL_NULL;
518 	  }
519       }; break;
520     default:
521       {
522 	//convert to string
523 	Str str;
524 	atoms[i]->tostring(S, str);
525 	char *p = (char*)str;
526 	JSString *s = JS_NewStringCopyN(cx, p, strlen(p));
527 	args[i] = STRING_TO_JSVAL(s);
528       }
529     }
530   }
531 
532   //call function
533   jsval rval;
534   JSBool status = JS_CallFunctionName(cx, JS_GetGlobalObject(cx),
535 				      (char*)name, argc, args, &rval);
536   if ( JS_IsExceptionPending(cx) )
537     {
538       JS_ClearPendingException(cx);
539     }
540 
541   //remove XSLT context
542   JS_DeleteProperty(cx, JS_GetGlobalObject(cx), "XSLTContext");
543 
544   delete[] args;
545 
546   if (status) {
547     if (JSVAL_IS_VOID(rval))
548       {
549 	//return an emtpy nodeset
550 	GP( Context ) newc = new Context(c->getCurrentNode());
551 	retxpr.setAtom(newc.keep());
552       }
553     if (JSVAL_IS_NULL(rval))
554       {
555 	External e(cx, NULL);
556 	retxpr.setAtom(e);
557       }
558     if (JSVAL_IS_OBJECT(rval))
559       {
560 	JSObject *obj = JSVAL_TO_OBJECT(rval);
561 	//node lists
562 	if (sjs_instanceOf(cx, obj, &nlistClass))
563 	  {
564 	    jsuint len;
565 	    JS_GetArrayLength(cx, obj, &len);
566 	    GP( Context ) newc = new Context(c->getCurrentNode());
567 	    for (unsigned int i = 0; i < len; i++)
568 	      {
569 		jsval jnode;
570 		JS_GetElement(cx, obj, i, &jnode);
571 		JSObject *onode = JSVAL_TO_OBJECT(jnode);
572 		if (onode && sjs_instanceOf(cx, onode, &nodeClass))
573 		  {
574 		    NodePrivate *priv = (NodePrivate*)JS_GetPrivate(cx, onode);
575 		    sabassert(priv);
576 		    (*newc).append(priv -> node);
577 		  }
578 	      }
579 	    retxpr.setAtom(newc.keep());
580 	  }
581 	//single nodes
582 	else if (sjs_instanceOf(cx, obj, &nodeClass))
583 	  {
584 	    GP( Context ) newc = new Context(c->getCurrentNode());
585 
586 	    NodePrivate *priv = (NodePrivate*)JS_GetPrivate(cx, obj);
587 	    sabassert(priv);
588 	    (*newc).append(priv -> node);
589 
590 	    retxpr.setAtom(newc.keep());
591 	  }
592 	//other objects
593 	else
594 	  {
595 	    External e(cx, obj);
596 	    retxpr.setAtom(e);
597 	  }
598       }
599     //strings
600     else if (JSVAL_IS_STRING(rval))
601       {
602 	JSString *str = JS_ValueToString(cx, rval);
603 	Str rstr = (char*) JS_GetStringBytes(str);
604 	retxpr.setAtom(rstr);
605       }
606     else if (JSVAL_IS_NUMBER(rval))
607       {
608 	jsdouble d;
609 	JS_ValueToNumber(cx, rval, &d);
610 	Number num((double)d);
611 	retxpr.setAtom(num);
612       }
613     else if (JSVAL_IS_BOOLEAN(rval))
614       {
615 	retxpr.setAtom(JSVAL_TO_BOOLEAN(rval));
616       }
617     else
618       {
619 	//all other types are treated as strings (shouldn't happen)
620 	JSString *str = JS_ValueToString(cx, rval);
621 	Str rstr = (char*) JS_GetStringBytes(str);
622 	retxpr.setAtom(rstr);
623       }
624   }
625 
626 #ifdef ENABLE_JS_THREADS
627   JS_SuspendRequest(cx);
628 #endif
629 
630   //run GC
631   JS_MaybeGC(cx);
632   //JS_GC(cx);
633   return status;
634 }
635 
636 #endif //ENABLE_JS
637