📄 http.c
字号:
return( status );
}
/****************************************************************************
* *
* Read/write Response Header *
* *
****************************************************************************/
/* Write an HTTP response header */
static int writeResponseHeader( STREAM *stream, const int length )
{
char headerBuffer[ HTTP_LINEBUF_SIZE ];
int headerPos;
/* We don't use a stream to encode the header lines for responses since
all of the lines are quite short and can't overflow the buffer */
if( isHTTP10( stream ) )
strcpy( headerBuffer, "HTTP/1.0 200 OK\r\n" );
else
{
strcpy( headerBuffer, "HTTP/1.1 200 OK\r\n" );
if( stream->flags & STREAM_NFLAG_LASTMSG )
strcat( headerBuffer, "Connection: close\r\n" );
}
headerPos = strlen( headerBuffer );
strcat( headerBuffer + headerPos, "Content-Type: " );
strcat( headerBuffer + headerPos, stream->contentType );
strcat( headerBuffer + headerPos, "\r\nContent-Length: " );
headerPos += strlen( headerBuffer + headerPos );
sPrintf( headerBuffer + headerPos,
"%d\r\nCache-Control: no-cache\r\n", length );
if( isHTTP10( stream ) )
strcat( headerBuffer + headerPos, "Pragma: no-cache\r\n" );
strcat( headerBuffer + headerPos, "\r\n" );
headerPos = strlen( headerBuffer );
return( sendHTTPData( stream, headerBuffer, headerPos,
TRANSPORT_FLAG_NONE ) );
}
/* Read an HTTP response header */
static int readResponseHeader( STREAM *stream, int *contentLength, char *buffer,
const int maxLength,
const BOOLEAN expandBuffer, int *flags )
{
int repeatCount, status;
/* Clear return value */
*contentLength = CRYPT_ERROR;
/* If it's a stateless HTTP read, we need to send the fetch request
before we can read anything back */
if( stream->protocol == STREAM_PROTOCOL_HTTP )
{
assert( !*stream->contentType );
status = writeRequestHeader( stream, 0 );
if( cryptStatusError( status ) )
return( status );
}
/* Read the returned response header from the server, taking various
special-case conditions into account. In theory we could also handle
the 503 "Retry-After" status, but there's no sensible reason why
anyone should send us this, and even if they do it'll screw up a lot
of the PKI protocols, which have timeliness constraints built in */
for( repeatCount = 0; repeatCount < MAX_RETRY_COUNT; repeatCount++ )
{
BOOLEAN needsSpecialHandling = FALSE;
int httpStatus;
/* Read the response header */
status = readFirstHeaderLine( stream, &httpStatus, buffer, maxLength );
if( status == OK_SPECIAL )
{
/* If it's a special-case header (e.g. a 100 Continue), turn the
read into a no-op read that drains the input to get to the
real data */
*flags |= HTTP_FLAG_NOOP;
needsSpecialHandling = TRUE;
}
else
if( cryptStatusError( status ) )
{
int localFlags = *flags | HTTP_FLAG_NOOP;
/* Drain the input and exit */
readHeaderLines( stream, buffer, NULL, NULL, &localFlags,
5, maxLength, FALSE );
return( status );
}
/* Process the remaining header lines. 5 bytes is the minimum-size
object that can be returned from any HTTP-based message which is
exchanged by cryptlib, this being an OCSP response containing a
single-byte status value, i.e. SEQUENCE { ENUM x } */
status = readHeaderLines( stream, buffer, contentLength, NULL,
flags, 5, maxLength, expandBuffer );
*flags &= ~HTTP_FLAG_NOOP;
if( cryptStatusError( status ) )
return( status );
/* If it's not something like a redirect that needs special-case
handling, we're done */
if( !needsSpecialHandling )
return( CRYPT_OK );
assert( httpStatus == 100 || httpStatus == 301 || \
httpStatus == 302 || httpStatus == 307 );
/* If we got a 100 Continue response, try for another header that
follows the first one */
if( httpStatus == 100 )
continue;
/* If we got a 301, 302, or 307 Redirect then in theory we should
proceed roughly as per the code below, however in practice it's
not nearly as simple as this, because what we're in effect doing
is taking a stream and replacing it with a completely new stream
(different host/abs-path/query info, new socket with optional
proxy handling, etc etc). One way to do this would be to read
the new location into the current stream buffer and pass it back
with a special status telling the stream-level code to create a
new stream, clean up the old one, and perform a deep copy of the
new stream over to the old one. We'll leave this for a time when
it's really needed.
In addition the semantics of the following don't quite follow
those of RFC 2616 because of the HTTP-as-a-substrate use rather
than direct use in a browser. Specifically, anything other than
a GET for a 302 or 307 isn't supposed to perform an automatic
redirect without asking the user, because of concerns that it'll
change the semantics of the request. However, since we're not an
interactive web browser there's no way that we can ask a user for
redirect permission, and in any case since we're merely using
HTTP as a substrate for a cryptographically protected PKI
message (and specifically assuming that the HTTP layer is
completely insecure), any problems will be caught by the crypto
protocol layer */
#if 0
if( !*location )
return( CRYPT_ERROR_READ );
stream->closeSocketFunction( stream );
clFree( "readResponseHeader", stream->host );
stream->host = NULL;
status = parseLocation( stream, location );
if( cryptStatusError( status ) )
return( CRYPT_ERROR_READ );
#endif /* 0 */
retExtStream( stream, CRYPT_ERROR_READ,
"Unable to process HTTP 301/302 redirect" );
}
/* We used up our maximum number of retries, bail out */
retExtStream( stream, CRYPT_ERROR_READ,
"HTTP retry/redirection loop detected" );
}
/****************************************************************************
* *
* HTTP Access Functions *
* *
****************************************************************************/
/* Read data from an HTTP stream */
static int readFunction( STREAM *stream, void *buffer, int length )
{
void *bufPtr = buffer;
int flags = HTTP_FLAG_NONE, contentLength, readLength, status;
/* Read the HTTP packet header and adjust the read buffer size if
necessary. This adjustment only occurs on the client side, which
needs to be able to handle arbitrary-length responses from the
server */
if( stream->flags & STREAM_NFLAG_ISSERVER )
status = readRequestHeader( stream, &contentLength, buffer, length,
&flags );
else
status = readResponseHeader( stream, &contentLength, buffer, length,
( stream->callbackFunction != NULL ) ? \
TRUE : FALSE, &flags );
if( cryptStatusError( status ) )
return( status );
if( contentLength > length )
{
if( stream->callbackFunction != NULL )
{
/* There's a buffer-adjust callback present, try and increase the
buffer size */
assert( stream->callbackParams != NULL );
status = stream->callbackFunction( stream->callbackParams,
&bufPtr, contentLength );
if( cryptStatusError( status ) )
return( status );
assert( isWritePtr( bufPtr, contentLength ) );
}
else
return( CRYPT_ERROR_OVERFLOW );
}
/* If it's an idempotent read, all the information was contained in the
header and we're done */
if( stream->flags & STREAM_NFLAG_IDEMPOTENT )
return( contentLength );
/* Read the payload data from the client/server */
readLength = status = \
stream->bufferedTransportReadFunction( stream, bufPtr, contentLength,
TRANSPORT_FLAG_NONE );
if( cryptStatusError( status ) )
return( status );
if( readLength < contentLength )
/* We timed out before reading all the data. Usually this will be
reported as a CRYPT_ERROR_TIMEOUT by the lower-level read
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, we perform an
explicit check here to make sure that we got everything */
retExtStream( stream, CRYPT_ERROR_TIMEOUT,
"HTTP read timed out before all data could be read" );
/* If it's a plain-text error message, return it to the caller */
if( flags & HTTP_FLAG_TEXTMSG )
{
BYTE *byteBufPtr = bufPtr;
/* Usually a body returned as plain text is an error message that
(for some reason) is sent as content rather than an HTTP error,
however in some unusual cases the content will be the requested
object marked as plain text. This only seems to occur with
straight HTTP fetches from misconfigured servers rather than when
HTTP is being used as a tunnelling mechanism for a PKI protocol,
so we can filter this by requiring that the fetch is a straight
HTTP fetch (not a request/response PKI protocol fetch), that the
request is over a minimum size (most error messages are quite
short), and that the first bytes match what would be seen in a
PKI object such as a cert or CRL */
if( stream->protocol != STREAM_PROTOCOL_HTTP || \
contentLength < 256 || ( byteBufPtr[ 0 ] != 0x30 ) || \
!( byteBufPtr[ 1 ] & 0x80 ) || \
( isAlpha( byteBufPtr[ 2 ] ) && isAlpha( byteBufPtr[ 3 ] ) && \
isAlpha( byteBufPtr[ 4 ] ) ) )
{
byteBufPtr[ min( readLength, MAX_ERRMSG_SIZE - 32 ) ] = '\0';
retExtStream( stream, CRYPT_ERROR_READ,
"HTTP server reported: '%s'",
sanitiseString( buffer ) );
}
}
/* If we're reading chunked data, drain the input by processing the
trailer. The reason why there can be extra header lines at the end
of the chunked data is because it's designed to be an indefinite-
length streamable format that doesn't require buffering the entire
message before emitting it. Since some header information may not be
available until the entire message has been generated, the HTTP spec.
makes provisions for adding further header lines as a trailer. In
theory we should check for the HTTP_FLAG_TRAILER flag before reading
trailer lines rather than just swallowing the last CRLF, however the
"Trailer:" header wasn't added until RFC 2616 (RFC 2068 didn't have
it) so we can't rely on its presence:
CRLF
"0" CRLF
trailer-lines*
CRLF
Normally we wouldn't have to worry about trailer data, but if it's an
HTTP 1.1 persistent connection we need to clear the way for the next
lot of data */
if( flags & HTTP_FLAG_CHUNKED )
{
char headerBuffer[ HTTP_LINEBUF_SIZE + 8 ];
int noopFlags = HTTP_FLAG_NOOP;
status = readLine( stream, headerBuffer, HTTP_LINEBUF_SIZE );
if( !cryptStatusError( status ) )
status = readLine( stream, headerBuffer, HTTP_LINEBUF_SIZE );
if( cryptStatusError( status ) )
return( status );
status = getChunkLength( headerBuffer, status );
if( status != 0 )
retExtStream( stream, CRYPT_ERROR_BADDATA,
"Unexpected additional data in HTTP chunked data" );
status = readHeaderLines( stream, headerBuffer, NULL, NULL,
&noopFlags, 0, HTTP_LINEBUF_SIZE, FALSE );
}
return( cryptStatusError( status ) ? status : readLength );
}
/* Write data to an HTTP stream */
static int writeFunction( STREAM *stream, const void *buffer,
const int length )
{
int localLength = length, status;
/* Send the out-of-band HTTP header data to the client or server */
if( stream->flags & STREAM_NFLAG_ISSERVER )
{
/* If it's an idempotent get, decode the returned data */
if( stream->flags & STREAM_NFLAG_IDEMPOTENT )
{
const BYTE *bufPtr = buffer;
status = ( short int ) mgetWord( bufPtr );
if( cryptStatusError( status ) )
{
char headerBuffer[ HTTP_LINEBUF_SIZE ];
/* It's an error status response, send the translated
error status and exit. We have to map the send return
value to a written byte count to avoid triggering the
incomplete-write check at the higher level */
status = sendHTTPError( stream, headerBuffer,
( status == CRYPT_ERROR_NOTFOUND ) ? 404 : \
( status == CRYPT_ERROR_PERMISSION ) ? 401 : \
400 );
return( cryptStatusError( status ) ? status : length );
}
buffer = bufPtr;
localLength -= 2;
}
status = writeResponseHeader( stream, localLength );
}
else
{
assert( ( stream->flags & STREAM_NFLAG_HTTPTUNNEL ) || \
strlen( stream->contentType ) );
assert( !( ( stream->flags & STREAM_NFLAG_HTTPPROXY ) &&
( stream->flags & STREAM_NFLAG_HTTPTUNNEL ) ) );
assert( stream->host != NULL );
status = writeRequestHeader( stream, localLength );
}
if( cryptStatusError( status ) )
return( status );
/* Send the payload data to the client/server. Since we may have
modified the length of the data being written we have to be careful
to return the correct amount to avoid triggering incomplete-write
checks */
status = stream->bufferedTransportWriteFunction( stream, buffer, localLength,
TRANSPORT_FLAG_FLUSH );
return( ( status == localLength ) ? length : status );
}
int setStreamLayerHTTP( STREAM *stream )
{
/* Set the access method pointers */
stream->writeFunction = writeFunction;
stream->readFunction = readFunction;
/* HTTP provides its own data-size and flow-control indicators so we
don't want the higher-level code to try and do this for us */
stream->flags |= STREAM_NFLAG_ENCAPS;
return( CRYPT_OK );
}
#endif /* USE_HTTP */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -