| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845 | /*** JNetLib** Copyright (C) 2000-2007 Nullsoft, Inc.** Author: Justin Frankel** File: httpget.cpp - JNL HTTP GET implementation** License: see jnetlib.h*/#include "netinc.h"#include "util.h"#include "httpget.h"#include "foundation/error.h"#ifdef USE_SSL#include "sslconnection.h"#endif#include <stdio.h>#define STRSAFE_NO_DEPRECATE#include "nu/strsafe.h"#include "nu/AutoLock.h"char *JNL_HTTPGet::g_proxy = 0;static nu::LockGuard proxy_guard;char *JNL_HTTPGet::get_proxy(){	nu::AutoLock auto_lock( proxy_guard );	if ( g_proxy )		return _strdup( g_proxy );	else		return 0;}void JNL_HTTPGet::set_proxy( const char *proxy ){	nu::AutoLock auto_lock( proxy_guard );	free( g_proxy );	if ( proxy )		g_proxy = _strdup( proxy );	else		g_proxy = 0;}JNL_HTTPGet::JNL_HTTPGet( size_t recvbufsize, size_t sendbufsize ){	persistent             = false;	accept_all_reply_codes = false;	zlibStream             = 0;	allowCompression       = false;	m_dns                  = JNL_AUTODNS;	m_con                  = NULL;	m_http_proxylpinfo     = 0;	m_http_proxyhost       = 0;	m_http_proxyport       = 0;	m_sendbufsize          = sendbufsize;	m_sendheaders          = NULL;	reinit();	m_recvbufsize          = recvbufsize;	char *p = get_proxy();	if ( p )	{		char *r = NULL;		do_parse_url( p, &m_http_proxyhost, &m_http_proxyport, &r, &m_http_proxylpinfo );		free( r );		free( p );	}}JNL_HTTPGet::~JNL_HTTPGet(){	deinit();	free( m_sendheaders );	free( m_http_proxylpinfo );	free( m_http_proxyhost );}void JNL_HTTPGet::reinit(){	m_errstr           = 0;	m_recvheaders      = NULL;	m_recvheaders_size = 0;	m_http_state       = 0;	m_http_port        = 0;	m_http_url         = 0;	m_reply            = 0;	m_http_host        = m_http_lpinfo = m_http_request = NULL;}void JNL_HTTPGet::deinit( bool full ){	if ( !persistent || full || ( m_con && m_con->get_state() == JNL_Connection::STATE_ERROR ) )	{		delete m_con;		m_con = NULL;	}	free( m_recvheaders );	free( m_http_url );	free( m_http_host );	free( m_http_lpinfo );	free( m_http_request );	free( m_errstr );	free( m_reply );	if ( zlibStream )		inflateEnd( zlibStream );	free( zlibStream );	zlibStream = 0;	reinit();}void JNL_HTTPGet::set_sendbufsize(size_t sendbufsize){	m_sendbufsize = sendbufsize;}int JNL_HTTPGet::set_recv_buffer_size( size_t new_buffer_size ){	if ( m_con )	{		int ret = m_con->set_recv_buffer_size( new_buffer_size );		if ( ret == NErr_NoAction )// this will get returned if new_buffer_size is smaller than existing.  			return NErr_Success;		else if ( ret != NErr_Success )			return ret;	}	m_recvbufsize = new_buffer_size;	return NErr_Success;}void JNL_HTTPGet::addheader( const char *header ){	if ( strstr( header, "\r" ) || strstr( header, "\n" ) )		return;	if ( !m_sendheaders )	{		size_t len = strlen( header ) + 3;		m_sendheaders = (char *)malloc( len );		if ( m_sendheaders )		{			char *itr = m_sendheaders;			StringCchCopyExA( itr, len, header, &itr, &len, 0 );			StringCchCatExA( itr, len, "\r\n", &itr, &len, 0 );		}	}	else	{		size_t len = strlen( header ) + strlen( m_sendheaders ) + 1 + 2;		char *t = (char *)malloc( len );		if ( t )		{			char *newHeaders = t;			StringCchCopyExA( t, len, m_sendheaders, &t, &len, 0 );			StringCchCatExA( t, len, header, &t, &len, 0 );			StringCchCatExA( t, len, "\r\n", &t, &len, 0 );			free( m_sendheaders );			m_sendheaders = newHeaders;		}	}}void JNL_HTTPGet::addheadervalue( const char *header, const char *value ){	size_t additional = strlen( header ) + 2 + strlen( value ) + 2 + 1;	if ( !m_sendheaders )	{		m_sendheaders = (char *)malloc( additional );		if ( m_sendheaders )		{			char *p = m_sendheaders;			StringCchCopyExA( p, additional, header, &p, &additional, 0 );			StringCchCatExA( p, additional, ": ", &p, &additional, 0 );			StringCchCatExA( p, additional, value, &p, &additional, 0 );			StringCchCatExA( p, additional, "\r\n", &p, &additional, 0 );		}	}	else	{		size_t alloc_len = strlen( m_sendheaders ) + additional;		char *t = (char *)malloc( alloc_len );		if ( t )		{			char *p = t;			StringCchCopyExA( p, alloc_len, m_sendheaders, &p, &alloc_len, 0 );			StringCchCatExA( p, alloc_len, header, &p, &alloc_len, 0 );			StringCchCatExA( p, alloc_len, ": ", &p, &alloc_len, 0 );			StringCchCatExA( p, alloc_len, value, &p, &alloc_len, 0 );			StringCchCatExA( p, alloc_len, "\r\n", &p, &alloc_len, 0 );			free( m_sendheaders );			m_sendheaders = t;		}	}}void JNL_HTTPGet::do_encode_mimestr( char *in, char *out ){	char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";	int shift = 0;	int accum = 0;	while ( in && *in )	{		if ( *in )		{			accum <<= 8;			shift += 8;			accum |= *in++;		}		while ( shift >= 6 )		{			shift -= 6;			*out++ = alphabet[ ( accum >> shift ) & 0x3F ];		}	}	if ( shift == 4 )	{		*out++ = alphabet[ ( accum & 0xF ) << 2 ];		*out++ = '=';	}	else if ( shift == 2 )	{		*out++ = alphabet[ ( accum & 0x3 ) << 4 ];		*out++ = '=';		*out++ = '=';	}	*out++ = 0;}void JNL_HTTPGet::connect( const char *url, int ver, const char *requestmethod ){	deinit( false );	m_http_url = _strdup( url );	do_parse_url( m_http_url, &m_http_host, &m_http_port, &m_http_request, &m_http_lpinfo );	if ( !m_http_host || !m_http_host[ 0 ] || !m_http_port )	{		m_http_state = -1;		seterrstr( "invalid URL" );		return;	}	size_t sendbufferlen = 0;	if ( !m_http_proxyhost || !m_http_proxyhost[ 0 ] )		sendbufferlen += strlen( requestmethod ) + 1 /* GET */ + strlen( m_http_request ) + 9 /* HTTP/1.0 */ + 2;	else	{		sendbufferlen += strlen( requestmethod ) + 1 /* GET */ + strlen( m_http_url ) + 9 /* HTTP/1.0 */ + 2;		if ( m_http_proxylpinfo && m_http_proxylpinfo[ 0 ] )			sendbufferlen += 58 + strlen( m_http_proxylpinfo ) * 2; // being safe here	}	sendbufferlen += 5 /* Host: */ + strlen( m_http_host ) + 2;	if ( m_http_port != 80 )		sendbufferlen += 6;	if ( m_http_lpinfo && m_http_lpinfo[ 0 ] )		sendbufferlen += 46 + strlen( m_http_lpinfo ) * 2; // being safe here	if ( m_sendheaders )		sendbufferlen += strlen( m_sendheaders );	size_t strLen = sendbufferlen + 1024;	char *str = (char *)calloc( strLen, sizeof( char ) );	char *connectString = str;	if ( !str )	{		seterrstr( "error allocating memory" );		m_http_state = -1;	}	if ( !m_http_proxyhost || !m_http_proxyhost[ 0 ] )	{		StringCchPrintfExA( str, strLen, &str, &strLen, 0, "%s %s HTTP/1.%d\r\n", requestmethod, m_http_request, ver % 10 );	}	else	{		char *myp = NULL;		if ( strncasecmp( m_http_url, "uvox://", 7 ) == 0 )		{			myp = m_http_url + 7;			StringCchPrintfExA( str, strLen, &str, &strLen, 0, "%s http://%s HTTP/1.%d\r\n", requestmethod, myp, ver % 10 );		}		else if ( strncasecmp( m_http_url, "unsv://", 7 ) == 0 )		{			myp = m_http_url + 7;			StringCchPrintfExA( str, strLen, &str, &strLen, 0, "%s http://%s HTTP/1.%d\r\n", requestmethod, myp, ver % 10 );		}		else if ( strncasecmp( m_http_url, "uasf://", 7 ) == 0 )		{			myp = m_http_url + 7;			StringCchPrintfExA( str, strLen, &str, &strLen, 0, "%s http://%s HTTP/1.%d\r\n", requestmethod, myp, ver % 10 );		}		else			StringCchPrintfExA( str, strLen, &str, &strLen, 0, "%s %s HTTP/1.%d\r\n", requestmethod, m_http_url, ver % 10 );	}	if ( m_http_port == 80 )		StringCchPrintfExA( str, strLen, &str, &strLen, 0, "Host: %s\r\n", m_http_host );	else		StringCchPrintfExA( str, strLen, &str, &strLen, 0, "Host: %s:%d\r\n", m_http_host, m_http_port );	if ( m_http_lpinfo && m_http_lpinfo[ 0 ] )	{		StringCchCatExA( str, strLen, "Authorization: Basic ", &str, &strLen, 0 );		do_encode_mimestr( m_http_lpinfo, str );		StringCchCatExA( str, strLen, "\r\n", &str, &strLen, 0 );	}	if ( m_http_proxylpinfo && m_http_proxylpinfo[ 0 ] )	{		StringCchCatExA( str, strLen, "Proxy-Authorization: Basic ", &str, &strLen, 0 );		do_encode_mimestr( m_http_proxylpinfo, str );		StringCchCatExA( str, strLen, "\r\n", &str, &strLen, 0 );	}	if ( allowCompression )		StringCchCatExA( str, strLen, "Accept-Encoding: gzip\r\n", &str, &strLen, 0 );	if ( m_sendheaders )		StringCchCatExA( str, strLen, m_sendheaders, &str, &strLen, 0 );	StringCchCatExA( str, strLen, "\r\n", &str, &strLen, 0 );	int a = (int)m_recvbufsize;	if ( a < 4096 )		a = 4096;	if ( !m_con )	{		//m_con=new JNL_Connection(m_dns,strlen(str)+4,a);		/*		**  Joshua Teitelbaum delta 1/15/2006		*/#ifdef USE_SSL		/*		**  Joshua Teitelbaum 1/27/2006		**  Check for secure		*/		if ( !_strnicmp( m_http_url, "https:", strlen( "https:" ) ) )		{			size_t send_buffer_size = strlen( connectString ) + 4;			if ( send_buffer_size < 8192 )				send_buffer_size = 8192;			send_buffer_size += m_sendbufsize;			if ( m_sendbufsize == 0 && !strcmp( requestmethod, "POST" ) )				send_buffer_size += 8192; // some extra room for posting data if it wasn't explicitly defined			m_con = new JNL_SSL_Connection( NULL, m_dns, send_buffer_size, a );		}		else		{#endif			size_t send_buffer_size = strlen( connectString ) + 4 + m_sendbufsize;			if ( m_sendbufsize == 0 && !strcmp( requestmethod, "POST" ) )				send_buffer_size += 8192; // some extra room for posting data if it wasn't explicitly defined			m_con = new JNL_Connection( m_dns, send_buffer_size, a );#ifdef USE_SSL		}#endif		if ( m_con )		{			if ( !m_http_proxyhost || !m_http_proxyhost[ 0 ] )				m_con->connect( m_http_host, m_http_port );			else				m_con->connect( m_http_proxyhost, m_http_proxyport );			m_con->send_string( connectString );		}		else		{			m_http_state = -1;			seterrstr( "could not create connection object" );		}	}	else	{		m_con->reuse();		m_con->send_string( connectString );	}	free(connectString);}void JNL_HTTPGet::do_parse_url( const char *url, char **host, unsigned short *port, char **req, char **lp ){	char *l_port = 0;	JNL::parse_url( url, &l_port, host, port, req, lp );	if ( !*port )	{		if ( l_port )		{			addrinfo *res;			addrinfo hints;			memset( &hints, 0, sizeof( hints ) );			hints.ai_family = PF_UNSPEC;			hints.ai_flags = 0;			hints.ai_socktype = SOCK_STREAM;			if ( getaddrinfo( 0, l_port, &hints, &res ) == 0 )			{				if ( res->ai_family == AF_INET )					*port = htons( ( (sockaddr_in *)res->ai_addr )->sin_port );				else if ( res->ai_family == AF_INET6 )					*port = htons( ( (sockaddr_in6 *)res->ai_addr )->sin6_port );				else // wtf?					*port = 80;			}			else				*port = 80;		}		else			*port = 80;	}	if ( l_port )		free( l_port );	if ( !*req )		*req = _strdup( "/" );}const char *JNL_HTTPGet::getallheaders(){	// double null terminated, null delimited list	if (m_recvheaders)		return m_recvheaders;	else		return "\0\0";}const char *JNL_HTTPGet::getheader( const char *headername ){	char *ret = NULL;	if ( headername[ 0 ] == 0 || !m_recvheaders )		return NULL;	size_t headername_size = strlen( headername );	char *buf = (char *)malloc( headername_size + 2 );#ifdef _WIN32	StringCchCopyA( buf, headername_size + 2, headername );#elif defined(__APPLE__)	strlcpy( buf, headername, headername_size + 2 );#else	strncpy( buf, headername, headername_size + 1 );	buf[ headername_size + 1 ] = 0;#endif	if ( buf[ headername_size - 1 ] != ':' )	{		buf[ headername_size++ ] = ':';		buf[ headername_size ]   = 0;	}	char *p = m_recvheaders;	while ( p && *p )	{		if ( !strncasecmp( buf, p, headername_size ) )		{			ret = p + headername_size;			while ( ret && *ret && *ret == ' ' )				ret++;			break;		}		p += strlen( p ) + 1;	}	free( buf );	return ret;}int JNL_HTTPGet::run(){	int cnt = 0;	if ( m_http_state == -1 || !m_con )		return HTTPGET_RUN_ERROR; // errorrun_again:	m_con->run();	if ( m_con->get_state() == JNL_Connection::STATE_ERROR )	{		seterrstr( m_con->get_errstr() );		return HTTPGET_RUN_ERROR;	}	if ( m_con->get_state() == JNL_Connection::STATE_CLOSED )		return HTTPGET_RUN_CONNECTION_CLOSED;	if ( m_http_state == 0 ) // connected, waiting for reply	{		if ( m_con->recv_lines_available() > 0 )		{			char buf[ 4096 ] = { 0 };			m_con->recv_line( buf, 4096 );			buf[ 4095 ] = 0;			if ( m_reply && getreplycode() == 100 )			{				free( m_reply );				m_reply = 0;				goto run_again;			}			m_reply = _strdup( buf );			int code = getreplycode();			if ( code >= 200 && code <= 206 )				m_http_state = 2; // proceed to read headers normally			else if ( code == 301 || code == 302 || code == 303 || code == 307 )			{				m_http_state = 1; // redirect city			}			else if ( code != 100 ) // in case of HTTP 100 Continue code, we'll keep looping			{				if ( accept_all_reply_codes )				{					m_http_state = 2; // proceed to read headers normally				}				else				{					seterrstr( buf );					m_http_state = -1;					return HTTPGET_RUN_ERROR;				}			}			cnt = 0;		}		else if ( !cnt++ )			goto run_again;	}	if ( m_http_state == 1 ) // redirect	{		char *loc = 0;		while ( m_con->recv_lines_available() > 0 )		{			char buf[ 4096 ] = { 0 };			m_con->recv_line( buf, 4096 );			buf[ 4095 ] = 0;			if ( !buf[ 0 ] )			{				if ( !loc )				{					m_http_state = -1;					return HTTPGET_RUN_ERROR;				}				else					break;			}			if ( !strncasecmp( buf, "Location:", 9 ) )			{				char *p = buf + 9;				while ( p && *p && *p == ' ' ) p++;				if ( p && *p )				{					// TODO need to make this match the request type					loc = _strdup( p );				}			}		}		if ( loc )		{			connect( loc );			free( loc );			return HTTPGET_RUN_OK;		}	}	/* ----- read headers ----- */	if ( m_http_state == 2 )	{		if ( !cnt++ && m_con->recv_lines_available() < 1 )			goto run_again;		while ( m_con->recv_lines_available() > 0 )		{			char buf[ 8192 ] = { 0 };			m_con->recv_line( buf, 8192 );			buf[ 8191 ] = 0;			if ( !buf[ 0 ] )			{				const char *compression = getheader( "Content-Encoding" );				if ( compression && !strcmp( compression, "gzip" ) )				{					zlibStream = (z_stream *)malloc( sizeof( z_stream ) );					zlibStream->next_in = Z_NULL;					zlibStream->avail_in = Z_NULL;					zlibStream->next_out = Z_NULL;					zlibStream->avail_out = Z_NULL;					zlibStream->zalloc = (alloc_func)0;					zlibStream->zfree = (free_func)0;					zlibStream->opaque = 0;					int z_err = inflateInit2( zlibStream, 15 + 16 /* +16 for gzip */ );					if ( z_err != Z_OK )					{						free( zlibStream );						zlibStream = 0;					}				}				else				{					if ( zlibStream )					{						free( zlibStream );						zlibStream = 0;					}				}				m_http_state = 3;				break;			}			if ( !m_recvheaders )			{				m_recvheaders_size = strlen( buf ) + 1;				if ( m_recvheaders_size == 0 || m_recvheaders_size == (size_t)-1 ) // check for overflow				{					m_http_state = -1;					return HTTPGET_RUN_ERROR;				}				m_recvheaders = (char *)malloc( m_recvheaders_size + 1 );				if ( m_recvheaders )				{					strcpy( m_recvheaders, buf ); // safe because we malloc'd specifically above					m_recvheaders[ m_recvheaders_size ] = 0;				}				else				{					m_http_state = -1;					return HTTPGET_RUN_ERROR;				}			}			else			{				size_t oldsize = m_recvheaders_size;				m_recvheaders_size += strlen( buf ) + 1;				if ( m_recvheaders_size + 1 < oldsize ) // check for overflow				{					m_http_state = -1;					return HTTPGET_RUN_ERROR;				}				char *n = (char *)realloc( m_recvheaders, m_recvheaders_size + 1 );				if ( !n )				{					m_http_state = -1;					return HTTPGET_RUN_ERROR;				}				strcpy( n + oldsize, buf ); // safe because we malloc specifially for the size				n[ m_recvheaders_size ] = 0; // double null terminate				m_recvheaders = n;			}		}	}	return HTTPGET_RUN_OK;}int JNL_HTTPGet::get_status() // returns 0 if connecting, 1 if reading headers, 2 if reading content, -1 if error.{	if ( m_http_state < 0 )		return HTTPGET_STATUS_ERROR;	if ( m_http_state < 2 )		return HTTPGET_STATUS_CONNECTING;	if ( m_http_state == 2 )		return HTTPGET_STATUS_READING_HEADERS;	if ( m_http_state == 3 )		return HTTPGET_STATUS_READING_CONTENT;	return HTTPGET_STATUS_ERROR;}int JNL_HTTPGet::getreplycode() // returns 0 if none yet, otherwise returns http reply code.{	if ( !m_reply )		return 0;	char *p = m_reply;	while ( p && *p && *p != ' ' )		p++; // skip over HTTP/x.x	if ( !p || !*p )		return 0;	return atoi( ++p );}size_t JNL_HTTPGet::bytes_available(){	if ( m_con && m_http_state == 3 )		return m_con->recv_bytes_available();	return 0;}size_t JNL_HTTPGet::get_bytes( char *buf, size_t len ){	if ( m_con && m_http_state == 3 )	{		if ( zlibStream )		{			// TODO: benski> we need to pick a better buffer size			// either alloca() and use the passed in length			// or malloc a buffer based on the constructor-initted buffer size			char temp[ 8192 ] = { 0 };			int size = (int)m_con->peek_bytes( temp, 8192 );			if ( size )			{				zlibStream->next_in   = reinterpret_cast<Bytef *>( temp );				zlibStream->avail_in  = (uInt)size;				zlibStream->next_out  = reinterpret_cast<Bytef *>( buf );				zlibStream->avail_out = (uInt)len;				int zlib_err = inflate( zlibStream, Z_SYNC_FLUSH );				if ( zlib_err == Z_OK || zlib_err == Z_STREAM_END )				{					m_con->recv_bytes( 0, size - zlibStream->avail_in ); // since we only peeked above					return len - zlibStream->avail_out;				}				else					return 0; // TODO: should we do something else here?			}		}		else			return m_con->recv_bytes( buf, len );	}	return 0;}size_t JNL_HTTPGet::peek_bytes( char *buf, size_t len ){	if ( m_con && m_http_state == 3 )	{		if ( zlibStream )			return 0; // TODO: benski> how are we going to do peek_bytes, since the inflater saves state?		else			return m_con->peek_bytes( buf, len );	}	return 0;}uint64_t JNL_HTTPGet::content_length(){	const char *p = getheader( "content-length" );	if ( p && *p )		return strtoull( p, 0, 10 );	else	{		// TODO need to check this further for reliability!		// Helps to handle responses without content-length		if ( m_recvheaders_size > 0 && bytes_available() > 0 )			return bytes_available() - m_recvheaders_size;	}	return 0;}void JNL_HTTPGet::seterrstr( const char *str ){	if ( m_errstr )		free( m_errstr );	m_errstr = _strdup( str );}void JNL_HTTPGet::AllowCompression(){	allowCompression = true;}void JNL_HTTPGet::reset_headers(){	if ( m_sendheaders )	{		free( m_sendheaders );		m_sendheaders = 0;	}}void JNL_HTTPGet::set_accept_all_reply_codes(){	accept_all_reply_codes = true;}void JNL_HTTPGet::set_persistent(){	persistent = true;}
 |