mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-30 21:55:31 +00:00
4f9004db76
- Move JSCF_Execute to the mocha thread. - Add JS_BeginRequest and JS_EndRequest for thread safety.
619 lines
17 KiB
C
619 lines
17 KiB
C
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
*
|
|
* The contents of this file are subject to the Netscape Public License
|
|
* Version 1.0 (the "NPL"); you may not use this file except in
|
|
* compliance with the NPL. You may obtain a copy of the NPL at
|
|
* http://www.mozilla.org/NPL/
|
|
*
|
|
* Software distributed under the NPL is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
|
|
* for the specific language governing rights and limitations under the
|
|
* NPL.
|
|
*
|
|
* The Initial Developer of this code under the NPL is Netscape
|
|
* Communications Corporation. Portions created by Netscape are
|
|
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
|
|
* Reserved.
|
|
*/
|
|
/*
|
|
jscookie.c -- javascript reflection of cookies for filters.
|
|
Created: Frederick G.M. Roeber <roeber@netscape.com>, 12-Jul-97.
|
|
Adopted: Judson Valeski, 1997.
|
|
Large chunks of this were stolen from jsmsg.c.
|
|
*/
|
|
|
|
#include "mkutils.h"
|
|
#include "mkutils.h"
|
|
#include "mkparse.h"
|
|
#include "mkgeturl.h"
|
|
#include "cookies.h"
|
|
#include "prefapi.h"
|
|
#include "jsapi.h"
|
|
#include "xp_core.h"
|
|
#include "xp_mcom.h"
|
|
#include "jscookie.h"
|
|
#include "ds.h"
|
|
#include "xpgetstr.h"
|
|
#include "net_xp_file.h"
|
|
|
|
extern int MK_ACCESS_JAVASCRIPT_COOKIE_FILTER;
|
|
|
|
static JSObject *filter_obj = NULL;
|
|
static JSContext *filter_context = NULL;
|
|
|
|
static JSBool error_reporter_installed = JS_FALSE;
|
|
static JSErrorReporter previous_error_reporter;
|
|
|
|
/* tells us when we should recompile the file. */
|
|
static JSBool need_compile = JS_TRUE;
|
|
|
|
/* This is the private instance data associated with a cookie */
|
|
typedef struct JSCookieData {
|
|
JSContext *js_context;
|
|
JSObject *js_object;
|
|
JSCFCookieData *data;
|
|
PRPackedBool property_changed, rejected, accepted, ask, decision_made;
|
|
} JSCookieData;
|
|
|
|
/* The properties of a cookie that we reflect */
|
|
enum cookie_slot {
|
|
COOKIE_PATH = -1,
|
|
COOKIE_DOMAIN = -2,
|
|
COOKIE_NAME = -3,
|
|
COOKIE_VALUE = -4,
|
|
COOKIE_EXPIRES = -5,
|
|
COOKIE_URL = -6,
|
|
COOKIE_IS_SECURE = -7,
|
|
COOKIE_IS_DOMAIN = -8,
|
|
COOKIE_PROMPT_PREF = -9,
|
|
COOKIE_PREF = -10
|
|
};
|
|
|
|
/*
|
|
* Should more of these be readonly? What does it mean for a cookie
|
|
* to de secure? -chouck
|
|
*/
|
|
static JSPropertySpec cookie_props[] = {
|
|
{ "path", COOKIE_PATH, JSPROP_ENUMERATE },
|
|
{ "domain", COOKIE_DOMAIN, JSPROP_ENUMERATE },
|
|
{ "name", COOKIE_NAME, JSPROP_ENUMERATE },
|
|
{ "value", COOKIE_VALUE, JSPROP_ENUMERATE },
|
|
{ "expires", COOKIE_EXPIRES, JSPROP_ENUMERATE },
|
|
{ "url", COOKIE_URL, JSPROP_ENUMERATE|JSPROP_READONLY },
|
|
{ "isSecure", COOKIE_IS_SECURE, JSPROP_ENUMERATE },
|
|
{ "isDomain", COOKIE_IS_DOMAIN, JSPROP_ENUMERATE },
|
|
{ "prompt", COOKIE_PROMPT_PREF, JSPROP_ENUMERATE|JSPROP_READONLY },
|
|
{ "preference", COOKIE_PREF, JSPROP_ENUMERATE|JSPROP_READONLY },
|
|
{ 0 }
|
|
};
|
|
|
|
PR_STATIC_CALLBACK(JSBool)
|
|
cookie_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
|
|
{
|
|
JSCookieData *data;
|
|
JSString *str;
|
|
jsint slot;
|
|
|
|
data = (JSCookieData *)JS_GetPrivate(cx, obj);
|
|
if (!data)
|
|
return JS_TRUE;
|
|
|
|
if (!JSVAL_IS_INT(id))
|
|
return JS_TRUE;
|
|
|
|
slot = JSVAL_TO_INT(id);
|
|
|
|
switch (slot) {
|
|
case COOKIE_PATH:
|
|
str = JS_NewStringCopyZ(cx, (const char *)data->data->path_from_header);
|
|
if( (JSString *)0 == str )
|
|
return JS_FALSE;
|
|
*vp = STRING_TO_JSVAL(str);
|
|
break;
|
|
case COOKIE_DOMAIN:
|
|
str = JS_NewStringCopyZ(cx, (const char *)data->data->host_from_header);
|
|
if( (JSString *)0 == str )
|
|
return JS_FALSE;
|
|
*vp = STRING_TO_JSVAL(str);
|
|
break;
|
|
case COOKIE_NAME:
|
|
str = JS_NewStringCopyZ(cx, (const char *)data->data->name_from_header);
|
|
if( (JSString *)0 == str )
|
|
return JS_FALSE;
|
|
*vp = STRING_TO_JSVAL(str);
|
|
break;
|
|
case COOKIE_VALUE:
|
|
str = JS_NewStringCopyZ(cx, (const char *)data->data->cookie_from_header);
|
|
if( (JSString *)0 == str )
|
|
return JS_FALSE;
|
|
*vp = STRING_TO_JSVAL(str);
|
|
break;
|
|
case COOKIE_EXPIRES:
|
|
*vp = INT_TO_JSVAL(data->data->expires);
|
|
break;
|
|
case COOKIE_URL:
|
|
str = JS_NewStringCopyZ(cx, (const char *)data->data->url);
|
|
if( (JSString *)0 == str )
|
|
return JS_FALSE;
|
|
*vp = STRING_TO_JSVAL(str);
|
|
break;
|
|
case COOKIE_IS_SECURE:
|
|
*vp = BOOLEAN_TO_JSVAL(data->data->secure);
|
|
break;
|
|
case COOKIE_IS_DOMAIN:
|
|
*vp = BOOLEAN_TO_JSVAL(data->data->domain);
|
|
break;
|
|
case COOKIE_PROMPT_PREF:
|
|
*vp = BOOLEAN_TO_JSVAL(data->data->prompt);
|
|
break;
|
|
case COOKIE_PREF:
|
|
*vp = INT_TO_JSVAL(data->data->preference);
|
|
break;
|
|
}
|
|
|
|
return JS_TRUE;
|
|
}
|
|
|
|
PR_STATIC_CALLBACK(JSBool)
|
|
cookie_setProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
|
|
{
|
|
JSCookieData *data;
|
|
jsint slot;
|
|
PRInt32 i;
|
|
JSBool b;
|
|
|
|
data = (JSCookieData *)JS_GetPrivate(cx, obj);
|
|
if (!data)
|
|
return JS_TRUE;
|
|
|
|
if (!JSVAL_IS_INT(id))
|
|
return JS_TRUE;
|
|
|
|
slot = JSVAL_TO_INT(id);
|
|
|
|
if (slot == COOKIE_PATH) {
|
|
data->data->path_from_header = JS_GetStringBytes(JSVAL_TO_STRING(*vp));
|
|
}
|
|
else if (slot == COOKIE_DOMAIN) {
|
|
data->data->host_from_header = JS_GetStringBytes(JSVAL_TO_STRING(*vp));
|
|
}
|
|
else if (slot == COOKIE_NAME) {
|
|
data->data->name_from_header = JS_GetStringBytes(JSVAL_TO_STRING(*vp));
|
|
}
|
|
else if (slot == COOKIE_VALUE) {
|
|
data->data->cookie_from_header = JS_GetStringBytes(JSVAL_TO_STRING(*vp));
|
|
}
|
|
else if (slot == COOKIE_EXPIRES) {
|
|
if( !JS_ValueToInt32(cx, *vp, (long *)&i) )
|
|
return JS_FALSE;
|
|
data->data->expires = i;
|
|
}
|
|
else if (slot == COOKIE_IS_SECURE) {
|
|
if( !JS_ValueToBoolean(cx, *vp, &b) )
|
|
return JS_FALSE;
|
|
data->data->secure = b;
|
|
}
|
|
else if (slot == COOKIE_IS_DOMAIN) {
|
|
if( !JS_ValueToBoolean(cx, *vp, &b) )
|
|
return JS_FALSE;
|
|
data->data->domain = b;
|
|
}
|
|
|
|
data->property_changed = TRUE;
|
|
return JS_TRUE;
|
|
}
|
|
|
|
PR_STATIC_CALLBACK(void)
|
|
cookie_finalize(JSContext *cx, JSObject *obj)
|
|
{
|
|
JSCookieData *cookie;
|
|
cookie = JS_GetPrivate(cx, obj);
|
|
FREEIF(cookie);
|
|
}
|
|
|
|
/* So we can possibly add functions "global" to filters... */
|
|
static JSClass global_class = {
|
|
"CookieFilters", 0 /* no private data */,
|
|
JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
|
|
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub
|
|
};
|
|
|
|
static JSClass js_cookie_class = {
|
|
"Cookie", JSCLASS_HAS_PRIVATE,
|
|
JS_PropertyStub, JS_PropertyStub, cookie_getProperty, cookie_setProperty,
|
|
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, cookie_finalize
|
|
};
|
|
|
|
/* cookie.accept() -- mark it okay */
|
|
PR_STATIC_CALLBACK(JSBool)
|
|
cookie_accept(JSContext *cx, JSObject *obj, uint argc, jsval *argv, jsval * rval)
|
|
{
|
|
JSCookieData *data;
|
|
|
|
if (!(data = (JSCookieData*)JS_GetInstancePrivate(cx, obj, &js_cookie_class, argv)))
|
|
return JS_FALSE;
|
|
|
|
data->accepted = TRUE;
|
|
data->rejected = FALSE;
|
|
data->ask = FALSE;
|
|
data->decision_made = TRUE;
|
|
|
|
return JS_TRUE;
|
|
}
|
|
|
|
/* cookie.reject() -- reject it out of hand */
|
|
PR_STATIC_CALLBACK(JSBool)
|
|
cookie_reject(JSContext *cx, JSObject *obj, uint argc, jsval *argv, jsval * rval)
|
|
{
|
|
JSCookieData *data;
|
|
|
|
if (!(data = (JSCookieData*)JS_GetInstancePrivate(cx, obj, &js_cookie_class, argv)))
|
|
return JS_FALSE;
|
|
|
|
data->accepted = FALSE;
|
|
data->rejected = TRUE;
|
|
data->ask = FALSE;
|
|
data->decision_made = TRUE;
|
|
|
|
return JS_TRUE;
|
|
}
|
|
|
|
/* cookie.ask() -- ask the luser, even if that pref is off */
|
|
PR_STATIC_CALLBACK(JSBool)
|
|
cookie_ask(JSContext *cx, JSObject *obj, uint argc, jsval *argv, jsval * rval)
|
|
{
|
|
JSCookieData *data;
|
|
|
|
if (!(data = (JSCookieData*)JS_GetInstancePrivate(cx, obj, &js_cookie_class, argv)))
|
|
return JS_FALSE;
|
|
|
|
data->accepted = FALSE;
|
|
data->rejected = FALSE;
|
|
data->ask = TRUE;
|
|
data->decision_made = TRUE;
|
|
|
|
return JS_TRUE;
|
|
}
|
|
|
|
/* cookie.confirm() -- pop up a confirmation box */
|
|
PR_STATIC_CALLBACK(JSBool)
|
|
cookie_confirm(JSContext *cx, JSObject *obj, uint argc, jsval *argv, jsval * rval)
|
|
{
|
|
JSCookieData *data;
|
|
JSString *str;
|
|
char *msg = (char *)0;
|
|
Bool result;
|
|
MWContext * context = XP_FindSomeContext();
|
|
|
|
if (argc < 1 || !context)
|
|
return JS_FALSE;
|
|
|
|
if (!(data = (JSCookieData*)JS_GetInstancePrivate(cx, obj, &js_cookie_class, argv)))
|
|
return JS_FALSE;
|
|
|
|
str = JS_ValueToString(cx, argv[0]);
|
|
if (!str)
|
|
return JS_FALSE;
|
|
|
|
StrAllocCopy(msg, XP_GetString(MK_ACCESS_JAVASCRIPT_COOKIE_FILTER));
|
|
StrAllocCat(msg, JS_GetStringBytes(str));
|
|
if (!msg)
|
|
return JS_FALSE;
|
|
|
|
result = FE_Confirm(context, msg);
|
|
FREEIF(msg);
|
|
|
|
*rval = BOOLEAN_TO_JSVAL(result);
|
|
|
|
return JS_TRUE;
|
|
}
|
|
|
|
#if DEBUG
|
|
/* trace() -- outputs spew to stderr. Actually, this (or something like it
|
|
that perhaps outputs to the same file that the rest of the filter logging code
|
|
writes to) would probably be very useful in the normal course of writing filters. */
|
|
PR_STATIC_CALLBACK(JSBool)
|
|
cookie_filter_trace(JSContext *cx, JSObject * obj, uint argc, jsval *argv, jsval * rval)
|
|
{
|
|
|
|
if (argc > 0)
|
|
{
|
|
JSString *str;
|
|
const char *trace_str;
|
|
if (!(str = JS_ValueToString(cx, argv[0])))
|
|
return JS_FALSE;
|
|
|
|
trace_str = JS_GetStringBytes(str);
|
|
if (*trace_str != '\0')
|
|
{
|
|
fprintf (stderr, "cookie filter trace: %s\n", trace_str);
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
return JS_FALSE;
|
|
}
|
|
#endif
|
|
|
|
static JSFunctionSpec cookie_methods[] = {
|
|
{ "accept", cookie_accept, 0 },
|
|
{ "reject", cookie_reject, 0 },
|
|
{ "ask", cookie_ask, 0 },
|
|
{ "confirm", cookie_confirm, 1 },
|
|
{ 0 }
|
|
};
|
|
|
|
static JSFunctionSpec filter_methods[] = {
|
|
#ifdef DEBUG
|
|
{ "trace", cookie_filter_trace, 1 },
|
|
#endif
|
|
{ 0 }
|
|
};
|
|
|
|
PRIVATE void
|
|
destroyJSCookieFilterStuff(void)
|
|
{
|
|
filter_obj = NULL;
|
|
}
|
|
|
|
/*
|
|
* This function used to take an MWContext and store it as the private data
|
|
* of the filter object. That is a no-no since the filter_obj is going to
|
|
* be around until the end of time but there is no guarentee that the
|
|
* context won't get free'd out from under us. The solution is to not
|
|
* hold onto any particular context but just call XP_FindSomeContext() or
|
|
* some derivative of it when we need to.
|
|
*/
|
|
PRIVATE JSContext *
|
|
initializeJSCookieFilterStuff()
|
|
{
|
|
|
|
/* Only bother initializing once */
|
|
if (filter_obj)
|
|
return filter_context;
|
|
|
|
/* If we can't get the mozilla-thread global context just bail */
|
|
if ( (PREF_OK != PREF_GetConfigContext(&filter_context))
|
|
|| !filter_context)
|
|
return NULL;
|
|
|
|
JS_BeginRequest(filter_context);
|
|
/* create our "global" object. We make the message object a child of this */
|
|
filter_obj = JS_NewObject(filter_context, &global_class, NULL, NULL);
|
|
|
|
/* MLM - don't do JS_InitStandardClasses() twice */
|
|
if (!filter_obj
|
|
|| !JS_DefineFunctions(filter_context, filter_obj, filter_methods))
|
|
{
|
|
JS_EndRequest(filter_context);
|
|
destroyJSCookieFilterStuff();
|
|
return NULL;
|
|
}
|
|
|
|
JS_EndRequest(filter_context);
|
|
return filter_context;
|
|
}
|
|
|
|
PRIVATE JSObject *
|
|
newCookieObject(void)
|
|
{
|
|
JSObject *rv;
|
|
JSCookieData *cookie_data;
|
|
|
|
rv = JS_DefineObject(filter_context, filter_obj,
|
|
"cookie", &js_cookie_class,
|
|
NULL, JSPROP_ENUMERATE);
|
|
|
|
if( (JSObject *)0 == rv )
|
|
return (JSObject *)0;
|
|
|
|
cookie_data = PR_NEWZAP(JSCookieData);
|
|
|
|
if( (JSCookieData *)0 == cookie_data )
|
|
return (JSObject *)0;
|
|
|
|
if( !JS_SetPrivate(filter_context, rv, cookie_data)
|
|
|| !JS_DefineProperties(filter_context, rv, cookie_props)
|
|
|| !JS_DefineFunctions(filter_context, rv, cookie_methods)) {
|
|
return (JSObject *)0;
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
PR_STATIC_CALLBACK(void)
|
|
jscookie_ErrorReporter(JSContext *cx, const char *message, JSErrorReport *report)
|
|
{
|
|
char *msg = NULL;
|
|
MWContext *context;
|
|
|
|
context = XP_FindSomeContext();
|
|
if(!context || !report)
|
|
return;
|
|
|
|
/*XXX: i18n-ise this */
|
|
msg = PR_sprintf_append(NULL,
|
|
"JavaScript Cookie Filter Error:\n"
|
|
"You will be prompted manually to accept or reject this cookie.\n"
|
|
"Filename: %s\n"
|
|
"Line #: %u\n"
|
|
"%s\n"
|
|
"%.*s\n",
|
|
report->filename,
|
|
report->lineno,
|
|
report->linebuf,
|
|
(int)(report->tokenptr - report->linebuf) + 1,
|
|
"^"
|
|
);
|
|
|
|
if (!msg)
|
|
return;
|
|
|
|
FE_Alert(context, msg);
|
|
PR_Free(msg);
|
|
|
|
return;
|
|
}
|
|
|
|
PRIVATE JSBool
|
|
compileJSCookieFilters(void)
|
|
{
|
|
static time_t m_time = 0; /* the last modification time of filters.js */
|
|
static JSBool ret_val = JS_FALSE;
|
|
char *filename;
|
|
XP_File fp;
|
|
XP_StatStruct stats;
|
|
|
|
if (!need_compile)
|
|
return ret_val;
|
|
|
|
filename = WH_FileName("", xpJSCookieFilters);
|
|
|
|
PR_LogPrint("+Filename for script filter is %s\n", filename);
|
|
|
|
/* If we can't get to the file, get the hell outa dodge. */
|
|
if(NET_XP_Stat(filename, &stats, xpJSCookieFilters))
|
|
return ret_val;
|
|
|
|
if (stats.st_mtime > m_time || need_compile)
|
|
{
|
|
long fileLength;
|
|
char *buffer;
|
|
jsval rval;
|
|
|
|
m_time = stats.st_mtime;
|
|
|
|
fileLength = stats.st_size;
|
|
if (fileLength <= 1)
|
|
{
|
|
ret_val = JS_FALSE;
|
|
return ret_val;
|
|
}
|
|
|
|
if( !(fp = NET_XP_FileOpen(filename, xpJSCookieFilters, "r")) ) {
|
|
ret_val = JS_FALSE;
|
|
return ret_val;
|
|
}
|
|
|
|
buffer = (char*)malloc(fileLength);
|
|
if (!buffer) {
|
|
NET_XP_FileClose(fp);
|
|
ret_val = JS_FALSE;
|
|
return ret_val;
|
|
}
|
|
|
|
fileLength = NET_XP_FileRead(buffer, fileLength, fp);
|
|
|
|
NET_XP_FileClose(fp);
|
|
|
|
PR_LogPrint("+Compiling filters.js...\n");
|
|
|
|
ret_val = JS_EvaluateScript(filter_context, filter_obj, buffer, fileLength,
|
|
filename, 1, &rval);
|
|
|
|
PR_LogPrint("+Done.\n");
|
|
|
|
PR_Free(buffer);
|
|
|
|
need_compile = JS_FALSE;
|
|
}
|
|
|
|
return ret_val;
|
|
}
|
|
|
|
PUBLIC JSCFResult
|
|
JSCF_Execute(
|
|
MWContext *mwcontext,
|
|
const char *script_name,
|
|
JSCFCookieData *data,
|
|
Bool *data_changed
|
|
)
|
|
{
|
|
jsval result;
|
|
jsval filter_arg; /* we will this in with the message object we create. */
|
|
JSObject *cookie_obj;
|
|
JSCookieData *cookie_data;
|
|
|
|
if (!script_name)
|
|
return JSCF_error;
|
|
|
|
/* initialize the filter stuff, and bomb out early if it fails */
|
|
if (!initializeJSCookieFilterStuff())
|
|
return JSCF_error;
|
|
|
|
JS_BeginRequest(filter_context);
|
|
|
|
/*
|
|
* try loading (reloading if necessary) the filter file before bothering
|
|
* to create any JS-objects
|
|
*/
|
|
if (!compileJSCookieFilters()) {
|
|
JS_EndRequest(filter_context);
|
|
return JSCF_error;
|
|
}
|
|
|
|
if (!error_reporter_installed)
|
|
{
|
|
error_reporter_installed = JS_TRUE;
|
|
previous_error_reporter = JS_SetErrorReporter(filter_context,
|
|
jscookie_ErrorReporter);
|
|
}
|
|
|
|
|
|
cookie_obj = newCookieObject();
|
|
if( (JSObject *)0 == cookie_obj ) {
|
|
JS_EndRequest(filter_context);
|
|
return JSCF_error;
|
|
}
|
|
|
|
cookie_data = (JSCookieData *)JS_GetPrivate(filter_context, cookie_obj);
|
|
cookie_data->js_context = filter_context;
|
|
cookie_data->js_object = cookie_obj;
|
|
cookie_data->data = data;
|
|
cookie_data->property_changed = FALSE;
|
|
cookie_data->rejected = FALSE;
|
|
cookie_data->accepted = FALSE;
|
|
cookie_data->decision_made = FALSE;
|
|
|
|
filter_arg = OBJECT_TO_JSVAL(cookie_obj);
|
|
JS_CallFunctionName(filter_context, filter_obj, script_name, 1,
|
|
&filter_arg, &result);
|
|
|
|
JS_EndRequest(filter_context);
|
|
|
|
*data_changed = cookie_data->property_changed;
|
|
if( cookie_data->decision_made ) {
|
|
if( cookie_data->rejected )
|
|
return JSCF_reject;
|
|
else if( cookie_data->accepted )
|
|
return JSCF_accept;
|
|
else if( cookie_data->ask )
|
|
return JSCF_ask;
|
|
}
|
|
|
|
return JSCF_whatever;
|
|
}
|
|
|
|
PUBLIC void
|
|
JSCF_Cleanup(void)
|
|
{
|
|
TRACEMSG(("+Cleaning up JS Cookie Filters"));
|
|
|
|
need_compile = JS_TRUE;
|
|
|
|
if (filter_context)
|
|
{
|
|
JS_BeginRequest(filter_context);
|
|
if (error_reporter_installed)
|
|
{
|
|
error_reporter_installed = JS_FALSE;
|
|
JS_SetErrorReporter(filter_context, previous_error_reporter);
|
|
}
|
|
|
|
JS_GC(filter_context);
|
|
|
|
destroyJSCookieFilterStuff();
|
|
JS_EndRequest(filter_context);
|
|
}
|
|
}
|