1 /**********************************************************************
2  *
3  * Project:  MapServer
4  * Purpose:  V8 JavaScript Engine Support
5  * Author:   Alan Boudreault (aboudreault@mapgears.com)
6  *
7  **********************************************************************
8  * Copyright (c) 2013, Alan Boudreault, MapGears
9  *
10  * Permission is hereby granted, free of charge, to any person obtaining a
11  * copy of this software and associated documentation files (the "Software"),
12  * to deal in the Software without restriction, including without limitation
13  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14  * and/or sell copies of the Software, and to permit persons to whom the
15  * Software is furnished to do so, subject to the following conditions:
16  *
17  * The above copyright notice and this permission notice shall be included in
18  * all copies of this Software or works derived from this Software.
19  *
20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
23  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26  * DEALINGS IN THE SOFTWARE.
27  **********************************************************************/
28 
29 #include "mapserver-config.h"
30 #ifdef USE_V8_MAPSCRIPT
31 
32 #include "mapserver.h"
33 #include "v8_mapscript.h"
34 
35 /* This file could be refactored in the future to encapsulate the global
36    functions and internal use functions in a class. */
37 
38 /* Handler for Javascript Exceptions. Not exposed to JavaScript, used internally.
39    Most of the code from v8 shell example.
40 */
msV8ReportException(TryCatch * try_catch,const char * msg="")41 void msV8ReportException(TryCatch* try_catch, const char *msg = "")
42 {
43   HandleScope handle_scope;
44 
45   if (!try_catch || !try_catch->HasCaught()) {
46     msSetError(MS_V8ERR, "%s.", "msV8ReportException()", msg);
47     return;
48   }
49 
50   String::Utf8Value exception(try_catch->Exception());
51   const char* exception_string = *exception;
52   Handle<Message> message = try_catch->Message();
53   if (message.IsEmpty()) {
54     msSetError(MS_V8ERR, "Javascript Exception: %s.", "msV8ReportException()",
55                exception_string);
56   } else {
57     String::Utf8Value filename(message->GetScriptResourceName());
58     const char* filename_string = *filename;
59     int linenum = message->GetLineNumber();
60     msSetError(MS_V8ERR, "Javascript Exception: %s:%i: %s", "msV8ReportException()",
61                filename_string, linenum, exception_string);
62     String::Utf8Value sourceline(message->GetSourceLine());
63     const char* sourceline_string = *sourceline;
64     msSetError(MS_V8ERR, "Exception source line: %s", "msV8ReportException()",
65                sourceline_string);
66     String::Utf8Value stack_trace(try_catch->StackTrace());
67     if (stack_trace.length() > 0) {
68       const char* stack_trace_string = *stack_trace;
69       msSetError(MS_V8ERR, "Exception StackTrace: %s", "msV8ReportException()",
70                  stack_trace_string);
71     }
72   }
73 }
74 
75 /* This function load a javascript file in memory. */
msV8ReadFile(V8Context * v8context,const char * path)76 static Handle<Value> msV8ReadFile(V8Context *v8context, const char *path)
77 {
78   FILE* file = fopen(path, "rb");
79   if (file == NULL) {
80     char err[MS_MAXPATHLEN+21];
81     sprintf(err, "Error opening file: %s", path);
82     msV8ReportException(NULL, err);
83     return Handle<String>(String::New(""));
84   }
85 
86   fseek(file, 0, SEEK_END);
87   int size = ftell(file);
88   rewind(file);
89 
90   char* chars = new char[size + 1];
91   chars[size] = '\0';
92   for (int i = 0; i < size;) {
93     int read = static_cast<int>(fread(&chars[i], 1, size - i, file));
94     if (read == 0) {
95       delete [] chars;
96       fclose(file);
97       msDebug("msV8ReadFile: error while reading file '%s'\n", path);
98       return Undefined();
99     }
100     i += read;
101   }
102 
103   fclose(file);
104   Handle<String> result = String::New(chars, size);
105   delete[] chars;
106   return result;
107 }
108 
109 /* Returns a compiled javascript script. */
msV8CompileScript(Handle<String> source,Handle<String> script_name)110 static Handle<Script> msV8CompileScript(Handle<String> source, Handle<String> script_name)
111 {
112   TryCatch try_catch;
113 
114   if (source.IsEmpty() || source->Length() == 0) {
115     msV8ReportException(NULL, "No source to compile");
116     return Handle<Script>();
117   }
118 
119   Handle<Script> script = Script::Compile(source, script_name);
120   if (script.IsEmpty() && try_catch.HasCaught()) {
121     msV8ReportException(&try_catch);
122   }
123 
124   return script;
125 }
126 
127 /* Runs a compiled script */
msV8RunScript(Handle<Script> script)128 static Handle<Value> msV8RunScript(Handle<Script> script)
129 {
130   if (script.IsEmpty()) {
131     ThrowException(String::New("No script to run"));
132     return Handle<Value>();
133   }
134 
135   TryCatch try_catch;
136   Handle<Value> result = script->Run();
137 
138   if (result.IsEmpty() && try_catch.HasCaught()) {
139     msV8ReportException(&try_catch);
140   }
141 
142   return result;
143 }
144 
145 /* Execute a javascript file */
msV8ExecuteScript(const char * path,int throw_exception=MS_FALSE)146 static Handle<Value> msV8ExecuteScript(const char *path, int throw_exception = MS_FALSE)
147 {
148   char fullpath[MS_MAXPATHLEN];
149   map<string, Persistent<Script> >::iterator it;
150   Isolate *isolate = Isolate::GetCurrent();
151   V8Context *v8context = (V8Context*)isolate->GetData();
152 
153   /* construct the path */
154   msBuildPath(fullpath, v8context->paths.top().c_str(), path);
155   char *filepath = msGetPath((char*)fullpath);
156   v8context->paths.push(filepath);
157   free(filepath);
158 
159   Handle<Script> script;
160   it = v8context->scripts.find(fullpath);
161   if (it == v8context->scripts.end()) {
162     Handle<Value> source = msV8ReadFile(v8context, fullpath);
163     Handle<String> script_name = String::New(msStripPath((char*)path));
164     script = msV8CompileScript(source->ToString(), script_name);
165     if (script.IsEmpty()) {
166       v8context->paths.pop();
167       if (throw_exception) {
168         return ThrowException(String::New("Error compiling script"));
169       }
170     }
171     /* cache the compiled script */
172     Persistent<Script> pscript;
173     pscript.Reset(isolate, script);
174     v8context->scripts[fullpath] = pscript;
175   } else {
176     script = v8context->scripts[fullpath];
177   }
178 
179   Handle<Value> result = msV8RunScript(script);
180   v8context->paths.pop();
181   if (result.IsEmpty() && throw_exception) {
182     return ThrowException(String::New("Error running script"));
183   }
184 
185   return result;
186 }
187 
188 /* END OF INTERNAL JAVASCRIPT FUNCTIONS */
189 
190 /* JAVASCRIPT EXPOSED FUNCTIONS */
191 
192 /* JavaScript Function to load javascript dependencies.
193    Exposed to JavaScript as 'require()'. */
msV8Require(const Arguments & args)194 static Handle<Value> msV8Require(const Arguments& args)
195 {
196   TryCatch try_catch;
197 
198   for (int i = 0; i < args.Length(); i++) {
199     String::Utf8Value filename(args[i]);
200     msV8ExecuteScript(*filename, MS_TRUE);
201     if (try_catch.HasCaught()) {
202       return try_catch.ReThrow();
203     }
204   }
205 
206   return Undefined();
207 }
208 
209 /* Javascript Function print: print to debug file.
210    Exposed to JavaScript as 'print()'. */
msV8Print(const Arguments & args)211 static Handle<Value> msV8Print(const Arguments& args)
212 {
213   for (int i = 0; i < args.Length(); i++) {
214     String::Utf8Value str(args[i]);
215     msDebug("msV8Print: %s\n", *str);
216   }
217 
218   return Undefined();
219 }
220 
221 /* END OF JAVASCRIPT EXPOSED FUNCTIONS */
222 
223 /* Create and return a v8 context. Thread safe. */
msV8CreateContext(mapObj * map)224 void msV8CreateContext(mapObj *map)
225 {
226 
227   Isolate *isolate = Isolate::GetCurrent();
228   Isolate::Scope isolate_scope(isolate);
229   HandleScope handle_scope(isolate);
230 
231   V8Context *v8context = new V8Context(isolate);
232 
233   Handle<ObjectTemplate> global_templ = ObjectTemplate::New();
234   global_templ->Set(String::New("require"), FunctionTemplate::New(msV8Require));
235   global_templ->Set(String::New("print"), FunctionTemplate::New(msV8Print));
236   global_templ->Set(String::New("alert"), FunctionTemplate::New(msV8Print));
237 
238   Handle<Context> context_ = Context::New(v8context->isolate, NULL, global_templ);
239   v8context->context.Reset(v8context->isolate, context_);
240 
241   /* we have to enter the context before getting global instance */
242   Context::Scope context_scope(context_);
243   Handle<Object> global = context_->Global();
244   Shape::Initialize(global);
245   Point::Initialize(global);
246   Line::Initialize(global);
247 
248   v8context->paths.push(map->mappath);
249   v8context->isolate->SetData(v8context);
250   v8context->layer = NULL;
251 
252   map->v8context = (void*)v8context;
253 }
254 
msV8ContextSetLayer(mapObj * map,layerObj * layer)255 void msV8ContextSetLayer(mapObj *map, layerObj *layer)
256 {
257   V8Context* v8context = V8CONTEXT(map);
258 
259   if (!v8context) {
260     msSetError(MS_V8ERR, "V8 Persistent Context is not created.", "msV8ContextSetLayer()");
261     return;
262   }
263 
264   v8context->layer = layer;
265 }
266 
msV8FreeContextScripts(V8Context * v8context)267 static void msV8FreeContextScripts(V8Context *v8context)
268 {
269   map<string, Persistent<Script> >::iterator it;
270   for(it=v8context->scripts.begin(); it!=v8context->scripts.end(); ++it)
271   {
272     ((*it).second).Dispose();
273   }
274 }
275 
msV8FreeContext(mapObj * map)276 void msV8FreeContext(mapObj *map)
277 {
278   V8Context* v8context = V8CONTEXT(map);
279   Shape::Dispose();
280   Point::Dispose();
281   Line::Dispose();
282   msV8FreeContextScripts(v8context);
283   v8context->context.Dispose();
284   delete v8context;
285 }
286 
287 /* Create a V8 execution context, execute a script and return the feature
288  * style. */
msV8GetFeatureStyle(mapObj * map,const char * filename,layerObj * layer,shapeObj * shape)289 char* msV8GetFeatureStyle(mapObj *map, const char *filename, layerObj *layer, shapeObj *shape)
290 {
291   TryCatch try_catch;
292   V8Context* v8context = V8CONTEXT(map);
293   char *ret = NULL;
294 
295   if (!v8context) {
296     msSetError(MS_V8ERR, "V8 Persistent Context is not created.", "msV8ReportException()");
297     return NULL;
298   }
299 
300   Isolate::Scope isolate_scope(v8context->isolate);
301   HandleScope handle_scope(v8context->isolate);
302 
303   /* execution context */
304   Local<Context> context = Local<Context>::New(v8context->isolate, v8context->context);
305   Context::Scope context_scope(context);
306   Handle<Object> global = context->Global();
307 
308   Shape *shape_ = new Shape(shape);
309   shape_->setLayer(layer); // hack to set the attribute names, should change in future.
310   shape_->disableMemoryHandler(); /* the internal object should not be freed by the v8 GC */
311   Handle<Value> ext = External::New(shape_);
312   global->Set(String::New("shape"),
313               Shape::Constructor()->NewInstance(1, &ext));
314 
315   msV8ExecuteScript(filename);
316   Handle<Value> value = global->Get(String::New("styleitem"));
317   if (value->IsUndefined()) {
318     msDebug("msV8GetFeatureStyle: Function 'styleitem' is missing.\n");
319     return ret;
320   }
321   Handle<Function> func = Handle<Function>::Cast(value);
322   Handle<Value> result = func->Call(global, 0, 0);
323   if (result.IsEmpty() && try_catch.HasCaught()) {
324     msV8ReportException(&try_catch);
325   }
326 
327   if (!result.IsEmpty() && !result->IsUndefined()) {
328      String::AsciiValue ascii(result);
329      ret = msStrdup(*ascii);
330   }
331 
332   return ret;
333 }
334 
335 /* for geomtransform, we don't have the mapObj */
msV8TransformShape(shapeObj * shape,const char * filename)336 shapeObj *msV8TransformShape(shapeObj *shape, const char* filename)
337 {
338   TryCatch try_catch;
339   Isolate *isolate = Isolate::GetCurrent();
340   V8Context *v8context = (V8Context*)isolate->GetData();
341 
342   HandleScope handle_scope(v8context->isolate);
343 
344   /* execution context */
345   Local<Context> context = Local<Context>::New(v8context->isolate, v8context->context);
346   Context::Scope context_scope(context);
347   Handle<Object> global = context->Global();
348 
349   Shape* shape_ = new Shape(shape);
350   shape_->setLayer(v8context->layer);
351   shape_->disableMemoryHandler();
352   Handle<Value> ext = External::New(shape_);
353   global->Set(String::New("shape"),
354               Shape::Constructor()->NewInstance(1, &ext));
355 
356   msV8ExecuteScript(filename);
357   Handle<Value> value = global->Get(String::New("geomtransform"));
358   if (value->IsUndefined()) {
359     msDebug("msV8TransformShape: Function 'geomtransform' is missing.\n");
360     return NULL;
361   }
362   Handle<Function> func = Handle<Function>::Cast(value);
363   Handle<Value> result = func->Call(global, 0, 0);
364   if (result.IsEmpty() && try_catch.HasCaught()) {
365     msV8ReportException(&try_catch);
366   }
367 
368   if (!result.IsEmpty() && result->IsObject()) {
369     Handle<Object> obj = result->ToObject();
370     if (obj->GetConstructorName()->Equals(String::New("shapeObj"))) {
371       Shape* new_shape = ObjectWrap::Unwrap<Shape>(result->ToObject());
372       if (shape == new_shape->get()) {
373         shapeObj *new_shape_ = (shapeObj *)msSmallMalloc(sizeof(shapeObj));
374         msInitShape(new_shape_);
375         msCopyShape(shape, new_shape_);
376         return new_shape_;
377       }
378       else {
379         new_shape->disableMemoryHandler();
380         return new_shape->get();
381       }
382     }
383   }
384 
385   return NULL;
386 }
387 
388 #endif /* USE_V8_MAPSCRIPT */
389