http_parse.c

来自「cryptlib安全工具包」· C语言 代码 · 共 1,421 行 · 第 1/4 页

C
1,421
字号
				   servers work (you can see this by serving a JPEG file as 
				   text/plain, MSIE will display it as a JPEG while Mozilla/
				   Firefox/Opera/etc will display it as text or prompt for a 
				   helper app to handle it).  Since this content-type 
				   guessing is a potential security hole, MS finally made it
				   configurable in Windows XP SP2, but it's still enabled
				   by default even there.

				   In practice however errors-via-HTTP is more common than
				   certs-via-text.  We try and detect the cert-as-plain-text
				   special-case at a later point when we've got the message
				   body available.

				   Since we're now looking at the content-type line (even if
				   we don't really process it in any way), we perform at 
				   least a minimal validity check for * '/' * [ ';*' ] */
				status = getUriSegmentLength( lineBufPtr, lineLength, 
											  &contentTypeLen, 
											  &typeParseInfo, NULL );
				if( cryptStatusError( status ) )
					{
					/* We need to have at least "xx/"* present (length is
					   guaranteed by getUriSegmentLength()) */
					return( retHeaderError( stream, 
								"Invalid HTTP content type '%s', line %d",
								lineBufPtr, lineLength, lineCount ) );
					}
				contentType = lineBufPtr;
				lineBufPtr += contentTypeLen + 1;	/* Skip delimiter */
				lineLength -= contentTypeLen + 1;
				status = getUriSegmentLength( lineBufPtr, lineLength, 
											  &subTypeLen, 
											  &subtypeParseInfo, &dummy );
				if( cryptStatusError( status ) )
					{
					/* We need to have at least 'xx/yy' present (length is
					   guaranteed by getUriSegmentLength()) */
					return( retHeaderError( stream, 
								"Invalid HTTP content subtype '%s', line %d",
								lineBufPtr, lineLength, lineCount ) );
					}
				if( contentTypeLen == 4 && \
					!strCompare( contentType, "text", 4 ) )
					headerInfo->flags |= HTTP_FLAG_TEXTMSG;
				break;
				}

			case HTTP_HEADER_TRANSFER_ENCODING:
				if( lineLength < 7 || \
					strCompare( lineBufPtr, "Chunked", 7 ) )
					{
					return( retHeaderError( stream, 
							  "Invalid HTTP transfer encoding method "
							  "'%s', expected 'Chunked', line %d",
							  lineBufPtr, lineLength, lineCount ) );
					}

				/* If it's a chunked encoding, the length is part of the
				   data and must be read later */
				if( seenLength )
					{
					retExt( CRYPT_ERROR_BADDATA,
							( CRYPT_ERROR_BADDATA, NETSTREAM_ERRINFO, 
							  "Duplicate HTTP 'Content-Length:' header, "
							  "line %d", lineCount + 2 ) );
					}
				headerInfo->flags |= HTTP_FLAG_CHUNKED;
				seenLength = TRUE;
				break;

			case HTTP_HEADER_CONTENT_ENCODING:
				/* We can't handle any type of content encoding (e.g. gzip,
				   compress, deflate, mpeg4, interpretive dance) except the
				   no-op identity encoding */
				if( lineLength < 8 || \
					strCompare( lineBufPtr, "Identity", 8 ) )
					{
					headerInfo->httpStatus = 415;	/* Unsupp.media type */
					return( retHeaderError( stream, 
							  "Invalid HTTP content encoding method "
							  "'%s', expected 'Identity', line %d",
							  lineBufPtr, lineLength, lineCount ) );
					}
				break;

			case HTTP_HEADER_CONTENT_TRANSFER_ENCODING:
				/* HTTP uses Transfer-Encoding, not the MIME Content-
				   Transfer-Encoding types such as base64 or quoted-
				   printable.  If any implementations erroneously use a
				   C-T-E, we make sure that it's something that we can
				   handle */
				if( lineLength < 6 || \
					( strCompare( lineBufPtr, "Identity", 8 ) && \
					  strCompare( lineBufPtr, "Binary", 6 ) ) )
					{
					headerInfo->httpStatus = 415;	/* Unsupp.media type */
					return( retHeaderError( stream, 
							  "Invalid HTTP content transfer encoding "
							  "method '%s', expected 'Identity' or "
							  "'Binary', line %d", lineBufPtr, lineLength, 
							  lineCount ) );
					}
				break;

			case HTTP_HEADER_TRAILER:
				/* The body is followed by trailer lines, used with chunked
				   encodings where some header lines can't be produced until
				   the entire body has been generated.  This wasn't added
				   until RFC 2616, since many implementations are based on
				   RFC 2068 and don't produce this header we don't do
				   anything with it.  The trailer can be auto-detected
				   anyway, it's only present to tell the receiver to perform
				   certain actions such as creating an MD5 hash of the data
				   as it arrives */
				headerInfo->flags |= HTTP_FLAG_TRAILER;
				break;

			case HTTP_HEADER_CONNECTION:
				/* If the other side has indicated that it's going to close
				   the connection, remember that the stream is now no longer
				   usable */
				if( lineLength >= 5 && \
					!strCompare( lineBufPtr, "Close", 5 ) )
					sioctl( stream, STREAM_IOCTL_CONNSTATE, NULL, FALSE );
				break;

			case HTTP_HEADER_WARNING:
				/* Read the HTTP status info from the warning.  
				   readHTTPStatus() will process the error status in the
				   warning line, but since we're passing in a NULL pointer 
				   for the status info it'll only report an error in the
				   warning content itself, it won't return the processed
				   warning status as an error */
				status = readHTTPStatus( lineBufPtr, lineLength, NULL, 
										 NETSTREAM_ERRINFO );
				if( cryptStatusError( status ) )
					{
					return( retHeaderError( stream, 
							  "Invalid HTTP warning information '%s', "
							  "line %d", lineBufPtr, lineLength, 
							  lineCount ) );
					}
				break;

			case HTTP_HEADER_LOCATION:
				{
#if defined( __WIN32__ ) && !defined( NDEBUG ) && 1
				URL_INFO urlInfo;
#endif /* Win32 debug build only */

				/* Make sure that we've been given an HTTP URL as the 
				   redirect location.  We need to do this because 
				   sNetParseURL() will accept a wide range of URL types
				   while we only allow "http://"* */
				if( lineLength < 10 || \
					strCompare( lineBufPtr, "http://", 7 ) )
					{
					return( retHeaderError( stream, 
							  "Invalid HTTP redirect location '%s', line %d",
							  lineBufPtr, lineLength, lineCount ) );
					}

				/* Process the redirect location */
#if defined( __WIN32__ ) && !defined( NDEBUG ) && 1
				/* We don't try and parse the URL other than in the Win32 
				   debug build because we don't do redirects yet so there's 
				   no need to expose ourselves to possibly maliciously-
				   created URLs from external sources */
				status = sNetParseURL( &urlInfo, lineBufPtr, lineLength, 
									   URL_TYPE_HTTP );
				if( cryptStatusError( status ) )
					{
					return( retHeaderError( stream, 
							  "Invalid HTTP redirect location '%s', line %d",
							  lineBufPtr, lineLength, lineCount ) );
					}
#endif /* Win32 debug build only */
				break;
				}

			case HTTP_HEADER_EXPECT:
				/* If the other side wants the go-ahead to continue, give it
				   to them.  We do this automatically because we're merely
				   using HTTP as a substrate, the real decision will be made
				   at the higher-level protocol layer */
				if( lineLength >= 12 && \
					!strCompare( lineBufPtr, "100-Continue", 12 ) )
					sendHTTPError( stream, lineBufPtr, lineBufMaxLen, 100 );
				break;

			case HTTP_HEADER_NONE:
				/* It's something that we don't know/care about, skip it */
				break;

			default:
				retIntError();
			}
		}
	if( lineCount >= FAILSAFE_ITERATIONS_MED )
		{
		retExt( CRYPT_ERROR_OVERFLOW,
				( CRYPT_ERROR_OVERFLOW, NETSTREAM_ERRINFO, 
				  "Too many HTTP header lines" ) );
		}

	/* If this is a tunnel being opened via an HTTP proxy, we're done */
	if( !( netStream->nFlags & STREAM_NFLAG_ISSERVER ) && \
		( netStream->nFlags & STREAM_NFLAG_HTTPTUNNEL ) )
		return( CRYPT_OK );

	/* If it's a chunked encoding for which the length is kludged on before
	   the data as a hex string, decode the length value */
	if( headerInfo->flags & HTTP_FLAG_CHUNKED )
		{
		BOOLEAN textDataError;
		int lineLength;

		status = readTextLine( readCharFunction, stream, lineBuffer, 
							   lineBufMaxLen, &lineLength, &textDataError );
		if( cryptStatusError( status ) )
			{
			return( retTextLineError( stream, status, textDataError, 
									  "Invalid HTTP chunked encoding "
									  "header line %d: ", lineCount + 2 ) );
			}
		if( lineLength <= 0 )
			{
			retExt( CRYPT_ERROR_BADDATA,
					( CRYPT_ERROR_BADDATA, NETSTREAM_ERRINFO, 
					  "Missing HTTP chunk length, line %d", lineCount + 2 ) );
			}
		status = contentLength = getChunkLength( lineBuffer, lineLength );
		if( cryptStatusError( status ) )
			{
			retExt( CRYPT_ERROR_BADDATA,
					( CRYPT_ERROR_BADDATA, NETSTREAM_ERRINFO, 
					  "Invalid length for HTTP chunked encoding, line %d",
					  lineCount + 2 ) );
			}
		}

	/* If this is a no-op read (for example lines following an error or 100
	   Continue response), all that we're interested in is draining the
	   input, so we don't check any further */
	if( headerInfo->flags & HTTP_FLAG_NOOP )
		return( CRYPT_OK );

	/* If we're a server talking HTTP 1.1 and we haven't seen a "Host:"
	   header from the client, return an error */
	if( ( netStream->nFlags & STREAM_NFLAG_ISSERVER ) && \
		!isHTTP10( stream ) && !seenHost )
		{
		headerInfo->httpStatus = 400;	/* Bad request */
		retExt( CRYPT_ERROR_BADDATA,
				( CRYPT_ERROR_BADDATA, NETSTREAM_ERRINFO, 
				  "Missing HTTP 1.1 'Host:' header" ) );
		}

	/* If it's a GET request there's no length so we can exit now */
	if( headerInfo->flags & HTTP_FLAG_GET )
		{
		if( seenLength )
			{
			retExt( CRYPT_ERROR_BADDATA,
					( CRYPT_ERROR_BADDATA, NETSTREAM_ERRINFO, 
					  "Unexpected %d bytes HTTP body content received in "
					  "idempotent read", contentLength ) );
			}
		return( CRYPT_OK );
		}

	/* Make sure that we've been given a length.  In theory a server could
	   indicate the length implicitly by closing the connection once it's
	   sent the last byte, but this isn't allowed for PKI messages.  The
	   client can't use this option either since that would make it
	   impossible for us to send back the response */
	if( !seenLength )
		{
		headerInfo->httpStatus = 411;	/* Length required */
		retExt( CRYPT_ERROR_BADDATA, 
				( CRYPT_ERROR_BADDATA, NETSTREAM_ERRINFO, 
				  "Missing HTTP length" ) );
		}

	/* Make sure that the length is sensible */
	if( contentLength < headerInfo->minContentLength || \
		contentLength > headerInfo->maxContentLength )
		{
		retExt( ( contentLength < headerInfo->minContentLength ) ? \
				CRYPT_ERROR_UNDERFLOW : CRYPT_ERROR_OVERFLOW,
				( ( contentLength < headerInfo->minContentLength ) ? \
					CRYPT_ERROR_UNDERFLOW : CRYPT_ERROR_OVERFLOW, 
				  NETSTREAM_ERRINFO,
				  "Invalid HTTP content length %d bytes, expected "
				  "%d...%d bytes", contentLength,
				  headerInfo->minContentLength, 
				  headerInfo->maxContentLength ) );
		}
	headerInfo->contentLength = contentLength;

	return( CRYPT_OK );
	}

/* Read the HTTP trailer lines that follow chunked data:

			CRLF
			"0" CRLF
			trailer-lines*
			CRLF */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
int readTrailerLines( INOUT STREAM *stream, 
					  OUT_BUFFER_FIXED( lineBufMaxLen ) char *lineBuffer, 
					  IN_LENGTH_SHORT_MIN( 256 ) const int lineBufMaxLen )
	{
	NET_STREAM_INFO *netStream = ( NET_STREAM_INFO * ) stream->netStreamInfo;
	HTTP_HEADER_INFO headerInfo;
	BOOLEAN textDataError;
	int readLength = DUMMY_INIT, dummy, status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isReadPtr( lineBuffer, lineBufMaxLen ) );

	REQUIRES( lineBufMaxLen >= 256 && lineBufMaxLen < MAX_INTLENGTH_SHORT );

	/* Read the blank line and chunk length */
	status = readTextLine( readCharFunction, stream, lineBuffer,
						   lineBufMaxLen, &dummy, &textDataError );
	if( cryptStatusOK( status ) )
		status = readTextLine( readCharFunction, stream, lineBuffer, 
							   lineBufMaxLen, &readLength, &textDataError );
	if( cryptStatusError( status ) )
		return( retTextLineError( stream, status, textDataError, 
								  "Invalid HTTP chunked trailer line: ", 
								  0 ) );

	/* Make sure that there are no more chunks to follow */
	status = getChunkLength( lineBuffer, readLength );
	if( status != 0 )
		{
		retExt( CRYPT_ERROR_BADDATA,
				( CRYPT_ERROR_BADDATA, NETSTREAM_ERRINFO, 
				  "Unexpected additional data following HTTP chunked "
				  "data" ) );
		}

	/* Read any remaining trailer lines */
	initHeaderInfo( &headerInfo, 0, 0, HTTP_FLAG_NOOP );
	return( readHeaderLines( stream, lineBuffer, lineBufMaxLen,
							 &headerInfo ) );
	}
#endif /* USE_HTTP */

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?