From 1e6176911a6356c181fc42e4bdf35dae52f5615b Mon Sep 17 00:00:00 2001 From: Eric Frias Date: Fri, 10 Jul 2015 10:07:14 -0400 Subject: [PATCH] Fix bugs in http request when using chunked content encoding. Add missing support for parsing/reconstructing URLs with query strings to fc::url. Add md5 hash algorithm. --- CMakeLists.txt | 1 + include/fc/crypto/md5.hpp | 76 ++++++++++++++++++++++ include/fc/network/url.hpp | 1 + src/crypto/md5.cpp | 96 ++++++++++++++++++++++++++++ src/network/http/http_connection.cpp | 35 ++++++---- src/network/url.cpp | 55 ++++++++++++---- 6 files changed, 241 insertions(+), 23 deletions(-) create mode 100644 include/fc/crypto/md5.hpp create mode 100644 src/crypto/md5.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 1d847c8..2435f25 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -206,6 +206,7 @@ set( fc_sources src/crypto/sha256.cpp src/crypto/sha224.cpp src/crypto/sha512.cpp + src/crypto/md5.cpp src/crypto/dh.cpp src/crypto/blowfish.cpp src/crypto/elliptic_common.cpp diff --git a/include/fc/crypto/md5.hpp b/include/fc/crypto/md5.hpp new file mode 100644 index 0000000..06141f7 --- /dev/null +++ b/include/fc/crypto/md5.hpp @@ -0,0 +1,76 @@ +#pragma once +#include +#include + +namespace fc +{ + +class md5 +{ + public: + md5(); + explicit md5( const string& hex_str ); + + string str()const; + operator string()const; + + char* data()const; + size_t data_size()const { return 128 / 8; } + + static md5 hash( const char* d, uint32_t dlen ); + static md5 hash( const string& ); + + template + static md5 hash( const T& t ) + { + md5::encoder e; + e << t; + return e.result(); + } + + class encoder + { + public: + encoder(); + ~encoder(); + + void write( const char* d, uint32_t dlen ); + void put( char c ) { write( &c, 1 ); } + void reset(); + md5 result(); + + private: + struct impl; + fc::fwd my; + }; + + template + inline friend T& operator<<( T& ds, const md5& ep ) { + ds.write( ep.data(), sizeof(ep) ); + return ds; + } + + template + inline friend T& operator>>( T& ds, md5& ep ) { + ds.read( ep.data(), sizeof(ep) ); + return ds; + } + friend md5 operator << ( const md5& h1, uint32_t i ); + friend bool operator == ( const md5& h1, const md5& h2 ); + friend bool operator != ( const md5& h1, const md5& h2 ); + friend md5 operator ^ ( const md5& h1, const md5& h2 ); + friend bool operator >= ( const md5& h1, const md5& h2 ); + friend bool operator > ( const md5& h1, const md5& h2 ); + friend bool operator < ( const md5& h1, const md5& h2 ); + + uint64_t _hash[2]; +}; + + class variant; + void to_variant( const md5& bi, variant& v ); + void from_variant( const variant& v, md5& bi ); + +} // fc + +#include +FC_REFLECT_TYPENAME( fc::md5 ) diff --git a/include/fc/network/url.hpp b/include/fc/network/url.hpp index 6f8c745..4d6066c 100644 --- a/include/fc/network/url.hpp +++ b/include/fc/network/url.hpp @@ -48,6 +48,7 @@ namespace fc { ostring pass()const; opath path()const; ovariant_object args()const; + std::string args_as_string()const; fc::optional port()const; private: diff --git a/src/crypto/md5.cpp b/src/crypto/md5.cpp new file mode 100644 index 0000000..fd304eb --- /dev/null +++ b/src/crypto/md5.cpp @@ -0,0 +1,96 @@ +#include +#include +#include +#include +#include +#include + +namespace fc { + + md5::md5() { memset( _hash, 0, sizeof(_hash) ); } + md5::md5( const string& hex_str ) { + fc::from_hex( hex_str, (char*)_hash, sizeof(_hash) ); + } + + string md5::str()const { + return fc::to_hex( (char*)_hash, sizeof(_hash) ); + } + md5::operator string()const { return str(); } + + char* md5::data()const { return (char*)&_hash[0]; } + + + struct md5::encoder::impl { + MD5_CTX ctx; + }; + + md5::encoder::~encoder() {} + md5::encoder::encoder() { + reset(); + } + + md5 md5::hash( const char* d, uint32_t dlen ) { + encoder e; + e.write(d,dlen); + return e.result(); + } + md5 md5::hash( const string& s ) { + return hash( s.c_str(), s.size() ); + } + + void md5::encoder::write( const char* d, uint32_t dlen ) { + MD5_Update( &my->ctx, d, dlen); + } + md5 md5::encoder::result() { + md5 h; + MD5_Final((uint8_t*)h.data(), &my->ctx ); + return h; + } + void md5::encoder::reset() { + MD5_Init( &my->ctx); + } + + md5 operator << ( const md5& h1, uint32_t i ) { + md5 result; + uint8_t* r = (uint8_t*)result._hash; + uint8_t* s = (uint8_t*)h1._hash; + for( uint32_t p = 0; p < sizeof(h1._hash)-1; ++p ) + r[p] = s[p] << i | (s[p+1]>>(8-i)); + r[63] = s[63] << i; + return result; + } + md5 operator ^ ( const md5& h1, const md5& h2 ) { + md5 result; + result._hash[0] = h1._hash[0] ^ h2._hash[0]; + result._hash[1] = h1._hash[1] ^ h2._hash[1]; + return result; + } + bool operator >= ( const md5& h1, const md5& h2 ) { + return memcmp( h1._hash, h2._hash, sizeof(h1._hash) ) >= 0; + } + bool operator > ( const md5& h1, const md5& h2 ) { + return memcmp( h1._hash, h2._hash, sizeof(h1._hash) ) > 0; + } + bool operator < ( const md5& h1, const md5& h2 ) { + return memcmp( h1._hash, h2._hash, sizeof(h1._hash) ) < 0; + } + bool operator != ( const md5& h1, const md5& h2 ) { + return memcmp( h1._hash, h2._hash, sizeof(h1._hash) ) != 0; + } + bool operator == ( const md5& h1, const md5& h2 ) { + return memcmp( h1._hash, h2._hash, sizeof(h1._hash) ) == 0; + } + + void to_variant( const md5& bi, variant& v ) + { + v = std::vector( (const char*)&bi, ((const char*)&bi) + sizeof(bi) ); + } + void from_variant( const variant& v, md5& bi ) + { + std::vector ve = v.as< std::vector >(); + if( ve.size() ) + memcpy(&bi, ve.data(), fc::min(ve.size(),sizeof(bi)) ); + else + memset( &bi, char(0), sizeof(bi) ); + } +} diff --git a/src/network/http/http_connection.cpp b/src/network/http/http_connection.cpp index b90a469..a44375d 100644 --- a/src/network/http/http_connection.cpp +++ b/src/network/http/http_connection.cpp @@ -9,7 +9,7 @@ #include #include #include - +#include class fc::http::connection::impl { @@ -67,14 +67,25 @@ class fc::http::connection::impl if (is_chunked) { - // Chunked means we get a hexadecimal number of bytes on a line, followed by the content - s = read_until( line.data(), line.data()+line.size(), '\n' ); // DESCRIPTION - if (line[strlen(line.data())] == '\r') - line[strlen(line.data())] = 0; - unsigned length; - if (sscanf(line.data(), "%x", &length) != 1) - FC_THROW("Invalid content length: ${length}", ("length", fc::string(line.data()))); - content_length = length; + do + { + // Chunked means we get a hexadecimal number of bytes on a line, followed by the content + s = read_until( line.data(), line.data()+line.size(), '\n' ); //read chunk length + if (line[strlen(line.data())] == '\r') + line[strlen(line.data())] = 0; + unsigned length; + if (sscanf(line.data(), "%x", &length) != 1) + FC_THROW("Invalid content length: ${length}", ("length", fc::string(line.data()))); + content_length = length; + if (*content_length) + { + std::vector temp_data(*content_length); + sock.read( temp_data.data(), *content_length ); + boost::push_back(rep.body, temp_data); + read_until( line.data(), line.data()+line.size(), '\n' ); //discard cr/lf after each chunk + } + } + while (*content_length != 0); } if (content_length) @@ -85,7 +96,7 @@ class fc::http::connection::impl sock.read( rep.body.data(), *content_length ); } } - else + else //just read until closed if no content length or chunking { std::shared_ptr buf(new char); while (true) @@ -140,7 +151,7 @@ http::reply connection::request( const fc::string& method, } try { fc::stringstream req; - req << method <<" "<generic_string()<<" HTTP/1.1\r\n"; + req << method << " " << parsed_url.path()->generic_string() << parsed_url.args_as_string() << " HTTP/1.1\r\n"; req << "Host: "<<*parsed_url.host()<<"\r\n"; req << "Content-Type: " << content_type << "\r\n"; for( auto i = he.begin(); i != he.end(); ++i ) @@ -152,10 +163,12 @@ http::reply connection::request( const fc::string& method, fc::string head = req.str(); my->sock.write( head.c_str(), head.size() ); + //elog("Sending header ${head}", ("head", head)); // fc::cerr.write( head.c_str() ); if( body.size() ) { my->sock.write( body.c_str(), body.size() ); + //elog("Sending body ${body}", ("body", body)); // fc::cerr.write( body.c_str() ); } // fc::cerr.flush(); diff --git a/src/network/url.cpp b/src/network/url.cpp index 635dd2d..d7d6339 100644 --- a/src/network/url.cpp +++ b/src/network/url.cpp @@ -60,9 +60,22 @@ namespace fc _path = fc::path( "/" ) / _lpath; #endif std::getline( ss, _largs ); - if( _args.valid() && _args->size() ) + if( _largs.size() ) { - // TODO: args = fc::move(_args); + mutable_variant_object new_args; + std::istringstream args_stream(_largs); + std::string _larg; + while (std::getline(args_stream, _larg, '&')) + { + std::string::size_type equals_pos = _larg.find('='); + if (equals_pos != std::string::npos) + { + std::string key = _larg.substr(0, equals_pos); + std::string value = _larg.substr(equals_pos + 1); + new_args[key] = value; + } + } + _args = new_args; } } @@ -88,18 +101,21 @@ namespace fc url::operator string()const { std::stringstream ss; - ss<_proto<<"://"; - if( my->_user.valid() ) { + ss << my->_proto << "://"; + if( my->_user.valid() ) + { ss << *my->_user; - if( my->_pass.valid() ) { - ss<<":"<<*my->_pass; - } - ss<<"@"; + if( my->_pass.valid() ) + ss << ":" << *my->_pass; + ss << "@"; } - if( my->_host.valid() ) ss<<*my->_host; - if( my->_port.valid() ) ss<<":"<<*my->_port; - if( my->_path.valid() ) ss<_path->generic_string(); - // if( my->_args ) ss<<"?"<<*my->_args; + if( my->_host.valid() ) + ss << *my->_host; + if( my->_port.valid() ) + ss << ":" << *my->_port; + if( my->_path.valid() ) + ss << my->_path->generic_string(); + ss << args_as_string(); return ss.str(); } @@ -189,6 +205,21 @@ namespace fc { return my->_args; } + std::string url::args_as_string()const + { + std::ostringstream ss; + if( my->_args ) + { + bool first = true; + for (auto iter = my->_args->begin(); iter != my->_args->end(); ++iter) + { + ss << (first ? "?" : "&"); + first = false; + ss << iter->key() << "=" << iter->value().as_string(); + } + } + return ss.str(); + } fc::optional url::port()const { return my->_port;