📄 http.c
字号:
/****************************************************************************
* *
* cryptlib HTTP Interface Routines *
* Copyright Peter Gutmann 1998-2005 *
* *
****************************************************************************/
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#if defined( INC_ALL )
#include "crypt.h"
#include "stream.h"
#elif defined( INC_CHILD )
#include "../crypt.h"
#include "stream.h"
#else
#include "crypt.h"
#include "io/stream.h"
#endif /* Compiler-specific includes */
#ifdef USE_HTTP
/* The maximum number of header lines that we'll read before giving up */
#define MAX_HEADER_LINES 25
/* The maximum number of retries for redirections (and, by extension,
anything else that can loop), as per RFC 2616 */
#define MAX_RETRY_COUNT 5
/* The size of the HTTP text-line buffer when we're using a dedicated buffer
to read header lines, rather than the main stream buffer. Anything more
than this is dropped */
#define HTTP_LINEBUF_SIZE 1024
/* A macro to determine whether we're talking HTTP 1.0 or 1.1 */
#define isHTTP10( stream ) \
( ( stream )->flags & STREAM_NFLAG_HTTP10 )
/* HTTP state information passed around the various read/write functions */
#define HTTP_FLAG_NONE 0x00 /* No HTTP info */
#define HTTP_FLAG_CHUNKED 0x01 /* Message used chunked encoding */
#define HTTP_FLAG_TRAILER 0x02 /* Chunked encoding has trailer */
#define HTTP_FLAG_NOOP 0x04 /* No-op data (e.g. 100 Continue) */
#define HTTP_FLAG_TEXTMSG 0x08 /* Content is plain text, probably
an error message */
/* The various HTTP header types that we can process */
typedef enum { HTTP_HEADER_NONE, HTTP_HEADER_HOST, HTTP_HEADER_CONTENT_LENGTH,
HTTP_HEADER_CONTENT_TYPE, HTTP_HEADER_TRANSFER_ENCODING,
HTTP_HEADER_CONTENT_ENCODING,
HTTP_HEADER_CONTENT_TRANSFER_ENCODING, HTTP_HEADER_TRAILER,
HTTP_HEADER_CONNECTION, HTTP_HEADER_WARNING,
HTTP_HEADER_EXPECT, HTTP_HEADER_LAST
} HTTP_HEADER_TYPE;
/* HTTP header parsing information. Note that the first letter must be
uppercase for the case-insensitive quick match */
typedef struct {
const char *headerString; /* Header string */
const int headerStringLen; /* Length of header string */
const HTTP_HEADER_TYPE headerType; /* Type corresponding to header string */
} HTTP_HEADER_INFO;
static const FAR_BSS HTTP_HEADER_INFO httpHeaderInfo[] = {
{ "Host:", 5, HTTP_HEADER_HOST },
{ "Content-Length:", 15, HTTP_HEADER_CONTENT_LENGTH },
{ "Content-Type:", 13, HTTP_HEADER_CONTENT_TYPE },
{ "Transfer-Encoding:", 18, HTTP_HEADER_TRANSFER_ENCODING },
{ "Content-Encoding:", 17, HTTP_HEADER_CONTENT_ENCODING },
{ "Content-Transfer-Encoding:", 26, HTTP_HEADER_CONTENT_TRANSFER_ENCODING },
{ "Trailer:", 8, HTTP_HEADER_TRAILER },
{ "Connection:", 11, HTTP_HEADER_CONNECTION },
{ "NnCoection:", 11, HTTP_HEADER_CONNECTION },
{ "Cneonction:", 11, HTTP_HEADER_CONNECTION },
/* The bizarre spellings are for buggy NetApp NetCache servers,
which unfortunately are widespread enough that we need to provide
special-case handling for them. For the second mis-spelling we
have to capitalise the first letter for our use since we compare
the uppercase form for a quick match */
{ "Warning:", 8, HTTP_HEADER_WARNING },
{ "Expect:", 7, HTTP_HEADER_EXPECT },
{ NULL, 0, HTTP_HEADER_NONE }
};
/* HTTP error/warning messages. The mapped status for 30x redirects is
somewhat special-case, see the comment in readResponseHeader() for
details. This table also contains known non-HTTP codes in the
expectation that, when used as a general-purpose substrate, it'll be
pressed into use in all sorts of situations */
typedef struct {
const int httpStatus; /* Numeric status value */
const char *httpStatusString; /* String status value */
const char *httpErrorString; /* Text description of status */
const int status; /* Equivalent cryptlib status */
} HTTP_STATUS_INFO;
static const FAR_BSS HTTP_STATUS_INFO httpStatusInfo[] = {
{ 100, "100", "Continue", OK_SPECIAL },
{ 101, "101", "Switching Protocols", CRYPT_ERROR_READ },
{ 110, "110", "Warning: Response is stale", CRYPT_OK },
{ 111, "111", "Warning: Revalidation failed", CRYPT_OK },
{ 112, "112", "Warning: Disconnected operation", CRYPT_OK },
{ 113, "113", "Warning: Heuristic expiration", CRYPT_OK },
{ 199, "199", "Warning: Miscellaneous warning", CRYPT_OK },
{ 200, "200", "OK", CRYPT_OK },
{ 201, "201", "Created", CRYPT_ERROR_READ },
{ 202, "202", "Accepted", CRYPT_ERROR_READ },
{ 203, "203", "Non-Authoritative Information", CRYPT_OK },
{ 204, "204", "No Content", CRYPT_ERROR_READ },
{ 205, "205", "Reset Content", CRYPT_ERROR_READ },
{ 206, "206", "Partial Content", CRYPT_ERROR_READ },
{ 214, "214", "Warning: Transformation applied", CRYPT_OK },
{ 250, "250", "RTSP: Low on Storage Space", CRYPT_OK },
{ 299, "299", "Warning: Miscellaneous persistent warning", CRYPT_OK },
{ 300, "300", "Multiple Choices", CRYPT_ERROR_READ },
{ 301, "301", "Moved Permanently", OK_SPECIAL },
{ 302, "302", "Moved Temporarily/Found", OK_SPECIAL },
{ 303, "303", "See Other", CRYPT_ERROR_READ },
{ 304, "304", "Not Modified", CRYPT_ERROR_READ },
{ 305, "305", "Use Proxy", CRYPT_ERROR_READ },
{ 306, "306", "Unused/obsolete", CRYPT_ERROR_READ },
{ 307, "307", "Temporary Redirect", OK_SPECIAL },
{ 400, "400", "Bad Request", CRYPT_ERROR_READ },
{ 401, "401", "Unauthorized", CRYPT_ERROR_PERMISSION },
{ 402, "402", "Payment Required", CRYPT_ERROR_READ },
{ 403, "403", "Forbidden", CRYPT_ERROR_PERMISSION },
{ 404, "404", "Not Found", CRYPT_ERROR_NOTFOUND },
{ 405, "405", "Method Not Allowed", CRYPT_ERROR_NOTAVAIL },
{ 406, "406", "Not Acceptable", CRYPT_ERROR_READ },
{ 407, "407", "Proxy Authentication Required", CRYPT_ERROR_READ },
{ 408, "408", "Request Time-out", CRYPT_ERROR_READ },
{ 409, "409", "Conflict", CRYPT_ERROR_READ },
{ 410, "410", "Gone", CRYPT_ERROR_NOTFOUND },
{ 411, "411", "Length Required", CRYPT_ERROR_READ },
{ 412, "412", "Precondition Failed", CRYPT_ERROR_READ },
{ 413, "413", "Request Entity too Large", CRYPT_ERROR_OVERFLOW },
{ 414, "414", "Request-URI too Large", CRYPT_ERROR_OVERFLOW },
{ 415, "415", "Unsupported Media Type", CRYPT_ERROR_READ },
{ 416, "416", "Requested range not satisfiable", CRYPT_ERROR_READ },
{ 417, "417", "Expectation Failed", CRYPT_ERROR_READ },
{ 426, "426", "Upgrade Required", CRYPT_ERROR_READ },
{ 451, "451", "RTSP: Parameter not Understood", CRYPT_ERROR_BADDATA },
{ 452, "452", "RTSP: Conference not Found", CRYPT_ERROR_NOTFOUND },
{ 453, "453", "RTSP: Not enough Bandwidth", CRYPT_ERROR_NOTAVAIL },
{ 454, "454", "RTSP: Session not Found", CRYPT_ERROR_NOTFOUND },
{ 455, "455", "RTSP: Method not Valid in this State", CRYPT_ERROR_NOTAVAIL },
{ 456, "456", "RTSP: Header Field not Valid for Resource", CRYPT_ERROR_NOTAVAIL },
{ 457, "457", "RTSP: Invalid Range", CRYPT_ERROR_READ },
{ 458, "458", "RTSP: Parameter is Read-Only", CRYPT_ERROR_PERMISSION },
{ 459, "459", "RTSP: Aggregate Operation not Allowed", CRYPT_ERROR_PERMISSION },
{ 460, "460", "RTSP: Only Aggregate Operation Allowed", CRYPT_ERROR_PERMISSION },
{ 461, "461", "RTSP: Unsupported Transport", CRYPT_ERROR_NOTAVAIL },
{ 462, "462", "RTSP: Destination Unreachable", CRYPT_ERROR_OPEN },
{ 500, "500", "Internal Server Error", CRYPT_ERROR_READ },
{ 501, "501", "Not Implemented", CRYPT_ERROR_NOTAVAIL },
{ 502, "502", "Bad Gateway", CRYPT_ERROR_READ },
{ 503, "503", "Service Unavailable", CRYPT_ERROR_READ },
{ 504, "504", "Gateway Time-out", CRYPT_ERROR_READ },
{ 505, "505", "HTTP Version not supported", CRYPT_ERROR_READ },
{ 510, "510", "HTTP-Ext: Not Extended", CRYPT_ERROR_READ },
{ 551, "551", "RTSP: Option not supported", CRYPT_ERROR_READ },
{ 0, NULL, "Unknown HTTP status condition", CRYPT_ERROR_READ }
};
/****************************************************************************
* *
* Utility Functions *
* *
****************************************************************************/
/* When reading text data over a network we don't know how much more data is
to come so we have to read a byte at a time looking for an EOL. In
addition we can't use the simple optimisation of reading two bytes at a
time because some servers only send a LF even though the spec requires a
CRLF. This is horribly inefficient but is pretty much eliminated through
the use of opportunistic read-ahead buffering */
static int readLine( STREAM *stream, char *buffer, const int maxSize )
{
MIME_STATE state;
int status;
initMIMEstate( &state, maxSize );
do
{
BYTE ch;
status = stream->bufferedTransportReadFunction( stream, &ch, 1,
TRANSPORT_FLAG_NONE );
if( cryptStatusError( status ) )
/* Network-level error, don't overwrite the extended error
information */
return( status );
status = addMIMEchar( &state, buffer, ch );
}
while( cryptStatusOK( status ) );
if( cryptStatusError( status ) && status != OK_SPECIAL )
/* We got an error other than an EOL condition, exit */
retExtStream( stream, status, "Invalid HTTP header line" );
return( endMIMEstate( &state ) );
}
/* Skip whitespace in a line of text. We only need to check for spaces as
whitespace since it's been canonicalised already */
static const char *skipWhitespace( const char *buffer )
{
if( buffer == NULL )
return( NULL );
while( *buffer == ' ' )
buffer++;
return( *buffer ? buffer : NULL );
}
/* Decode a hex nibble */
static int getNibble( const char srcCh )
{
int ch;
ch = toLower( srcCh );
if( !isXDigit( ch ) )
return( CRYPT_ERROR_BADDATA );
return( ( ch <= '9' ) ? ch - '0' : ch - ( 'a' - 10 ) );
}
/* Encode/decode a string as per RFC 1866 (although the list of characters
that need to be escaped is itself given in RFC 2396). 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), and then to check for any special-case
chars */
static void encodeRFC1866( STREAM *headerStream, const char *source )
{
static const char *allowedChars = "$-_.!*'(),\"/"; /* RFC 1738 + '/' */
while( *source )
{
const int ch = *source++;
int i;
if( isAlnum( ch ) )
{
sputc( headerStream, ch );
continue;
}
if( ch == ' ' )
{
sputc( headerStream, '+' );
continue;
}
for( i = 0; allowedChars[ i ] && ch != allowedChars[ i ]; i++ );
if( allowedChars[ i ] )
/* It's in the allowed-chars list, output it verbatim */
sputc( headerStream, ch );
else
{
char escapeString[ 8 ];
int escapeStringLen;
/* It's a special char, escape it */
escapeStringLen = sPrintf( escapeString, "%%%02X", ch );
swrite( headerStream, escapeString, escapeStringLen );
}
}
}
static int decodeRFC1866( char *dest, const char *source,
const int sourceLen )
{
const char *origDestPtr = dest;
BOOLEAN seenEscape = FALSE;
int i;
for( i = 0; i < sourceLen; i++ )
{
int chLo, chHi, ch;
/* If it's not an escape, just copy it straight over */
if( *source != '%' )
{
*dest++ = *source++;
continue;
}
source++; /* Skip '%' */
i += 2; /* Skip escape sequence */
seenEscape = TRUE;
/* Decode the escaped character */
if( sourceLen - i < 1 )
return( CRYPT_ERROR_BADDATA );
chHi = getNibble( *source ); source++;
chLo = getNibble( *source ); source++;
if( cryptStatusError( chHi ) || cryptStatusError( chLo ) )
return( CRYPT_ERROR_BADDATA );
ch = ( chHi << 4 ) | chLo;
if( !isPrint( ch ) )
/* It's a special-case/control character of some kind, report
it as an error. This gets rid of things like nulls (treated
as string terminators by some functions) and CR/LF line
terminators, which can be embedded into strings to turn a
single line of supplied text into multi-line responses
containing user-controlled type:value pairs (in other words
they allow user data to be injected into the control
channel) */
return( CRYPT_ERROR_BADDATA );
*dest++ = ch;
}
/* If we've seen an escape sequence, tell the caller the new length,
otherwise tell them that nothing's changed */
return( seenEscape ? dest - origDestPtr : OK_SPECIAL );
}
/* Convert a hex ASCII string used with chunked encoding into a numeric
value */
static int getChunkLength( const char *buffer, const int bufLen )
{
int i, chunkLength = 0, length = bufLen;
/* Chunk size information can have extensions tacked onto it following a
';', strip these before we start */
for( i = 0; i < bufLen; i++ )
if( buffer[ i ] == ';' )
{
/* Move back to the end of the string that precedes the ';' */
while( i > 0 && buffer[ i - 1 ] == ' ' )
i--;
length = i;
break;
}
/* The other side shouldn't be sending us more than 64K of data, given
that what we're expecting is a short PKI message */
if( length < 1 || length > 4 )
return( CRYPT_ERROR_BADDATA );
/* Walk down the string converting hex characters into their numeric
values */
for( i = 0; i < length; i++ )
{
const int ch = getNibble( buffer[ i ] );
if( cryptStatusError( ch ) )
return( CRYPT_ERROR_BADDATA );
chunkLength = ( chunkLength << 4 ) | ch;
}
return( chunkLength );
}
/* If we time out when sending HTTP header data this would usually be
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -