File:  [Public] / libwww / Library / src / HTSocket.c
Revision 2.4: download - view: text, annotated - select for diffs
Sat Jul 8 03:23:14 1995 UTC (28 years, 11 months ago) by frystyk
Branches: MAIN
CVS tags: v3/1pre3, v3/1, NT1, NNTP, HEAD
most of version 3.1pre3

/*								     HTSocket.c
**	MANAGES READ AND WRITE TO AND FROM THE NETWORK
**
**	(c) COPYRIGHT MIT 1995.
**	Please first read the full copyright statement in the file COPYRIGH.
**
**
** HISTORY:
**	6 June 95  HFN	Spawned off from HTFormat
*/

/* Library Include files */
#include "tcp.h"
#include "HTUtils.h"
#include "HTString.h"
#include "HTAccess.h"
#include "HTProt.h"
#include "HTTCP.h"
#include "HTStream.h"
#include "HTFormat.h"
#include "HTThread.h"
#include "HTError.h"
#include "HTSocket.h"					 /* Implemented here */

struct _HTInputSocket {
    char	input_buffer[INPUT_BUFFER_SIZE];
    char *	input_pointer;
    char *	input_limit;
    SOCKFD	input_file_number;
};

struct _HTStream {
    CONST HTStreamClass *	isa;
};

/* ------------------------------------------------------------------------- */
/* 				SOCKET INPUT BUFFERING			     */
/* ------------------------------------------------------------------------- */
/*			
**	This code is used because one cannot in general open a
**	file descriptor for a socket.
**
**	The input file is read using the macro which can read from
**	a socket or a file, but this should not be used for files
**	as fopen() etc is more portable of course.
**
**	The input buffer size, if large will give greater efficiency and
**	release the server faster, and if small will save space on PCs etc.
*/


/*	Set up the buffering
**
**	These routines are public because they are in fact needed by
**	many parsers, and on PCs and Macs we should not duplicate
**	the static buffer area.
*/
PUBLIC HTInputSocket * HTInputSocket_new ARGS1 (SOCKFD, file_number)
{
    HTInputSocket *isoc = (HTInputSocket *)calloc(1, sizeof(*isoc));
    if (!isoc) outofmem(__FILE__, "HTInputSocket_new");
    isoc->input_file_number = file_number;
    isoc->input_pointer = isoc->input_limit = isoc->input_buffer;
    return isoc;
}

/* This should return HT_INTERRUPTED if interrupted BUT the connection
   MUST not be closed */ 
PUBLIC int HTInputSocket_getCharacter ARGS1(HTInputSocket*, isoc)
{
    int ch;
    do {
	if (isoc->input_pointer >= isoc->input_limit) {
	    int status = NETREAD(isoc->input_file_number,
				 isoc->input_buffer, INPUT_BUFFER_SIZE);
	    if (status <= 0) {
		if (status == 0)
		    return EOF;
		if (status == HT_INTERRUPTED) {
		    if (TRACE)
			fprintf(TDEST, "Get Char.... Interrupted in HTInputSocket_getCharacter\n");
		    return HT_INTERRUPTED;
		}
		if (PROT_TRACE)
		    fprintf(TDEST, "Read Socket. READ ERROR %d\n", socerrno);
		return EOF; 	/* -1 is returned by UCX at end of HTTP link */
	    }
	    isoc->input_pointer = isoc->input_buffer;
	    isoc->input_limit = isoc->input_buffer + status;
	}
	ch = (unsigned char) *isoc->input_pointer++;
    } while (ch == 13);			     /* Ignore ASCII carriage return */
    
    return FROMASCII(ch);
}

PUBLIC void HTInputSocket_free ARGS1(HTInputSocket *, me)
{
    if (me) free(me);
}


PUBLIC char * HTInputSocket_getBlock ARGS2(HTInputSocket*,	isoc,
					   int *,		len)
{
    if (isoc->input_pointer >= isoc->input_limit) {
	int status = NETREAD(isoc->input_file_number,
			     isoc->input_buffer,
			     ((*len < INPUT_BUFFER_SIZE) ?
			      *len : INPUT_BUFFER_SIZE));
	if (status <= 0) {
	    isoc->input_limit = isoc->input_buffer;
	    if (status < 0) {
		if (PROT_TRACE)
		    fprintf(TDEST, "Read Socket. READ ERROR %d\n", socerrno);
	    }
	    *len = 0;
	    return NULL;
	}
	else {
	    *len = status;
	    return isoc->input_buffer;
	}
    }
    else {
	char * ret = isoc->input_pointer;
	*len = isoc->input_limit - isoc->input_pointer;
	isoc->input_pointer = isoc->input_limit;
	return ret;
    }
}


PRIVATE int fill_in_buffer ARGS1(HTInputSocket *, isoc)
{
    if (isoc) {
	int status;

	isoc->input_pointer = isoc->input_buffer;
	status = NETREAD(isoc->input_file_number,
			 isoc->input_buffer,
			 INPUT_BUFFER_SIZE);
	if (status <= 0) {
	    isoc->input_limit = isoc->input_buffer;
	    if (status < 0) {
		if (PROT_TRACE)
		    fprintf(TDEST, "Read Socket. READ ERROR %d\n", socerrno);
	    }
	}
	else 
	    isoc->input_limit = isoc->input_buffer + status;
	return status;
    }
    return -1;
}


PRIVATE void ascii_cat ARGS3(char **,	linep,
			     char *,	start,
			     char *,	end)
{
    if (linep && start && end && start <= end) {
	char *ptr;

	if (*linep) {
	    int len = strlen(*linep);
	    *linep = (char*)realloc(*linep, len + end-start + 1);
	    ptr = *linep + len;
	}
	else {
	    ptr = *linep = (char*)malloc(end-start + 1);
	}

	while (start < end) {
	    *ptr = FROMASCII(*start);
	    ptr++;
	    start++;
	}
	*ptr = 0;
    }
}


PRIVATE char * get_some_line ARGS2(HTInputSocket *,	isoc,
				   BOOL,		unfold)
{
    if (!isoc)
	return NULL;
    else {
	BOOL check_unfold = NO;
	int prev_cr = 0;
	char *start = isoc->input_pointer;
	char *cur = isoc->input_pointer;
	char * line = NULL;

	for(;;) {
	    /*
	    ** Get more if needed to complete line
	    */
	    if (cur >= isoc->input_limit) { /* Need more data */
		ascii_cat(&line, start, cur);
		if (fill_in_buffer(isoc) <= 0)
		    return line;
		start = cur = isoc->input_pointer;
	    } /* if need more data */

	    /*
	    ** Find a line feed if there is one
	    */
	    for(; cur < isoc->input_limit; cur++) {
		char c = FROMASCII(*cur);
		if (!c) {
		    if (line) free(line);	/* Leak fixed AL 6 Feb 94 */
		    return NULL;	/* Panic! read a 0! */
		}
		if (check_unfold  &&  c != ' '  &&  c != '\t') {
		    return line;  /* Note: didn't update isoc->input_pointer */
		}
		else {
		    check_unfold = NO;
		}

		if (c=='\r') {
		    prev_cr = 1;
		}
		else {
		    if (c=='\n') {		/* Found a line feed */
			ascii_cat(&line, start, cur-prev_cr);
			start = isoc->input_pointer = cur+1;

			if (line && (int) strlen(line) > 0 && unfold) {
			    check_unfold = YES;
			}
			else {
			    return line;
			}
		    } /* if NL */
		    /* else just a regular character */
		    prev_cr = 0;
		} /* if not CR */
	    } /* while characters in buffer remain */
	} /* until line read or end-of-file */
    } /* valid parameters to function */
}

/* The returned string must be freed by the caller */
PUBLIC char * HTInputSocket_getLine ARGS1(HTInputSocket *, isoc)
{
    return get_some_line(isoc, NO);
}

/* The returned string must be freed by the caller */
PUBLIC char * HTInputSocket_getUnfoldedLine ARGS1(HTInputSocket *, isoc)
{
    return get_some_line(isoc, YES);
}


/*	Push data from a socket down a stream
**	-------------------------------------
**
**   This routine is responsible for creating and PRESENTING any
**   graphic (or other) objects described by the file.
**
**   The file number given is assumed to be a TELNET stream ie containing
**   CRLF at the end of lines which need to be stripped to LF for unix
**   when the format is textual.
**
**   RETURNS the number of bytes transferred.
**
*/
PUBLIC int HTCopy ARGS2(
	SOCKFD,			file_number,
	HTStream*,		sink)
{
    HTStreamClass targetClass;    
    HTInputSocket * isoc;
    int cnt = 0;

/*	Push the data down the stream
**
*/
    targetClass = *(sink->isa);	/* Copy pointers to procedures */
    isoc = HTInputSocket_new(file_number);
    
    /*	Push binary from socket down sink
    **
    **		This operation could be put into a main event loop
    */
    for(;;) {
	int status = NETREAD(
		file_number, isoc->input_buffer, INPUT_BUFFER_SIZE);
	if (status <= 0) {
	    if (status == 0) break;
	    if (TRACE) fprintf(TDEST,
		"Socket Copy. Read error, read returns %d with errno=%d\n",
		status, socerrno);
	    break;
	}

#ifdef NOT_ASCII
	{
	    char * p;
	    for(p = isoc->input_buffer; p < isoc->input_buffer+status; p++) {
		*p = FROMASCII(*p);
	    }
	}
#endif

	(*targetClass.put_block)(sink, isoc->input_buffer, status);
	cnt += status;
    } /* next bufferload */

    HTInputSocket_free(isoc);

    return cnt;
}



/*	Push data from a file pointer down a stream
**	-------------------------------------
**
**   This routine is responsible for creating and PRESENTING any
**   graphic (or other) objects described by the file.
**
**
*/
PUBLIC void HTFileCopy ARGS2(
	FILE *,			fp,
	HTStream*,		sink)
{
    HTStreamClass targetClass;    
    char input_buffer[INPUT_BUFFER_SIZE];
    
/*	Push the data down the stream
**
*/
    targetClass = *(sink->isa);	/* Copy pointers to procedures */
    
    /*	Push binary from socket down sink
    */
    for(;;) {
	int status = fread(
	       input_buffer, 1, INPUT_BUFFER_SIZE, fp);
	if (status == 0) { /* EOF or error */
	    if (ferror(fp) == 0) break;
	    if (TRACE) fprintf(TDEST,
		"File Copy... Read error, read returns %d\n", ferror(fp));
	    break;
	}
	(*targetClass.put_block)(sink, input_buffer, status);
    } /* next bufferload */	
}




/*	Push data from a socket down a stream STRIPPING CR
**	--------------------------------------------------
**
**   This routine is responsible for creating and PRESENTING any
**   graphic (or other) objects described by the socket.
**
**   The file number given is assumed to be a TELNET stream ie containing
**   CRLF at the end of lines which need to be stripped to LF for unix
**   when the format is textual.
**	
**	Character handling is now of type int, Henrik, May 09-94
*/
PUBLIC void HTCopyNoCR ARGS2(
	SOCKFD,			file_number,
	HTStream*,		sink)
{
    HTStreamClass targetClass;
    HTInputSocket * isoc;   
    int ch;
    
/*	Push the data, ignoring CRLF, down the stream
**
*/
    targetClass = *(sink->isa);	/* Copy pointers to procedures */

/*	Push text from telnet socket down sink
**
**	@@@@@ To push strings could be faster? (especially is we
**	cheat and don't ignore CR! :-}
*/  
    isoc = HTInputSocket_new(file_number);
    while ((ch = HTInputSocket_getCharacter(isoc)) >= 0)
	(*targetClass.put_character)(sink, ch);
    HTInputSocket_free(isoc);
}


/*	Parse a socket given format and file number
**
**   This routine is responsible for creating and PRESENTING any
**   graphic (or other) objects described by the file.
**
**   The file number given is assumed to be a TELNET stream ie containing
**   CRLF at the end of lines which need to be stripped to LF for unix
**   when the format is textual.
**
**	Returns <0 on error, HT_LOADED on success.
*/

/* The parameter to this function and HTParsefile should be HTRequest */

PUBLIC int HTParseSocket ARGS3(
	HTFormat,		rep_in,
	SOCKFD,			file_number,
	HTRequest *,		request)
{
    HTStream * stream;
    HTStreamClass targetClass;    

    if (request->error_stack) {
	if (TRACE) fprintf(TDEST, "ParseSocket. Called whith non-empty error stack, so I return right away!\n");
	return -1;
    }

    /* Set up stream stack */
    if ((stream = HTStreamStack(rep_in, request->output_format,
				request->output_stream,
				request, YES)) == NULL)
	return -1;
    
/*	Push the data, ignoring CRLF if necessary, down the stream
**
**
**   @@  Bug:  This decision ought to be made based on "encoding"
**   rather than on format.  @@@  When we handle encoding.
**   The current method smells anyway.
*/
    targetClass = *(stream->isa);	/* Copy pointers to procedures */
    if (rep_in == WWW_BINARY || rep_in == WWW_UNKNOWN
	|| (HTAnchor_encoding(request->anchor) != HTAtom_for("8bit") &&
	    HTAnchor_encoding(request->anchor) != HTAtom_for("7bit"))
        || strstr(HTAtom_name(rep_in), "image/")
	|| strstr(HTAtom_name(rep_in), "video/")) { /* @@@@@@ */
	HTCopy(file_number, stream);
    } else
        HTCopyNoCR(file_number, stream);
    (*targetClass._free)(stream);
    
    return HT_LOADED;
}



/*	Parse a file given format and file pointer
**
**   This routine is responsible for creating and PRESENTING any
**   graphic (or other) objects described by the file.
**
**   The file number given is assumed to be a TELNET stream ie containing
**   CRLF at the end of lines which need to be stripped to \n for unix
**   when the format is textual.
**
*/
PRIVATE int HTParseFile ARGS3(
	HTFormat,		rep_in,
	FILE *,			fp,
	HTRequest *,		request)
{
    HTStream * stream;
    HTStreamClass targetClass;    

    if (request->error_stack) {
	if (TRACE) fprintf(TDEST, "ParseFile... Called whith non-empty error stack, so I return right away!\n");
	return -1;
    }

    /* Set up stream stack */
    if ((stream = HTStreamStack(rep_in, request->output_format,
				request->output_stream, request, YES)) == NULL)
	return -1;
    
/*	Push the data down the stream
**
**
**   @@  Bug:  This decision ought to be made based on "encoding"
**   rather than on content-type.  @@@  When we handle encoding.
**   The current method smells anyway.
*/
    targetClass = *(stream->isa);	/* Copy pointers to procedures */
    HTFileCopy(fp, stream);
    (*targetClass._free)(stream);
    
    return HT_LOADED;
}


/* ------------------------------------------------------------------------- */
/*			MULTI THREADED IMPLEMENTATIONS			     */
/* ------------------------------------------------------------------------- */

/*	Push data from a socket down a stream
**	-------------------------------------
**
**   This routine is responsible for creating and PRESENTING any
**   graphic (or other) objects described by the file. As this function
**   max reads a chunk of data on size INPUT_BUFFER_SIZE, it can be used
**   with both blocking or non-blocking sockets. It will always return to
**   the event loop, however if we are using blocking I/O then we get a full
**   buffer read, otherwise we get what's available.
**
** Returns      HT_LOADED	if finished reading
**		HT_OK		if OK, but more to read
**	      	HT_ERROR	if error,
**    		HT_INTERRUPTED	if interrupted
**     		HT_WOULD_BLOCK	if read would block
*/
PUBLIC int HTSocketRead ARGS2(HTRequest *, request, HTStream *, target)
{
    HTInputSocket *isoc = request->net_info->isoc;
    int b_read = isoc->input_limit-isoc->input_buffer;
    BOOL blocking = HTProtocol_isBlocking(request);
    int status;
    if (!isoc || isoc->input_file_number==INVSOC) {
	if (PROT_TRACE) fprintf(TDEST, "Read Socket. Bad argument\n");
	return HT_ERROR;
    }

    if (HTThreadIntr(isoc->input_file_number))		      /* Interrupted */
	return HT_INTERRUPTED;

    /* Read from socket if we got rid of all the data previously read */
    do {
	if (isoc->input_pointer >= isoc->input_limit) {
	    if ((b_read = NETREAD(isoc->input_file_number, isoc->input_buffer,
				  INPUT_BUFFER_SIZE)) < 0) {
#ifdef EAGAIN
		if (socerrno==EAGAIN || socerrno==EWOULDBLOCK)      /* POSIX */
#else
		    if (socerrno==EWOULDBLOCK) /* BSD */
#endif
			{
			    if (PROT_TRACE)
				fprintf(TDEST, "Read Socket. WOULD BLOCK soc %d\n",
					isoc->input_file_number);
			    HTThreadState(isoc->input_file_number, THD_SET_READ);
			    return HT_WOULD_BLOCK;
			} else { /* We have a real error */
			    if (PROT_TRACE)
				fprintf(TDEST, "Read Socket. READ ERROR %d\n",
					socerrno);
			    return HT_ERROR;
			}
	    } else if (!b_read) {
		if (PROT_TRACE)
		    fprintf(TDEST, "Read Socket. Finished loading socket %d\n",
			    isoc->input_file_number);
		HTThreadState(isoc->input_file_number, THD_CLR_READ);
		return HT_LOADED;
	    }

	    /* Remember how much we have read from the input socket */
	    isoc->input_pointer = isoc->input_buffer;
	    isoc->input_limit = isoc->input_buffer + b_read;

#ifdef NOT_ASCII
	    {
		char *p = isoc->input_buffer;
		while (p < isoc->input_limit) {
		    *p = FROMASCII(*p);
		    p++;
		}
	    }
#endif
	    if (PROT_TRACE)
		fprintf(TDEST, "Read Socket. %d bytes read from socket %d\n",
			b_read, isoc->input_file_number);
	}
	
	/* Now push the data down the stream */
	if ((status = (*target->isa->put_block)(target, isoc->input_buffer,
						b_read)) != HT_OK) {
	    if (status==HT_WOULD_BLOCK) {
		if (PROT_TRACE)
		    fprintf(TDEST, "Read Socket. Stream WOULD BLOCK\n");
		HTThreadState(isoc->input_file_number, THD_CLR_READ);
		return HT_WOULD_BLOCK;
	    } else {		/* We have a real error */
		if (PROT_TRACE)
		    fprintf(TDEST, "Read Socket. Stream ERROR\n");
		return status;
	    }
	}
	isoc->input_pointer = isoc->input_buffer + b_read;
	HTThreadState(isoc->input_file_number, THD_SET_READ);
    } while (blocking);
    return HT_WOULD_BLOCK;
}



/*	Push data from an ANSI file descriptor down a stream
**	----------------------------------------------------
**
**   This routine is responsible for creating and PRESENTING any
**   graphic (or other) objects described by the file.
**
**   Bugs: When we can wait on a file then this should also check interrupts!
**
**   Returns    HT_LOADED	if finished reading
**	      	HT_ERROR	if error,
*/
PUBLIC int HTFileRead ARGS3(FILE *, fp, HTRequest *, request,
			    HTStream *, target)
{
    HTInputSocket *isoc = request->net_info->isoc;
    int b_read;
    int status;
    if (!fp) {
	if (PROT_TRACE) fprintf(TDEST, "Read File... Bad argument\n");
	return HT_ERROR;
    }

    while(1) {
	if ((b_read = fread(isoc->input_buffer, 1, INPUT_BUFFER_SIZE, fp))==0){
	    if (ferror(fp)) {
		if (PROT_TRACE)
		    fprintf(TDEST, "Read File... READ ERROR\n");
	    } else
		return HT_LOADED;
	}
	isoc->input_pointer = isoc->input_buffer;
	isoc->input_limit = isoc->input_buffer + b_read;
	if (PROT_TRACE)
	    fprintf(TDEST, "Read File... %d bytes read from file %p\n",
		    b_read, fp);

	/* Now push the data down the stream (we use blocking I/O) */
	if ((status = (*target->isa->put_block)(target, isoc->input_buffer,
						b_read)) != HT_OK) {
	    if (PROT_TRACE)
		fprintf(TDEST, "Read File... Stream ERROR\n");
	    return status;
	}
	isoc->input_pointer = isoc->input_buffer + b_read;
    }
}



Webmaster