http_parse.c
来自「cryptlib安全工具包」· C语言 代码 · 共 1,421 行 · 第 1/4 页
C
1,421 行
isResponseStatus ? "response " : "" ) );
}
if( httpStatus != NULL )
*httpStatus = value;
/* Try and translate the HTTP status code into a cryptlib equivalent.
Most of the HTTP codes don't have any meaning in a cryptlib context
so they're mapped to a generic CRYPT_ERROR_READ by the HTTP status
decoding table */
thirdChar = data[ 2 ];
for( i = 0; httpStatusInfo[ i ].httpStatus != 0 && \
i < FAILSAFE_ARRAYSIZE( httpStatusInfo, HTTP_STATUS_INFO );
i++ )
{
/* We check the third digit (the one most likely to be different)
for a mismatch to avoid a large number of calls to the string-
compare function */
if( httpStatusInfo[ i ].httpStatusString[ 2 ] == thirdChar && \
!strCompare( data, httpStatusInfo[ i ].httpStatusString, 3 ) )
break;
}
ENSURES( i < FAILSAFE_ARRAYSIZE( httpStatusInfo, HTTP_STATUS_INFO ) );
httpStatusPtr = &httpStatusInfo[ i ];
/* If we're doing a status read from something in a header line rather
than an HTTP response (for example a Warning line, which only
requires a status code but no status message), we're done */
if( !isResponseStatus )
return( CRYPT_OK );
/* We're doing a status read from an HTTP response, make sure that
there's status text present alongside the status code */
remainderLength = dataLength - 3;
if( remainderLength < 2 || \
( offset = strSkipWhitespace( data + 3, remainderLength ) ) < 0 || \
dataLength - offset < 1 )
{
retExt( CRYPT_ERROR_BADDATA,
( CRYPT_ERROR_BADDATA, errorInfo,
"Missing HTTP response status text" ) );
}
/* If it's a special-case condition such as a redirect, tell the caller
to handle it specially */
if( httpStatusPtr->status == OK_SPECIAL )
return( OK_SPECIAL );
/* If it's an error condition, return extended error info (from the
information we have, not from any externally-supplied message) */
if( httpStatusPtr->status != CRYPT_OK )
{
assert( httpStatusPtr->httpStatusString != NULL );
/* Catch oddball errors in debug version */
retExt( httpStatusPtr->status,
( httpStatusPtr->status, errorInfo,
"HTTP response status: %s",
httpStatusPtr->httpErrorString ) );
}
return( CRYPT_OK );
}
/* Process an HTTP header line looking for anything that we can handle */
CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 3, 4 ) ) \
static int processHeaderLine( IN_BUFFER( dataLength ) const char *data,
IN_LENGTH_SHORT const int dataLength,
OUT_ENUM_OPT( HTTP_HEADER_TYPE ) \
HTTP_HEADER_TYPE *headerType,
INOUT ERROR_INFO *errorInfo,
IN_RANGE( 1, 999 ) const int errorLineNo )
{
const HTTP_HEADER_PARSE_INFO *headerParseInfoPtr = NULL;
const char firstChar = toUpper( *data );
int processedLength, dataLeft, i;
assert( isReadPtr( data, dataLength ) );
assert( isWritePtr( headerType, sizeof( HTTP_HEADER_TYPE ) ) );
REQUIRES( dataLength > 0 && dataLength < MAX_INTLENGTH_SHORT );
REQUIRES( errorLineNo > 0 && errorLineNo < 1000 );
REQUIRES( errorInfo != NULL );
/* Clear return value */
*headerType = HTTP_HEADER_NONE;
/* Look for a header line that we recognise */
for( i = 0;
httpHeaderParseInfo[ i ].headerString != NULL && \
i < FAILSAFE_ARRAYSIZE( httpHeaderParseInfo, \
HTTP_HEADER_PARSE_INFO );
i++ )
{
if( httpHeaderParseInfo[ i ].headerString[ 0 ] == firstChar && \
dataLength >= httpHeaderParseInfo[ i ].headerStringLen && \
!strCompare( data, httpHeaderParseInfo[ i ].headerString, \
httpHeaderParseInfo[ i ].headerStringLen ) )
{
headerParseInfoPtr = &httpHeaderParseInfo[ i ];
break;
}
}
ENSURES( i < FAILSAFE_ARRAYSIZE( httpHeaderParseInfo, \
HTTP_HEADER_PARSE_INFO ) );
if( headerParseInfoPtr == NULL )
{
/* It's nothing that we can handle, exit */
return( 0 );
}
processedLength = headerParseInfoPtr->headerStringLen;
dataLeft = dataLength - processedLength;
/* Make sure that there's an attribute value present */
if( dataLeft > 0 )
{
const int extraLength = \
strSkipWhitespace( data + processedLength, dataLeft );
if( cryptStatusError( extraLength ) )
{
/* There was a problem, make sure that we fail the following
check */
dataLeft = CRYPT_ERROR;
}
else
{
if( extraLength > 0 )
{
/* We skipped some whitespace before the attribute value,
adjust the consumed/remaining byte counts */
dataLeft -= extraLength;
processedLength += extraLength;
}
}
}
if( dataLeft < 1 )
{
retExt( CRYPT_ERROR_BADDATA,
( CRYPT_ERROR_BADDATA, errorInfo,
"Missing HTTP header value for '%s' token, line %d",
headerParseInfoPtr->headerString, errorLineNo ) );
}
/* Tell the caller what we found */
*headerType = headerParseInfoPtr->headerType;
return( processedLength );
}
/* Read the first line in an HTTP response header */
CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2, 4 ) ) \
int readFirstHeaderLine( INOUT STREAM *stream,
OUT_BUFFER_FIXED( dataMaxLength ) char *dataBuffer,
IN_LENGTH_SHORT const int dataMaxLength,
OUT_RANGE( 0, 999 ) int *httpStatus )
{
NET_STREAM_INFO *netStream = ( NET_STREAM_INFO * ) stream->netStreamInfo;
BOOLEAN textDataError;
int length, processedLength, dataLeft, status;
assert( isWritePtr( stream, sizeof( STREAM ) ) );
assert( isWritePtr( dataBuffer, dataMaxLength ) );
assert( isWritePtr( httpStatus, sizeof( int ) ) );
REQUIRES( dataMaxLength > 0 && dataMaxLength < MAX_INTLENGTH_SHORT );
/* Clear return value */
*httpStatus = 999;
/* Read the header and check for an HTTP ID */
status = readTextLine( readCharFunction, stream, dataBuffer,
dataMaxLength, &length, &textDataError );
if( cryptStatusError( status ) )
return( retTextLineError( stream, status, textDataError,
"Invalid HTTP header line 1: ", 0 ) );
if( length < 8 )
{
retExt( CRYPT_ERROR_BADDATA,
( CRYPT_ERROR_BADDATA, NETSTREAM_ERRINFO,
"Invalid HTTP header line length %d ", length ) );
}
processedLength = checkHTTPID( dataBuffer, length, stream );
if( cryptStatusError( processedLength ) )
{
retExt( cryptStatusError( processedLength ) ? \
processedLength : CRYPT_ERROR_BADDATA,
( cryptStatusError( processedLength ) ? \
processedLength : CRYPT_ERROR_BADDATA,
NETSTREAM_ERRINFO, "Invalid HTTP ID/version" ) );
}
dataLeft = length - processedLength;
/* Skip the whitespace between the HTTP ID and status info */
if( dataLeft > 0 )
{
const int extraLength = \
strSkipWhitespace( dataBuffer + processedLength, dataLeft );
if( cryptStatusError( extraLength ) )
{
/* There was a problem, make sure that we fail the following
check */
dataLeft = CRYPT_ERROR;
}
else
{
if( extraLength > 0 )
{
/* We skipped some whitespace before the HTTP status info,
adjust the consumed/remaining byte counts */
dataLeft -= extraLength;
processedLength += extraLength;
}
}
}
if( dataLeft < 1 )
{
retExt( CRYPT_ERROR_BADDATA,
( CRYPT_ERROR_BADDATA, NETSTREAM_ERRINFO,
"Missing HTTP status code, line 1" ) );
}
/* Read the HTTP status info */
return( readHTTPStatus( dataBuffer + processedLength, dataLeft,
httpStatus, NETSTREAM_ERRINFO ) );
}
/* Read the remaining HTTP header lines after the first one */
CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2, 4 ) ) \
int readHeaderLines( INOUT STREAM *stream,
OUT_BUFFER_FIXED( lineBufMaxLen ) char *lineBuffer,
IN_LENGTH_SHORT_MIN( 256 ) const int lineBufMaxLen,
INOUT HTTP_HEADER_INFO *headerInfo )
{
NET_STREAM_INFO *netStream = ( NET_STREAM_INFO * ) stream->netStreamInfo;
BOOLEAN seenHost = FALSE, seenLength = FALSE;
int contentLength = 0, lineCount, status;
assert( isWritePtr( stream, sizeof( STREAM ) ) );
assert( isWritePtr( lineBuffer, lineBufMaxLen ) );
assert( isWritePtr( headerInfo, sizeof( HTTP_HEADER_INFO ) ) );
REQUIRES( lineBufMaxLen >= 256 && lineBufMaxLen < MAX_INTLENGTH_SHORT );
/* Read each line in the header checking for any fields that we need to
handle. We check for a couple of basic problems with the header to
avoid malformed-header attacks, for example an attacker could send a
request with two 'Content-Length:' headers, one of which covers the
entire message body and the other which indicates that there's a
second request that begins halfway through the message body. Some
proxies/caches will take the first length, some the second, if the
proxy is expected to check/rewrite the request as it passes through
then the single/dual-message issue can be used to bypass the checking
on the tunnelled second message. Because of this we only allow a
single Host: and Content-Length: header, and disallow a chunked
encoding in combination with a content-length (Apache does some
really strange things with chunked encodings). We can't be too
finicky with the checking though or we'll end up rejecting non-
malicious requests from some of the broken HTTP implementations out
there */
for( lineCount = 0; lineCount < FAILSAFE_ITERATIONS_MED; lineCount++ )
{
HTTP_HEADER_TYPE headerType;
BOOLEAN textDataError;
char *lineBufPtr;
int lineLength;
status = readTextLine( readCharFunction, stream, lineBuffer,
lineBufMaxLen, &lineLength, &textDataError );
if( cryptStatusError( status ) )
return( retTextLineError( stream, status, textDataError,
"Invalid HTTP header line %d: ",
lineCount + 2 ) );
if( lineLength <= 0 )
{
/* End of input, exit */
break;
}
status = processHeaderLine( lineBuffer, lineLength, &headerType,
NETSTREAM_ERRINFO, lineCount + 2 );
if( cryptStatusError( status ) )
return( status );
lineBufPtr = lineBuffer + status;
lineLength -= status;
ENSURES( lineLength > 0 ); /* Guaranteed by processHeaderLine() */
switch( headerType )
{
case HTTP_HEADER_HOST:
/* Make sure that it's a non-duplicate, and remember that
we've seen a Host: line, to meet the HTTP 1.1
requirements */
if( seenHost )
{
retExt( CRYPT_ERROR_BADDATA,
( CRYPT_ERROR_BADDATA, NETSTREAM_ERRINFO,
"Duplicate HTTP 'Host:' header, line %d",
lineCount + 2 ) );
}
seenHost = TRUE;
break;
case HTTP_HEADER_CONTENT_LENGTH:
/* Make sure that it's a non-duplicate and get the content
length. At this point all that we do is perform a
general sanity check that the length looks OK, a specific
check against the caller-supplied minimum/maximum
allowable length is performed later since the content
length may also be provided as a chunked encoding length,
which we can't check until we've processed all of the
header lines */
if( seenLength )
{
retExt( CRYPT_ERROR_BADDATA,
( CRYPT_ERROR_BADDATA, NETSTREAM_ERRINFO,
"Duplicate HTTP 'Content-Length:' header, "
"line %d", lineCount + 2 ) );
}
status = strGetNumeric( lineBufPtr, lineLength,
&contentLength, 1, MAX_INTLENGTH );
if( cryptStatusError( status ) )
{
retExt( CRYPT_ERROR_BADDATA,
( CRYPT_ERROR_BADDATA, NETSTREAM_ERRINFO,
"Invalid HTTP content length, line %d",
lineCount + 2 ) );
}
seenLength = TRUE;
break;
case HTTP_HEADER_CONTENT_TYPE:
{
static const URI_PARSE_INFO typeParseInfo = \
{ '/', '\0', 2, CRYPT_MAX_TEXTSIZE };
static const URI_PARSE_INFO subtypeParseInfo = \
{ '\0', ';', 2, CRYPT_MAX_TEXTSIZE };
BOOLEAN dummy;
char *contentType;
int contentTypeLen, subTypeLen;
/* Sometimes if there's an error it'll be returned as content
at the HTTP level rather than at the tunnelled-over-HTTP
protocol level. The easiest way to check for this would
be to make sure that the content-type matches the
expected type and report anything else as an error.
Unfortunately due to the hit-and-miss handling of content-
types by PKI software using HTTP as a substrate it's not
safe to do this, so we have to default to allow-all
rather than deny-all, treating only straight text as a
problem type.
To compound the problem there are also apps out there
that send their PKI messages marked as plain text, so
this isn't 100% foolproof. This is particularly
problematic for web browsers, where so many servers were
misconfigured to return pretty much anything as
text/plain that Microsoft added content-type guessing
code to MSIE to make web pages served from misconfigured
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?