netscape-revival
lib/libmocha/lm_doc.c
/*
** Mocha reflection of the current Navigator Document.
**
** Brendan Eich, 9/8/95
*/
#include "lm.h"
#include "prtypes.h"
#include "prhash.h"
#include "prmem.h"
#include "prprf.h"
#include "prtime.h"
#include "xp.h"
#include "fe_proto.h" /* just for FE_ClearView */
#include "lo_ele.h"
#include "shist.h"
#include "structs.h" /* for MWContext */
#include "layout.h" /* included via -I../layout */
#include "mkaccess.h" /* from ../libnet */
#include "mkcache.h" /* from ../libnet */
#include "pa_parse.h" /* included via -I../libparse */
#include "pa_tags.h" /* included via -I../libparse */
#if HAVE_SECURITY /* jwz */
#include "cert.h" /* for CERT_DupCertificate */
#endif
enum doc_slot {
DOC_LENGTH = -1,
DOC_ELEMENTS = -2,
DOC_FORMS = -3,
DOC_LINKS = -4,
DOC_ANCHORS = -5,
DOC_APPLETS = -6,
DOC_EMBEDS = -7,
DOC_IMAGES = -8,
DOC_TITLE = -9,
DOC_URL = -10,
DOC_REFERRER = -11,
DOC_LAST_MODIFIED = -12,
DOC_LOADED_DATE = -13,
DOC_COOKIE = -14,
DOC_DOMAIN = -15,
DOC_BG_COLOR = -16,
DOC_FG_COLOR = -17,
DOC_LINK_COLOR = -18,
DOC_VLINK_COLOR = -19,
DOC_ALINK_COLOR = -20
};
static MochaPropertySpec doc_props[] = {
{lm_length_str, DOC_LENGTH, MDF_READONLY|MDF_TAINTED},
{"elements", DOC_ELEMENTS, MDF_READONLY|MDF_TAINTED},
{lm_forms_str, DOC_FORMS, MDF_ENUMERATE|MDF_READONLY|MDF_TAINTED},
{lm_links_str, DOC_LINKS, MDF_ENUMERATE|MDF_READONLY|MDF_TAINTED},
{lm_anchors_str, DOC_ANCHORS, MDF_ENUMERATE|MDF_READONLY},
{lm_applets_str, DOC_APPLETS, MDF_ENUMERATE|MDF_READONLY},
{lm_embeds_str, DOC_EMBEDS, MDF_ENUMERATE|MDF_READONLY},
{lm_plugins_str, DOC_EMBEDS, MDF_READONLY},
{lm_images_str, DOC_IMAGES, MDF_ENUMERATE|MDF_READONLY},
{"title", DOC_TITLE, MDF_ENUMERATE|MDF_READONLY|MDF_TAINTED},
{"URL", DOC_URL, MDF_ENUMERATE|MDF_READONLY|MDF_TAINTED},
{"referrer", DOC_REFERRER, MDF_ENUMERATE|MDF_READONLY|MDF_TAINTED},
{"lastModified", DOC_LAST_MODIFIED, MDF_ENUMERATE|MDF_READONLY|MDF_TAINTED},
{"loadedDate", DOC_LOADED_DATE, MDF_READONLY},
{"cookie", DOC_COOKIE, MDF_ENUMERATE|MDF_TAINTED},
{"domain", DOC_DOMAIN, MDF_ENUMERATE|MDF_TAINTED},
{"bgColor", DOC_BG_COLOR, MDF_ENUMERATE},
{"fgColor", DOC_FG_COLOR, MDF_ENUMERATE},
{"linkColor", DOC_LINK_COLOR, MDF_ENUMERATE},
{"vlinkColor", DOC_VLINK_COLOR, MDF_ENUMERATE},
{"alinkColor", DOC_ALINK_COLOR, MDF_ENUMERATE},
{0}
};
#define LO_COLOR_TYPE(slot) \
(((slot) == DOC_BG_COLOR) ? LO_COLOR_BG \
: ((slot) == DOC_FG_COLOR) ? LO_COLOR_FG \
: ((slot) == DOC_LINK_COLOR) ? LO_COLOR_LINK \
: ((slot) == DOC_VLINK_COLOR) ? LO_COLOR_VLINK \
: ((slot) == DOC_ALINK_COLOR) ? LO_COLOR_ALINK \
: -1)
typedef struct MochaDocument {
MochaDecoder *decoder;
MochaObject *object;
} MochaDocument;
static MochaBoolean
doc_get_property(MochaContext *mc, MochaObject *obj, MochaSlot slot,
MochaDatum *dp)
{
MochaDocument *doc;
MochaDecoder *decoder;
MWContext *context;
History_entry *he;
MochaAtom *atom;
int64 prsec, scale, prusec;
PRTime prtime;
char *cookie, buf[100];
const char *domain;
uint32 type;
LO_Color rgb;
doc = obj->data;
decoder = doc->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);
switch (slot) {
case DOC_FORMS:
MOCHA_INIT_DATUM(mc, dp, MOCHA_OBJECT, u.obj, lm_GetFormArray(decoder));
(void) LO_EnumerateForms(context);
return MOCHA_TRUE;
case DOC_LINKS:
MOCHA_INIT_DATUM(mc, dp, MOCHA_OBJECT, u.obj, lm_GetLinkArray(decoder));
(void) LO_EnumerateLinks(context);
return MOCHA_TRUE;
case DOC_ANCHORS:
MOCHA_INIT_DATUM(mc, dp, MOCHA_OBJECT, u.obj, lm_GetNameArray(decoder));
(void) LO_EnumerateNamedAnchors(context);
return MOCHA_TRUE;
case DOC_APPLETS:
#ifdef JAVA
MOCHA_INIT_DATUM(mc, dp, MOCHA_OBJECT, u.obj, lm_GetAppletArray(decoder));
(void) LO_EnumerateApplets(context);
#endif
return MOCHA_TRUE;
case DOC_EMBEDS:
#ifdef JAVA
MOCHA_INIT_DATUM(mc, dp, MOCHA_OBJECT, u.obj, lm_GetEmbedArray(decoder));
(void) LO_EnumerateEmbeds(context);
#endif
return MOCHA_TRUE;
case DOC_IMAGES:
MOCHA_INIT_DATUM(mc, dp, MOCHA_OBJECT, u.obj, lm_GetImageArray(decoder));
(void) LO_EnumerateImages(context);
return MOCHA_TRUE;
case DOC_TITLE:
atom = MOCHA_Atomize(mc, he ? he->title : "");
break;
case DOC_URL:
atom = MOCHA_Atomize(mc, he ? he->address : "");
break;
case DOC_REFERRER:
atom = MOCHA_Atomize(mc, he ? he->referer : "");
break;
case DOC_LAST_MODIFIED:
if (he) {
LL_I2L(prsec, he->last_modified);
LL_I2L(scale, 1000000);
LL_MUL(prusec, prsec, scale);
PR_ExplodeTime(&prtime, prusec);
PR_FormatTime(buf, sizeof buf, "%c", &prtime);
} else {
buf[0] = '\0';
}
atom = MOCHA_Atomize(mc, buf);
break;
case DOC_LOADED_DATE:
/* XXX todo */
atom = MOCHA_empty.u.atom;
break;
case DOC_COOKIE:
if (he && he->address) {
/* XXX overloaded return - can't distinguish "none" from "error" */
cookie = NET_GetCookie(context, he->address);
} else {
cookie = 0;
}
atom = MOCHA_Atomize(mc, cookie);
break;
case DOC_DOMAIN:
if (!decoder->origin_url && !lm_GetOriginURL(mc, decoder))
return MOCHA_FALSE;
domain = NET_ParseURL(decoder->origin_url, GET_HOST_PART);
if (!domain) {
MOCHA_ReportOutOfMemory(mc);
return MOCHA_FALSE;
}
atom = MOCHA_Atomize(mc, domain);
XP_FREE((char *)domain);
break;
default:
type = LO_COLOR_TYPE(slot);
if (type >= LO_NCOLORS) {
/* Don't mess with a user-defined or method property. */
return MOCHA_TRUE;
}
LO_GetDocumentColor(context, type, &rgb);
XP_SPRINTF(buf, "#%02x%02x%02x", rgb.red, rgb.green, rgb.blue);
atom = MOCHA_Atomize(mc, buf);
break;
}
/* Common tail code for string-type properties. */
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)
return lm_GetTaint(mc, decoder, &dp->taint);
return MOCHA_TRUE;
}
static MochaBoolean
doc_set_property(MochaContext *mc, MochaObject *obj, MochaSlot slot,
MochaDatum *dp)
{
MochaDocument *doc;
MochaDecoder *decoder;
MWContext *context;
History_entry *he;
char *cookie, *protocol, *pathname, *new_origin_url;
const char *domain, *new_domain, *domain_suffix;
size_t domain_len, new_domain_len;
MochaBoolean ok;
uint32 type, color;
LO_Color rgb;
doc = obj->data;
decoder = doc->decoder;
if ((dp->flags & MDF_TAINTED) && !lm_CheckOrigins(mc, decoder))
return MOCHA_FALSE;
context = decoder->window_context;
if (!context)
return MOCHA_TRUE;
switch (slot) {
case DOC_COOKIE:
if (!decoder->origin_url && !lm_GetOriginURL(mc, decoder))
return MOCHA_FALSE;
if (!lm_CheckTaint(mc, dp->taint, "cookie for ", decoder->origin_url))
return MOCHA_FALSE;
if (dp->tag != MOCHA_STRING &&
!MOCHA_ConvertDatum(mc, *dp, MOCHA_STRING, dp)) {
return MOCHA_FALSE;
}
he = SHIST_GetCurrent(&context->hist);
if (he && he->address) {
/* Must make a writable copy for netlib to munge gratuitously. */
cookie = MOCHA_strdup(mc, MOCHA_GetAtomName(mc, dp->u.atom));
if (!cookie)
return MOCHA_FALSE;
NET_SetCookieString(context, he->address, cookie);
MOCHA_free(mc, cookie);
}
break;
case DOC_DOMAIN:
if (!decoder->origin_url && !lm_GetOriginURL(mc, decoder))
return MOCHA_FALSE;
domain = NET_ParseURL(decoder->origin_url, GET_HOST_PART);
if (!domain) {
MOCHA_ReportOutOfMemory(mc);
return MOCHA_FALSE;
}
if (dp->tag != MOCHA_STRING &&
!MOCHA_ConvertDatum(mc, *dp, MOCHA_STRING, dp)) {
XP_FREE((char *)domain);
return MOCHA_FALSE;
}
new_domain = MOCHA_GetAtomName(mc, dp->u.atom);
new_domain_len = XP_STRLEN(new_domain);
domain_len = XP_STRLEN(domain);
domain_suffix = domain + domain_len - new_domain_len;
ok = (domain_len > new_domain_len &&
!XP_STRCASECMP(domain_suffix, new_domain) &&
(domain_suffix[-1] == '.' || domain_suffix[-1] == '/'));
if (!ok) {
MOCHA_ReportError(mc, "illegal document.domain value %s",
new_domain);
} else {
protocol = NET_ParseURL(decoder->origin_url, GET_PROTOCOL_PART);
pathname = NET_ParseURL(decoder->origin_url,
GET_PATH_PART | GET_HASH_PART |
GET_SEARCH_PART);
new_origin_url = PR_smprintf("%s//%s%s",
protocol, new_domain, pathname);
ok = (protocol && pathname && new_origin_url);
if (!ok) {
MOCHA_ReportOutOfMemory(mc);
} else {
ok = (lm_SetOriginURL(mc, decoder, new_origin_url) != 0);
if (ok)
decoder->domain_set = MOCHA_TRUE;
}
PR_FREEIF(protocol);
PR_FREEIF(pathname);
PR_FREEIF(new_origin_url);
}
XP_FREE((char *)domain);
return ok;
case DOC_BG_COLOR:
case DOC_FG_COLOR:
case DOC_LINK_COLOR:
case DOC_VLINK_COLOR:
case DOC_ALINK_COLOR:
type = LO_COLOR_TYPE(slot);
if (type >= LO_NCOLORS)
break;
switch (dp->tag) {
default:
if (!MOCHA_ConvertDatum(mc, *dp, MOCHA_STRING, dp))
return MOCHA_FALSE;
/* FALL THROUGH */
case MOCHA_STRING:
/* XXX why not pass &rgb? */
if (LO_ParseRGB((char *)MOCHA_GetAtomName(mc, dp->u.atom),/*XXX*/
&rgb.red, &rgb.green, &rgb.blue)) {
LO_SetDocumentColor(context, type, &rgb);
}
break;
case MOCHA_NUMBER:
color = (uint32)dp->u.fval;
if ((MochaFloat)color == dp->u.fval && (color >> 24) == 0) {
rgb.red = color >> 16;
rgb.green = (color >> 8) & 0xff;
rgb.blue = color & 0xff;
LO_SetDocumentColor(context, type, &rgb);
}
break;
}
break;
}
return doc_get_property(mc, obj, slot, dp);
}
static MochaBoolean
doc_list_properties(MochaContext *mc, MochaObject *obj)
{
#ifdef JAVA
/* reflect applets eagerly, anything else? */
MochaDocument *doc;
MWContext *context;
doc = obj->data;
context = doc->decoder->window_context;
if (!context)
return MOCHA_TRUE;
(void) LO_EnumerateApplets(context);
(void) LO_EnumerateEmbeds(context);
#endif
return MOCHA_TRUE;
}
static MochaBoolean
doc_resolve_name(MochaContext *mc, MochaObject *obj, const char *name)
{
if (!XP_STRCMP(name, lm_location_str)) {
MochaDocument *doc = obj->data;
return MOCHA_DefineObject(mc, obj, name, doc->decoder->location, 0);
}
return doc_list_properties(mc, obj);
}
static void
doc_finalize(MochaContext *mc, MochaObject *obj)
{
MochaDocument *doc = obj->data;
LM_UNLINK_OBJECT(doc->decoder, obj);
MOCHA_free(mc, doc);
}
static MochaClass doc_class = {
"Document",
doc_get_property, doc_set_property, doc_list_properties,
doc_resolve_name, MOCHA_ConvertStub, doc_finalize
};
static MochaBoolean
Document(MochaContext *mc, MochaObject *obj,
uint argc, MochaDatum *argv, MochaDatum *rval)
{
return MOCHA_TRUE;
}
static MochaBoolean
doc_to_string(MochaContext *mc, MochaObject *obj,
uint argc, MochaDatum *argv, MochaDatum *rval)
{
*rval = MOCHA_empty; /* XXX make tainted string of whole doc */
return MOCHA_TRUE;
}
char *
LM_GetBaseHrefTag(MochaContext *mc)
{
MochaDecoder *running;
char *new_origin_url;
const char *origin_url;
const char *cp, *qp, *last_slash;
char *tag;
running = MOCHA_GetGlobalObject(mc)->data;
if (!running->origin_url && !lm_GetOriginURL(mc, running))
return 0;
origin_url = running->origin_url;
new_origin_url = 0;
cp = origin_url;
if ((qp = XP_STRCHR(cp, '"')) != 0) {
do {
new_origin_url = PR_sprintf_append(new_origin_url, "%.*s%%%x",
qp - cp, cp, *qp);
cp = qp + 1;
} while ((qp = XP_STRCHR(cp, '"')) != 0);
new_origin_url = PR_sprintf_append(new_origin_url, "%s", cp);
if (!new_origin_url) {
MOCHA_ReportOutOfMemory(mc);
return 0;
}
origin_url = new_origin_url;
}
last_slash = XP_STRRCHR(origin_url, '/');
if (last_slash) {
tag = PR_smprintf("<BASE HREF=\"%.*s/\">\n",
last_slash - origin_url, origin_url);
} else {
tag = PR_smprintf("<BASE HREF=\"%s\">\n", origin_url);
}
PR_FREEIF(new_origin_url);
if (!tag)
MOCHA_ReportOutOfMemory(mc);
return tag;
}
static int
doc_write_stream(MochaContext *mc, MochaDecoder *decoder,
NET_StreamClass *stream, char *str, size_t len,
uint16 taint)
{
MochaDecoder *running;
MochaTaintInfo *info;
MWContext *context;
lo_TopState *top_state;
int32 pre_doc_id;
int status;
/* XXX lacking MochaData everywhere, we must impute accumulated taint to
the generated document, and check for it in LM_CheckGetURL. */
decoder->write_taint_accum = MOCHA_GetTaintInfo(mc)->accum;
MOCHA_MIX_TAINT(mc, decoder->write_taint_accum, taint);
running = MOCHA_GetGlobalObject(mc)->data;
MOCHA_MIX_TAINT(mc, decoder->write_taint_accum, running->write_taint_accum);
info = MOCHA_GetTaintInfo(decoder->mocha_context);
MOCHA_MIX_TAINT(mc, info->accum, decoder->write_taint_accum);
context = decoder->window_context;
top_state = lo_GetMochaTopState(context);
if (top_state)
top_state->mocha_write_level++;
pre_doc_id = XP_DOCID(context);
status = (*stream->put_block)(stream->data_object, str, len);
if (top_state && XP_DOCID(context) == pre_doc_id)
top_state->mocha_write_level--;
return status;
}
#ifdef MOCHA_CACHES_WRITES
static char *
context_pathname(MochaContext *mc, MWContext *context)
{
MWContext *parent;
int count, index;
char *name = 0;
while ((parent = context->grid_parent) != 0) {
if (context->name) {
name = PR_sprintf_append(name, "%s.", context->name);
} else {
/* XXX silly xp_cntxt.c puts newer contexts at the front! */
count = XP_ListCount(parent->grid_children);
index = XP_ListGetNumFromObject(parent->grid_children, context);
name = PR_sprintf_append(name, "%u.", count - index);
}
context = parent;
}
name = PR_sprintf_append(name, "%ld", (long)XP_DOCID(context));
if (!name)
MOCHA_ReportOutOfMemory(mc);
return name;
}
/* Make a wysiwyg: URL containing the context and our origin. */
static char *
make_wysiwyg_url(MochaContext *mc, MochaDecoder *decoder)
{
MochaDecoder *running;
const char *pathname;
char *url_string;
running = MOCHA_GetGlobalObject(mc)->data;
if (!running->origin_url && !lm_GetOriginURL(mc, running))
return 0;
pathname = context_pathname(mc, decoder->window_context);
if (!pathname)
return 0;
url_string = PR_smprintf("wysiwyg://%s/%s", pathname, running->origin_url);
XP_FREE((char *)pathname);
if (!url_string) {
MOCHA_ReportOutOfMemory(mc);
return 0;
}
return url_string;
}
NET_StreamClass *
doc_cache_converter(MochaContext *mc, MochaDecoder *decoder,
URL_Struct *url_struct, char *wysiwyg_url)
{
MWContext *context;
lo_TopState *top_state;
char *address;
NET_StreamClass *cache_stream;
History_entry *he;
/* If we don't have a top state, layout must have run out of memory. */
context = decoder->window_context;
top_state = lo_GetMochaTopState(context);
if (!top_state) {
MOCHA_ReportOutOfMemory(mc);
return 0;
}
/* Save a wysiwyg copy in the URL struct for resize-reloads. */
url_struct->wysiwyg_url = XP_STRDUP(wysiwyg_url);
if (!url_struct->wysiwyg_url) {
MOCHA_ReportOutOfMemory(mc);
return 0;
}
/* Then pass it via url_struct to create a cache converter stream. */
address = url_struct->address;
url_struct->address = url_struct->wysiwyg_url;
cache_stream = NET_CacheConverter(FO_CACHE_ONLY,
(void *)1, /* XXX don't hold url */
url_struct,
context);
url_struct->address = address;
/* Finally, keep a strong (freeing) link to it in our history entry. */
he = SHIST_GetCurrent(&context->hist);
if (he && cache_stream) {
PR_FREEIF(he->wysiwyg_url);
he->wysiwyg_url = wysiwyg_url;
}
top_state->mocha_write_stream = cache_stream;
return cache_stream;
}
static char nscp_open_tag[] = "<" PT_NSCP_OPEN ">";
NET_StreamClass *
LM_WysiwygCacheConverter(MWContext *context, URL_Struct *url_struct,
NET_StreamClass *stream)
{
MochaDecoder *decoder;
MochaContext *mc;
char *wysiwyg_url, *tag;
NET_StreamClass *cache_stream;
decoder = LM_GetMochaDecoder(context);
if (!decoder)
return 0;
/* Feed layout an internal tag so it will create a new top state. */
if (stream) {
stream->put_block(stream->data_object, nscp_open_tag,
sizeof nscp_open_tag - 1);
}
mc = decoder->mocha_context;
wysiwyg_url = make_wysiwyg_url(mc, decoder);
if (!wysiwyg_url) {
cache_stream = 0;
} else {
cache_stream = doc_cache_converter(mc, decoder, url_struct,
wysiwyg_url);
if (!cache_stream) {
XP_FREE(wysiwyg_url);
} else {
/* Wysiwyg files need a base tag that removes that URL prefix. */
tag = LM_GetBaseHrefTag(mc);
if (tag) {
(void) cache_stream->put_block(cache_stream->data_object, tag,
XP_STRLEN(tag));
XP_FREE(tag);
}
}
}
LM_PutMochaDecoder(decoder);
return cache_stream;
}
#endif /* MOCHA_CACHES_WRITES */
static NET_StreamClass *
doc_open_stream(MochaContext *mc, MochaDecoder *decoder, const char *mime_type,
MochaBoolean replace)
{
MWContext *context;
char *wysiwyg_url, *tag;
URL_Struct *url_struct, *cached_url;
MochaDecoder *running;
History_entry *he;
NET_StreamClass *stream;
MochaBoolean is_text_html;
context = decoder->window_context;
if (!context) return 0;
/* We must be called after any loading URL has finished. */
XP_ASSERT(!decoder->url_struct);
wysiwyg_url = make_wysiwyg_url(mc, decoder);
if (!wysiwyg_url)
return 0;
url_struct = NET_CreateURLStruct(wysiwyg_url, NET_DONT_RELOAD);
if (!url_struct) {
XP_FREE(wysiwyg_url);
MOCHA_ReportOutOfMemory(mc);
return 0;
}
/* Set content type so it can be cached. */
StrAllocCopy(url_struct->content_type, mime_type);
url_struct->preset_content_type = TRUE;
url_struct->must_cache = TRUE;
/* Set these to null so we can goto bad from here onward. */
stream = 0;
cached_url = 0;
/* If the writer is secure, pass its security info into the cache. */
running = MOCHA_GetGlobalObject(mc)->data;
he = SHIST_GetCurrent(&running->window_context->hist);
#if HAVE_SECURITY /* jwz */
if (he && he->security_on) {
/* Copy security stuff (checking for malloc failure) */
url_struct->security_on = he->security_on;
StrAllocCopy(url_struct->key_cipher, he->key_cipher);
if (he->key_cipher && !url_struct->key_cipher)
goto bad;
url_struct->key_size = he->key_size;
url_struct->key_secret_size = he->key_secret_size;
/* Make a url struct just to lookup a cached certificate. */
cached_url = NET_CreateURLStruct(he->address, NET_DONT_RELOAD);
if (!cached_url)
goto bad;
(void) NET_FindURLInCache(cached_url, running->window_context);
if (cached_url->certificate) {
url_struct->certificate
= CERT_DupCertificate(cached_url->certificate);
if (!url_struct->certificate)
goto bad;
}
NET_FreeURLStruct(cached_url);
cached_url = 0;
}
#endif /* HAVE_SECURITY */
/* Free any old doc before stream building, which may discard too. */
LO_DiscardDocument(context);
if (replace) {
/* If replacing, flag current entry and url_struct specially. */
lm_ReplaceURL(context, url_struct);
}
/* We must be called after any open stream has been closed. */
XP_ASSERT(!decoder->stream);
stream = NET_StreamBuilder(FO_PRESENT, url_struct, context);
if (!stream) {
MOCHA_ReportError(mc, "cannot open document for %s", mime_type);
goto bad;
}
/* Layout discards documents lazily, but we are now eager. */
if (!decoder->document && !lm_InitWindowContent(decoder))
goto bad;
decoder->writing_input = MOCHA_TRUE;
if (!lm_SetInputStream(mc, decoder, stream, url_struct))
goto bad;
is_text_html = !XP_STRCMP(mime_type, TEXT_HTML);
#ifdef MOCHA_CACHES_WRITES
if (is_text_html) {
/* Feed layout an internal tag so it will create a new top state. */
stream->put_block(stream->data_object, nscp_open_tag,
sizeof nscp_open_tag - 1);
}
if ((is_text_html || !XP_STRCMP(mime_type, TEXT_PLAIN)) &&
!doc_cache_converter(mc, decoder, url_struct, wysiwyg_url)) {
XP_FREE(wysiwyg_url);
wysiwyg_url = 0;
}
#else
XP_FREE(wysiwyg_url);
wysiwyg_url = 0;
#endif
/* Auto-generate a BASE HREF= tag for generated HTML documents. */
if (is_text_html) {
tag = LM_GetBaseHrefTag(mc);
if (!tag)
goto bad;
(void) doc_write_stream(mc, decoder, stream, tag, XP_STRLEN(tag),
MOCHA_TAINT_IDENTITY);
XP_FREE(tag);
}
return stream;
bad:
decoder->writing_input = MOCHA_FALSE;
decoder->stream = 0;
decoder->url_struct = 0;
if (stream) {
(*stream->abort)(stream->data_object, MK_UNABLE_TO_CONVERT);
XP_DELETE(stream);
}
NET_FreeURLStruct(url_struct);
if (cached_url)
NET_FreeURLStruct(cached_url);
PR_FREEIF(wysiwyg_url);
return 0;
}
static MochaBoolean
doc_stream_active(MochaDecoder *decoder)
{
MWContext *context;
lo_TopState *top_state;
if (!decoder->writing_input)
return MOCHA_TRUE;
context = decoder->window_context;
if (context) {
top_state = lo_GetMochaTopState(context);
if (top_state && top_state->mocha_write_level)
return MOCHA_TRUE;
}
return MOCHA_FALSE;
}
/* XXX shared from lm_win.c for compatibility hack */
extern MochaBoolean
win_open(MochaContext *mc, MochaObject *obj,
uint argc, MochaDatum *argv, MochaDatum *rval);
static MochaBoolean
doc_open(MochaContext *mc, MochaObject *obj,
uint argc, MochaDatum *argv, MochaDatum *rval)
{
MochaDocument *doc;
MochaDecoder *decoder;
MochaAtom *atom, *atom2;
const char *mime_type, *options;
MochaBoolean ok, replace;
NET_StreamClass *stream;
if (!MOCHA_InstanceOf(mc, obj, &doc_class, argv[-1].u.fun))
return MOCHA_FALSE;
doc = obj->data;
decoder = doc->decoder;
if (argc > 2 ||
(argv[0].tag == MOCHA_STRING && argv[0].u.atom == MOCHA_empty.u.atom)) {
/* XXX hideous compatibility hack to call window.open */
obj = MOCHA_HoldObject(mc, decoder->window_object);
ok = win_open(mc, obj, argc, argv, rval);
MOCHA_DropObject(mc, obj);
return ok;
}
atom = atom2 = 0;
mime_type = TEXT_HTML;
replace = MOCHA_FALSE;
if (argc >= 1) {
if (!MOCHA_DatumToString(mc, argv[0], &atom))
return MOCHA_FALSE;
mime_type = MOCHA_GetAtomName(mc, atom);
if (argc >= 2) {
if (!MOCHA_DatumToString(mc, argv[1], &atom2)) {
MOCHA_DropAtom(mc, atom);
return MOCHA_FALSE;
}
options = MOCHA_GetAtomName(mc, atom2);
replace = !XP_STRCASECMP(options, "replace");
}
}
stream = decoder->stream;
if (stream) {
if (doc_stream_active(decoder)) {
/* Don't close a stream being fed by netlib. */
*rval = MOCHA_null;
MOCHA_DropAtom(mc, atom);
MOCHA_DropAtom(mc, atom2);
return MOCHA_TRUE;
}
LM_ClearDecoderStream(decoder);
}
stream = doc_open_stream(mc, decoder, mime_type, replace);
MOCHA_DropAtom(mc, atom);
MOCHA_DropAtom(mc, atom2);
if (!stream)
return MOCHA_FALSE;
MOCHA_INIT_DATUM(mc, rval, MOCHA_OBJECT, u.obj, doc->object);
return MOCHA_TRUE;
}
static MochaBoolean
doc_close(MochaContext *mc, MochaObject *obj,
uint argc, MochaDatum *argv, MochaDatum *rval)
{
MochaDocument *doc;
MochaDecoder *decoder;
if (!MOCHA_InstanceOf(mc, obj, &doc_class, argv[-1].u.fun))
return MOCHA_FALSE;
doc = obj->data;
decoder = doc->decoder;
if (!decoder->stream || doc_stream_active(decoder)) {
/* Don't close a closed stream, or a stream being fed by netlib. */
*rval = MOCHA_false;
return MOCHA_TRUE;
}
LM_ClearDecoderStream(decoder);
*rval = MOCHA_true;
return MOCHA_TRUE;
}
static MochaBoolean
doc_clear(MochaContext *mc, MochaObject *obj,
uint argc, MochaDatum *argv, MochaDatum *rval)
{
MochaDocument *doc;
MochaDecoder *decoder;
MWContext *context;
if (!MOCHA_InstanceOf(mc, obj, &doc_class, argv[-1].u.fun))
return MOCHA_FALSE;
doc = obj->data;
decoder = doc->decoder;
context = decoder->window_context;
if (!context)
return MOCHA_TRUE;
if (!doc_close(mc, obj, argc, argv, rval))
return MOCHA_FALSE;
/* XXX need to open and push a tag through layout? */
FE_ClearView(context, FE_VIEW);
return MOCHA_TRUE;
}
static NET_StreamClass *
doc_get_stream(MochaContext *mc, MochaDecoder *decoder)
{
NET_StreamClass *stream;
URL_Struct *url_struct;
MWContext *context;
lo_TopState *top_state;
stream = decoder->stream;
if (!stream) {
stream = doc_open_stream(mc, decoder, TEXT_HTML, MOCHA_FALSE);
} else {
url_struct = decoder->url_struct;
XP_ASSERT(url_struct);
if (url_struct && !url_struct->wysiwyg_url) {
context = decoder->window_context;
if (context) {
top_state = lo_GetMochaTopState(context);
if (top_state && !top_state->mocha_write_stream) {
#ifdef MOCHA_CACHES_WRITES
top_state->mocha_write_stream
= NET_CloneWysiwygCacheFile(context, url_struct,
(uint32)top_state->script_bytes);
#endif
}
}
}
}
return stream;
}
static MochaBoolean
doc_write(MochaContext *mc, MochaObject *obj,
uint argc, MochaDatum *argv, MochaDatum *rval)
{
MochaDocument *doc;
MochaDecoder *decoder;
NET_StreamClass *stream;
uint i;
MochaAtom *atom;
char *str;
int status;
if (!MOCHA_InstanceOf(mc, obj, &doc_class, argv[-1].u.fun))
return MOCHA_FALSE;
doc = obj->data;
decoder = doc->decoder;
stream = doc_get_stream(mc, decoder);
if (!stream)
return MOCHA_FALSE;
for (i = 0; i < argc; i++) {
if (!MOCHA_DatumToString(mc, argv[i], &atom))
return MOCHA_FALSE;
/* XXX dup it just so libparse can gratuitously overwrite it */
str = MOCHA_strdup(mc, MOCHA_GetAtomName(mc, atom));
MOCHA_DropAtom(mc, atom);
if (!str)
return MOCHA_FALSE;
status = doc_write_stream(mc, decoder, stream, str, XP_STRLEN(str),
argv[i].taint);
MOCHA_free(mc, str);
if (status < 0) {
*rval = MOCHA_false;
return MOCHA_TRUE;
}
}
if (!decoder->document && !lm_InitWindowContent(decoder))
return MOCHA_FALSE;
*rval = MOCHA_true;
return MOCHA_TRUE;
}
static MochaBoolean
doc_writeln(MochaContext *mc, MochaObject *obj,
uint argc, MochaDatum *argv, MochaDatum *rval)
{
MochaDocument *doc;
MochaDecoder *decoder;
NET_StreamClass *stream;
int status;
static char eol[] = "\n";
if (!MOCHA_InstanceOf(mc, obj, &doc_class, argv[-1].u.fun))
return MOCHA_FALSE;
doc = obj->data;
decoder = doc->decoder;
stream = doc_get_stream(mc, decoder);
if (!stream)
return MOCHA_FALSE;
if (!doc_write(mc, obj, argc, argv, rval))
return MOCHA_FALSE;
status = doc_write_stream(mc, decoder, stream, eol, sizeof eol - 1,
MOCHA_TAINT_IDENTITY);
if (status < 0)
rval->u.bval = MOCHA_FALSE;
return MOCHA_TRUE;
}
static MochaFunctionSpec doc_methods[] = {
{"clear", doc_clear, 0},
{"close", doc_close, 0},
{"open", doc_open, 1},
{lm_toString_str, doc_to_string, 0},
{"write", doc_write, 0},
{"writeln", doc_writeln, 0},
{0}
};
MochaObject *
lm_DefineDocument(MochaDecoder *decoder)
{
MochaObject *obj;
MochaContext *mc;
MochaDocument *doc;
obj = decoder->document;
if (obj)
return obj;
mc = decoder->mocha_context;
doc = MOCHA_malloc(mc, sizeof *doc);
if (!doc)
return 0;
obj = MOCHA_InitClass(mc, decoder->window_object, &doc_class, doc,
Document, 0, doc_props, doc_methods, 0, 0);
if (!obj) {
MOCHA_free(mc, doc);
return 0;
}
if (!MOCHA_DefineObject(mc, decoder->window_object, "document", obj,
MDF_ENUMERATE | MDF_READONLY)) {
return 0;
}
XP_BZERO(doc, sizeof *doc);
doc->object = obj;
doc->decoder = decoder;
decoder->document = MOCHA_HoldObject(mc, obj);
obj->parent = decoder->window_object;
LM_LINK_OBJECT(decoder, obj, "document");
return obj;
}
void
LM_ReleaseDocument(MWContext *context, MochaBoolean resize_reload)
{
MochaDecoder *decoder;
MochaContext *mc;
if (resize_reload)
return;
/* Be defensive about Mocha-unaware contexts. */
mc = context->mocha_context;
if (!mc)
return;
/* Hold the context's decoder rather than calling LM_GetMochaDecoder. */
decoder = MOCHA_GetGlobalObject(mc)->data;
MOCHA_HoldObject(mc, decoder->window_object);
lm_FreeWindowContent(decoder);
LM_PutMochaDecoder(decoder);
}