📄 http.c
字号:
reported as a CRYPT_ERROR_TIMEOUT by the lower-level network I/O
routines, however due to the multiple layers of I/O and special case
timeout handling when (for example) a cryptlib transport session is
layered over the network I/O layer and the fact that to the caller the
write of the out-of-band HTTP header data is invisible, we have to
perform an explicit check to make sure that we sent everything */
static int sendHTTPData( STREAM *stream, void *buffer, const int length,
const int flags )
{
int status;
status = stream->bufferedTransportWriteFunction( stream, buffer, length,
flags );
if( cryptStatusError( status ) )
/* Network-level error, the lower-level layers have reported the
error details */
return( status );
if( status < length )
/* The write timed out, convert the incomplete HTTP header write to
the appropriate timeout error */
retExtStream( stream, CRYPT_ERROR_TIMEOUT,
"HTTP write timed out before all data could be "
"written" );
return( CRYPT_OK );
}
/* Send an HTTP error message */
static int sendHTTPError( STREAM *stream, char *headerBuffer,
const int httpStatus )
{
const char *statusString = "400";
const char *errorString = "Bad Request";
int length, i;
/* Find the HTTP error string that corresponds to the HTTP status
value */
for( i = 0; httpStatusInfo[ i ].httpStatus && \
httpStatusInfo[ i ].httpStatus != httpStatus; i++ );
if( httpStatusInfo[ i ].httpStatus )
{
statusString = httpStatusInfo[ i ].httpStatusString;
errorString = httpStatusInfo[ i ].httpErrorString;
}
/* Send the error message to the peer. We have to be careful with return
values since we could time out before all the data is sent */
length = sPrintf( headerBuffer, "%s %s %s\r\n\r\n",
isHTTP10( stream ) ? "HTTP/1.0" : "HTTP/1.1",
statusString, errorString );
return( sendHTTPData( stream, headerBuffer, length,
TRANSPORT_FLAG_FLUSH ) );
}
/****************************************************************************
* *
* HTTP Parsing Functions *
* *
****************************************************************************/
/* Parse a sub-segment of a URI, returning its length */
static int parseUriSegment( const char *buffer, const char endChar )
{
int length;
/* Parse the current query sub-segment */
for( length = 0; length <= CRYPT_MAX_TEXTSIZE && \
*buffer && *buffer != endChar; \
length++ )
buffer++;
/* Make sure that we didn't run out of data */
if( length >= CRYPT_MAX_TEXTSIZE || !*buffer )
return( CRYPT_ERROR_BADDATA );
return( length );
}
/* Parse a URI of the form * '?' attribute '=' value */
static int parseURI( char *outBuffer, int *outBufPos,
const char *inBuffer, const int inBufLen )
{
const char *origOutBuffer = outBuffer, *bufPtr = inBuffer;
const char *namePtr, *valuePtr;
int nameLength, valueLength, bufLen = inBufLen, status;
/* Clear return value */
*outBufPos = 0;
/* Decode the URI line. Since there can be multiple nested levels of
encoding, we keep iteratively decoding until decodeRFC1866() cries
Uncle. The first time through the loop we decode from the inBuffer to
the outBuffer, in successive iterations we decode in-place in the
outBuffer */
do
{
status = decodeRFC1866( outBuffer, bufPtr, bufLen );
if( !cryptStatusError( status ) )
/* It's a length-change notification, record the new length */
bufLen = status;
else
if( status != OK_SPECIAL )
return( CRYPT_ERROR_BADDATA );
bufPtr = outBuffer;
}
while( status != OK_SPECIAL );
/* Open up a gap at the start of the output buffer to allow for the
encoded return form */
memmove( outBuffer + 8, outBuffer, bufLen );
bufPtr = outBuffer + 8;
/* Parse a URI of the form * '?' attribute '=' value */
status = parseUriSegment( bufPtr, '?' );
if( cryptStatusError( status ) )
return( status );
bufPtr += status + 1; /* Skip '?' */
namePtr = bufPtr;
nameLength = parseUriSegment( bufPtr, '=' );
if( cryptStatusError( nameLength ) )
return( nameLength );
bufPtr += nameLength + 1; /* Skip '=' */
valuePtr = bufPtr;
valueLength = parseUriSegment( bufPtr, ' ' );
if( cryptStatusError( valueLength ) )
return( valueLength );
/* Encode the location, attribute, and value for use by the caller */
mputWord( outBuffer, 0 );
mputWord( outBuffer, nameLength );
memmove( outBuffer, namePtr, nameLength );
outBuffer += nameLength;
mputWord( outBuffer, valueLength );
memmove( outBuffer, valuePtr, valueLength );
outBuffer += valueLength;
*outBufPos = outBuffer - origOutBuffer;
return( ( valuePtr + valueLength ) - origOutBuffer );
}
/* Check an "HTTP 1.x" ID string. No PKI client should be sending us an 0.9
ID, so we only allow 1.x */
static int checkHTTPID( STREAM *stream, const char *buffer, const int length )
{
if( length < 8 || strCompare( buffer, "HTTP/1.", 7 ) )
return( CRYPT_ERROR_BADDATA );
if( buffer[ 7 ] == '0' )
stream->flags |= STREAM_NFLAG_HTTP10;
else
if( buffer[ 7 ] != '1' )
return( CRYPT_ERROR_BADDATA );
return( 8 );
}
/* Read an HTTP status code. Some status values are warnings only and
don't return an error status */
static int readHTTPStatus( STREAM *stream, int *httpStatus,
const char *lineBuffer )
{
const HTTP_STATUS_INFO *httpStatusPtr;
const char *lineBufPtr;
char thirdChar;
int i;
/* Clear return value */
if( httpStatus != NULL )
*httpStatus = CRYPT_OK;
/* Process the numeric HTTP status code and translate it into a cryptlib
equivalent. 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. Most of the HTTP codes don't have any
meaning in a cryptlib context, so we return a generic
CRYPT_ERROR_READ */
lineBufPtr = skipWhitespace( lineBuffer );
if( lineBufPtr == NULL || strlen( lineBufPtr ) < 3 || \
!isDigit( *lineBufPtr ) )
retExtStream( stream, CRYPT_ERROR_BADDATA,
"Invalid/missing HTTP status code" );
thirdChar = lineBufPtr[ 2 ];
for( i = 0; \
httpStatusInfo[ i ].httpStatus && \
( httpStatusInfo[ i ].httpStatusString[ 2 ] != thirdChar || \
strCompare( lineBufPtr, httpStatusInfo[ i ].httpStatusString, 3 ) ); \
i++ );
httpStatusPtr = &httpStatusInfo[ i ];
if( httpStatus != NULL )
*httpStatus = aToI( lineBufPtr );
if( httpStatusPtr->status == OK_SPECIAL )
/* It's a special-case condition such as a redirect, tell the caller
to handle it specially */
return( OK_SPECIAL );
if( httpStatusPtr->status != CRYPT_OK )
{
/* It's an error condition, return extended error info */
assert( httpStatusPtr->httpStatusString != NULL );
/* Catch oddball errors in debug version */
retExtStream( stream, httpStatusPtr->status, "HTTP status: %s",
httpStatusPtr->httpErrorString );
}
return( CRYPT_OK );
}
/* Process an HTTP header line looking for anything that we can handle */
static int checkHeaderLine( char **lineBufPtrPtr,
HTTP_HEADER_TYPE *headerType, void *stream )
{
const HTTP_HEADER_INFO *headerInfoPtr;
const char *lineBufPtr = *lineBufPtrPtr;
const char firstChar = toUpper( *lineBufPtr );
const int lineLength = strlen( lineBufPtr );
int i;
/* Clear return value */
*headerType = HTTP_HEADER_NONE;
/* Look for a header line that we recognise */
for( i = 0;
httpHeaderInfo[ i ].headerString != NULL && \
( httpHeaderInfo[ i ].headerString[ 0 ] != firstChar || \
lineLength < httpHeaderInfo[ i ].headerStringLen || \
strCompare( lineBufPtr, httpHeaderInfo[ i ].headerString, \
httpHeaderInfo[ i ].headerStringLen ) );
i++ );
headerInfoPtr = &httpHeaderInfo[ i ];
if( headerInfoPtr->headerString == NULL )
/* It's nothing that we can handle, exit */
return( CRYPT_OK );
/* Make sure that there's a token present */
lineBufPtr = skipWhitespace( lineBufPtr + headerInfoPtr->headerStringLen );
if( lineBufPtr == NULL )
retExtStream( stream, CRYPT_ERROR_BADDATA,
"Missing HTTP header token for '%s'",
headerInfoPtr->headerString );
/* Tell the caller what we found */
*lineBufPtrPtr = ( char * ) lineBufPtr;
*headerType = headerInfoPtr->headerType;
return( CRYPT_OK );
}
/* Read the first line in an HTTP response header */
static int readFirstHeaderLine( STREAM *stream, int *httpStatus,
char *lineBuffer, const int maxLength )
{
int status;
*httpStatus = CRYPT_OK;
/* Read the header and check for an HTTP ID */
status = readLine( stream, lineBuffer, maxLength );
if( cryptStatusError( status ) )
return( status );
status = checkHTTPID( stream, lineBuffer, status );
if( cryptStatusError( status ) )
retExtStream( stream, status, "Invalid HTTP ID/version" );
/* Read the HTTP status info */
return( readHTTPStatus( stream, httpStatus, lineBuffer + status ) );
}
/* Read the remaining HTTP header lines after the first one */
static int readHeaderLines( STREAM *stream, char *lineBuffer,
int *contentLength, int *httpErrorStatus,
int *flags, const int minLength,
const int maxLength, const BOOLEAN expandBuffer )
{
BOOLEAN seenHost = FALSE, seenLength = FALSE;
int localLength = 0, lineCount, status;
/* Clear return value */
if( httpErrorStatus != NULL )
*httpErrorStatus = 0;
if( contentLength != NULL )
*contentLength = 0;
/* 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). Note that we can't be
too finicky with the checking or we'll end up rejecting non-malicious
requests from some of the broken HTTP implementations out there */
for( lineCount = 0; lineCount < MAX_HEADER_LINES; lineCount++ )
{
HTTP_HEADER_TYPE headerType;
char *lineBufPtr = lineBuffer;
status = readLine( stream, lineBuffer, maxLength );
if( cryptStatusError( status ) )
return( status );
if( status == 0 )
/* End of input, exit */
break;
status = checkHeaderLine( &lineBufPtr, &headerType, stream );
if( cryptStatusError( status ) )
return( status );
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 )
retExtStream( stream, CRYPT_ERROR_BADDATA,
"Duplicate 'Host:' header line" );
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 we do is a general sanity
check that the length looks OK, a specific check against
the caller-supplied minimum allowable length is performed
later since the content length may also be provided as a
chunked encoding length */
if( seenLength || ( *flags & HTTP_FLAG_CHUNKED ) )
retExtStream( stream, CRYPT_ERROR_BADDATA,
"Duplicate 'Content-Length:' header line" );
localLength = aToI( lineBufPtr );
if( localLength <= 0 || localLength > MAX_INTLENGTH )
retExtStream( stream, CRYPT_ERROR_BADDATA,
"Invalid HTTP content length %d",
localLength );
seenLength = TRUE;
break;
case HTTP_HEADER_CONTENT_TYPE:
/* 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.
Unfortunately there are also apps out there that send
their PKI messages marked as plain text, so this isn't
100% foolproof, but in practice 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 */
if( !strCompare( lineBufPtr, "text/", 5 ) )
*flags |= HTTP_FLAG_TEXTMSG;
break;
case HTTP_HEADER_TRANSFER_ENCODING:
if( !strCompare( lineBuffer, "Chunked", 7 ) )
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -