diff --git a/ChangeLog b/ChangeLog index 316cb687e..49e1233c0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -27,6 +27,7 @@ * almost completely changed the storage interface (for custom storage) * added support for hashing pieces in multiple threads + * improved error handling of gzip * fixed crash when web seeds redirect * fix compiler warnings diff --git a/include/libtorrent/gzip.hpp b/include/libtorrent/gzip.hpp index be1767b14..047018e38 100644 --- a/include/libtorrent/gzip.hpp +++ b/include/libtorrent/gzip.hpp @@ -34,19 +34,98 @@ POSSIBILITY OF SUCH DAMAGE. #define TORRENT_GZIP_HPP_INCLUDED #include "libtorrent/config.hpp" -#include +#include "libtorrent/error_code.hpp" + #include namespace libtorrent { - TORRENT_EXTRA_EXPORT bool inflate_gzip( + TORRENT_EXTRA_EXPORT void inflate_gzip( char const* in, int size , std::vector& buffer , int maximum_size - , std::string& error); + , error_code& error); + + // get the ``error_category`` for zip errors + TORRENT_EXPORT boost::system::error_category& get_gzip_category(); + + namespace gzip_errors + { + // libtorrent uses boost.system's ``error_code`` class to represent errors. libtorrent has + // its own error category get_gzip_category() whith the error codes defined by error_code_enum. + enum error_code_enum + { + // Not an error + no_error = 0, + + // the supplied gzip buffer has invalid header + invalid_gzip_header, + + // the gzip buffer would inflate to more bytes than the specified + // maximum size, and was rejected. + inflated_data_too_large, + + // available inflate data did not terminate + data_did_not_terminate, + + // output space exhausted before completing inflate + space_exhausted, + + // invalid block type (type == 3) + invalid_block_type, + + // stored block length did not match one's complement + invalid_stored_block_length, + + // dynamic block code description: too many length or distance codes + too_many_length_or_distance_codes, + + // dynamic block code description: code lengths codes incomplete + code_lengths_codes_incomplete, + + // dynamic block code description: repeat lengths with no first length + repeat_lengths_with_no_first_length, + + // dynamic block code description: repeat more than specified lengths + repeat_more_than_specified_lengths, + + // dynamic block code description: invalid literal/length code lengths + invalid_literal_length_code_lengths, + + // dynamic block code description: invalid distance code lengths + invalid_distance_code_lengths, + + // invalid literal/length or distance code in fixed or dynamic block + invalid_literal_code_in_block, + + // distance is too far back in fixed or dynamic block + distance_too_far_back_in_block, + + // an unknown error occurred during gzip inflation + unknown_gzip_error, + + // the number of error codes + error_code_max + }; + + // hidden + TORRENT_EXPORT boost::system::error_code make_error_code(error_code_enum e); + } } +#if BOOST_VERSION >= 103500 +namespace boost { namespace system { + +template<> +struct is_error_code_enum +{ + static const bool value = true; +}; + +} } +#endif // BOOST_VERSION + #endif diff --git a/include/libtorrent/i2p_stream.hpp b/include/libtorrent/i2p_stream.hpp index b050f48fd..d7d21cbd3 100644 --- a/include/libtorrent/i2p_stream.hpp +++ b/include/libtorrent/i2p_stream.hpp @@ -65,6 +65,8 @@ namespace libtorrent { duplicated_id, num_errors }; + + TORRENT_EXPORT boost::system::error_code make_error_code(i2p_error_code e); } // returns the error category for I2P errors @@ -211,6 +213,19 @@ private: }; } + +#if BOOST_VERSION >= 103500 +namespace boost { namespace system { + +template<> +struct is_error_code_enum +{ + static const bool value = true; +}; + +} } +#endif // BOOST_VERSION + #endif // TORRENT_USE_I2P #endif diff --git a/include/libtorrent/lazy_entry.hpp b/include/libtorrent/lazy_entry.hpp index 68b9eb4e9..9818e6cd3 100644 --- a/include/libtorrent/lazy_entry.hpp +++ b/include/libtorrent/lazy_entry.hpp @@ -422,10 +422,7 @@ namespace libtorrent }; // hidden - inline boost::system::error_code make_error_code(error_code_enum e) - { - return boost::system::error_code(e, get_bdecode_category()); - } + TORRENT_EXPORT boost::system::error_code make_error_code(error_code_enum e); } TORRENT_EXTRA_EXPORT char const* parse_int(char const* start @@ -434,5 +431,16 @@ namespace libtorrent } +#if BOOST_VERSION >= 103500 + +namespace boost { namespace system { + + template<> struct is_error_code_enum + { static const bool value = true; }; + +} } + +#endif + #endif diff --git a/src/gzip.cpp b/src/gzip.cpp index ad54735db..9af00606e 100644 --- a/src/gzip.cpp +++ b/src/gzip.cpp @@ -32,6 +32,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/assert.hpp" #include "libtorrent/puff.hpp" +#include "libtorrent/gzip.hpp" #include #include @@ -55,6 +56,59 @@ namespace namespace libtorrent { + struct gzip_error_category : boost::system::error_category + { + virtual const char* name() const BOOST_SYSTEM_NOEXCEPT; + virtual std::string message(int ev) const BOOST_SYSTEM_NOEXCEPT; + virtual boost::system::error_condition default_error_condition(int ev) const BOOST_SYSTEM_NOEXCEPT + { return boost::system::error_condition(ev, *this); } + }; + + const char* gzip_error_category::name() const BOOST_SYSTEM_NOEXCEPT + { + return "gzip error"; + } + + std::string gzip_error_category::message(int ev) const BOOST_SYSTEM_NOEXCEPT + { + static char const* msgs[] = + { + "no error", + "invalid gzip header", + "inflated data too large", + "available inflate data did not terminate", + "output space exhausted before completing inflate", + "invalid block type (type == 3)", + "stored block length did not match one's complement", + "dynamic block code description: too many length or distance codes", + "dynamic block code description: code lengths codes incomplete", + "dynamic block code description: repeat lengths with no first length", + "dynamic block code description: repeat more than specified lengths", + "dynamic block code description: invalid literal/length code lengths", + "dynamic block code description: invalid distance code lengths", + "invalid literal/length or distance code in fixed or dynamic block", + "distance is too far back in fixed or dynamic block", + "unknown gzip error", + }; + if (ev < 0 || ev >= int(sizeof(msgs)/sizeof(msgs[0]))) + return "Unknown error"; + return msgs[ev]; + } + + boost::system::error_category& get_gzip_category() + { + static gzip_error_category gzip_category; + return gzip_category; + } + + namespace gzip_errors + { + boost::system::error_code make_error_code(error_code_enum e) + { + return boost::system::error_code(e, get_gzip_category()); + } + } + // returns -1 if gzip header is invalid or the header size in bytes int gzip_header(const char* buf, int size) { @@ -129,21 +183,21 @@ namespace libtorrent return total_size - size; } - // TODO: 2 it would be nice to use proper error handling here - TORRENT_EXTRA_EXPORT bool inflate_gzip( + TORRENT_EXTRA_EXPORT void inflate_gzip( char const* in , int size , std::vector& buffer , int maximum_size - , std::string& error) + , error_code& ec) { + ec.clear(); TORRENT_ASSERT(maximum_size > 0); int header_len = gzip_header(in, size); if (header_len < 0) { - error = "invalid gzip header"; - return true; + ec = gzip_errors::invalid_gzip_header; + return; } // start off with 4 kilobytes and grow @@ -158,9 +212,8 @@ namespace libtorrent TORRENT_TRY { buffer.resize(destlen); } TORRENT_CATCH(std::exception& e) { - error = "out of memory: "; - error += e.what(); - return true; + ec = errors::no_memory; + return; } ret = puff((unsigned char*)&buffer[0], &destlen, (unsigned char*)in, &srclen); @@ -172,8 +225,8 @@ namespace libtorrent { if (destlen == boost::uint32_t(maximum_size)) { - error = "inflated data too big"; - return true; + ec = gzip_errors::inflated_data_too_large; + return; } destlen *= 2; @@ -184,19 +237,31 @@ namespace libtorrent if (ret != 0) { - error = "error while inflating data"; - return true; + switch (ret) + { + case 2: ec = gzip_errors::data_did_not_terminate; return; + case 1: ec = gzip_errors::space_exhausted; return; + case -1: ec = gzip_errors::invalid_block_type; return; + case -2: ec = gzip_errors::invalid_stored_block_length; return; + case -3: ec = gzip_errors::too_many_length_or_distance_codes; return; + case -4: ec = gzip_errors::code_lengths_codes_incomplete; return; + case -5: ec = gzip_errors::repeat_lengths_with_no_first_length; return; + case -6: ec = gzip_errors::repeat_more_than_specified_lengths; return; + case -7: ec = gzip_errors::invalid_literal_length_code_lengths; return; + case -8: ec = gzip_errors::invalid_distance_code_lengths; return; + case -9: ec = gzip_errors::invalid_literal_code_in_block; return; + case -10: ec = gzip_errors::distance_too_far_back_in_block; return; + default: ec = gzip_errors::unknown_gzip_error; return; + } } if (destlen > buffer.size()) { - error = "internal gzip error"; - return true; + ec = gzip_errors::unknown_gzip_error; + return; } buffer.resize(destlen); - - return false; } } diff --git a/src/http_connection.cpp b/src/http_connection.cpp index c1e23ba9b..7c567ef16 100644 --- a/src/http_connection.cpp +++ b/src/http_connection.cpp @@ -682,10 +682,12 @@ void http_connection::callback(error_code e, char const* data, int size) std::string const& encoding = m_parser.header("content-encoding"); if ((encoding == "gzip" || encoding == "x-gzip") && size > 0 && data) { - std::string error; - if (inflate_gzip(data, size, buf, m_max_bottled_buffer_size, error)) + error_code ec; + inflate_gzip(data, size, buf, m_max_bottled_buffer_size, ec); + + if (ec) { - if (m_handler) m_handler(errors::http_failed_decompress, m_parser, data, size, *this); + if (m_handler) m_handler(ec, m_parser, data, size, *this); close(); return; } diff --git a/src/i2p_stream.cpp b/src/i2p_stream.cpp index f60a35064..0eeaabea7 100644 --- a/src/i2p_stream.cpp +++ b/src/i2p_stream.cpp @@ -76,11 +76,20 @@ namespace libtorrent }; - TORRENT_EXPORT boost::system::error_category& get_i2p_category() + boost::system::error_category& get_i2p_category() { static i2p_error_category i2p_category; return i2p_category; } + + namespace i2p_error + { + boost::system::error_code make_error_code(i2p_error_code e) + { + return error_code(e, get_i2p_category()); + } + } + i2p_connection::i2p_connection(io_service& ios) : m_state(sam_idle) , m_io_service(ios) diff --git a/src/lazy_bdecode.cpp b/src/lazy_bdecode.cpp index 4034a59d4..bd59dd72d 100644 --- a/src/lazy_bdecode.cpp +++ b/src/lazy_bdecode.cpp @@ -685,5 +685,12 @@ namespace libtorrent return bdecode_category; } + namespace bdecode_errors + { + boost::system::error_code make_error_code(error_code_enum e) + { + return boost::system::error_code(e, get_bdecode_category()); + } + } }; diff --git a/src/torrent.cpp b/src/torrent.cpp index 0096cf5c4..49e0bb6ce 100644 --- a/src/torrent.cpp +++ b/src/torrent.cpp @@ -428,11 +428,12 @@ namespace libtorrent if ((encoding == "gzip" || encoding == "x-gzip") && m_torrent_file_buf.size()) { std::vector buf; - std::string error; - if (inflate_gzip(&m_torrent_file_buf[0], m_torrent_file_buf.size() - , buf, 4 * 1024 * 1024, error)) + error_code ec; + inflate_gzip(&m_torrent_file_buf[0], m_torrent_file_buf.size() + , buf, 4 * 1024 * 1024, ex); + if (ec) { - set_error(errors::http_failed_decompress, error_file_url); + set_error(ec, error_file_url); pause(); std::vector().swap(m_torrent_file_buf); return; diff --git a/test/test_gzip.cpp b/test/test_gzip.cpp index 4bebca54b..86341a9d4 100644 --- a/test/test_gzip.cpp +++ b/test/test_gzip.cpp @@ -48,13 +48,12 @@ int test_main() TEST_CHECK(!ec); std::vector inflated; - std::string error; - bool ret = inflate_gzip(&zipped[0], zipped.size(), inflated, 1000000, error); + inflate_gzip(&zipped[0], zipped.size(), inflated, 1000000, ec); - if (ret != 0) { - fprintf(stderr, "failed to unzip\n"); + if (ec) { + fprintf(stderr, "failed to unzip: %s\n", ec.message().c_str()); } - TEST_CHECK(ret == 0); + TEST_CHECK(!ec); TEST_CHECK(inflated.size() > 0); for (int i = 0; i < inflated.size(); ++i) TEST_EQUAL(inflated[i], 0);