1 /* The SpiderMonkey window object implementation. */
2 
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
6 
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10 
11 #include "elinks.h"
12 
13 #include "ecmascript/spidermonkey/util.h"
14 
15 #include "bfu/dialog.h"
16 #include "cache/cache.h"
17 #include "cookies/cookies.h"
18 #include "dialogs/menu.h"
19 #include "dialogs/status.h"
20 #include "document/html/frames.h"
21 #include "document/document.h"
22 #include "document/forms.h"
23 #include "document/view.h"
24 #include "ecmascript/ecmascript.h"
25 #include "ecmascript/spidermonkey/window.h"
26 #include "intl/gettext/libintl.h"
27 #include "main/select.h"
28 #include "osdep/newwin.h"
29 #include "osdep/sysname.h"
30 #include "protocol/http/http.h"
31 #include "protocol/uri.h"
32 #include "session/history.h"
33 #include "session/location.h"
34 #include "session/session.h"
35 #include "session/task.h"
36 #include "terminal/tab.h"
37 #include "terminal/terminal.h"
38 #include "util/conv.h"
39 #include "util/memory.h"
40 #include "util/string.h"
41 #include "viewer/text/draw.h"
42 #include "viewer/text/form.h"
43 #include "viewer/text/link.h"
44 #include "viewer/text/vs.h"
45 
46 
47 static JSBool window_get_property(JSContext *ctx, JSObject *obj, jsval id, jsval *vp);
48 static JSBool window_set_property(JSContext *ctx, JSObject *obj, jsval id, jsval *vp);
49 
50 const JSClass window_class = {
51 	"window",
52 	JSCLASS_HAS_PRIVATE,	/* struct view_state * */
53 	JS_PropertyStub, JS_PropertyStub,
54 	window_get_property, window_set_property,
55 	JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub
56 };
57 
58 
59 enum window_prop {
60 	JSP_WIN_CLOSED,
61 	JSP_WIN_PARENT,
62 	JSP_WIN_SELF,
63 	JSP_WIN_TOP,
64 };
65 /* "location" is special because we need to simulate "location.href"
66  * when the code is asking directly for "location". We do not register
67  * it as a "known" property since that was yielding strange bugs
68  * (SpiderMonkey was still asking us about the "location" string after
69  * assigning to it once), instead we do just a little string
70  * comparing. */
71 const JSPropertySpec window_props[] = {
72 	{ "closed",	JSP_WIN_CLOSED,	JSPROP_ENUMERATE | JSPROP_READONLY },
73 	{ "parent",	JSP_WIN_PARENT,	JSPROP_ENUMERATE | JSPROP_READONLY },
74 	{ "self",	JSP_WIN_SELF,	JSPROP_ENUMERATE | JSPROP_READONLY },
75 	{ "top",	JSP_WIN_TOP,	JSPROP_ENUMERATE | JSPROP_READONLY },
76 	{ "window",	JSP_WIN_SELF,	JSPROP_ENUMERATE | JSPROP_READONLY },
77 	{ NULL }
78 };
79 
80 
81 static JSObject *
try_resolve_frame(struct document_view * doc_view,unsigned char * id)82 try_resolve_frame(struct document_view *doc_view, unsigned char *id)
83 {
84 	struct session *ses = doc_view->session;
85 	struct frame *target;
86 
87 	assert(ses);
88 	target = ses_find_frame(ses, id);
89 	if (!target) return NULL;
90 	if (target->vs.ecmascript_fragile)
91 		ecmascript_reset_state(&target->vs);
92 	if (!target->vs.ecmascript) return NULL;
93 	return JS_GetGlobalObject(target->vs.ecmascript->backend_data);
94 }
95 
96 #if 0
97 static struct frame_desc *
98 find_child_frame(struct document_view *doc_view, struct frame_desc *tframe)
99 {
100 	struct frameset_desc *frameset = doc_view->document->frame_desc;
101 	int i;
102 
103 	if (!frameset)
104 		return NULL;
105 
106 	for (i = 0; i < frameset->n; i++) {
107 		struct frame_desc *frame = &frameset->frame_desc[i];
108 
109 		if (frame == tframe)
110 			return frame;
111 	}
112 
113 	return NULL;
114 }
115 #endif
116 
117 /* @window_class.getProperty */
118 static JSBool
window_get_property(JSContext * ctx,JSObject * obj,jsval id,jsval * vp)119 window_get_property(JSContext *ctx, JSObject *obj, jsval id, jsval *vp)
120 {
121 	struct view_state *vs;
122 
123 	/* This can be called if @obj if not itself an instance of the
124 	 * appropriate class but has one in its prototype chain.  Fail
125 	 * such calls.  */
126 	if (!JS_InstanceOf(ctx, obj, (JSClass *) &window_class, NULL))
127 		return JS_FALSE;
128 
129 	vs = JS_GetInstancePrivate(ctx, obj, (JSClass *) &window_class, NULL);
130 
131 	/* No need for special window.location measurements - when
132 	 * location is then evaluated in string context, toString()
133 	 * is called which we overrode for that class below, so
134 	 * everything's fine. */
135 	if (JSVAL_IS_STRING(id)) {
136 		struct document_view *doc_view = vs->doc_view;
137 		JSObject *obj;
138 
139 		obj = try_resolve_frame(doc_view, jsval_to_string(ctx, &id));
140 		/* TODO: Try other lookups (mainly element lookup) until
141 		 * something yields data. */
142 		if (obj) {
143 			object_to_jsval(ctx, vp, obj);
144 		}
145 		return JS_TRUE;
146 	}
147 
148 	if (!JSVAL_IS_INT(id))
149 		return JS_TRUE;
150 
151 	undef_to_jsval(ctx, vp);
152 
153 	switch (JSVAL_TO_INT(id)) {
154 	case JSP_WIN_CLOSED:
155 		/* TODO: It will be a major PITA to implement this properly.
156 		 * Well, perhaps not so much if we introduce reference tracking
157 		 * for (struct session)? Still... --pasky */
158 		boolean_to_jsval(ctx, vp, 0);
159 		break;
160 	case JSP_WIN_SELF:
161 		object_to_jsval(ctx, vp, obj);
162 		break;
163 	case JSP_WIN_PARENT:
164 		/* XXX: It would be nice if the following worked, yes.
165 		 * The problem is that we get called at the point where
166 		 * document.frame properties are going to be mostly NULL.
167 		 * But the problem is deeper because at that time we are
168 		 * yet building scrn_frames so our parent might not be there
169 		 * yet (XXX: is this true?). The true solution will be to just
170 		 * have struct document_view *(document_view.parent). --pasky */
171 		/* FIXME: So now we alias window.parent to window.top, which is
172 		 * INCORRECT but works for the most common cases of just two
173 		 * frames. Better something than nothing. */
174 #if 0
175 	{
176 		/* This is horrible. */
177 		struct document_view *doc_view = vs->doc_view;
178 		struct session *ses = doc_view->session;
179 		struct frame_desc *frame = doc_view->document->frame;
180 
181 		if (!ses->doc_view->document->frame_desc) {
182 			INTERNAL("Looking for parent but there're no frames.");
183 			break;
184 		}
185 		assert(frame);
186 		doc_view = ses->doc_view;
187 		if (find_child_frame(doc_view, frame))
188 			goto found_parent;
189 		foreach (doc_view, ses->scrn_frames) {
190 			if (find_child_frame(doc_view, frame))
191 				goto found_parent;
192 		}
193 		INTERNAL("Cannot find frame %s parent.",doc_view->name);
194 		break;
195 
196 found_parent:
197 		some_domain_security_check();
198 		if (doc_view->vs.ecmascript_fragile)
199 			ecmascript_reset_state(&doc_view->vs);
200 		assert(doc_view->ecmascript);
201 		object_to_jsval(ctx, vp, JS_GetGlobalObject(doc_view->ecmascript->backend_data));
202 		break;
203 	}
204 #endif
205 	case JSP_WIN_TOP:
206 	{
207 		struct document_view *doc_view = vs->doc_view;
208 		struct document_view *top_view = doc_view->session->doc_view;
209 		JSObject *newjsframe;
210 
211 		assert(top_view && top_view->vs);
212 		if (top_view->vs->ecmascript_fragile)
213 			ecmascript_reset_state(top_view->vs);
214 		if (!top_view->vs->ecmascript)
215 			break;
216 		newjsframe = JS_GetGlobalObject(top_view->vs->ecmascript->backend_data);
217 
218 		/* Keep this unrolled this way. Will have to check document.domain
219 		 * JS property. */
220 		/* Note that this check is perhaps overparanoid. If top windows
221 		 * is alien but some other child window is not, we should still
222 		 * let the script walk thru. That'd mean moving the check to
223 		 * other individual properties in this switch. */
224 		if (compare_uri(vs->uri, top_view->vs->uri, URI_HOST))
225 			object_to_jsval(ctx, vp, newjsframe);
226 		/* else */
227 			/****X*X*X*** SECURITY VIOLATION! RED ALERT, SHIELDS UP! ***X*X*X****\
228 			|* (Pasky was apparently looking at the Links2 JS code   .  ___ ^.^ *|
229 			\* for too long.)                                        `.(,_,)\o/ */
230 		break;
231 	}
232 	default:
233 		/* Unrecognized integer property ID; someone is using
234 		 * the object as an array.  SMJS builtin classes (e.g.
235 		 * js_RegExpClass) just return JS_TRUE in this case
236 		 * and leave *@vp unchanged.  Do the same here.
237 		 * (Actually not quite the same, as we already used
238 		 * @undef_to_jsval.)  */
239 		break;
240 	}
241 
242 	return JS_TRUE;
243 }
244 
245 void location_goto(struct document_view *doc_view, unsigned char *url);
246 
247 /* @window_class.setProperty */
248 static JSBool
window_set_property(JSContext * ctx,JSObject * obj,jsval id,jsval * vp)249 window_set_property(JSContext *ctx, JSObject *obj, jsval id, jsval *vp)
250 {
251 	struct view_state *vs;
252 
253 	/* This can be called if @obj if not itself an instance of the
254 	 * appropriate class but has one in its prototype chain.  Fail
255 	 * such calls.  */
256 	if (!JS_InstanceOf(ctx, obj, (JSClass *) &window_class, NULL))
257 		return JS_FALSE;
258 
259 	vs = JS_GetInstancePrivate(ctx, obj, (JSClass *) &window_class, NULL);
260 
261 	if (JSVAL_IS_STRING(id)) {
262 		if (!strcmp(jsval_to_string(ctx, &id), "location")) {
263 			struct document_view *doc_view = vs->doc_view;
264 
265 			location_goto(doc_view, jsval_to_string(ctx, vp));
266 			/* Do NOT touch our .location property, evil
267 			 * SpiderMonkey!! */
268 			return JS_FALSE;
269 		}
270 		return JS_TRUE;
271 	}
272 
273 	if (!JSVAL_IS_INT(id))
274 		return JS_TRUE;
275 
276 	switch (JSVAL_TO_INT(id)) {
277 	default:
278 		/* Unrecognized integer property ID; someone is using
279 		 * the object as an array.  SMJS builtin classes (e.g.
280 		 * js_RegExpClass) just return JS_TRUE in this case.
281 		 * Do the same here.  */
282 		return JS_TRUE;
283 	}
284 
285 	return JS_TRUE;
286 }
287 
288 
289 static JSBool window_alert(JSContext *ctx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
290 static JSBool window_open(JSContext *ctx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
291 
292 const spidermonkeyFunctionSpec window_funcs[] = {
293 	{ "alert",	window_alert,		1 },
294 	{ "open",	window_open,		3 },
295 	{ NULL }
296 };
297 
298 /* @window_funcs{"alert"} */
299 static JSBool
window_alert(JSContext * ctx,JSObject * obj,uintN argc,jsval * argv,jsval * rval)300 window_alert(JSContext *ctx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
301 {
302 	struct view_state *vs;
303 	unsigned char *string;
304 
305 	if (!JS_InstanceOf(ctx, obj, (JSClass *) &window_class, argv)) return JS_FALSE;
306 
307 	vs = JS_GetInstancePrivate(ctx, obj, (JSClass *) &window_class, argv);
308 
309 	if (argc != 1)
310 		return JS_TRUE;
311 
312 	string = jsval_to_string(ctx, &argv[0]);
313 	if (!*string)
314 		return JS_TRUE;
315 
316 	info_box(vs->doc_view->session->tab->term, MSGBOX_FREE_TEXT,
317 		N_("JavaScript Alert"), ALIGN_CENTER, stracpy(string));
318 
319 	undef_to_jsval(ctx, rval);
320 	return JS_TRUE;
321 }
322 
323 struct delayed_open {
324 	struct session *ses;
325 	struct uri *uri;
326 	unsigned char *target;
327 };
328 
329 static void
delayed_open(void * data)330 delayed_open(void *data)
331 {
332 	struct delayed_open *deo = data;
333 
334 	assert(deo);
335 	open_uri_in_new_tab(deo->ses, deo->uri, 0, 0);
336 	done_uri(deo->uri);
337 	mem_free(deo);
338 }
339 
340 static void
delayed_goto_uri_frame(void * data)341 delayed_goto_uri_frame(void *data)
342 {
343 	struct delayed_open *deo = data;
344 
345 	assert(deo);
346 	goto_uri_frame(deo->ses, deo->uri, deo->target, CACHE_MODE_NORMAL);
347 	done_uri(deo->uri);
348 	mem_free(deo);
349 }
350 
351 /* @window_funcs{"open"} */
352 static JSBool
window_open(JSContext * ctx,JSObject * obj,uintN argc,jsval * argv,jsval * rval)353 window_open(JSContext *ctx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
354 {
355 	struct view_state *vs;
356 	struct document_view *doc_view;
357 	struct session *ses;
358 	unsigned char *target = "";
359 	unsigned char *url;
360 	struct uri *uri;
361 	static time_t ratelimit_start;
362 	static int ratelimit_count;
363 
364 	if (!JS_InstanceOf(ctx, obj, (JSClass *) &window_class, argv)) return JS_FALSE;
365 
366 	vs = JS_GetInstancePrivate(ctx, obj, (JSClass *) &window_class, argv);
367 	doc_view = vs->doc_view;
368 	ses = doc_view->session;
369 
370 	if (get_opt_bool("ecmascript.block_window_opening")) {
371 #ifdef CONFIG_LEDS
372 		set_led_value(ses->status.popup_led, 'P');
373 #endif
374 		return JS_TRUE;
375 	}
376 
377 	if (argc < 1) return JS_TRUE;
378 
379 	/* Ratelimit window opening. Recursive window.open() is very nice.
380 	 * We permit at most 20 tabs in 2 seconds. The ratelimiter is very
381 	 * rough but shall suffice against the usual cases. */
382 
383 	if (!ratelimit_start || time(NULL) - ratelimit_start > 2) {
384 		ratelimit_start = time(NULL);
385 		ratelimit_count = 0;
386 	} else {
387 		ratelimit_count++;
388 		if (ratelimit_count > 20)
389 			return JS_TRUE;
390 	}
391 
392 	url = jsval_to_string(ctx, &argv[0]);
393 
394 	/* TODO: Support for window naming and perhaps some window features? */
395 
396 	url = join_urls(doc_view->document->uri,
397 	                trim_chars(url, ' ', 0));
398 	if (!url) return JS_TRUE;
399 	uri = get_uri(url, 0);
400 	mem_free(url);
401 	if (!uri) return JS_TRUE;
402 
403 	if (argc > 1) target = jsval_to_string(ctx, &argv[1]);
404 
405 	if (*target && c_strcasecmp(target, "_blank")) {
406 		struct delayed_open *deo = mem_calloc(1, sizeof(*deo));
407 
408 		if (deo) {
409 			deo->ses = ses;
410 			deo->uri = get_uri_reference(uri);
411 			deo->target = target;
412 			register_bottom_half(delayed_goto_uri_frame, deo);
413 			boolean_to_jsval(ctx, rval, 1);
414 			goto end;
415 		}
416 	}
417 
418 	if (!get_cmd_opt_bool("no-connect")
419 	    && !get_cmd_opt_bool("no-home")
420 	    && !get_cmd_opt_bool("anonymous")
421 	    && can_open_in_new(ses->tab->term)) {
422 		open_uri_in_new_window(ses, uri, NULL, ENV_ANY,
423 				       CACHE_MODE_NORMAL, TASK_NONE);
424 		boolean_to_jsval(ctx, rval, 1);
425 	} else {
426 		/* When opening a new tab, we might get rerendered, losing our
427 		 * context and triggerring a disaster, so postpone that. */
428 		struct delayed_open *deo = mem_calloc(1, sizeof(*deo));
429 
430 		if (deo) {
431 			deo->ses = ses;
432 			deo->uri = get_uri_reference(uri);
433 			register_bottom_half(delayed_open, deo);
434 			boolean_to_jsval(ctx, rval, 1);
435 		} else {
436 			undef_to_jsval(ctx, rval);
437 		}
438 	}
439 
440 end:
441 	done_uri(uri);
442 
443 	return JS_TRUE;
444 }
445