Milán Major

netscape-revival

1 branch
Code

lib/libmocha/lm_url.c

/*
** Mocha reflection of the current Navigator URL (Location) object.
**
** Brendan Eich, 9/8/95
*/
#include "lm.h"
#include "xp.h"
#include "net.h"		/* for URL_Struct */
#include "shist.h"		/* for session history stuff */
#include "structs.h"		/* for MWContext */
#include "fe_proto.h"
#include "layout.h"		/* included via -I../layout */
#include "mkparse.h"		/* included via -I../libnet */

/* NB: these named properties use non-negative slots; code below knows that. */
enum url_slot {
    URL_HREF,			/* the entire URL as a string */
    URL_PROTOCOL,		/* protocol:... */
    URL_HOST,			/* protocol://host/... */
    URL_HOSTNAME,		/* protocol://hostname:... */
    URL_PORT,			/* protocol://hostname:port/... */
    URL_PATHNAME,		/* protocol://host/pathname[#?...] */
    URL_HASH,			/* protocol://host/pathname#hash */
    URL_SEARCH,			/* protocol://host/pathname?search */
    URL_TARGET,			/* target window or null */
    LOC_ASSIGN  = -1,
    LOC_RELOAD  = -2,
    LOC_REPLACE = -3
};

static MochaPropertySpec url_props[] = {
    {"href",            URL_HREF,       MDF_ENUMERATE | MDF_TAINTED},
    {"protocol",        URL_PROTOCOL,   MDF_ENUMERATE | MDF_TAINTED},
    {"host",            URL_HOST,       MDF_ENUMERATE | MDF_TAINTED},
    {"hostname",        URL_HOSTNAME,   MDF_ENUMERATE | MDF_TAINTED},
    {"port",            URL_PORT,       MDF_ENUMERATE | MDF_TAINTED},
    {"pathname",        URL_PATHNAME,   MDF_ENUMERATE | MDF_TAINTED},
    {"hash",            URL_HASH,       MDF_ENUMERATE | MDF_TAINTED},
    {"search",          URL_SEARCH,     MDF_ENUMERATE | MDF_TAINTED},
    {"target",          URL_TARGET,     MDF_ENUMERATE},
    {lm_assign_str,     LOC_ASSIGN,     MDF_READONLY},
    {lm_reload_str,     LOC_RELOAD,     MDF_READONLY},
    {lm_replace_str,    LOC_REPLACE,    MDF_READONLY},
    {0}
};

#define ParseURL(url,part)	((url) ? NET_ParseURL(url,part) : 0)

static MochaBoolean
url_get_property(MochaContext *mc, MochaObject *obj, MochaSlot slot,
		 MochaDatum *dp)
{
    MochaURL *url;
    MochaAtom *atom;
    char *str, *port;

    url = obj->data;
    atom = 0;
    str = 0;

    switch (slot) {
      case URL_HREF:
	atom = url->href;
	break;

      case URL_PROTOCOL:
	str = ParseURL(MOCHA_GetAtomName(mc, url->href), GET_PROTOCOL_PART);
	break;

      case URL_HOST:
	str = ParseURL(MOCHA_GetAtomName(mc, url->href), GET_HOST_PART);
	break;

      case URL_HOSTNAME:
	str = ParseURL(MOCHA_GetAtomName(mc, url->href), GET_HOST_PART);
	if (str && (port = XP_STRCHR(str, ':')) != 0)
	    *port = '\0';
	break;

      case URL_PORT:
	str = ParseURL(MOCHA_GetAtomName(mc, url->href), GET_HOST_PART);
	if (str && (port = XP_STRCHR(str, ':')) != 0)
	    port++;
	else
	    port = "";
	atom = MOCHA_Atomize(mc, port);
	break;

      case URL_PATHNAME:
	str = ParseURL(MOCHA_GetAtomName(mc, url->href), GET_PATH_PART);
	break;

      case URL_HASH:
	str = ParseURL(MOCHA_GetAtomName(mc, url->href), GET_HASH_PART);
	break;

      case URL_SEARCH:
	str = ParseURL(MOCHA_GetAtomName(mc, url->href), GET_SEARCH_PART);
	break;

      case URL_TARGET:
	if (!url->target) {
	    *dp = MOCHA_null;
	    return MOCHA_TRUE;
	}
	atom = url->target;
	break;

      default:
	/* Don't mess with user-defined or method properties. */
	return MOCHA_TRUE;
    }

    if (!atom && str)
	atom = MOCHA_Atomize(mc, str);
    if (str)
	XP_FREE(str);
    if (!atom)
	return MOCHA_FALSE;
    MOCHA_INIT_DATUM(mc, dp, MOCHA_STRING, u.atom, atom);
    if ((dp->flags & MDF_TAINTED) && dp->taint == MOCHA_TAINT_IDENTITY)
	dp->taint = url->taint;
    return MOCHA_TRUE;
}

static MochaBoolean
url_set_property(MochaContext *mc, MochaObject *obj, MochaSlot slot,
		 MochaDatum *dp)
{
    MochaURL *url;
    const char *href, *name, *checked_href;
    char *new_href;
    uint16 new_taint;
    MochaBoolean free_href;
    MochaDatum tmp;
    MochaAtom *atom;
    MWContext *context;
    LO_AnchorData *anchor_data;
    MochaBoolean ok;

    if (slot < 0) {
	/* Don't mess with user-defined or method properties. */
	return MOCHA_TRUE;
    }

    if (dp->tag != MOCHA_STRING &&
	!MOCHA_ConvertDatum(mc, *dp, MOCHA_STRING, dp)) {
	return MOCHA_FALSE;
    }
    url = obj->data;
    ok = MOCHA_TRUE;

    switch (slot) {
      case URL_HREF:
	if (dp->u.atom != url->href) {
	    MOCHA_DropAtom(mc, url->href);
	    url->href = MOCHA_HoldAtom(mc, dp->u.atom);
	}
	url->taint = dp->taint;
	href = MOCHA_GetAtomName(mc, dp->u.atom);
	free_href = MOCHA_FALSE;
	break;

      case URL_PROTOCOL:
      case URL_HOST:
      case URL_HOSTNAME:
      case URL_PORT:
      case URL_PATHNAME:
      case URL_HASH:
      case URL_SEARCH:
	/* a component property changed -- recompute href and taint. */
	new_href = 0;
	new_taint = MOCHA_TAINT_IDENTITY;

#define GET_SLOT(aslot, ptmp) {                                               \
    if (aslot == slot) {                                                      \
	(ptmp)->u.atom = dp->u.atom;                                          \
	(ptmp)->taint = dp->taint;                                            \
    } else {                                                                  \
	if (!MOCHA_GetSlot(mc, obj, aslot, ptmp)) {                           \
	    if (new_href) XP_FREE(new_href);                                  \
	    return MOCHA_FALSE;                                               \
	}                                                                     \
    }                                                                         \
}

#define ADD_SLOT(aslot, separator) {                                          \
    GET_SLOT(aslot, &tmp);                                                    \
    name = MOCHA_GetAtomName(mc, tmp.u.atom);                                 \
    if (*name) {                                                              \
	if (separator) StrAllocCat(new_href, separator);                      \
	StrAllocCat(new_href, name);                                          \
	MOCHA_MIX_TAINT(mc, new_taint, tmp.taint);                            \
    }                                                                         \
}

	GET_SLOT(URL_PROTOCOL, &tmp);
	StrAllocCopy(new_href, MOCHA_GetAtomName(mc, tmp.u.atom));
	if (slot == URL_HOST) {
	    ADD_SLOT(URL_HOST, "//");
	} else {
	    ADD_SLOT(URL_HOSTNAME, "//");
	    ADD_SLOT(URL_PORT, ":");
	}
	ADD_SLOT(URL_PATHNAME, 0);
	ADD_SLOT(URL_HASH, "#");
	ADD_SLOT(URL_SEARCH, 0);

	if (!new_href) {
	    MOCHA_ReportOutOfMemory(mc);
	    return MOCHA_FALSE;
	}

	free_href = MOCHA_TRUE;
	href = new_href;
	atom = MOCHA_Atomize(mc, href);
	if (!atom) {
	    ok = MOCHA_FALSE;
	    goto out;
	}
	if (atom != url->href) {
	    MOCHA_DropAtom(mc, url->href);
	    url->href = MOCHA_HoldAtom(mc, atom);
	}
	url->taint = new_taint;
	break;

      case URL_TARGET:
	if (dp->u.atom != url->target) {
	    MOCHA_DropAtom(mc, url->target);
	    url->target = MOCHA_HoldAtom(mc, dp->u.atom);
	}
	if (url->index != URL_NOT_INDEXED) {
	    context = url->url_decoder->window_context;
	    if (context) {
		anchor_data = LO_GetLinkByIndex(context, url->index);
		if (anchor_data) {
		    name = MOCHA_GetAtomName(mc, dp->u.atom);
		    if (!lm_CheckWindowName(mc, name))
			return MOCHA_FALSE;
		    if (!lm_SaveParamString(mc, &anchor_data->target, name))
			return MOCHA_FALSE;
		}
	    }
	}
	/* Note early return, to bypass href update and freeing. */
	return MOCHA_TRUE;

      default:
	/* Don't mess with a user-defined property. */
	return ok;
    }

    if (url->index != URL_NOT_INDEXED) {
	context = url->url_decoder->window_context;
	if (context) {
	    anchor_data = LO_GetLinkByIndex(context, url->index);
	    if (anchor_data) {
		checked_href = lm_CheckURL(mc, href);
		if (!checked_href ||
		    !lm_SaveParamString(mc, &anchor_data->anchor,
					checked_href)) {
		    ok = MOCHA_FALSE;
		    goto out;
		}
		XP_FREE((char *)checked_href);
	    }
	}
    }

out:
    if (free_href && href)
	XP_FREE((char *)href);
    return ok;
}

static void
url_finalize(MochaContext *mc, MochaObject *obj)
{
    MochaURL *url = obj->data;
    MWContext *context;
    LO_AnchorData *anchor_data;

    if (!url) return;
    if (url->index != URL_NOT_INDEXED) {
	context = url->url_decoder->window_context;
	if (context) {
	    anchor_data = LO_GetLinkByIndex(context, url->index);
	    if (anchor_data)
		anchor_data->mocha_object = 0;
	}
    }
    MOCHA_DropAtom(mc, url->href);
    MOCHA_DropAtom(mc, url->target);
    LM_UNLINK_OBJECT(url->url_decoder, obj);
    XP_DELETE(url);
}

static MochaClass url_class = {
    "URL",
    url_get_property, url_set_property, MOCHA_ListPropStub,
    MOCHA_ResolveStub, MOCHA_ConvertStub, url_finalize
};

static MochaBoolean
URL(MochaContext *mc, MochaObject *obj,
    uint argc, MochaDatum *argv, MochaDatum *rval)
{
    return MOCHA_TRUE;
}

static MochaBoolean
url_to_string(MochaContext *mc, MochaObject *obj,
	      uint argc, MochaDatum *argv, MochaDatum *rval)
{
    rval->flags |= MDF_TAINTED;
    return url_get_property(mc, obj, URL_HREF, rval);
}

static MochaFunctionSpec url_methods[] = {
    {lm_toString_str,	url_to_string,	0},
    {0}
};

MochaURL *
lm_NewURL(MochaContext *mc, MochaDecoder *decoder, char *href, char *target)
{
    MochaObject *obj;
    MochaURL *url;
    MochaAtom *atom;

    if (!decoder->url_prototype) {
	obj = MOCHA_InitClass(mc, decoder->window_object, &url_class, 0,
			      URL, 0, url_props, 0, 0, 0);
	if (!obj)
	    return 0;
	decoder->url_prototype = MOCHA_HoldObject(mc, obj);
    }

    url = MOCHA_malloc(mc, sizeof *url);
    if (!url)
	return 0;
    XP_BZERO(url, sizeof *url);
    url->url_decoder = decoder;
    url->url_type = FORM_TYPE_NONE;
    url->index = URL_NOT_INDEXED;

    obj = MOCHA_NewObject(mc, &url_class, url, decoder->url_prototype,
			  decoder->active_form ? decoder->active_form
					       : decoder->document,
			  0, 0);
    if (!obj) {
	MOCHA_free(mc, url);
	return 0;
    }
    MOCHA_DefineFunctions(mc, obj, url_methods);
    url->url_object = obj;

    atom = MOCHA_Atomize(mc, href);
    if (!atom) {
	MOCHA_DestroyObject(mc, obj);
	return 0;
    }
    url->href = MOCHA_HoldAtom(mc, atom);
    if (!lm_GetTaint(mc, decoder, &url->taint)) {
	MOCHA_DestroyObject(mc, obj);
	return 0;
    }

    atom = MOCHA_Atomize(mc, target);
    if (!atom) {
	MOCHA_DestroyObject(mc, obj);
	return 0;
    }
    url->target = MOCHA_HoldAtom(mc, atom);
    LM_LINK_OBJECT(decoder, obj, href);
    return url;
}

/*
** Top-level window URL object, a reflection of the "Location:" GUI field.
*/
static MochaBoolean
loc_get_property(MochaContext *mc, MochaObject *obj, MochaSlot slot,
		 MochaDatum *dp)
{
    MochaURL *url;
    MochaDecoder *decoder;
    MWContext *context;
    History_entry *he;
    const char *url_string;
    MochaAtom *atom;

    url = obj->data;
    decoder = url->url_decoder;
    if ((dp->flags & MDF_TAINTED) && !lm_CheckOrigins(mc, decoder))
	return MOCHA_FALSE;
    context = decoder->window_context;
    if (!context) return MOCHA_TRUE;
    he = SHIST_GetCurrent(&context->hist);
    if (he) {
	url_string = he->address;
	if (NET_URL_Type(url_string) == WYSIWYG_TYPE_URL &&
	    !(url_string = LM_SkipWysiwygURLPrefix(url_string))) {
	    url_string = he->address;
	}
	atom = MOCHA_Atomize(mc, url_string);
	if (!atom)
	    return MOCHA_FALSE;
	if (atom != url->href) {
	    MOCHA_DropAtom(mc, url->href);
	    url->href = MOCHA_HoldAtom(mc, atom);
	}
	if (!lm_GetTaint(mc, decoder, &url->taint))
	    return MOCHA_FALSE;
    }
    return url_get_property(mc, obj, slot, dp);
}

static void
url_notify(URL_Struct *url_struct, int status, MWContext *context)
{
}

void
lm_ReplaceURL(MWContext *context, URL_Struct *url_struct)
{
    History_entry *he;

    he = SHIST_GetCurrent(&context->hist);
    if (!he)
	return;
    he->history_num = SHIST_GetIndex(&context->hist, he);
    he->replace = TRUE;
    url_struct->history_num = he->history_num;
}

static void
get_next_url(URL_Struct *url_struct, int status, MWContext *context)
{
    MochaDecoder *decoder;
    MochaNextURL *next_url;

    /* Notify anyone waiting on this URL. */
    url_notify(url_struct, status, context);

    /* Get the mocha decoder for this context.  Be paranoid. */
    decoder = LM_GetMochaDecoder(context);
    if (!decoder)
	return;

    next_url = decoder->next_url_list;
    if (next_url) {
	/* The next url_struct belongs to the FE now... */
	url_struct = next_url->url_struct;
	decoder->next_url_list = next_url->next;
	if (next_url->replace)
	    lm_ReplaceURL(context, url_struct);
	if (!decoder->next_url_list) {
	    decoder->url_list_tail = &decoder->next_url_list;
	    url_struct->pre_exit_fn = url_notify;
	} else {
	    url_struct->pre_exit_fn = get_next_url;
	}
	MOCHA_free(decoder->mocha_context, next_url);
	FE_GetURL(context, url_struct);
    }
    LM_PutMochaDecoder(decoder);
}

void
lm_ClearNextURLList(MochaDecoder *decoder)
{
    MochaNextURL *next_url;

    while ((next_url = decoder->next_url_list) != 0) {
	NET_FreeURLStruct(next_url->url_struct);
	decoder->next_url_list = next_url->next;
	MOCHA_free(decoder->mocha_context, next_url);
    }
    decoder->url_list_tail = &decoder->next_url_list;
}

MochaBoolean
lm_GetURL(MochaContext *mc, MochaDecoder *decoder, URL_Struct *url_struct,
	  uint16 taint)
{
    MWContext *context;
    MochaNextURL *next_url;

    context = decoder->window_context;
    if (!context) {
	NET_FreeURLStruct(url_struct);
	return MOCHA_TRUE;
    }

    if (!lm_CheckTaint(mc, taint, lm_location_str, url_struct->address)) {
	NET_FreeURLStruct(url_struct);
	return MOCHA_FALSE;
    }

    if (decoder->url_struct) {
	/*
	** Because decoder->url_struct is non-null, we know we are still
	** loading from a URL containing Mocha, either embedded in HTML or
	** from a 'mocha:location = "http://foo.com/bar/baz"' URL.
	**
	** Set the currently-loading URL_Struct's pre_exit_fn to point to
	** a special FE_GetURL wrapper function so we can chain indirectly
	** from the "mocha:" URL to the new location.
	*/
	next_url = MOCHA_malloc(mc, sizeof *next_url);
	if (!next_url) {
	    NET_FreeURLStruct(url_struct);
	    return MOCHA_FALSE;
	}
	next_url->url_struct = url_struct;
	next_url->replace = decoder->replace_location;
	decoder->replace_location = MOCHA_FALSE;
	next_url->next = 0;
	*decoder->url_list_tail = next_url;
	decoder->url_list_tail = &next_url->next;
	decoder->url_struct->pre_exit_fn = get_next_url;
    } else {
	/*
	** No chaining required, we must be in a non-loading context.
	*/
	if (decoder->replace_location) {
	    decoder->replace_location = MOCHA_FALSE;
	    lm_ReplaceURL(context, url_struct);
	}
	url_struct->pre_exit_fn = url_notify;
	FE_GetURL(context, url_struct);
    }
    return MOCHA_TRUE;
}

const char *
lm_CheckURL(MochaContext *mc, const char *url_string)
{
    char *protocol, *absolute;
    MochaDecoder *decoder;
    lo_TopState *top_state;

    protocol = NET_ParseURL(url_string, GET_PROTOCOL_PART);
    if (!protocol || *protocol == '\0') {
	decoder = MOCHA_GetGlobalObject(mc)->data;
	top_state = lo_GetMochaTopState(decoder->window_context);
	if (top_state && top_state->base_url) {
	    absolute = NET_MakeAbsoluteURL(top_state->base_url,
					   (char *)url_string);	/*XXX*/
	} else {
	    absolute = 0;
	}
	if (absolute) {
	    if (protocol) XP_FREE(protocol);
	    protocol = NET_ParseURL(absolute, GET_PROTOCOL_PART);
	}
    } else {
	absolute = MOCHA_strdup(mc, url_string);
	if (!absolute) {
	    XP_FREE(protocol);
	    return 0;
	}
    }

    if (absolute) {
	if (mc != lm_global_decoder->mocha_context) {
	    /* Make sure it's a safe URL type. */
	    switch (NET_URL_Type(protocol)) {
	      case FILE_TYPE_URL:
	      case FTP_TYPE_URL:
	      case GOPHER_TYPE_URL:
	      case HTTP_TYPE_URL:
	      case MAILTO_TYPE_URL:
	      case NEWS_TYPE_URL:
	      case RLOGIN_TYPE_URL:
	      case TELNET_TYPE_URL:
	      case TN3270_TYPE_URL:
	      case WAIS_TYPE_URL:
	      case SECURE_HTTP_TYPE_URL:
	      case URN_TYPE_URL:
	      case MOCHA_TYPE_URL:
	      case VIEW_SOURCE_TYPE_URL:
		/* These are "safe". */
		break;
	      case ABOUT_TYPE_URL:
		if (XP_STRCASECMP(absolute, "about:blank") == 0)
		    break;
		/* FALL THROUGH */
	      default:
		/* All others are naughty. */
		XP_FREE(absolute);
		absolute = 0;
		break;
	    }
	}
    }

    if (!absolute) {
	MOCHA_ReportError(mc, "illegal URL method '%s'",
			  protocol && *protocol ? protocol : "unknown");
    }
    if (protocol)
	XP_FREE(protocol);
    return absolute;
}

static MochaBoolean
url_load(MochaContext *mc, MochaObject *obj, NET_ReloadMethod reload_how)
{
    MochaURL *url;
    const char *url_string;
    URL_Struct *url_struct;

    url = obj->data;
    url_string = MOCHA_GetAtomName(mc, url->href);
    url_struct = NET_CreateURLStruct(url_string, reload_how);
    if (!url_struct) {
	MOCHA_ReportOutOfMemory(mc);
	return MOCHA_FALSE;
    }
    return lm_GetURL(mc, url->url_decoder, url_struct, url->taint);
}

static MochaBoolean
loc_set_property(MochaContext *mc, MochaObject *obj, MochaSlot slot,
		 MochaDatum *dp)
{
    const char *url_string;
    MochaAtom *atom;
    MochaDatum datum;

    /* Setting these properties should not cause a FE_GetURL. */
    if (slot < 0 || slot == URL_TARGET)
        return url_set_property(mc, obj, slot, dp);

    /* Make sure dp is a string. */
    if (dp->tag != MOCHA_STRING &&
	!MOCHA_ConvertDatum(mc, *dp, MOCHA_STRING, dp)) {
	return MOCHA_FALSE;
    }

    /* Two cases: setting href vs. setting a component property. */
    if (slot == URL_HREF || slot == URL_PROTOCOL) {
	/* Make sure the URL is absolute and sanity-check its protocol. */
	url_string = MOCHA_GetAtomName(mc, dp->u.atom);
	url_string = lm_CheckURL(mc, url_string);
	if (!url_string)
	    return MOCHA_FALSE;
	atom = MOCHA_Atomize(mc, url_string);
	XP_FREE((char *)url_string);
	if (!atom) return MOCHA_FALSE;
	MOCHA_INIT_FULL_DATUM(mc, &datum, MOCHA_STRING, 0,
			      MOCHA_TAINT_IDENTITY, u.atom, atom);
	dp = &datum;
    } else {
	/* Get href from session history before setting a piece of it. */
	if (!loc_get_property(mc, obj, URL_HREF, &datum))
	    return MOCHA_FALSE;
    }

    /* Set slot's property. */
    if (!url_set_property(mc, obj, slot, dp))
	return MOCHA_FALSE;
    return url_load(mc, obj, NET_DONT_RELOAD);
}

static MochaClass loc_class = {
    "Location",
    loc_get_property, loc_set_property, MOCHA_ListPropStub,
    MOCHA_ResolveStub, MOCHA_ConvertStub, url_finalize
};

static MochaBoolean
Location(MochaContext *mc, MochaObject *obj,
	 uint argc, MochaDatum *argv, MochaDatum *rval)
{
    return MOCHA_TRUE;
}

static MochaBoolean
loc_to_string(MochaContext *mc, MochaObject *obj,
	      uint argc, MochaDatum *argv, MochaDatum *rval)
{
    rval->flags |= MDF_TAINTED;
    return loc_get_property(mc, obj, URL_HREF, rval);
}

static MochaBoolean
loc_assign(MochaContext *mc, MochaObject *obj,
	   uint argc, MochaDatum *argv, MochaDatum *rval)
{
    MochaDatum d;
    MochaURL *url;

    if (!MOCHA_InstanceOf(mc, obj, &loc_class, argv[-1].u.fun))
        return MOCHA_FALSE;
    d = argv[0];
    if (!loc_set_property(mc, obj, URL_HREF, &d))
	return MOCHA_FALSE;
    url = obj->data;
    MOCHA_INIT_DATUM(mc, rval, MOCHA_OBJECT, u.obj, url->url_object);
    return MOCHA_TRUE;
}

static MochaBoolean
loc_reload(MochaContext *mc, MochaObject *obj,
	   uint argc, MochaDatum *argv, MochaDatum *rval)
{
    if (!MOCHA_InstanceOf(mc, obj, &loc_class, argv[-1].u.fun))
        return MOCHA_FALSE;
    return url_load(mc, obj,
		    (argv[0].tag == MOCHA_BOOLEAN && argv[0].u.bval)
		    ? NET_SUPER_RELOAD
		    : NET_NORMAL_RELOAD);
}

static MochaBoolean
loc_replace(MochaContext *mc, MochaObject *obj,
	    uint argc, MochaDatum *argv, MochaDatum *rval)
{
    MochaURL *url;

    if (!MOCHA_InstanceOf(mc, obj, &loc_class, argv[-1].u.fun))
        return MOCHA_FALSE;
    url = obj->data;
    url->url_decoder->replace_location = MOCHA_TRUE;
    return loc_assign(mc, obj, argc, argv, rval);
}

static MochaFunctionSpec loc_methods[] = {
    {lm_toString_str,   loc_to_string,  0},
    {lm_assign_str,     loc_assign,     1},
    {lm_reload_str,     loc_reload,     1},
    {lm_replace_str,    loc_replace,    1},
    {0}
};

MochaObject *
lm_DefineLocation(MochaDecoder *decoder)
{
    MochaObject *obj;
    MochaContext *mc;
    MochaURL *url;

    obj = decoder->location;
    if (obj)
	return obj;

    mc = decoder->mocha_context;
    url = MOCHA_malloc(mc, sizeof *url);
    if (!url)
	return 0;
    obj = MOCHA_InitClass(mc, decoder->window_object, &loc_class, url,
			  Location, 0, url_props, loc_methods, 0, 0);
    if (!obj) {
	MOCHA_free(mc, url);
	return 0;
    }
    if (!MOCHA_DefineObject(mc, decoder->window_object, lm_location_str, obj,
			    MDF_ENUMERATE | MDF_READONLY)) {
	return 0;
    }

    /* Define the Location object (the current URL). */
    XP_BZERO(url, sizeof *url);
    url->url_decoder = decoder;
    url->url_type = FORM_TYPE_NONE;
    url->url_object = obj;
    url->index = URL_NOT_INDEXED;
    decoder->location = MOCHA_HoldObject(mc, obj);
    obj->parent = decoder->window_object;
    LM_LINK_OBJECT(decoder, obj, lm_location_str);
    return obj;
}