/*
* @(#)inStr.c 1.2 95/06/14 Warren Harris
*
* Copyright (c) 1995 Netscape Communications Corporation. All Rights Reserved.
*
*/
#include "nsn.h"
#include "prmem.h"
#include "prmon.h"
#include "prlog.h"
#include "prthread.h"
#include "netscape_net_URLConnection.h"
#include "netscape_net_URLInputStream.h"
#include "javaString.h"
#include "javaThreads.h"
nsn_TickleHookProcPtr g_nsn_TickleHookProc = NULL;
/******************************************************************************/
PUBLIC void nsn_InstallTickleHookProc(nsn_TickleHookProcPtr newProc)
{
g_nsn_TickleHookProc = newProc;
}
PRIVATE void
nsn_GetURL_exit(URL_Struct* url, int status, MWContext* window_id)
{
/*
** This method is called whenever netlib initiates the closing of a
** connection.
**
** When the Java stream is complete (all data has arrived) we destroy
** the NET_StreamClass object, indicating that the net stream is
** finished. Whatever input is left in the JavaStreamData's
** DataQueue is left for the reader. In the Java world, the stream is
** not considered closed until all the data is read, or until it is
** explicitly closed or garbage collected.
*/
nsn_JavaStreamData* javaData = (nsn_JavaStreamData*)url->fe_data;
ENTER_METHOD("GetURL_Exit", SILENTLY_FAIL)
if( (javaData->errorStatus >= 0) && (status < 0) ) {
/*
** If we got back an error then remember it...
*/
if (url->error_msg != NULL)
javaData->errorMessage = XP_STRDUP(url->error_msg);
javaData->errorStatus = status;
}
switch (javaData->state) {
case OPENED:
/* In this case, libnet is done with the stream first: */
javaData->state = LIBNET_DONE;
/*
** Important: this will close down the entire connection because the
** state is now LIBNET_DONE !!
*/
if( javaData->errorStatus < 0 ) {
nsn_JavaStream_javaClose(javaData);
}
PR_LOG(NSJAVA, debug,
("GetURL_exit(%s) libnet done, status=%d",
URL_ADDR(javaData), status));
break;
case LIBNET_DONE:
case CLOSED:
PR_LOG(NSJAVA, debug,
("GetURL_exit(%s) stream already closed!",
URL_ADDR(javaData)));
PR_ASSERT(0); /* Someone incorrectly advanced the state machine :-( */
break;
case OPENING:
/*
** If abort was called because the NET_GetURL immediately failed
** (which is why we're still in the OPENING state)...
*/
PR_LOG(NSJAVA, debug,
("GetURL_exit(%s) libnet opening", URL_ADDR(javaData)));
nsn_JavaStream_javaClose(javaData);
/* fall through */
case JAVA_DONE:
/*
** In this case, java has already indicated it's desire to close
** the stream, so libnet owns the url.
*/
nsn_JavaStream_close(javaData);
PR_LOG(NSJAVA, debug,
("GetURL_exit(%s) libnet closed", URL_ADDR(javaData)));
break;
case POSTING:
default:
PR_ASSERT(0); /* all bases should be covered, above */
break;
}
(javaData->notifies)++;
/* If we are done with the data data (no more references),
dispose of it. */
if (--(javaData->refcount) == 0) {
if( javaData->url ) {
javaData->url->fe_data = NULL;
}
if( javaData->javaContext ) {
javaData->javaContext->javaContextStreamData = NULL;
}
NSN_FREEIF(javaData->errorMessage);
NSN_FREE(javaData);
DECR(javaDataCount);
}
/* Notify any waiting readers that the stream has ended: */
PR_LOG(NSJAVA, debug,
("(GetURL_exit%s) calling monitorNotifyAll",
URL_ADDR(javaData)));
monitorNotifyAll(nsn_JavaStream_monitor(javaData));
EXIT_METHOD("GetURL_exit")
}
PUBLIC void
netscape_net_URLInputStream_open(struct Hnetscape_net_URLInputStream* this)
{
Hnetscape_net_URLConnection* con_h = unhand(this)->connection;
Classnetscape_net_URLConnection* con = unhand(con_h);
nsn_JavaStreamData* javaData = (nsn_JavaStreamData*)con->pStreamData;
FO_Present_Types presentType;
URL_Struct* url;
char* postHeaders;
JRIEnv* env = JRI_GetCurrentEnv();
PR_ASSERT(javaData != NULL); /* javaData was garbage collected */
/*
** XXX Currently we don't allow network activity from the mozilla
** thread. This is because we need to call wait to make this stuff
** work, but that locks out netlib (which runs on the mozilla
** thread). So for now, we'll throw an exception.
*/
if (LJ_MozillaRunning(env) == PR_TRUE) {
SignalError(0, "java/lang/InternalError",
"Can't call network functions from JavaScript yet.");
return;
}
PR_LOG_BEGIN(NSJAVA, debug,
("URLInputStream.open(%s)", URL_ADDR(javaData)));
/* Enter the monitor to add the final touches to the post_headers */
monitorEnter(nsn_JavaStream_monitor(javaData));
switch (javaData->state) {
case OPENING:
/* The usual case */
break;
case OPENED:
case LIBNET_DONE:
/* Herb says to silently succeed. */
PR_LOG(NSJAVA, debug,
("URLInputStream.open(%s) already opened: state %d",
URL_ADDR(javaData), javaData->state));
monitorExit(nsn_JavaStream_monitor(javaData));
return;
case POSTING:
case JAVA_DONE:
case CLOSED:
PR_LOG(NSJAVA, debug,
("URLInputStream.open(%s) stream closed: state %d",
URL_ADDR(javaData), javaData->state));
SignalError(0, "java/io/IOException", "stream closed");
monitorExit(nsn_JavaStream_monitor(javaData));
return;
default:
PR_ASSERT(0); /* should never get here */
break;
}
presentType = con->useCaches ? FO_CACHE_AND_DO_JAVA : FO_DO_JAVA;
url = javaData->url;
postHeaders = makeCString(con->postHeaders); /* freed by gc */
if (url->post_data != NULL) {
/*
** Add a Content-length. Subtract 2 for the size of the final
** CRLF that we tacked on in netscape_net_URLOutputStream_pClose:
*/
url->post_headers =
PR_smprintf("%sContent-length: %ld" CRLF,
postHeaders, url->post_data_size - 2);
url->post_data_is_file = FALSE;
url->method = URL_POST_METHOD;
con->postHeaders = NULL; /* zero it out for next time */
PR_LOG(NSJAVA, debug,
("URLInputStream.open(%s) calling NET_GetURL with post data",
URL_ADDR(javaData)));
}
else {
PR_LOG(NSJAVA, debug,
("URLInputStream.open(%s) calling NET_GetURL",
URL_ADDR(javaData)));
}
monitorExit(nsn_JavaStream_monitor(javaData));
/*
** Lou tells me that the status returned from NET_GetURL is not the
** error code, and that that gets passed to you through the abort
** routine. We're looking for that below in the case the stream was
** both created and terminated on our thread (we better get both, or
** we're hosed). So what's the result for?
*/
(void)NET_GetURL(url, presentType, javaData->javaContext, nsn_GetURL_exit);
/*
** Enter our monitor before checking the javaData state because
** nsn_JavaStream_make is going to change it or notify us when it's
** ready. We'd hate to get notified before we waited. Also, we can't
** enter the monitor before calling NET_GetURL because it will grab
** the netlib lock and cause a deadlock with the mozilla thread. The
** deadlock comes when mozilla wants to destroy the applet and its
** connection while we're still waiting to get connected.
*/
monitorEnter(nsn_JavaStream_monitor(javaData));
/*
** There are two cases for waiting for the make proc to be
** called. Sometimes the make proc is called by this thread during
** the call to NET_GetURL; sometimes its called by some other thread
** later. If it's called by ourselves then a useless notify will be
** done before we get to this line of code and the state will not be
** OPENING. If it's done by some other thread, then the other thread
** will block on the monitor we are holding until we release it, and
** therefore there is no race on the state change.
*/
if (javaData->state == OPENING) {
/*
** Then wait for it to be created on the upcall:
*/
PR_LOG(NSJAVA, debug,
("URLInputStream.open(%s) monitorWait",
URL_ADDR(javaData)));
/* On the mac (and most likely win16), Net_ProcessNet and
** network tickling can only occur on the main thread.
** therefore, we provide a hook here for the case that the
** main thread needs to tickle itself. In this case,
** we make sure that we give up the monitor so that the
** tickle code can notify it without freezing.
** dkc 2/13/96
*/
if (g_nsn_TickleHookProc != NULL) {
monitorExit(nsn_JavaStream_monitor(javaData));
while (1) {
if ((*g_nsn_TickleHookProc)() || (javaData->state != OPENING))
break;
}
monitorEnter(nsn_JavaStream_monitor(javaData));
}
/* If the stream still isn't open after tickling, then
** wait on the monitor.
*/
if (javaData->state == OPENING)
monitorWait(nsn_JavaStream_monitor(javaData), TIMEOUT_INFINITY);
if (exceptionOccurred(EE())) {
/* we may get ThreadDeath */
PR_LOG(NSJAVA, debug,
("URLInputStream.open(%s) exception occurred",
URL_ADDR(javaData)));
monitorExit(nsn_JavaStream_monitor(javaData));
return;
}
/*
** We better should have either gotten an exception, or a
** notification that we were leaving the OPENING state.
*/
PR_ASSERT(javaData->state != OPENING);
}
else {
/*
** Otherwise, we've already done the NET_GetURL on our thread, so
** just check the error status (below).
*/
PR_LOG(NSJAVA, debug,
("URLInputStream.open(%s) monitorWait wasn't needed, status=%d: %s",
URL_ADDR(javaData), javaData->errorStatus, FIX_NULL(javaData->errorMessage)));
}
if (javaData->errorStatus < 0) {
PR_LOG(NSJAVA, debug,
("URLInputStream.open(%s) raising IOException: %s",
URL_ADDR(javaData), FIX_NULL(javaData->errorMessage)));
monitorExit(nsn_JavaStream_monitor(javaData));
SignalError(0, "java/io/IOException", FIX_NULL(javaData->errorMessage));
return;
}
PR_LOG_END(NSJAVA, debug,
("URLInputStream.open(%s)", URL_ADDR(javaData)));
monitorExit(nsn_JavaStream_monitor(javaData));
}
PUBLIC long
netscape_net_URLInputStream_read(struct Hnetscape_net_URLInputStream* this,
HArrayOfByte* bytes, long offset, long length)
{
Hnetscape_net_URLConnection* con = unhand(this)->connection;
nsn_JavaStreamData* javaData = (nsn_JavaStreamData*)unhand(con)->pStreamData;
long datalen;
long remaining;
long result = -1; /* java EOF */
int tickleAbort;
ENTER_METHOD("URLInputStream.read", IO_EXCEPTION)
switch (javaData->state) {
case OPENED:
case LIBNET_DONE:
datalen = obj_length(bytes);
if ((offset < 0) || (offset > datalen)) {
SignalError(0, JAVAPKG "ArrayIndexOutOfBoundsException", 0);
break;
}
if (offset + length > datalen) {
length = datalen - offset;
}
remaining = length;
/* Check if there's buffered input to consume: */
while (javaData->dataQueue.head == NULL) {
if (javaData->state == LIBNET_DONE) {
/* If there's no buffered input, and libnet is done,
there's not going to be anymore: */
PR_LOG(NSJAVA, debug,
("URLInputStream.read(%s), %d bytes, => EOF",
URL_ADDR(javaData), length));
goto done;
}
/* Wait for input: */
PR_LOG(NSJAVA, debug,
("URLInputStream.read(%s) monitorWait",
URL_ADDR(javaData)));
/* If there is a tickle proc installed, then we should call
** it instead of mon-waiting immediately in case we are being
** called by the main thread. This will prevent deadlock
** where the main thread would wait forever to be notified
** by itself (via NET_ProcessNET. If the tickle procedure
** returns false (continue tickling), then we repeat the tickle
** tickle until the stream is aborted or a write notifies,
** which we can tell from the counter javaData->notifies
** incrementing.
*/
tickleAbort = 1;
if (g_nsn_TickleHookProc != NULL) {
int beforeNotifies = javaData->notifies;
while (1) {
tickleAbort = (*g_nsn_TickleHookProc)();
if (tickleAbort || (javaData->notifies != beforeNotifies))
break;
}
}
if (tickleAbort)
monitorWait(nsn_JavaStream_monitor(javaData), TIMEOUT_INFINITY);
PR_LOG(NSJAVA, debug,
("URLInputStream.read(%s) monitorWait",
URL_ADDR(javaData)));
if (exceptionOccurred(EE())) {
/* we may get ThreadDeath */
PR_LOG(NSJAVA, debug,
("URLInputStream.read(%s), %d bytes, => exception occurred",
URL_ADDR(javaData), length));
goto done;
}
/* When we return from Wait, the stream may have been closed. Check: */
if (javaData->state != OPENED && javaData->state != LIBNET_DONE) {
PR_LOG(NSJAVA, debug,
("URLInputStream.read(%s), %d bytes, => stream closed",
URL_ADDR(javaData), length));
SignalError(0, "java/io/IOException", javaData->errorMessage);
goto done;
}
}
/* Snarf whatever data's available: */
result = nsn_DataQueue_readBytes(&javaData->dataQueue,
&(unhand(bytes)->body)[offset],
remaining);
break;
default:
PR_LOG(NSJAVA, debug,
("URLInputStream.read(%s), %d bytes, => stream not opened",
URL_ADDR(javaData), length));
SignalError(0, "java/io/IOException",
(javaData->errorMessage ? javaData->errorMessage : "stream closed"));
break;
}
done:
EXIT_METHOD("URLInputStream.read")
return result;
}
PUBLIC long
netscape_net_URLInputStream_available(struct Hnetscape_net_URLInputStream* this)
{
Hnetscape_net_URLConnection* con = unhand(this)->connection;
nsn_JavaStreamData* javaData = (nsn_JavaStreamData*)unhand(con)->pStreamData;
long result = 0;
ENTER_METHOD("URLInputStream.available", IO_EXCEPTION)
switch (javaData->state) {
case OPENED:
case LIBNET_DONE:
result = nsn_DataQueue_length(&javaData->dataQueue);
break;
default:
PR_LOG(NSJAVA, debug,
("URLInputStream.available(%s), raising IOException",
URL_ADDR(javaData)));
SignalError(0, "java/io/IOException",
(javaData->errorMessage ? javaData->errorMessage : "stream closed"));
break;
}
EXIT_METHOD("URLInputStream.available")
return result;
}
/******************************************************************************/