Retry only for https protocol
[elinks.git] / src / scripting / smjs / core.c
blob012c68e59e597614f866b6b2fe1c9f84d300b7a9
1 /* ECMAScript browser scripting module */
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
7 #include <stdlib.h>
9 #include "elinks.h"
11 #include "config/home.h"
12 #include "ecmascript/spidermonkey-shared.h"
13 #include "intl/charsets.h"
14 #include "main/module.h"
15 #include "osdep/osdep.h"
16 #include "scripting/scripting.h"
17 #include "scripting/smjs/core.h"
18 #include "scripting/smjs/elinks_object.h"
19 #include "scripting/smjs/global_object.h"
20 #include "scripting/smjs/smjs.h"
21 #include "util/file.h"
22 #include "util/string.h"
25 #define SMJS_HOOKS_FILENAME "hooks.js"
27 JSContext *smjs_ctx;
28 JSObject *smjs_elinks_object;
29 struct session *smjs_ses;
32 void
33 alert_smjs_error(unsigned char *msg)
35 report_scripting_error(&smjs_scripting_module,
36 smjs_ses, msg);
39 static void
40 error_reporter(JSContext *ctx, const char *message, JSErrorReport *report)
42 unsigned char *strict, *exception, *warning, *error;
43 struct string msg;
45 if (!init_string(&msg)) goto reported;
47 strict = JSREPORT_IS_STRICT(report->flags) ? " strict" : "";
48 exception = JSREPORT_IS_EXCEPTION(report->flags) ? " exception" : "";
49 warning = JSREPORT_IS_WARNING(report->flags) ? " warning" : "";
50 error = !report->flags ? " error" : "";
52 add_format_to_string(&msg, "A client script raised the following%s%s%s%s",
53 strict, exception, warning, error);
55 add_to_string(&msg, ":\n\n");
56 add_to_string(&msg, message);
58 if (report->linebuf && report->tokenptr) {
59 int pos = report->tokenptr - report->linebuf;
61 add_format_to_string(&msg, "\n\n%s\n.%*s^%*s.",
62 report->linebuf,
63 pos - 2, " ",
64 strlen(report->linebuf) - pos - 1, " ");
67 alert_smjs_error(msg.source);
68 done_string(&msg);
70 reported:
71 JS_ClearPendingException(ctx);
74 static int
75 smjs_do_file(unsigned char *path)
77 int ret = 1;
78 jsval rval;
79 struct string script;
81 if (!init_string(&script)) return 0;
83 if (!add_file_to_string(&script, path)
84 || JS_FALSE == JS_EvaluateScript(smjs_ctx,
85 JS_GetGlobalObject(smjs_ctx),
86 script.source, script.length, path, 1, &rval)) {
87 alert_smjs_error("error loading script file");
88 ret = 0;
91 done_string(&script);
93 return ret;
96 static JSBool
97 smjs_do_file_wrapper(JSContext *ctx, uintN argc, jsval *rval)
99 jsval *argv = JS_ARGV(ctx, rval);
100 JSString *jsstr = JS_ValueToString(smjs_ctx, *argv);
101 unsigned char *path = JS_EncodeString(smjs_ctx, jsstr);
103 if (smjs_do_file(path))
104 return JS_TRUE;
106 return JS_FALSE;
109 static void
110 smjs_load_hooks(void)
112 unsigned char *path;
114 assert(smjs_ctx);
116 if (elinks_home) {
117 path = straconcat(elinks_home, SMJS_HOOKS_FILENAME,
118 (unsigned char *) NULL);
119 } else {
120 path = stracpy(CONFDIR STRING_DIR_SEP SMJS_HOOKS_FILENAME);
123 if (file_exists(path))
124 smjs_do_file(path);
125 mem_free(path);
128 void
129 init_smjs(struct module *module)
131 if (!spidermonkey_runtime_addref()) return;
133 smjs_ctx = JS_NewContext(spidermonkey_runtime, 8192);
134 if (!smjs_ctx) {
135 spidermonkey_runtime_release();
136 return;
139 JS_SetOptions(smjs_ctx, JSOPTION_VAROBJFIX | JSOPTION_JIT | JSOPTION_METHODJIT);
140 JS_SetVersion(smjs_ctx, JSVERSION_LATEST);
142 JS_SetErrorReporter(smjs_ctx, error_reporter);
144 smjs_init_global_object();
146 smjs_init_elinks_object();
148 JS_DefineFunction(smjs_ctx, smjs_global_object, "do_file",
149 &smjs_do_file_wrapper, 1, 0);
151 smjs_load_hooks();
154 void
155 cleanup_smjs(struct module *module)
157 if (!smjs_ctx) return;
159 /* JS_DestroyContext also collects garbage in the JSRuntime.
160 * Because the JSObjects created in smjs_ctx have not been
161 * made visible to any other JSContext, and the garbage
162 * collector of SpiderMonkey is precise, SpiderMonkey
163 * finalizes all of those objects, so cache_entry_finalize
164 * gets called and resets each cache_entry.jsobject = NULL.
165 * If the garbage collector were conservative, ELinks would
166 * have to call smjs_detach_cache_entry_object on each cache
167 * entry before it releases the runtime here. */
168 JS_DestroyContext(smjs_ctx);
169 spidermonkey_runtime_release();
172 /** Convert a UTF-8 string to a JSString.
174 * @param ctx
175 * Allocate the string in this JSContext.
176 * @param[in] str
177 * The input string that should be converted.
178 * @param[in] length
179 * Length of @a str in bytes, or -1 if it is null-terminated.
181 * @return the new string. On error, report the error to SpiderMonkey
182 * and return NULL. */
183 JSString *
184 utf8_to_jsstring(JSContext *ctx, const unsigned char *str, int length)
186 size_t in_bytes;
187 const unsigned char *in_end;
188 size_t utf16_alloc;
189 jschar *utf16;
190 size_t utf16_used;
191 JSString *jsstr;
193 if (length == -1)
194 in_bytes = strlen(str);
195 else
196 in_bytes = length;
198 /* Each byte of input can become at most one UTF-16 unit.
199 * Check whether the multiplication could overflow. */
200 assert(!needs_utf16_surrogates(UCS_REPLACEMENT_CHARACTER));
201 if (in_bytes > ((size_t) -1) / sizeof(jschar)) {
202 #ifdef HAVE_JS_REPORTALLOCATIONOVERFLOW
203 JS_ReportAllocationOverflow(ctx);
204 #else
205 JS_ReportOutOfMemory(ctx);
206 #endif
207 return NULL;
209 utf16_alloc = in_bytes;
210 /* Use malloc because SpiderMonkey will handle the memory after
211 * this routine finishes. */
212 utf16 = malloc(utf16_alloc * sizeof(jschar));
213 if (utf16 == NULL) {
214 JS_ReportOutOfMemory(ctx);
215 return NULL;
218 in_end = str + in_bytes;
220 utf16_used = 0;
221 for (;;) {
222 unicode_val_T unicode;
224 unicode = utf8_to_unicode((unsigned char **) &str, in_end);
225 if (unicode == UCS_NO_CHAR)
226 break;
228 if (needs_utf16_surrogates(unicode)) {
229 assert(utf16_alloc - utf16_used >= 2);
230 if_assert_failed { free(utf16); return NULL; }
231 utf16[utf16_used++] = get_utf16_high_surrogate(unicode);
232 utf16[utf16_used++] = get_utf16_low_surrogate(unicode);
233 } else {
234 assert(utf16_alloc - utf16_used >= 1);
235 if_assert_failed { free(utf16); return NULL; }
236 utf16[utf16_used++] = unicode;
240 jsstr = JS_NewUCString(ctx, utf16, utf16_used);
241 /* Do not free if JS_NewUCString was successful because it takes over
242 * handling of the memory. */
243 if (jsstr == NULL) free(utf16);
245 return jsstr;
248 /** Convert a jschar array to UTF-8 and append it to struct string.
249 * Replace misused surrogate codepoints with UCS_REPLACEMENT_CHARACTER.
251 * @param[in,out] utf8
252 * The function appends characters to this UTF-8 string.
254 * @param[in] utf16
255 * Pointer to the first element in an array of jschars.
257 * @param[in] len
258 * Number of jschars in the @a utf16 array.
260 * @return @a utf8 if successful, or NULL if not. */
261 static struct string *
262 add_jschars_to_utf8_string(struct string *utf8,
263 const jschar *utf16, size_t len)
265 size_t pos;
267 for (pos = 0; pos < len; ) {
268 unicode_val_T unicode = utf16[pos++];
270 if (is_utf16_surrogate(unicode)) {
271 if (is_utf16_high_surrogate(unicode)
272 && pos < len
273 && is_utf16_low_surrogate(utf16[pos])) {
274 unicode = join_utf16_surrogates(unicode,
275 utf16[pos++]);
276 } else {
277 unicode = UCS_REPLACEMENT_CHARACTER;
281 if (unicode == 0) {
282 if (!add_char_to_string(utf8, '\0'))
283 return NULL;
284 } else {
285 if (!add_to_string(utf8, encode_utf8(unicode)))
286 return NULL;
290 return utf8;
293 /** Convert a JSString to a UTF-8 string.
295 * @param ctx
296 * For reporting errors.
297 * @param[in] jsstr
298 * The input string that should be converted. Must not be NULL.
299 * @param[out] length
300 * Optional. The number of bytes in the returned string,
301 * not counting the terminating null.
303 * @return the new string, which the caller must eventually free
304 * with mem_free(). On error, report the error to SpiderMonkey
305 * and return NULL; *@a length is then undefined. */
306 unsigned char *
307 jsstring_to_utf8(JSContext *ctx, JSString *jsstr, int *length)
309 size_t utf16_len;
310 const jschar *utf16;
311 struct string utf8;
313 utf16_len = JS_GetStringLength(jsstr);
314 utf16 = JS_GetStringCharsZ(ctx, jsstr); /* stays owned by jsstr */
315 if (utf16 == NULL) {
316 /* JS_GetStringChars doesn't have a JSContext *
317 * parameter so it can't report the error
318 * (and can't collect garbage either). */
319 JS_ReportOutOfMemory(ctx);
320 return NULL;
323 if (!init_string(&utf8)) {
324 JS_ReportOutOfMemory(ctx);
325 return NULL;
328 if (!add_jschars_to_utf8_string(&utf8, utf16, utf16_len)) {
329 done_string(&utf8);
330 JS_ReportOutOfMemory(ctx);
331 return NULL;
334 if (length)
335 *length = utf8.length;
336 return utf8.source;