Subj : Re: spidermonkey CGI To : netscape.public.mozilla.jseng From : Brendan Eich Date : Mon Jun 09 2003 05:36 pm This is a multi-part message in MIME format. --------------060400000006070402040009 Content-Type: text/plain; charset=ISO-8859-1; format=flowed Content-Transfer-Encoding: 7bit user@domain.invalid wrote: > Is it possible to use js for CGI? How can I get the $QUERY_STRING > environment variable? Or get the HTTP headers? I'm talking about an > ordinary shell script, not a server-side engine, e.g. > > #!/usr/bin/js > > print("Content-type: text/html\n\n"); // ... > > Is there a reference? (I had to google to find out, er guess, that > spidermonkey had a print function, based on a rhino example). Unfortunately, the current js.c can't be invoked without a script filename argument, but *with* subsequent arguments that are available via the top-level arguments object. So you might want to hack up js.c a bit, make it call getenv on several important variables such as QUERY_STRING, and then call JS_DefineProperty to expose them to the shell. But it would be better to add a built-in "environment" object to js.c by which arbitrary envariables could be reflected into JS. Then you could say "const QUERY_STRING = environment.QUERY_STRING;" and go to town. Something like the attached patch. Let me know how it works for you. The second attachment is a diff -pwu, just for easy reviewing -- don't apply it. Do apply the first attachment using patch to an up-to-date js.c. BTW, I also fixed js.c so you *can* invoke it with its script coming from stdin, but with trailing args available via the arguments object -- your #! magic would look like #! /usr/bin/js - $QUERY_STRING ... and you would pick up each envariable by its position, starting with QUERY_STRING's value in arguments[0]. /be --------------060400000006070402040009 Content-Type: text/plain; name="js.patch" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="js.patch" Index: js.c =================================================================== RCS file: /cvsroot/mozilla/js/src/js.c,v retrieving revision 3.65 diff -p -u -8 -r3.65 js.c --- js.c 3 Apr 2003 19:35:19 -0000 3.65 +++ js.c 9 Jun 2003 23:35:07 -0000 @@ -252,18 +252,18 @@ extern void add_history(char *line); static JSBool GetLine(JSContext *cx, char *bufp, FILE *file, const char *prompt) { #ifdef EDITLINE /* * Use readline only if file is stdin, because there's no way to specify * another handle. Are other filehandles interactive? */ if (file == stdin) { - char *linep; - if ((linep = readline(prompt)) == NULL) + char *linep = readline(prompt); + if (!linep) return JS_FALSE; if (linep[0] != '\0') add_history(linep); strcpy(bufp, linep); JS_free(cx, linep); bufp += strlen(bufp); *bufp++ = '\n'; *bufp = '\0'; @@ -272,17 +272,17 @@ GetLine(JSContext *cx, char *bufp, FILE { char line[256]; fprintf(gOutFile, prompt); fflush(gOutFile); #ifdef XP_MAC_MPW /* Print a CR after the prompt because MPW grabs the entire line when entering an interactive command */ fputc('\n', gOutFile); #endif - if (fgets(line, sizeof line, file) == NULL) + if (!fgets(line, sizeof line, file)) return JS_FALSE; strcpy(bufp, line); } return JS_TRUE; } static void Process(JSContext *cx, JSObject *obj, char *filename) @@ -292,17 +292,17 @@ Process(JSContext *cx, JSObject *obj, ch jsval result; JSString *str; char buffer[4096]; char *bufp; int lineno; int startline; FILE *file; - if (filename != NULL && strcmp(filename, "-") != 0) { + if (filename && strcmp(filename, "-") != 0) { file = fopen(filename, "r"); if (!file) { JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_CANT_OPEN, filename, strerror(errno)); gExitCode = EXITCODE_FILE_NOT_FOUND; return; } } else { @@ -413,96 +413,96 @@ ProcessArgs(JSContext *cx, JSObject *obj int i, j; char *filename = NULL; jsint length; jsval *vector; JSObject *argsObj; JSBool isInteractive = JS_TRUE; for (i=0; i < argc; i++) { - if (argv[i][0] == '-') { - switch (argv[i][1]) { - case 'v': - if (i+1 == argc) { - return usage(); - } - JS_SetVersion(cx, (JSVersion) atoi(argv[i+1])); - i++; - break; + if (argv[i][0] != '-' || argv[i][1] == '\0') { + filename = argv[i++]; + isInteractive = JS_FALSE; + break; + } - case 'w': - reportWarnings = JS_TRUE; - break; + switch (argv[i][1]) { + case 'v': + if (i+1 == argc) { + return usage(); + } + JS_SetVersion(cx, (JSVersion) atoi(argv[i+1])); + i++; + break; - case 'W': - reportWarnings = JS_FALSE; - break; + case 'w': + reportWarnings = JS_TRUE; + break; - case 's': - JS_ToggleOptions(cx, JSOPTION_STRICT); - break; + case 'W': + reportWarnings = JS_FALSE; + break; - case 'b': - gBranchLimit = atoi(argv[++i]); - JS_SetBranchCallback(cx, my_BranchCallback); - break; + case 's': + JS_ToggleOptions(cx, JSOPTION_STRICT); + break; - case 'c': - /* set stack chunk size */ - gStackChunkSize = atoi(argv[++i]); - break; + case 'b': + gBranchLimit = atoi(argv[++i]); + JS_SetBranchCallback(cx, my_BranchCallback); + break; - case 'f': - if (i+1 == argc) { - return usage(); - } - filename = argv[i+1]; - /* "-f -" means read from stdin */ - if (filename[0] == '-' && filename[1] == '\0') - filename = NULL; - Process(cx, obj, filename); - filename = NULL; - /* XXX: js -f foo.js should interpret foo.js and then - * drop into interactive mode, but that breaks test - * harness. Just execute foo.js for now. - */ - isInteractive = JS_FALSE; - i++; - break; + case 'c': + /* set stack chunk size */ + gStackChunkSize = atoi(argv[++i]); + break; - default: + case 'f': + if (i+1 == argc) { return usage(); } - } else { - filename = argv[i++]; + filename = argv[i+1]; + /* "-f -" means read from stdin */ + if (filename[0] == '-' && filename[1] == '\0') + filename = NULL; + Process(cx, obj, filename); + filename = NULL; + /* XXX: js -f foo.js should interpret foo.js and then + * drop into interactive mode, but that breaks test + * harness. Just execute foo.js for now. + */ isInteractive = JS_FALSE; + i++; break; + + default: + return usage(); } } length = argc - i; if (length == 0) { vector = NULL; } else { vector = (jsval *) JS_malloc(cx, length * sizeof(jsval)); - if (vector == NULL) + if (!vector) return 1; for (j = 0; j < length; j++) { JSString *str = JS_NewStringCopyZ(cx, argv[i++]); - if (str == NULL) + if (!str) return 1; vector[j] = STRING_TO_JSVAL(str); } } argsObj = JS_NewArrayObject(cx, length, vector); if (vector) JS_free(cx, vector); - if (argsObj == NULL) + if (!argsObj) return 1; if (!JS_DefineProperty(cx, obj, "arguments", OBJECT_TO_JSVAL(argsObj), NULL, NULL, 0)) { return 1; } if (filename || isInteractive) @@ -979,17 +979,17 @@ Disassemble(JSContext *cx, JSObject *obj #define SHOW_FLAG(flag) if (flags & JSFUN_##flag) fputs(" " #flag, stdout); SHOW_FLAG(LAMBDA); SHOW_FLAG(SETTER); SHOW_FLAG(GETTER); SHOW_FLAG(BOUND_METHOD); SHOW_FLAG(HEAVYWEIGHT); - + #undef SHOW_FLAG putchar('\n'); } } js_Disassemble(cx, script, lines, stdout); SrcNotes(cx, script); TryNotes(cx, script); @@ -1985,16 +1985,109 @@ global_resolve(JSContext *cx, JSObject * static JSClass global_class = { "global", JSCLASS_NEW_RESOLVE, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, global_enumerate, (JSResolveOp) global_resolve, JS_ConvertStub, JS_FinalizeStub }; +#ifdef XP_UNIX + +static JSBool +env_setProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + JSString *idstr, *valstr; + const char *name, *value; + + idstr = JS_ValueToString(cx, id); + valstr = JS_ValueToString(cx, *vp); + if (!idstr || !valstr) + return JS_FALSE; + name = JS_GetStringBytes(idstr); + value = JS_GetStringBytes(valstr); + if (setenv(name, value, 1) < 0) { + JS_ReportError(cx, "can't set envariable %s to %s", name, value); + return JS_FALSE; + } + *vp = STRING_TO_JSVAL(valstr); + return JS_TRUE; +} + +static JSBool +env_enumerate(JSContext *cx, JSObject *obj) +{ + extern char **environ; + static JSBool reflected; + char **evp, *name, *value; + JSString *valstr; + JSBool ok; + + if (reflected) + return JS_TRUE; + + for (evp = environ; (name = *evp) != NULL; evp++) { + value = strchr(name, '='); + if (!value) + continue; + *value++ = '\0'; + valstr = JS_NewStringCopyZ(cx, value); + if (!valstr) { + ok = JS_FALSE; + } else { + ok = JS_DefineProperty(cx, obj, name, STRING_TO_JSVAL(valstr), + NULL, NULL, JSPROP_ENUMERATE); + } + value[-1] = '='; + if (!ok) + return JS_FALSE; + } + + reflected = JS_TRUE; + return JS_TRUE; +} + +static JSBool +env_resolve(JSContext *cx, JSObject *obj, jsval id, uintN flags, + JSObject **objp) +{ + JSString *idstr, *valstr; + const char *name, *value; + + if (flags & JSRESOLVE_ASSIGNING) + return JS_TRUE; + + idstr = JS_ValueToString(cx, id); + if (!idstr) + return JS_FALSE; + name = JS_GetStringBytes(idstr); + value = getenv(name); + if (value) { + valstr = JS_NewStringCopyZ(cx, value); + if (!valstr) + return JS_FALSE; + if (!JS_DefineProperty(cx, obj, name, STRING_TO_JSVAL(valstr), + NULL, NULL, JSPROP_ENUMERATE)) { + return JS_FALSE; + } + *objp = obj; + } + return JS_TRUE; +} + +static JSClass env_class = { + "global", JSCLASS_NEW_RESOLVE, + JS_PropertyStub, JS_PropertyStub, + JS_PropertyStub, env_setProperty, + env_enumerate, (JSResolveOp) env_resolve, + JS_ConvertStub, JS_FinalizeStub +}; + +#endif /* XP_UNIX */ + int main(int argc, char **argv) { JSVersion version; JSRuntime *rt; JSContext *cx; JSObject *glob, *it; int result; @@ -2012,51 +2105,51 @@ main(int argc, char **argv) setbuf(stderr,0); #endif gErrFile = stderr; gOutFile = stdout; #ifdef XP_MAC #ifndef XP_MAC_MPW - initConsole("\pJavaScript Shell", "Welcome to js shell.", &argc, &argv); + initConsole("\pJavaScript Shell", "Welcome to js shell.", &argc, &argv); #endif #endif #ifdef MAC_TEST_HACK /* - Open a file "testArgs.txt" and read each line into argc/argv. - Re-direct all output to "results.txt" + Open a file "testArgs.txt" and read each line into argc/argv. + Re-direct all output to "results.txt" */ - { - char argText[256]; - FILE *f = fopen("testargs.txt", "r"); - if (f != NULL) { - int maxArgs = 32; /* arbitrary max !!! */ - int argText_strlen; - argc = 1; - argv = malloc(sizeof(char *) * maxArgs); - argv[0] = NULL; - while (fgets(argText, 255, f) != NULL) { - /* argText includes '\n' */ - argText_strlen = strlen(argText); - argv[argc] = malloc(argText_strlen); - strncpy(argv[argc], argText, - argText_strlen - 1); - argv[argc][argText_strlen - 1] = '\0'; - argc++; - if (argc >= maxArgs) break; - } - fclose(f); - } - gTestResultFile = fopen("results.txt", "w"); + { + char argText[256]; + FILE *f = fopen("testargs.txt", "r"); + if (f) { + int maxArgs = 32; /* arbitrary max !!! */ + int argText_strlen; + argc = 1; + argv = malloc(sizeof(char *) * maxArgs); + argv[0] = NULL; + while (fgets(argText, 255, f)) { + /* argText includes '\n' */ + argText_strlen = strlen(argText); + argv[argc] = malloc(argText_strlen); + strncpy(argv[argc], argText, argText_strlen - 1); + argv[argc][argText_strlen - 1] = '\0'; + argc++; + if (argc >= maxArgs) + break; + } + fclose(f); } + gTestResultFile = fopen("results.txt", "w"); + } - gErrFile = gTestResultFile; - gOutFile = gTestResultFile; + gErrFile = gTestResultFile; + gOutFile = gTestResultFile; #endif version = JSVERSION_DEFAULT; argc--; argv++; rt = JS_NewRuntime(8L * 1024L * 1024L); @@ -2125,18 +2218,23 @@ main(int argc, char **argv) */ #endif /* JSDEBUGGER_JAVA_UI */ #ifdef JSDEBUGGER_C_UI JSDB_InitDebugger(rt, _jsdc, 0); #endif /* JSDEBUGGER_C_UI */ #endif /* JSDEBUGGER */ #ifdef LIVECONNECT - if (!JSJ_SimpleInit(cx, glob, java_vm, getenv("CLASSPATH"))) - return 1; + if (!JSJ_SimpleInit(cx, glob, java_vm, getenv("CLASSPATH"))) + return 1; +#endif + +#ifdef XP_UNIX + if (!JS_DefineObject(cx, glob, "environment", &env_class, NULL, 0)) + return 1; #endif result = ProcessArgs(cx, glob, argv, argc); #ifdef JSDEBUGGER if (_jsdc) JSD_DebuggerOff(_jsdc); #endif /* JSDEBUGGER */ --------------060400000006070402040009 Content-Type: text/plain; name="js.patch-w" Content-Transfer-Encoding: 7bit Content-Disposition: inline; filename="js.patch-w" Index: js.c =================================================================== RCS file: /cvsroot/mozilla/js/src/js.c,v retrieving revision 3.65 diff -p -u -8 -w -r3.65 js.c --- js.c 3 Apr 2003 19:35:19 -0000 3.65 +++ js.c 9 Jun 2003 23:35:02 -0000 @@ -252,18 +252,18 @@ extern void add_history(char *line); static JSBool GetLine(JSContext *cx, char *bufp, FILE *file, const char *prompt) { #ifdef EDITLINE /* * Use readline only if file is stdin, because there's no way to specify * another handle. Are other filehandles interactive? */ if (file == stdin) { - char *linep; - if ((linep = readline(prompt)) == NULL) + char *linep = readline(prompt); + if (!linep) return JS_FALSE; if (linep[0] != '\0') add_history(linep); strcpy(bufp, linep); JS_free(cx, linep); bufp += strlen(bufp); *bufp++ = '\n'; *bufp = '\0'; @@ -272,17 +272,17 @@ GetLine(JSContext *cx, char *bufp, FILE { char line[256]; fprintf(gOutFile, prompt); fflush(gOutFile); #ifdef XP_MAC_MPW /* Print a CR after the prompt because MPW grabs the entire line when entering an interactive command */ fputc('\n', gOutFile); #endif - if (fgets(line, sizeof line, file) == NULL) + if (!fgets(line, sizeof line, file)) return JS_FALSE; strcpy(bufp, line); } return JS_TRUE; } static void Process(JSContext *cx, JSObject *obj, char *filename) @@ -292,17 +292,17 @@ Process(JSContext *cx, JSObject *obj, ch jsval result; JSString *str; char buffer[4096]; char *bufp; int lineno; int startline; FILE *file; - if (filename != NULL && strcmp(filename, "-") != 0) { + if (filename && strcmp(filename, "-") != 0) { file = fopen(filename, "r"); if (!file) { JS_ReportErrorNumber(cx, my_GetErrorMessage, NULL, JSSMSG_CANT_OPEN, filename, strerror(errno)); gExitCode = EXITCODE_FILE_NOT_FOUND; return; } } else { @@ -413,17 +413,22 @@ ProcessArgs(JSContext *cx, JSObject *obj int i, j; char *filename = NULL; jsint length; jsval *vector; JSObject *argsObj; JSBool isInteractive = JS_TRUE; for (i=0; i < argc; i++) { - if (argv[i][0] == '-') { + if (argv[i][0] != '-' || argv[i][1] == '\0') { + filename = argv[i++]; + isInteractive = JS_FALSE; + break; + } + switch (argv[i][1]) { case 'v': if (i+1 == argc) { return usage(); } JS_SetVersion(cx, (JSVersion) atoi(argv[i+1])); i++; break; @@ -466,43 +471,38 @@ ProcessArgs(JSContext *cx, JSObject *obj */ isInteractive = JS_FALSE; i++; break; default: return usage(); } - } else { - filename = argv[i++]; - isInteractive = JS_FALSE; - break; - } } length = argc - i; if (length == 0) { vector = NULL; } else { vector = (jsval *) JS_malloc(cx, length * sizeof(jsval)); - if (vector == NULL) + if (!vector) return 1; for (j = 0; j < length; j++) { JSString *str = JS_NewStringCopyZ(cx, argv[i++]); - if (str == NULL) + if (!str) return 1; vector[j] = STRING_TO_JSVAL(str); } } argsObj = JS_NewArrayObject(cx, length, vector); if (vector) JS_free(cx, vector); - if (argsObj == NULL) + if (!argsObj) return 1; if (!JS_DefineProperty(cx, obj, "arguments", OBJECT_TO_JSVAL(argsObj), NULL, NULL, 0)) { return 1; } if (filename || isInteractive) @@ -1985,16 +1985,109 @@ global_resolve(JSContext *cx, JSObject * static JSClass global_class = { "global", JSCLASS_NEW_RESOLVE, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, global_enumerate, (JSResolveOp) global_resolve, JS_ConvertStub, JS_FinalizeStub }; +#ifdef XP_UNIX + +static JSBool +env_setProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) +{ + JSString *idstr, *valstr; + const char *name, *value; + + idstr = JS_ValueToString(cx, id); + valstr = JS_ValueToString(cx, *vp); + if (!idstr || !valstr) + return JS_FALSE; + name = JS_GetStringBytes(idstr); + value = JS_GetStringBytes(valstr); + if (setenv(name, value, 1) < 0) { + JS_ReportError(cx, "can't set envariable %s to %s", name, value); + return JS_FALSE; + } + *vp = STRING_TO_JSVAL(valstr); + return JS_TRUE; +} + +static JSBool +env_enumerate(JSContext *cx, JSObject *obj) +{ + extern char **environ; + static JSBool reflected; + char **evp, *name, *value; + JSString *valstr; + JSBool ok; + + if (reflected) + return JS_TRUE; + + for (evp = environ; (name = *evp) != NULL; evp++) { + value = strchr(name, '='); + if (!value) + continue; + *value++ = '\0'; + valstr = JS_NewStringCopyZ(cx, value); + if (!valstr) { + ok = JS_FALSE; + } else { + ok = JS_DefineProperty(cx, obj, name, STRING_TO_JSVAL(valstr), + NULL, NULL, JSPROP_ENUMERATE); + } + value[-1] = '='; + if (!ok) + return JS_FALSE; + } + + reflected = JS_TRUE; + return JS_TRUE; +} + +static JSBool +env_resolve(JSContext *cx, JSObject *obj, jsval id, uintN flags, + JSObject **objp) +{ + JSString *idstr, *valstr; + const char *name, *value; + + if (flags & JSRESOLVE_ASSIGNING) + return JS_TRUE; + + idstr = JS_ValueToString(cx, id); + if (!idstr) + return JS_FALSE; + name = JS_GetStringBytes(idstr); + value = getenv(name); + if (value) { + valstr = JS_NewStringCopyZ(cx, value); + if (!valstr) + return JS_FALSE; + if (!JS_DefineProperty(cx, obj, name, STRING_TO_JSVAL(valstr), + NULL, NULL, JSPROP_ENUMERATE)) { + return JS_FALSE; + } + *objp = obj; + } + return JS_TRUE; +} + +static JSClass env_class = { + "global", JSCLASS_NEW_RESOLVE, + JS_PropertyStub, JS_PropertyStub, + JS_PropertyStub, env_setProperty, + env_enumerate, (JSResolveOp) env_resolve, + JS_ConvertStub, JS_FinalizeStub +}; + +#endif /* XP_UNIX */ + int main(int argc, char **argv) { JSVersion version; JSRuntime *rt; JSContext *cx; JSObject *glob, *it; int result; @@ -2024,31 +2117,31 @@ main(int argc, char **argv) #ifdef MAC_TEST_HACK /* Open a file "testArgs.txt" and read each line into argc/argv. Re-direct all output to "results.txt" */ { char argText[256]; FILE *f = fopen("testargs.txt", "r"); - if (f != NULL) { + if (f) { int maxArgs = 32; /* arbitrary max !!! */ int argText_strlen; argc = 1; argv = malloc(sizeof(char *) * maxArgs); argv[0] = NULL; - while (fgets(argText, 255, f) != NULL) { + while (fgets(argText, 255, f)) { /* argText includes '\n' */ argText_strlen = strlen(argText); argv[argc] = malloc(argText_strlen); - strncpy(argv[argc], argText, - argText_strlen - 1); + strncpy(argv[argc], argText, argText_strlen - 1); argv[argc][argText_strlen - 1] = '\0'; argc++; - if (argc >= maxArgs) break; + if (argc >= maxArgs) + break; } fclose(f); } gTestResultFile = fopen("results.txt", "w"); } gErrFile = gTestResultFile; gOutFile = gTestResultFile; @@ -2127,16 +2220,21 @@ main(int argc, char **argv) #ifdef JSDEBUGGER_C_UI JSDB_InitDebugger(rt, _jsdc, 0); #endif /* JSDEBUGGER_C_UI */ #endif /* JSDEBUGGER */ #ifdef LIVECONNECT if (!JSJ_SimpleInit(cx, glob, java_vm, getenv("CLASSPATH"))) return 1; +#endif + +#ifdef XP_UNIX + if (!JS_DefineObject(cx, glob, "environment", &env_class, NULL, 0)) + return 1; #endif result = ProcessArgs(cx, glob, argv, argc); #ifdef JSDEBUGGER if (_jsdc) JSD_DebuggerOff(_jsdc); #endif /* JSDEBUGGER */ --------------060400000006070402040009-- .