/* -*- Mode: C; tab-width: 4; 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.
 */
#include "mkutils.h"
#include "cvactive.h"
#include "mkgeturl.h"
#include "mkstream.h"
#include "glhist.h"
#include "xp.h"
#include "merrors.h"


extern int MK_OUT_OF_MEMORY;


typedef struct _DataObject {
	int               state;
	NET_StreamClass  *next_stream;
	char             *prev_buffer;
    int32             prev_buffer_len;
	MWContext        *window_id;
	int               format_out;
	URL_Struct       *URL_s;
	XP_Bool			  signal_at_end_of_multipart;
} DataObject;

#define NORMAL_S			1
#define FOUND_BOUNDARY_S    2

#define MAX_MIME_LINE 200

/* parse a mime/multipart stream.  The boundary is already
 * known and we start off in the content of the original
 * message/822 body.
 *
 * buffer data up to the size of the passed in data length
 * for efficiency of the upstream module.
 */
PRIVATE int net_MultipleDocumentWrite (NET_StreamClass *stream, CONST char* s, int32 l)
{
	int32 i=0;
	int32 line_length=1;
	int   rv=0;
	char *cp;
	char *line;
	char *push_buffer=NULL;
	int32 push_buffer_size=0;
	XP_Bool all_done=FALSE;
	DataObject *obj=stream->data_object;
	BlockAllocCat(obj->prev_buffer, obj->prev_buffer_len, s, l);
	obj->prev_buffer_len += l;	

	if(!obj->prev_buffer)
		return(MK_OUT_OF_MEMORY);

	line = obj->prev_buffer;

	/* try and find a line
	 */
	for(cp=obj->prev_buffer; i < obj->prev_buffer_len; cp++, i++, line_length++)
	  {
		if(*cp == '\n')
		  {
			
			switch(obj->state)
			  {
				case NORMAL_S:
				  {
					char *cp2 = line;
					int blength = XP_STRLEN(obj->URL_s->boundary);

				    /* look for boundary.  We can rest assured that these
					   XP_STRNCMP() calls are safe, because we know that the
					   valid portion of the string starting at cp2 has a
					   newline in it (at *cp), and we know that boundary
					   strings never have newlines in them.  */
		            if((!XP_STRNCMP(cp2, "--",2) && 
						!XP_STRNCMP(cp2+2, 
							        obj->URL_s->boundary, 
							        blength))
		              	|| (!XP_STRNCMP(cp2, 
							        obj->URL_s->boundary, 
							        blength)))
		              {
						TRACEMSG(("Found boundary: %s", obj->URL_s->boundary));
						obj->state = FOUND_BOUNDARY_S;
	
						if(obj->next_stream)
						  {

							if(push_buffer)
							  {
							    /* strip the last newline before the
							     * boundary
							     */
							    push_buffer_size--;
								/* if there is a CR as well as a LF
								 * strip that too
								 */
								if(push_buffer_size > 0 && push_buffer[push_buffer_size-1] == CR)
									push_buffer_size--;
	        				    rv = (*obj->next_stream->put_block)
													    (obj->next_stream, 
													    push_buffer, 
													    push_buffer_size);
							    FREE(push_buffer);
							    push_buffer = NULL; 
							    push_buffer_size = 0;
							  }

							TRACEMSG(("Completeing an open stream"));
							/* if this stream is not the last one, set a flag
							   before completion to let completion do special stuff */
							XP_ASSERT(cp2 + blength <= cp);
								/* Because the above XP_STRCMP calls succeeded.
								   Because this is true, we know the first call
								   to XP_STRNCMP below is always safe, but we
								   need to check lengths before we can be sure
								   the other call is safe. */

							if(   (cp2 + blength + 2 < cp && 
							 	   !XP_STRNCMP(cp2+2+blength, "--",2))
							   || !XP_STRNCMP(cp2+blength, "--",2))
							  {
								/* very last boundary */
								obj->next_stream->is_multipart = FALSE;

								/* set the all_done flag when
								 * we have found the final boundary
								 */
								all_done = TRUE;
							  }
							else
							  {
								obj->next_stream->is_multipart = TRUE;
							  }
							
			        	    /* complete the last stream
			         	     */
			        	    (*obj->next_stream->complete)
											    (obj->next_stream);
						    FREE(obj->next_stream);
						    obj->next_stream = NULL;
						  }
							
						/* move the line ptr up to new data */
						line = cp+1;
						line_length=1; /* reset */

						if(all_done && obj->signal_at_end_of_multipart)
							return(MK_MULTIPART_MESSAGE_COMPLETED);
						break;
					  }
					else 
					  {
						TRACEMSG(("Pushing line (actually buffering)"));

						/* didn't find the boundary
						 */
						if(obj->next_stream)
						  {

							BlockAllocCat(push_buffer, push_buffer_size,
										  line, line_length);
							push_buffer_size += line_length;
							if(!push_buffer)
								return(MK_OUT_OF_MEMORY);
						  }

						/* move the line ptr up to new data */
						line = cp+1;
						line_length=0; /* reset */
					  }
					break;
				  }

				case FOUND_BOUNDARY_S:

					XP_ASSERT(*cp == '\n'); /* from the 'if' above */

					/* terminate at the newline.
					 * now 'line' points to a valid NULL terminated C string
					 */
					*cp = '\0';

					TRACEMSG(("Parsing header: >%s<", line));
    
				    /* parse mime headers
				     * stop when a blank line is encountered
				     */
				    if(*line == '\0' || *line == '\r')
				      {
						int format_out;
					    obj->state = NORMAL_S;
    
					    TRACEMSG(("Found end of headers"));
    					if (obj->URL_s->content_type == NULL)
							StrAllocCopy(obj->URL_s->content_type, TEXT_PLAIN);

						/* abort all existing streams.  We
						 * have to do this to prevent the
						 * image lib and other things from
						 * continuing to load the page after
						 * we left.
						 *
						 * don't abort other streams if it's
						 * just a new inline image or if the
						 * stream is not going to the screen
						 */
						if(CLEAR_CACHE_BIT(obj->format_out) != FO_INTERNAL_IMAGE
							&& (!strncasecomp(obj->URL_s->content_type, 
											 "text", 4)
								||
								!strncasecomp(obj->URL_s->content_type, 
                                                "image", 4)) )
						  {
							NET_SilentInterruptWindow(obj->window_id);
							format_out = obj->format_out;
						  }
						else
						  {
							/* don't cache image animations... */
							format_out = CLEAR_CACHE_BIT(obj->format_out);
						  }
			
						/* libimg and libplugin use the fe_data to store 
						 * urls data, so clear it only if its not them */
						if( (CLEAR_CACHE_BIT(obj->format_out) != FO_INTERNAL_IMAGE) 
						      && (CLEAR_CACHE_BIT(obj->format_out) != FO_PLUGIN)
						      && (CLEAR_CACHE_BIT(obj->format_out) != FO_BYTERANGE)
						      && strncasecomp(obj->URL_s->content_type, "image", 5))
						  {
							obj->URL_s->fe_data = NULL;
						  }

					    /* build a stream
					     */
					    obj->next_stream = NET_StreamBuilder(format_out, 
														 	 obj->URL_s, 
														     obj->window_id);
	
					    if(!obj->next_stream)
						    return(MK_UNABLE_TO_CONVERT);
						
				      }
				    else if(!strncasecomp(line, "CONTENT-TYPE:", 13))
				      {
	
					    XP_STRTOK(line+13, ";"); /* terminate at ; */

					    StrAllocCopy(obj->URL_s->content_type, 
								     XP_StripLine(line+13));
	
					    if(!obj->URL_s->content_type || !*obj->URL_s->content_type)
						    StrAllocCopy(obj->URL_s->content_type, TEXT_PLAIN);

					    TRACEMSG(("found new content_type: %s", 
							       obj->URL_s->content_type));

				      }
					else
				      {
				    	/* Pass all other headers to the MIME header parser 
				     	 */
						char *value = XP_STRCHR(line, ':');
    					if(value)
        					value++;
    					NET_ParseMimeHeader(NET_AllowForeignCookies,
											obj->window_id, 
											obj->URL_s, 
											line, 
											value, FALSE);
				      }
					line = cp+1;
					line_length = 0;
				    break;

				default:
				    assert(0);
					break;
				
			  }
		  } /* end if */
	  } /* end for */

	if(line_length > MAX_MIME_LINE * 2)
	  {
		int32 new_size;

		TRACEMSG(("Line too long pushing it"));

		if(obj->next_stream)
		  {
			if(push_buffer)
			  {
        		rv = (*obj->next_stream->put_block)(obj->next_stream, 
												push_buffer, 
												push_buffer_size);
	        	rv = (*obj->next_stream->put_block)(obj->next_stream, 
													line, 
													MAX_MIME_LINE);
				FREE(push_buffer);
				push_buffer = 0;
				push_buffer_size = 0;
			  }
			else
			  {
	        	rv = (*obj->next_stream->put_block)(obj->next_stream, 
													line, 
													MAX_MIME_LINE);
			  }

		    if(rv < 0)
			    return(rv);
		  }

		/* newsize equals the old size minus the difference
		 * between line and the start of the old buffer and
		 * the MAX_MIME_LENGTH that we just wrote out
		 */
		new_size = obj->prev_buffer_len - 
				     ((line - obj->prev_buffer) + MAX_MIME_LINE);

		XP_MEMMOVE(obj->prev_buffer, line+MAX_MIME_LINE, new_size);
		obj->prev_buffer_len = new_size;
	
		return(0);
	  }

	if(line != obj->prev_buffer)
	  {
		/* some part of the line has been digested, get rid of
		 * the part that has been used
		 */
		obj->prev_buffer_len -= (line - obj->prev_buffer);
		XP_MEMMOVE(obj->prev_buffer, line, obj->prev_buffer_len);
	  }

	/* if there is anything in the push buffer send it now
	 */
	if(push_buffer)
	  {
		if(obj->next_stream)
		  {
			TRACEMSG(("Pushing buffered data"));
        	rv = (*obj->next_stream->put_block)(obj->next_stream, 
												push_buffer, 
												push_buffer_size);
		  }
		FREE(push_buffer);
        if (rv < 0)
            return rv;
	  }
    
	return(0);
}

/* is the stream ready for writeing?
 */
PRIVATE unsigned int net_MultipleDocumentWriteReady (NET_StreamClass * stream)
{
  DataObject *obj=stream->data_object;
  if(obj->next_stream)
    return((*obj->next_stream->is_write_ready)(obj->next_stream));
  else
	return(MAX_WRITE_READY);
}


PRIVATE void net_MultipleDocumentComplete (NET_StreamClass *stream)
{
	DataObject *obj=stream->data_object;	

    if(obj->next_stream)
	  {
	    (*obj->next_stream->complete)(obj->next_stream);
		FREE(obj->next_stream);
	  }

	FREEIF(obj->prev_buffer);
    FREE(obj);

    return;
}

PRIVATE void net_MultipleDocumentAbort (NET_StreamClass *stream, int status)
{
	DataObject *obj=stream->data_object;	
    if(obj->next_stream)
	  {
    	(*obj->next_stream->abort)(obj->next_stream, status);
		FREE(obj->next_stream);
	  }

	FREEIF(obj->prev_buffer);
    FREE(obj);

    return;
}


PUBLIC NET_StreamClass * 
CV_MakeMultipleDocumentStream (int         format_out,
                               void       *data_object,
                               URL_Struct *URL_s,
                               MWContext  *window_id)
{
    DataObject* obj;
    NET_StreamClass* stream;
    
    TRACEMSG(("Setting up display stream. Have URL: %s\n", URL_s->address));

	GH_UpdateGlobalHistory(URL_s);

	URL_s->is_active = TRUE;  /* set to disable view source */

    stream = XP_NEW(NET_StreamClass);
    if(stream == NULL) 
        return(NULL);

    obj = XP_NEW(DataObject);
    if (obj == NULL) 
        return(NULL);

	XP_MEMSET(obj, 0, sizeof(DataObject));

	if(CVACTIVE_SIGNAL_AT_END_OF_MULTIPART == (int) data_object)
		obj->signal_at_end_of_multipart = TRUE;
    
    stream->name           = "Multiple Document";
    stream->complete       = (MKStreamCompleteFunc) net_MultipleDocumentComplete;
    stream->abort          = (MKStreamAbortFunc) net_MultipleDocumentAbort;
    stream->put_block      = (MKStreamWriteFunc) net_MultipleDocumentWrite;
    stream->is_write_ready = (MKStreamWriteReadyFunc) net_MultipleDocumentWriteReady;
    stream->data_object    = obj;  /* document info object */
    stream->window_id      = window_id;

	/* don't cache these
	format_out = CLEAR_CACHE_BIT(format_out);
	 */

	/* enable clicking since it doesnt go through the cache 
	 * code
	 */
	FE_EnableClicking(window_id);

	obj->next_stream = NULL;
	obj->window_id  = window_id;
	obj->format_out = format_out;
	obj->URL_s      = URL_s;
	obj->state      = NORMAL_S;

	/* make sure that we have a real boundary.
 	 * if not fill in '--' as a boundary
	 */
	if(!URL_s->boundary)
		StrAllocCopy(URL_s->boundary, "--");

	/* overwrite the current content type with TEXT/PLAIN since
	 * we want that as the default content-type for
	 * body parts
	 */
	StrAllocCopy(URL_s->content_type, TEXT_PLAIN);

    TRACEMSG(("Returning stream from NET_MultipleDocumentConverter\n"));

    return stream;
}
