/****************************************************************************
*																			*
*						  cryptlib HTTP Write Routines						*
*						Copyright Peter Gutmann 1998-2025					*
*																			*
****************************************************************************/

#include <ctype.h>
#include <stdio.h>
#if defined( INC_ALL )
  #include "crypt.h"
  #include "http.h"
#else
  #include "crypt.h"
  #include "io/http.h"
#endif /* Compiler-specific includes */

#ifdef USE_HTTP

/****************************************************************************
*																			*
*								Utility Functions							*
*																			*
****************************************************************************/

/* Encode a string as per RFC 1866 (although the list of characters that 
   need to be escaped is itself given in RFC 2396, section 2.2 "Reserved 
   Characters").  Characters that are permitted/not permitted are:

	 !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
	x..x.xx....x...xxxxxxxxxxxx.xxxxx

   Because of this it's easier to check for the most likely permitted
   characters (alphanumerics) first, and only then to check for any special-
   case characters */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
static int encodeRFC1866( INOUT_PTR STREAM *headerStream, 
						  IN_BUFFER( stringLength) const char *string, 
						  IN_LENGTH_SHORT const int stringLength )
	{
	static const char allowedChars[] = "$-_.!*'(),\"/\x00\x00";
					  /* RFC 1738 section 2.2 "URL Character Encoding 
						 Issues" + '/' */
	LOOP_INDEX index;
	int status;

	assert( isWritePtr( headerStream, sizeof( STREAM ) ) );
	assert( isReadPtrDynamic( string, stringLength ) );

	REQUIRES( isShortIntegerRangeNZ( stringLength ) );

	LOOP_MAX( index = 0, index < stringLength, index++ )
		{
		int ch;
		LOOP_INDEX_ALT i;

		ENSURES( LOOP_INVARIANT_MAX( index, 0, stringLength - 1 ) );

		ch = byteToInt( string[ index ] );
		if( isAlnum( ch ) )
			{
			status = sputc( headerStream, ch );
			if( cryptStatusError( status ) )
				return( status );
			continue;
			}
		if( ch == ' ' )
			{
			status = sputc( headerStream, '+' );
			if( cryptStatusError( status ) )
				return( status );
			continue;
			}
		LOOP_MED_ALT( i = 0, 
					  i < FAILSAFE_ARRAYSIZE( allowedChars, char ) && \
							allowedChars[ i ] != '\0' && \
							ch != allowedChars[ i ],
						i++ )
			{
			ENSURES( LOOP_INVARIANT_MED_ALT( i, 0, 
											 FAILSAFE_ARRAYSIZE( allowedChars, \
																 char ) - 1 ) );
			}
		ENSURES( LOOP_BOUND_OK_ALT );
		ENSURES( i < FAILSAFE_ARRAYSIZE( allowedChars, char ) );
		if( allowedChars[ i ] != '\0' )
			{
			/* It's in the allowed-chars list, output it verbatim */
			status = sputc( headerStream, ch );
			if( cryptStatusError( status ) )
				return( status );
			}
		else
			{
			char escapeString[ 8 + 8 ];
			int escapeStringLen;

			/* It's a special char, escape it */
			escapeStringLen = sprintf_s( escapeString, 8, "%%%02X", ch );
			ENSURES( rangeCheck( escapeStringLen, 1, 7 ) );
			status = swrite( headerStream, escapeString, escapeStringLen );
			if( cryptStatusError( status ) )
				return( status );
			}
		}
	ENSURES( LOOP_BOUND_OK );
	
	return( CRYPT_OK );
	}

/* If we time out when sending HTTP header data this would usually be 
   reported as a CRYPT_ERROR_TIMEOUT by the lower-level network I/O 
   routines, however due to the multiple layers of I/O and the fact that to 
   the caller the write of the out-of-band HTTP header data (which can occur 
   as part of a standard HTTP write, but also in a GET or when sending an 
   error response) is invisible, we have to perform an explicit check to 
   make sure that we sent everything */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
int sendHTTPData( INOUT_PTR STREAM *stream, 
				  IN_BUFFER( length ) const void *buffer, 
				  IN_LENGTH const int length, 
				  IN_FLAGS_Z( TRANSPORT ) const int flags )
	{
	NET_STREAM_INFO *netStream = DATAPTR_GET( stream->netStream );
	int bytesWritten, status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isReadPtrDynamic( buffer, length ) );

	REQUIRES( isBufsizeRangeNZ( length ) );
	REQUIRES( flags == TRANSPORT_FLAG_NONE || \
			  flags == TRANSPORT_FLAG_FLUSH );
	REQUIRES( netStream != NULL && sanityCheckNetStream( netStream ) );

	status = bufferedTransportWrite( stream, buffer, length, &bytesWritten, 
									 flags );
	if( cryptStatusError( status ) )
		{
		/* Network-level error, the lower-level layers have reported the 
		   error details */
		return( status );
		}
	if( bytesWritten < length )
		{
		/* We timed out before writing all of the data.  Usually this will 
		   be reported as a CRYPT_ERROR_TIMEOUT by the lower-level write
		   routines, however due to the multiple layers of I/O layering that 
		   are possible and various bugs in networking stacks we perform an 
		   explicit check here to make sure that we got everything */
		retExt( CRYPT_ERROR_TIMEOUT, 
				( CRYPT_ERROR_TIMEOUT, NETSTREAM_ERRINFO, 
				  "HTTP write timed out before all data could be written, "
				  "only wrote %d of %d bytes", bytesWritten, length ) );
		}

	return( CRYPT_OK );
	}

/****************************************************************************
*																			*
*								Write Request Header						*
*																			*
****************************************************************************/

/* Write an HTTP request header.  The forceGet flag is used when we should 
   be using a POST but a broken server forces the use of a GET */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
static int writeReqMethod( INOUT_PTR STREAM *stream, 
						   IN_PTR const NET_STREAM_INFO *netStream,
						   IN_BOOL const BOOLEAN usePost )
	{
	int status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isReadPtr( netStream, sizeof( NET_STREAM_INFO ) ) );

	REQUIRES( isBooleanValue( usePost ) );

	if( TEST_FLAG( netStream->nhFlags, STREAM_NHFLAG_TUNNEL ) )
		swrite( stream, "CONNECT ", 8 );
	else
		{
		if( usePost )
			swrite( stream, "POST ", 5 );
		else
			swrite( stream, "GET ", 4 );
		}
	if( TEST_FLAG( netStream->nhFlags,
				   STREAM_NHFLAG_PROXY | STREAM_NHFLAG_TUNNEL ) )
		{
		/* If we're going through an HTTP proxy/tunnel, send an absolute URL 
		   rather than just the relative location */
		if( TEST_FLAG( netStream->nhFlags, STREAM_NHFLAG_PROXY ) )
			swrite( stream, "http://", 7 );
		status = swrite( stream, netStream->host, netStream->hostLen );
		if( cryptStatusOK( status ) && netStream->port != 80 )
			{
			char portString[ 16 + 8 ];
			int portStringLength;

			portStringLength = sprintf_s( portString, 16, ":%d", 
										  netStream->port );
			ENSURES( rangeCheck( portStringLength, 1, 15 ) );
			status = swrite( stream, portString, portStringLength );
			}
		if( cryptStatusError( status ) )
			return( status );
		}
	if( !TEST_FLAG( netStream->nhFlags, STREAM_NHFLAG_TUNNEL ) )
		{
		if( netStream->path != NULL && netStream->pathLen > 0 )
			{
			status = swrite( stream, netStream->path, 
							 netStream->pathLen );
			}
		else
			status = sputc( stream, '/' );
		if( cryptStatusError( status ) )
			return( status );
		}

	return( CRYPT_OK );
	}

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
static int writeReqURI( INOUT_PTR STREAM *stream, 
						IN_PTR const HTTP_REQ_INFO *httpReqInfo )
	{
	int status = CRYPT_OK;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isReadPtr( httpReqInfo, sizeof( HTTP_REQ_INFO ) ) );

	/* If there's no additional URI data present then we're done */
	if( httpReqInfo->attributeLen == 0 && httpReqInfo->valueLen == 0 && \
		httpReqInfo->extraDataLen == 0 )
		return( CRYPT_OK );

	REQUIRES( httpReqInfo->attributeLen > 0 && \
			  httpReqInfo->valueLen > 0 );

	/* Send optional additional request information:

		...?attribute=value&extraData */
	sputc( stream, '?' );
	swrite( stream, httpReqInfo->attribute, httpReqInfo->attributeLen );
	sputc( stream, '=' );
	status = encodeRFC1866( stream, httpReqInfo->value, 
							httpReqInfo->valueLen );
	if( cryptStatusOK( status ) && httpReqInfo->extraDataLen > 0 )
		{
		sputc( stream, '&' );
		status = swrite( stream, httpReqInfo->extraData, 
						 httpReqInfo->extraDataLen );
		}

	return( status );
	}

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
static int writeReqHeaderRemainder( INOUT_PTR STREAM *stream, 
									const NET_STREAM_INFO *netStream )
	{
	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isReadPtr( netStream, sizeof( NET_STREAM_INFO ) ) );

	/* If it's an HTTP 1.0 request we just write the basic header */
	if( isHTTP10( netStream ) )
		{
		swrite( stream, " HTTP/1.0\r\n", 11 );
		return( swrite( stream, "Connection: keep-alive\r\n", 24 ) );
		}

	/* HTTP 1.1 has an addition Host: header, as well as other situation-
	   specific headers, present */
	swrite( stream, " HTTP/1.1\r\nHost: ", 17 );
	swrite( stream, netStream->host, netStream->hostLen );
#ifdef USE_WEBSOCKETS
	if( TEST_FLAG( netStream->nhFlags, STREAM_NHFLAG_WS_UPGRADE ) )
		return( swrite( stream, "\r\n", 2 ) );
#endif /* USE_WEBSOCKETS */
	swrite( stream, "\r\n", 2 );
	if( TEST_FLAG( netStream->nFlags, STREAM_NFLAG_LASTMSGW ) )
		return( swrite( stream, "Connection: close\r\n", 19 ) );

	/* The following shouldn't be required for HTTP 1.1 but there are a 
	   sufficient number of broken caches and proxies around that we need to 
	   include it for the ones that helpfully close the *HTTP 1.1 
	   persistent* connection for us if they don't see an HTTP 1.0 keepalive 
	   in the header */
	return( swrite( stream, "Connection: keep-alive\r\n", 24 ) );
	}

#ifdef USE_WEBSOCKETS

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
static int writeReqUpgradeHeaders( INOUT_PTR STREAM *stream, 
								   IN_PTR const HTTP_REQ_INFO *httpReqInfo )
	{
	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isReadPtr( httpReqInfo, sizeof( HTTP_REQ_INFO ) ) );

	REQUIRES( isShortIntegerRangeNZ( httpReqInfo->authLen ) );

	/* We're doing a WebSockets protocol upgrade, write the necessary 
	   upgrade headers */
	swrite( stream, "Upgrade: websocket\r\n", 20 );
	swrite( stream, "Connection: upgrade\r\n", 21 );
	swrite( stream, "Sec-WebSocket-Key: ", 19 );
	swrite( stream, httpReqInfo->auth, httpReqInfo->authLen );
	swrite( stream, "\r\n", 2 );
	if( httpReqInfo->protocolLen > 0 )
		{
		swrite( stream, "Sec-WebSocket-Protocol: ", 24 );
		swrite( stream, httpReqInfo->protocol, httpReqInfo->protocolLen ); 
		swrite( stream, "\r\n", 2 );
		}
	swrite( stream, "Sec-Websocket-Version: 13\r\n", 27 );
	return( swrite( stream, "\r\n", 2 ) );
	}
#endif /* USE_WEBSOCKETS */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
static int writeContentHeaders( INOUT_PTR STREAM *stream, 
								IN_BUFFER( contentTypeLen ) \
									const char *contentType, 
								IN_LENGTH_SHORT const int contentTypeLen, 
								IN_LENGTH const int contentLength )
	{
	char lengthString[ 16 + 8 ];
	int lengthStringLength;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isReadPtrDynamic( contentType, contentTypeLen ) );

	REQUIRES( isShortIntegerRangeNZ( contentTypeLen ) );
	REQUIRES( isBufsizeRangeNZ( contentLength ) );

	/* In terms of caching, there are two control directives that we're
	   interested in, no-cache and no-store.  The HTTP 1.0 no-cache doesn't 
	   mean that the data won't be cached, merely that it has to be re-
	   validated on each fetch, while the HTTP 1.1 no-store actually means
	   what no-cache would seem to mean, that the data will never be stored/
	   cached.  However, no-store was meant mostly as a privacy mechanism,
	   indicating that a cache shouldn't store user credentials or PII.  In
	   the case of browsers, in order to get what you'd expect from no-cache
	   you need to use both no-cache and no-store, with different ones
	   affecting different browsers, and with the additional twist that some
	   browsers implement no-cache like it was no-store.
	   
	   For fun we could also send the RFC 8674 "Prefer: safe" (request) and
	   "Preference-Applied: safe" (response) headers but it's possible that
	   PKIX stuff is seen as objectionable and so we'd get nothing back */
	swrite( stream, "Content-Type: ", 14 );
	swrite( stream, contentType, contentTypeLen );
	swrite( stream, "\r\nContent-Length: ", 18 );
	lengthStringLength = sprintf_s( lengthString, 16, "%d", 
									contentLength );
	ENSURES( rangeCheck( lengthStringLength, 1, 15 ) );
	swrite( stream, lengthString, lengthStringLength );
	swrite( stream, "\r\nCache-Control: no-cache, no-store\r\n", 37 );
	return( swrite( stream, "\r\n", 2 ) );
	}

CHECK_RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
int writeRequestHeader( INOUT_PTR STREAM *stream, 
						IN_PTR_OPT const HTTP_REQ_INFO *httpReqInfo,
						IN_BUFFER_OPT( contentTypeLen ) \
							const char *contentType, 
						IN_LENGTH_SHORT_Z const int contentTypeLen, 
						IN_LENGTH_Z const int contentLength,
						IN_BOOL const BOOLEAN forceGet )
	{
	const NET_STREAM_INFO *netStream = DATAPTR_GET( stream->netStream );
	STREAM headerStream;
	char headerBuffer[ HTTP_LINEBUF_SIZE + 8 ];
	const int transportFlag = ( contentLength > 0 || forceGet ) ? \
							  TRANSPORT_FLAG_NONE : TRANSPORT_FLAG_FLUSH;
	int headerLength DUMMY_INIT, status DUMMY_INIT;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( ( httpReqInfo == NULL ) || \
			isReadPtr( httpReqInfo, sizeof( HTTP_REQ_INFO ) ) );
	assert( ( contentLength == 0 && contentType == NULL && \
			  contentTypeLen == 0 ) || \
			( contentLength >= 1 && \
			  isReadPtrDynamic( contentType, contentTypeLen ) ) );
	
	REQUIRES( ( contentLength == 0 && contentType == NULL && \
				contentTypeLen == 0 ) || \
			  ( isBufsizeRangeNZ( contentLength ) && \
			    contentType != NULL && \
				isShortIntegerRangeNZ( contentTypeLen ) ) );
	REQUIRES( ( httpReqInfo == NULL ) || \
			  ( httpReqInfo != NULL && \
				( ( httpReqInfo->attributeLen == 0 && \
					httpReqInfo->valueLen == 0 ) || \
				  ( httpReqInfo->valueLen > 0 ) ) ) );
	REQUIRES( isBooleanValue( forceGet ) );
	REQUIRES( netStream != NULL && sanityCheckNetStream( netStream ) );

	/* Write the method, "GET/POST/etc", and the URI */
	sMemOpen( &headerStream, headerBuffer, HTTP_LINEBUF_SIZE );
	status = writeReqMethod( &headerStream, netStream,
							 ( contentLength > 0 && !forceGet ) ? \
							   TRUE : FALSE );
	ENSURES( cryptStatusOK( status ) );
	if( httpReqInfo != NULL )
		{
		status = writeReqURI( &headerStream, httpReqInfo );
		ENSURES( cryptStatusOK( status ) );
		}

	/* If we're sending what should be a POST as a GET to deal with broken 
	   servers then we have to stop at this point because the payload data 
	   which is written by the caller is part of the request URI.  Otherwise
	   we continue with the rest of the request URI, "HTTP/1.1" and any
	   extra fields like Host: and Connection: */
	if( !forceGet )
		{
		status = writeReqHeaderRemainder( &headerStream, netStream );
		ENSURES( cryptStatusOK( status ) );
#ifdef USE_WEBSOCKETS
		if( TEST_FLAG( netStream->nhFlags, STREAM_NHFLAG_WS_UPGRADE ) )
			{
			REQUIRES( httpReqInfo != NULL );
			status = writeReqUpgradeHeaders( &headerStream, httpReqInfo );
			}
		else
#endif /* USE_WEBSOCKETS */
			{
			if( contentLength > 0 )
				{
				status = writeContentHeaders( &headerStream, contentType, 
											  contentTypeLen, 
											  contentLength );
				}
			else
				status = swrite( &headerStream, "\r\n", 2 );
			}
		}
	if( cryptStatusOK( status ) )
		headerLength = stell( &headerStream );
	sMemDisconnect( &headerStream );
	ENSURES( cryptStatusOK( status ) );
	ENSURES( isShortIntegerRangeNZ( headerLength ) );

	/* Send everything to the other side */
	return( sendHTTPData( stream, headerBuffer, headerLength, 
						  transportFlag ) );
	}

/****************************************************************************
*																			*
*								Write Response Header						*
*																			*
****************************************************************************/

/* Write an HTTP response header */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
static int writeRespMethod( INOUT_PTR STREAM *stream, 
							IN_PTR const NET_STREAM_INFO *netStream,
							IN_BOOL const BOOLEAN isHTTP10 )
	{
	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isReadPtr( netStream, sizeof( NET_STREAM_INFO ) ) );

	REQUIRES( isBooleanValue( isHTTP10 ) );

	/* Write the appropriate response method */
	if( isHTTP10 )
		{
		swrite( stream, "HTTP/1.0 200 OK\r\n", 17 );
		return( swrite( stream, "Connection: keep-alive\r\n", 24 ) );
		}
#ifdef USE_WEBSOCKETS
	if( TEST_FLAG( netStream->nhFlags, STREAM_NHFLAG_WS_UPGRADE ) )
		{
		return( swrite( stream, 
						"HTTP/1.1 101 Switching Underwear\r\n", 34 ) );
		}
#endif /* USE_WEBSOCKETS */
	swrite( stream, "HTTP/1.1 200 OK\r\n", 17 );
	if( TEST_FLAG( netStream->nFlags, STREAM_NFLAG_LASTMSGW ) )
		return( swrite( stream, "Connection: close\r\n", 19 ) );

	/* This shouldn't be required for HTTP 1.1 but there are a sufficient 
	   number of broken caches and proxies around that we need to include it 
	   for the ones that helpfully close the *HTTP 1.1 persistent* 
	   connection for us if they don't see an HTTP 1.0 keepalive in the 
	   header */
	return( swrite( stream, "Connection: keep-alive\r\n", 24 ) );
	}

#ifdef USE_WEBSOCKETS

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
static int writeRespUpgradeHeaders( INOUT_PTR STREAM *stream, 
									IN_PTR const HTTP_REQ_INFO *httpReqInfo )
	{
	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isReadPtr( httpReqInfo, sizeof( HTTP_REQ_INFO ) ) );

	REQUIRES( isShortIntegerRangeNZ( httpReqInfo->authLen ) );

	/* We're doing a WebSockets protocol upgrade, write the necessary 
	   upgrade headers */
	swrite( stream, "Upgrade: websocket\r\n", 20 );
	swrite( stream, "Connection: upgrade\r\n", 21 );
	swrite( stream, "Sec-WebSocket-Accept: ", 22 );
	swrite( stream, httpReqInfo->auth, httpReqInfo->authLen );
	swrite( stream, "\r\n", 2 );
	if( httpReqInfo->protocolLen > 0 )
		{
		swrite( stream, "Sec-WebSocket-Protocol: ", 24 );
		swrite( stream, httpReqInfo->protocol, httpReqInfo->protocolLen ); 
		swrite( stream, "\r\n", 2 );
		}
	return( swrite( stream, "\r\n", 2 ) );
	}
#endif /* USE_WEBSOCKETS */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
static int writeResponseHeader( INOUT_PTR STREAM *stream, 
								IN_PTR_OPT const HTTP_REQ_INFO *httpReqInfo,
								IN_BUFFER_OPT( contentTypeLen ) \
									const char *contentType, 
								IN_LENGTH_SHORT_Z const int contentTypeLen, 
								IN_LENGTH_Z const int contentLength )
	{
	const NET_STREAM_INFO *netStream = DATAPTR_GET( stream->netStream );
	STREAM headerStream;
	char headerBuffer[ HTTP_LINEBUF_SIZE + 8 ];
	const int transportFlag = ( contentLength > 0 ) ? \
							  TRANSPORT_FLAG_NONE : TRANSPORT_FLAG_FLUSH;
	int headerLength DUMMY_INIT, status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( ( contentLength == 0 && contentType == NULL && \
			  contentTypeLen == 0 && \
			  isReadPtr( httpReqInfo, sizeof( HTTP_REQ_INFO ) ) ) || \
			( contentLength >= 1 && \
			  isReadPtrDynamic( contentType, contentTypeLen ) && \
			  httpReqInfo == NULL ) );

	REQUIRES( ( contentLength == 0 && contentType == NULL && \
				contentTypeLen == 0 && httpReqInfo != NULL && \
				httpReqInfo->authLen != 0  ) || \
			  ( isBufsizeRangeNZ( contentLength ) && \
			    contentType != NULL && \
				isShortIntegerRangeNZ( contentTypeLen ) && \
				httpReqInfo == NULL ) );
	REQUIRES( netStream != NULL && sanityCheckNetStream( netStream ) );

	sMemOpen( &headerStream, headerBuffer, HTTP_LINEBUF_SIZE );
	status = writeRespMethod( &headerStream, netStream, 
							  isHTTP10( netStream ) );
	ENSURES( cryptStatusOK( status ) );
#ifdef USE_WEBSOCKETS
	if( TEST_FLAG( netStream->nhFlags, STREAM_NHFLAG_WS_UPGRADE ) )
		{
		REQUIRES( httpReqInfo != NULL );
		status = writeRespUpgradeHeaders( &headerStream, httpReqInfo );
		}
	else
#endif /* USE_WEBSOCKETS */
		{
		if( contentLength > 0 )
			{
			status = writeContentHeaders( &headerStream, contentType, 
										  contentTypeLen, contentLength );
			}
		else
			status = swrite( &headerStream, "\r\n", 2 );
		}
	if( cryptStatusOK( status ) )
		headerLength = stell( &headerStream );
	sMemDisconnect( &headerStream );
	ENSURES( cryptStatusOK( status ) );
	ENSURES( isShortIntegerRangeNZ( headerLength ) );
	return( sendHTTPData( stream, headerBuffer, headerLength, 
						  transportFlag ) );
	}

/****************************************************************************
*																			*
*							HTTP Access Functions							*
*																			*
****************************************************************************/

/* Write data to an HTTP stream */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2, 4 ) ) \
static int writeFunction( INOUT_PTR STREAM *stream, 
						  IN_BUFFER( maxLength ) const void *buffer, 
						  IN_LENGTH_FIXED( sizeof( HTTP_DATA_INFO ) ) \
							const int maxLength, 
						  OUT_DATALENGTH_Z int *length )
	{
	NET_STREAM_INFO *netStream = DATAPTR_GET( stream->netStream );
	HTTP_DATA_INFO *httpDataInfo = ( HTTP_DATA_INFO * ) buffer;
	BOOLEAN forceGet = FALSE;
	int status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isReadPtrDynamic( buffer, maxLength ) );
	assert( isWritePtr( length, sizeof( int ) ) );
	
	REQUIRES( netStream != NULL && sanityCheckNetStream( netStream ) );
	REQUIRES( maxLength == sizeof( HTTP_DATA_INFO ) );
	REQUIRES( sanityCheckHttpDataInfo( httpDataInfo ) );
	REQUIRES( ( httpDataInfo->buffer == NULL && \
				httpDataInfo->bytesToWrite == 0 ) || \
			  ( httpDataInfo->buffer != NULL && \
				httpDataInfo->bytesToWrite > 0 ) );

	/* Clear return value */
	*length = 0;

	/* Send the out-of-band HTTP header data to the client or server */
	if( TEST_FLAG( netStream->nFlags, STREAM_NFLAG_ISSERVER ) )
		{
		/* If it's an error status response, send the translated error 
		   status and exit */
		if( cryptStatusError( httpDataInfo->reqStatus ) )
			{
			status = sendHTTPError( stream, 
						( httpDataInfo->reqStatus == CRYPT_ERROR_NOTFOUND ) ? \
							404 : \
						( httpDataInfo->reqStatus == CRYPT_ERROR_PERMISSION ) ? \
							401 : 400 );
			if( cryptStatusError( status ) )
				return( status );
			*length = maxLength;

			return( CRYPT_OK );
			}

		status = writeResponseHeader( stream, httpDataInfo->reqInfo, 
									  httpDataInfo->contentType,
									  httpDataInfo->contentTypeLen,
									  httpDataInfo->bytesToWrite );
		}
	else
		{
		REQUIRES( TEST_FLAG( netStream->nhFlags, 
							 STREAM_NHFLAG_TUNNEL ) || \
				  TEST_FLAG( netStream->nhFlags, 
							 STREAM_NHFLAG_GET ) || \
				  TEST_FLAG( netStream->nhFlags, 
							 STREAM_NHFLAG_POST_AS_GET ) || \
				  httpDataInfo->contentTypeLen > 0 );
		REQUIRES( !( TEST_FLAG( netStream->nhFlags, 
								STREAM_NHFLAG_PROXY ) && 
					 TEST_FLAG( netStream->nhFlags, 
								STREAM_NHFLAG_TUNNEL ) ) );
		REQUIRES( netStream->host != NULL && netStream->hostLen > 0 );

		/* If we have to override the use of the (correct) POST with the 
		   (incorrect) GET in order to deal with a broken server then 
		   instead of writing the full request header we write the start of 
		   the "HTTP GET..." line and then exit, leaving the rest of the GET 
		   URI to be written as the payload data, followed by the remainder
		   of the HTTP header after the payload write:
		   
			writeRequestHeader(): "GET /..../...?foo="
			bufferedTransportWrite(): "abcdefghijklmnop"
			writeReqHeaderRemainder(): 
							" HTTP/1.1\r\nHost: xxx\r\nConnection: xxx" */
		if( TEST_FLAG( netStream->nhFlags, STREAM_NHFLAG_POST_AS_GET ) )
			{
			status = writeRequestHeader( stream, httpDataInfo->reqInfo, 
										 NULL, 0, 0, TRUE );
			forceGet = TRUE;
			}
		else
			{
			status = writeRequestHeader( stream, httpDataInfo->reqInfo, 
										 httpDataInfo->contentType,
										 httpDataInfo->contentTypeLen,
										 httpDataInfo->bytesToWrite, FALSE );
			}
		}
	if( cryptStatusError( status ) )
		return( status );

	/* If we're sending a headers-only request, for example for a GET 
	   request or to return an HTTP error, we're done */
	if( httpDataInfo->buffer == NULL )
		return( CRYPT_OK );

	/* Send the payload data to the client/server */
	status = bufferedTransportWrite( stream, httpDataInfo->buffer, 
									 httpDataInfo->bytesToWrite, 
									 &httpDataInfo->bytesTransferred, 
									 forceGet ? TRANSPORT_FLAG_NONE : \
												TRANSPORT_FLAG_FLUSH );
	if( cryptStatusError( status ) )
		return( status );
	if( forceGet )
		{
		STREAM headerStream;
		char headerBuffer[ HTTP_LINEBUF_SIZE + 8 ];
		int headerLength DUMMY_INIT;

		/* We've been forced to override the use of a POST with a GET due to 
		   a broken server so the header write was split into two parts with 
		   the request data in the middle, we now have to send the remainder 
		   of the header */
		sMemOpen( &headerStream, headerBuffer, HTTP_LINEBUF_SIZE );
		status = writeReqHeaderRemainder( &headerStream, netStream );
		ENSURES( cryptStatusOK( status ) );
		status = swrite( &headerStream, "\r\n", 2 );
		if( cryptStatusOK( status ) )
			headerLength = stell( &headerStream );
		sMemDisconnect( &headerStream );
		ENSURES( cryptStatusOK( status ) );
		ENSURES( isShortIntegerRangeNZ( headerLength ) );
		status = sendHTTPData( stream, headerBuffer, headerLength, 
							   TRANSPORT_FLAG_FLUSH );
		if( cryptStatusError( status ) )
			return( status );
		}
	*length = maxLength;

	return( CRYPT_OK );
	}

STDC_NONNULL_ARG( ( 1 ) ) \
void setStreamLayerHTTPwrite( INOUT_PTR NET_STREAM_INFO *netStream )
	{
	/* Set the remaining access method pointers */
	FNPTR_SET( netStream->writeFunction, writeFunction );
	}
#endif /* USE_HTTP */
