From d783c17151a98466e304b1e5f33bfca0be885fd8 Mon Sep 17 00:00:00 2001 From: Kelly Rauchenberger Date: Thu, 1 Dec 2016 08:49:01 -0500 Subject: Added a convenience overload to client::replyToTweet --- src/client.cpp | 5 +++++ src/client.h | 1 + 2 files changed, 6 insertions(+) diff --git a/src/client.cpp b/src/client.cpp index e16e30b..ccfd9a3 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -376,6 +376,11 @@ namespace twitter { .perform()); } + tweet client::replyToTweet(std::string msg, const tweet& in_response_to, std::list media_ids) const + { + return replyToTweet(msg, in_response_to.getID(), media_ids); + } + long client::uploadMedia(std::string media_type, const char* data, long data_length) const try { curl::curl_form form; diff --git a/src/client.h b/src/client.h index 2230dbb..c920f82 100644 --- a/src/client.h +++ b/src/client.h @@ -29,6 +29,7 @@ namespace twitter { long uploadMedia(std::string media_type, const char* data, long data_length) const; tweet replyToTweet(std::string msg, tweet_id in_response_to, std::list media_ids = {}) const; + tweet replyToTweet(std::string msg, const tweet& in_response_to, std::list media_ids = {}) const; std::set getFriends(user_id id) const; std::set getFriends(const user& u) const; -- cgit 1.4.1 From df906121dd862c0f704e44f28ee079158c431c41 Mon Sep 17 00:00:00 2001 From: Kelly Rauchenberger Date: Fri, 3 Feb 2017 09:37:34 -0500 Subject: Updated to nlohmann/json 2.0.9 --- vendor/json/json.hpp | 2219 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 1813 insertions(+), 406 deletions(-) diff --git a/vendor/json/json.hpp b/vendor/json/json.hpp index 4447412..23058be 100644 --- a/vendor/json/json.hpp +++ b/vendor/json/json.hpp @@ -1,7 +1,7 @@ /* __ _____ _____ _____ __| | __| | | | JSON for Modern C++ -| | |__ | | | | | | version 2.0.7 +| | |__ | | | | | | version 2.0.9 |_____|_____|_____|_|___| https://github.com/nlohmann/json Licensed under the MIT License . @@ -29,32 +29,32 @@ SOFTWARE. #ifndef NLOHMANN_JSON_HPP #define NLOHMANN_JSON_HPP -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include // all_of, for_each, transform +#include // array +#include // assert +#include // isdigit +#include // and, not, or +#include // isfinite, ldexp, signbit +#include // nullptr_t, ptrdiff_t, size_t +#include // int64_t, uint64_t +#include // strtod, strtof, strtold, strtoul +#include // strlen +#include // function, hash, less +#include // initializer_list +#include // setw +#include // istream, ostream +#include // advance, begin, bidirectional_iterator_tag, distance, end, inserter, iterator, iterator_traits, next, random_access_iterator_tag, reverse_iterator +#include // numeric_limits +#include // locale +#include // map +#include // addressof, allocator, allocator_traits, unique_ptr +#include // accumulate +#include // stringstream +#include // domain_error, invalid_argument, out_of_range +#include // getline, stoi, string, to_string +#include // add_pointer, enable_if, is_arithmetic, is_base_of, is_const, is_constructible, is_convertible, is_floating_point, is_integral, is_nothrow_move_assignable, std::is_nothrow_move_constructible, std::is_pointer, std::is_reference, std::is_same, remove_const, remove_pointer, remove_reference +#include // declval, forward, make_pair, move, pair, swap +#include // vector // exclude unsupported compilers #if defined(__clang__) @@ -75,6 +75,12 @@ SOFTWARE. #pragma GCC diagnostic ignored "-Wfloat-equal" #endif +// disable documentation warnings on clang +#if defined(__clang__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdocumentation" +#endif + // allow for portable deprecation warnings #if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) #define JSON_DEPRECATED __attribute__((deprecated)) @@ -122,26 +128,6 @@ struct has_mapped_type std::is_integral()))>::value; }; -/*! -@brief helper class to create locales with decimal point - -This struct is used a default locale during the JSON serialization. JSON -requires the decimal point to be `.`, so this function overloads the -`do_decimal_point()` function to return `.`. This function is called by -float-to-string conversions to retrieve the decimal separator between integer -and fractional parts. - -@sa https://github.com/nlohmann/json/issues/51#issuecomment-86869315 -@since version 2.0.0 -*/ -struct DecimalSeparator : std::numpunct -{ - char do_decimal_point() const - { - return '.'; - } -}; - } /*! @@ -242,6 +228,7 @@ class basic_json public: // forward declarations + template class iter_impl; template class json_reverse_iterator; class json_pointer; @@ -276,9 +263,9 @@ class basic_json using const_pointer = typename std::allocator_traits::const_pointer; /// an iterator for a basic_json container - class iterator; + using iterator = iter_impl; /// a const iterator for a basic_json container - class const_iterator; + using const_iterator = iter_impl; /// a reverse iterator for a basic_json container using reverse_iterator = json_reverse_iterator; /// a const reverse iterator for a basic_json container @@ -964,7 +951,7 @@ class basic_json With a parser callback function, the result of parsing a JSON text can be influenced. When passed to @ref parse(std::istream&, const - parser_callback_t) or @ref parse(const char*, const parser_callback_t), + parser_callback_t) or @ref parse(const CharT, const parser_callback_t), it is called on certain events (passed as @ref parse_event_t via parameter @a event) with a set recursion depth @a depth and context JSON value @a parsed. The return value of the callback function is a boolean @@ -1007,7 +994,7 @@ class basic_json skipped completely or replaced by an empty discarded object. @sa @ref parse(std::istream&, parser_callback_t) or - @ref parse(const char*, parser_callback_t) for examples + @ref parse(const CharT, const parser_callback_t) for examples @since version 1.0.0 */ @@ -2201,8 +2188,7 @@ class basic_json { std::stringstream ss; // fix locale problems - const static std::locale loc(std::locale(), new DecimalSeparator); - ss.imbue(loc); + ss.imbue(std::locale::classic()); // 6, 15 or 16 digits of precision allows round-trip IEEE 754 // string->float->string, string->double->string or string->long @@ -5053,6 +5039,102 @@ class basic_json return *this; } + /*! + @brief add an object to an array + + Creates a JSON value from the passed parameters @a args to the end of the + JSON value. If the function is called on a JSON null value, an empty array + is created before appending the value created from @a args. + + @param[in] args arguments to forward to a constructor of @ref basic_json + @tparam Args compatible types to create a @ref basic_json object + + @throw std::domain_error when called on a type other than JSON array or + null; example: `"cannot use emplace_back() with number"` + + @complexity Amortized constant. + + @liveexample{The example shows how `push_back()` can be used to add + elements to a JSON array. Note how the `null` value was silently converted + to a JSON array.,emplace_back} + + @since version 2.0.8 + */ + template + void emplace_back(Args&& ... args) + { + // emplace_back only works for null objects or arrays + if (not(is_null() or is_array())) + { + throw std::domain_error("cannot use emplace_back() with " + type_name()); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + // add element to array (perfect forwarding) + m_value.array->emplace_back(std::forward(args)...); + } + + /*! + @brief add an object to an object if key does not exist + + Inserts a new element into a JSON object constructed in-place with the given + @a args if there is no element with the key in the container. If the + function is called on a JSON null value, an empty object is created before + appending the value created from @a args. + + @param[in] args arguments to forward to a constructor of @ref basic_json + @tparam Args compatible types to create a @ref basic_json object + + @return a pair consisting of an iterator to the inserted element, or the + already-existing element if no insertion happened, and a bool + denoting whether the insertion took place. + + @throw std::domain_error when called on a type other than JSON object or + null; example: `"cannot use emplace() with number"` + + @complexity Logarithmic in the size of the container, O(log(`size()`)). + + @liveexample{The example shows how `emplace()` can be used to add elements + to a JSON object. Note how the `null` value was silently converted to a + JSON object. Further note how no value is added if there was already one + value stored with the same key.,emplace} + + @since version 2.0.8 + */ + template + std::pair emplace(Args&& ... args) + { + // emplace only works for null objects or arrays + if (not(is_null() or is_object())) + { + throw std::domain_error("cannot use emplace() with " + type_name()); + } + + // transform null object into an object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // add element to array (perfect forwarding) + auto res = m_value.object->emplace(std::forward(args)...); + // create result iterator and set iterator to the result of emplace + auto it = begin(); + it.m_it.object_iterator = res.first; + + // return pair of iterator and boolean + return {it, res.second}; + } + /*! @brief inserts element @@ -5829,7 +5911,7 @@ class basic_json o.width(0); // fix locale problems - const auto old_locale = o.imbue(std::locale(std::locale(), new DecimalSeparator)); + const auto old_locale = o.imbue(std::locale::classic()); // set precision // 6, 15 or 16 digits of precision allows round-trip IEEE 754 @@ -5928,11 +6010,11 @@ class basic_json @since version 1.0.0 (originally for @ref string_t) */ - template::value and - std::is_integral::type>::value and - sizeof(typename std::remove_pointer::type) == 1, int>::type = 0> - static basic_json parse(const CharPT s, + template::value and + std::is_integral::type>::value and + sizeof(typename std::remove_pointer::type) == 1, int>::type = 0> + static basic_json parse(const CharT s, const parser_callback_t cb = nullptr) { return parser(reinterpret_cast(s), cb).parse(); @@ -5957,7 +6039,7 @@ class basic_json @liveexample{The example below demonstrates the `parse()` function with and without callback function.,parse__istream__parser_callback_t} - @sa @ref parse(const char*, const parser_callback_t) for a version + @sa @ref parse(const CharT, const parser_callback_t) for a version that reads from a string @since version 1.0.0 @@ -6142,167 +6224,1596 @@ class basic_json /// @} + ////////////////////////////////////////// + // binary serialization/deserialization // + ////////////////////////////////////////// + + /// @name binary serialization/deserialization support + /// @{ private: - /////////////////////////// - // convenience functions // - /////////////////////////// + template + static void add_to_vector(std::vector& vec, size_t bytes, const T number) + { + assert(bytes == 1 or bytes == 2 or bytes == 4 or bytes == 8); + + switch (bytes) + { + case 8: + { + vec.push_back(static_cast((number >> 070) & 0xff)); + vec.push_back(static_cast((number >> 060) & 0xff)); + vec.push_back(static_cast((number >> 050) & 0xff)); + vec.push_back(static_cast((number >> 040) & 0xff)); + // intentional fall-through + } + + case 4: + { + vec.push_back(static_cast((number >> 030) & 0xff)); + vec.push_back(static_cast((number >> 020) & 0xff)); + // intentional fall-through + } + + case 2: + { + vec.push_back(static_cast((number >> 010) & 0xff)); + // intentional fall-through + } + + case 1: + { + vec.push_back(static_cast(number & 0xff)); + break; + } + } + } /*! - @brief return the type as string + @brief take sufficient bytes from a vector to fill an integer variable - Returns the type name as string to be used in error messages - usually to - indicate that a function was called on a wrong JSON type. + In the context of binary serialization formats, we need to read several + bytes from a byte vector and combine them to multi-byte integral data + types. - @return basically a string representation of a the @a m_type member + @param[in] vec byte vector to read from + @param[in] current_index the position in the vector after which to read - @complexity Constant. + @return the next sizeof(T) bytes from @a vec, in reverse order as T - @since version 1.0.0 + @tparam T the integral return type + + @throw std::out_of_range if there are less than sizeof(T)+1 bytes in the + vector @a vec to read + + In the for loop, the bytes from the vector are copied in reverse order into + the return value. In the figures below, let sizeof(T)=4 and `i` be the loop + variable. + + Precondition: + + vec: | | | a | b | c | d | T: | | | | | + ^ ^ ^ ^ + current_index i ptr sizeof(T) + + Postcondition: + + vec: | | | a | b | c | d | T: | d | c | b | a | + ^ ^ ^ + | i ptr + current_index + + @sa Code adapted from . */ - std::string type_name() const + template + static T get_from_vector(const std::vector& vec, const size_t current_index) { - switch (m_type) + if (current_index + sizeof(T) + 1 > vec.size()) { - case value_t::null: - return "null"; - case value_t::object: - return "object"; - case value_t::array: - return "array"; - case value_t::string: - return "string"; - case value_t::boolean: - return "boolean"; - case value_t::discarded: - return "discarded"; - default: - return "number"; + throw std::out_of_range("cannot read " + std::to_string(sizeof(T)) + " bytes from vector"); + } + + T result; + uint8_t* ptr = reinterpret_cast(&result); + for (size_t i = 0; i < sizeof(T); ++i) + { + *ptr++ = vec[current_index + sizeof(T) - i]; } + return result; } /*! - @brief calculates the extra space to escape a JSON string + @brief create a MessagePack serialization of a given JSON value - @param[in] s the string to escape - @return the number of characters required to escape string @a s + This is a straightforward implementation of the MessagePack specification. - @complexity Linear in the length of string @a s. + @param[in] j JSON value to serialize + @param[in,out] v byte vector to write the serialization to + + @sa https://github.com/msgpack/msgpack/blob/master/spec.md */ - static std::size_t extra_space(const string_t& s) noexcept + static void to_msgpack_internal(const basic_json& j, std::vector& v) { - return std::accumulate(s.begin(), s.end(), size_t{}, - [](size_t res, typename string_t::value_type c) + switch (j.type()) { - switch (c) + case value_t::null: { - case '"': - case '\\': - case '\b': - case '\f': - case '\n': - case '\r': - case '\t': + // nil + v.push_back(0xc0); + break; + } + + case value_t::boolean: + { + // true and false + v.push_back(j.m_value.boolean ? 0xc3 : 0xc2); + break; + } + + case value_t::number_integer: + { + if (j.m_value.number_integer >= 0) { - // from c (1 byte) to \x (2 bytes) - return res + 1; + // MessagePack does not differentiate between positive + // signed integers and unsigned integers. Therefore, we used + // the code from the value_t::number_unsigned case here. + if (j.m_value.number_unsigned < 128) + { + // positive fixnum + add_to_vector(v, 1, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= UINT8_MAX) + { + // uint 8 + v.push_back(0xcc); + add_to_vector(v, 1, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= UINT16_MAX) + { + // uint 16 + v.push_back(0xcd); + add_to_vector(v, 2, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= UINT32_MAX) + { + // uint 32 + v.push_back(0xce); + add_to_vector(v, 4, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= UINT64_MAX) + { + // uint 64 + v.push_back(0xcf); + add_to_vector(v, 8, j.m_value.number_unsigned); + } } - - default: + else { - if (c >= 0x00 and c <= 0x1f) + if (j.m_value.number_integer >= -32) { - // from c (1 byte) to \uxxxx (6 bytes) - return res + 5; + // negative fixnum + add_to_vector(v, 1, j.m_value.number_integer); } - else + else if (j.m_value.number_integer >= INT8_MIN and j.m_value.number_integer <= INT8_MAX) { - return res; + // int 8 + v.push_back(0xd0); + add_to_vector(v, 1, j.m_value.number_integer); + } + else if (j.m_value.number_integer >= INT16_MIN and j.m_value.number_integer <= INT16_MAX) + { + // int 16 + v.push_back(0xd1); + add_to_vector(v, 2, j.m_value.number_integer); + } + else if (j.m_value.number_integer >= INT32_MIN and j.m_value.number_integer <= INT32_MAX) + { + // int 32 + v.push_back(0xd2); + add_to_vector(v, 4, j.m_value.number_integer); + } + else if (j.m_value.number_integer >= INT64_MIN and j.m_value.number_integer <= INT64_MAX) + { + // int 64 + v.push_back(0xd3); + add_to_vector(v, 8, j.m_value.number_integer); } } + break; } - }); - } - - /*! - @brief escape a string - - Escape a string by replacing certain special characters by a sequence of - an escape character (backslash) and another character and other control - characters by a sequence of "\u" followed by a four-digit hex - representation. - - @param[in] s the string to escape - @return the escaped string - - @complexity Linear in the length of string @a s. - */ - static string_t escape_string(const string_t& s) - { - const auto space = extra_space(s); - if (space == 0) - { - return s; - } - - // create a result string of necessary size - string_t result(s.size() + space, '\\'); - std::size_t pos = 0; - for (const auto& c : s) - { - switch (c) + case value_t::number_unsigned: { - // quotation mark (0x22) - case '"': + if (j.m_value.number_unsigned < 128) { - result[pos + 1] = '"'; - pos += 2; - break; + // positive fixnum + add_to_vector(v, 1, j.m_value.number_unsigned); } - - // reverse solidus (0x5c) - case '\\': + else if (j.m_value.number_unsigned <= UINT8_MAX) { - // nothing to change - pos += 2; - break; + // uint 8 + v.push_back(0xcc); + add_to_vector(v, 1, j.m_value.number_unsigned); } - - // backspace (0x08) - case '\b': + else if (j.m_value.number_unsigned <= UINT16_MAX) { - result[pos + 1] = 'b'; - pos += 2; - break; + // uint 16 + v.push_back(0xcd); + add_to_vector(v, 2, j.m_value.number_unsigned); } - - // formfeed (0x0c) - case '\f': + else if (j.m_value.number_unsigned <= UINT32_MAX) { - result[pos + 1] = 'f'; - pos += 2; - break; + // uint 32 + v.push_back(0xce); + add_to_vector(v, 4, j.m_value.number_unsigned); } - - // newline (0x0a) - case '\n': + else if (j.m_value.number_unsigned <= UINT64_MAX) { - result[pos + 1] = 'n'; - pos += 2; - break; + // uint 64 + v.push_back(0xcf); + add_to_vector(v, 8, j.m_value.number_unsigned); } + break; + } - // carriage return (0x0d) - case '\r': + case value_t::number_float: + { + // float 64 + v.push_back(0xcb); + const uint8_t* helper = reinterpret_cast(&(j.m_value.number_float)); + for (size_t i = 0; i < 8; ++i) { - result[pos + 1] = 'r'; - pos += 2; - break; + v.push_back(helper[7 - i]); } + break; + } - // horizontal tab (0x09) - case '\t': + case value_t::string: + { + const auto N = j.m_value.string->size(); + if (N <= 31) + { + // fixstr + v.push_back(static_cast(0xa0 | N)); + } + else if (N <= 255) + { + // str 8 + v.push_back(0xd9); + add_to_vector(v, 1, N); + } + else if (N <= 65535) + { + // str 16 + v.push_back(0xda); + add_to_vector(v, 2, N); + } + else if (N <= 4294967295) + { + // str 32 + v.push_back(0xdb); + add_to_vector(v, 4, N); + } + + // append string + std::copy(j.m_value.string->begin(), j.m_value.string->end(), + std::back_inserter(v)); + break; + } + + case value_t::array: + { + const auto N = j.m_value.array->size(); + if (N <= 15) + { + // fixarray + v.push_back(static_cast(0x90 | N)); + } + else if (N <= 0xffff) + { + // array 16 + v.push_back(0xdc); + add_to_vector(v, 2, N); + } + else if (N <= 0xffffffff) + { + // array 32 + v.push_back(0xdd); + add_to_vector(v, 4, N); + } + + // append each element + for (const auto& el : *j.m_value.array) + { + to_msgpack_internal(el, v); + } + break; + } + + case value_t::object: + { + const auto N = j.m_value.object->size(); + if (N <= 15) + { + // fixmap + v.push_back(static_cast(0x80 | (N & 0xf))); + } + else if (N <= 65535) + { + // map 16 + v.push_back(0xde); + add_to_vector(v, 2, N); + } + else if (N <= 4294967295) + { + // map 32 + v.push_back(0xdf); + add_to_vector(v, 4, N); + } + + // append each element + for (const auto& el : *j.m_value.object) + { + to_msgpack_internal(el.first, v); + to_msgpack_internal(el.second, v); + } + break; + } + + default: + { + break; + } + } + } + + /*! + @brief create a CBOR serialization of a given JSON value + + This is a straightforward implementation of the CBOR specification. + + @param[in] j JSON value to serialize + @param[in,out] v byte vector to write the serialization to + + @sa https://tools.ietf.org/html/rfc7049 + */ + static void to_cbor_internal(const basic_json& j, std::vector& v) + { + switch (j.type()) + { + case value_t::null: + { + v.push_back(0xf6); + break; + } + + case value_t::boolean: + { + v.push_back(j.m_value.boolean ? 0xf5 : 0xf4); + break; + } + + case value_t::number_integer: + { + if (j.m_value.number_integer >= 0) + { + // CBOR does not differentiate between positive signed + // integers and unsigned integers. Therefore, we used the + // code from the value_t::number_unsigned case here. + if (j.m_value.number_integer <= 0x17) + { + add_to_vector(v, 1, j.m_value.number_integer); + } + else if (j.m_value.number_integer <= UINT8_MAX) + { + v.push_back(0x18); + // one-byte uint8_t + add_to_vector(v, 1, j.m_value.number_integer); + } + else if (j.m_value.number_integer <= UINT16_MAX) + { + v.push_back(0x19); + // two-byte uint16_t + add_to_vector(v, 2, j.m_value.number_integer); + } + else if (j.m_value.number_integer <= UINT32_MAX) + { + v.push_back(0x1a); + // four-byte uint32_t + add_to_vector(v, 4, j.m_value.number_integer); + } + else + { + v.push_back(0x1b); + // eight-byte uint64_t + add_to_vector(v, 8, j.m_value.number_integer); + } + } + else + { + // The conversions below encode the sign in the first byte, + // and the value is converted to a positive number. + const auto positive_number = -1 - j.m_value.number_integer; + if (j.m_value.number_integer >= -24) + { + v.push_back(static_cast(0x20 + positive_number)); + } + else if (positive_number <= UINT8_MAX) + { + // int 8 + v.push_back(0x38); + add_to_vector(v, 1, positive_number); + } + else if (positive_number <= UINT16_MAX) + { + // int 16 + v.push_back(0x39); + add_to_vector(v, 2, positive_number); + } + else if (positive_number <= UINT32_MAX) + { + // int 32 + v.push_back(0x3a); + add_to_vector(v, 4, positive_number); + } + else + { + // int 64 + v.push_back(0x3b); + add_to_vector(v, 8, positive_number); + } + } + break; + } + + case value_t::number_unsigned: + { + if (j.m_value.number_unsigned <= 0x17) + { + v.push_back(static_cast(j.m_value.number_unsigned)); + } + else if (j.m_value.number_unsigned <= 0xff) + { + v.push_back(0x18); + // one-byte uint8_t + add_to_vector(v, 1, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= 0xffff) + { + v.push_back(0x19); + // two-byte uint16_t + add_to_vector(v, 2, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= 0xffffffff) + { + v.push_back(0x1a); + // four-byte uint32_t + add_to_vector(v, 4, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= 0xffffffffffffffff) + { + v.push_back(0x1b); + // eight-byte uint64_t + add_to_vector(v, 8, j.m_value.number_unsigned); + } + break; + } + + case value_t::number_float: + { + // Double-Precision Float + v.push_back(0xfb); + const uint8_t* helper = reinterpret_cast(&(j.m_value.number_float)); + for (size_t i = 0; i < 8; ++i) + { + v.push_back(helper[7 - i]); + } + break; + } + + case value_t::string: + { + const auto N = j.m_value.string->size(); + if (N <= 0x17) + { + v.push_back(0x60 + N); // 1 byte for string + size + } + else if (N <= 0xff) + { + v.push_back(0x78); // one-byte uint8_t for N + add_to_vector(v, 1, N); + } + else if (N <= 0xffff) + { + v.push_back(0x79); // two-byte uint16_t for N + add_to_vector(v, 2, N); + } + else if (N <= 0xffffffff) + { + v.push_back(0x7a); // four-byte uint32_t for N + add_to_vector(v, 4, N); + } + // LCOV_EXCL_START + else if (N <= 0xffffffffffffffff) + { + v.push_back(0x7b); // eight-byte uint64_t for N + add_to_vector(v, 8, N); + } + // LCOV_EXCL_STOP + + // append string + std::copy(j.m_value.string->begin(), j.m_value.string->end(), + std::back_inserter(v)); + break; + } + + case value_t::array: + { + const auto N = j.m_value.array->size(); + if (N <= 0x17) + { + v.push_back(0x80 + N); // 1 byte for array + size + } + else if (N <= 0xff) + { + v.push_back(0x98); // one-byte uint8_t for N + add_to_vector(v, 1, N); + } + else if (N <= 0xffff) + { + v.push_back(0x99); // two-byte uint16_t for N + add_to_vector(v, 2, N); + } + else if (N <= 0xffffffff) + { + v.push_back(0x9a); // four-byte uint32_t for N + add_to_vector(v, 4, N); + } + // LCOV_EXCL_START + else if (N <= 0xffffffffffffffff) + { + v.push_back(0x9b); // eight-byte uint64_t for N + add_to_vector(v, 8, N); + } + // LCOV_EXCL_STOP + + // append each element + for (const auto& el : *j.m_value.array) + { + to_cbor_internal(el, v); + } + break; + } + + case value_t::object: + { + const auto N = j.m_value.object->size(); + if (N <= 0x17) + { + v.push_back(0xa0 + N); // 1 byte for object + size + } + else if (N <= 0xff) + { + v.push_back(0xb8); + add_to_vector(v, 1, N); // one-byte uint8_t for N + } + else if (N <= 0xffff) + { + v.push_back(0xb9); + add_to_vector(v, 2, N); // two-byte uint16_t for N + } + else if (N <= 0xffffffff) + { + v.push_back(0xba); + add_to_vector(v, 4, N); // four-byte uint32_t for N + } + // LCOV_EXCL_START + else if (N <= 0xffffffffffffffff) + { + v.push_back(0xbb); + add_to_vector(v, 8, N); // eight-byte uint64_t for N + } + // LCOV_EXCL_STOP + + // append each element + for (const auto& el : *j.m_value.object) + { + to_cbor_internal(el.first, v); + to_cbor_internal(el.second, v); + } + break; + } + + default: + { + break; + } + } + } + + /*! + @brief create a JSON value from a given MessagePack vector + + @param[in] v MessagePack serialization + @param[in] idx byte index to start reading from @a v + + @return deserialized JSON value + + @throw std::invalid_argument if unsupported features from MessagePack were + used in the given vector @a v or if the input is not valid MessagePack + @throw std::out_of_range if the given vector ends prematurely + + @sa https://github.com/msgpack/msgpack/blob/master/spec.md + */ + static basic_json from_msgpack_internal(const std::vector& v, size_t& idx) + { + // store and increment index + const size_t current_idx = idx++; + + if (v[current_idx] <= 0xbf) + { + if (v[current_idx] <= 0x7f) // positive fixint + { + return v[current_idx]; + } + else if (v[current_idx] <= 0x8f) // fixmap + { + basic_json result = value_t::object; + const size_t len = v[current_idx] & 0x0f; + for (size_t i = 0; i < len; ++i) + { + std::string key = from_msgpack_internal(v, idx); + result[key] = from_msgpack_internal(v, idx); + } + return result; + } + else if (v[current_idx] <= 0x9f) // fixarray + { + basic_json result = value_t::array; + const size_t len = v[current_idx] & 0x0f; + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_msgpack_internal(v, idx)); + } + return result; + } + else // fixstr + { + const size_t len = v[current_idx] & 0x1f; + const size_t offset = current_idx + 1; + idx += len; // skip content bytes + return std::string(reinterpret_cast(v.data()) + offset, len); + } + } + else if (v[current_idx] >= 0xe0) // negative fixint + { + return static_cast(v[current_idx]); + } + else + { + switch (v[current_idx]) + { + case 0xc0: // nil + { + return value_t::null; + } + + case 0xc2: // false + { + return false; + } + + case 0xc3: // true + { + return true; + } + + case 0xca: // float 32 + { + // copy bytes in reverse order into the double variable + float res; + for (size_t byte = 0; byte < sizeof(float); ++byte) + { + reinterpret_cast(&res)[sizeof(float) - byte - 1] = v[current_idx + 1 + byte]; + } + idx += sizeof(float); // skip content bytes + return res; + } + + case 0xcb: // float 64 + { + // copy bytes in reverse order into the double variable + double res; + for (size_t byte = 0; byte < sizeof(double); ++byte) + { + reinterpret_cast(&res)[sizeof(double) - byte - 1] = v[current_idx + 1 + byte]; + } + idx += sizeof(double); // skip content bytes + return res; + } + + case 0xcc: // uint 8 + { + idx += 1; // skip content byte + return get_from_vector(v, current_idx); + } + + case 0xcd: // uint 16 + { + idx += 2; // skip 2 content bytes + return get_from_vector(v, current_idx); + } + + case 0xce: // uint 32 + { + idx += 4; // skip 4 content bytes + return get_from_vector(v, current_idx); + } + + case 0xcf: // uint 64 + { + idx += 8; // skip 8 content bytes + return get_from_vector(v, current_idx); + } + + case 0xd0: // int 8 + { + idx += 1; // skip content byte + return get_from_vector(v, current_idx); + } + + case 0xd1: // int 16 + { + idx += 2; // skip 2 content bytes + return get_from_vector(v, current_idx); + } + + case 0xd2: // int 32 + { + idx += 4; // skip 4 content bytes + return get_from_vector(v, current_idx); + } + + case 0xd3: // int 64 + { + idx += 8; // skip 8 content bytes + return get_from_vector(v, current_idx); + } + + case 0xd9: // str 8 + { + const auto len = static_cast(get_from_vector(v, current_idx)); + const size_t offset = current_idx + 2; + idx += len + 1; // skip size byte + content bytes + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0xda: // str 16 + { + const auto len = static_cast(get_from_vector(v, current_idx)); + const size_t offset = current_idx + 3; + idx += len + 2; // skip 2 size bytes + content bytes + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0xdb: // str 32 + { + const auto len = static_cast(get_from_vector(v, current_idx)); + const size_t offset = current_idx + 5; + idx += len + 4; // skip 4 size bytes + content bytes + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0xdc: // array 16 + { + basic_json result = value_t::array; + const auto len = static_cast(get_from_vector(v, current_idx)); + idx += 2; // skip 2 size bytes + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_msgpack_internal(v, idx)); + } + return result; + } + + case 0xdd: // array 32 + { + basic_json result = value_t::array; + const auto len = static_cast(get_from_vector(v, current_idx)); + idx += 4; // skip 4 size bytes + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_msgpack_internal(v, idx)); + } + return result; + } + + case 0xde: // map 16 + { + basic_json result = value_t::object; + const auto len = static_cast(get_from_vector(v, current_idx)); + idx += 2; // skip 2 size bytes + for (size_t i = 0; i < len; ++i) + { + std::string key = from_msgpack_internal(v, idx); + result[key] = from_msgpack_internal(v, idx); + } + return result; + } + + case 0xdf: // map 32 + { + basic_json result = value_t::object; + const auto len = static_cast(get_from_vector(v, current_idx)); + idx += 4; // skip 4 size bytes + for (size_t i = 0; i < len; ++i) + { + std::string key = from_msgpack_internal(v, idx); + result[key] = from_msgpack_internal(v, idx); + } + return result; + } + + default: + { + throw std::invalid_argument("error parsing a msgpack @ " + std::to_string(current_idx) + ": " + std::to_string(static_cast(v[current_idx]))); + } + } + } + } + + /*! + @brief create a JSON value from a given CBOR vector + + @param[in] v CBOR serialization + @param[in] idx byte index to start reading from @a v + + @return deserialized JSON value + + @throw std::invalid_argument if unsupported features from CBOR were used in + the given vector @a v or if the input is not valid CBOR + @throw std::out_of_range if the given vector ends prematurely + + @sa https://tools.ietf.org/html/rfc7049 + */ + static basic_json from_cbor_internal(const std::vector& v, size_t& idx) + { + // store and increment index + const size_t current_idx = idx++; + + switch (v[current_idx]) + { + // Integer 0x00..0x17 (0..23) + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0a: + case 0x0b: + case 0x0c: + case 0x0d: + case 0x0e: + case 0x0f: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + { + return v[current_idx]; + } + + case 0x18: // Unsigned integer (one-byte uint8_t follows) + { + idx += 1; // skip content byte + return get_from_vector(v, current_idx); + } + + case 0x19: // Unsigned integer (two-byte uint16_t follows) + { + idx += 2; // skip 2 content bytes + return get_from_vector(v, current_idx); + } + + case 0x1a: // Unsigned integer (four-byte uint32_t follows) + { + idx += 4; // skip 4 content bytes + return get_from_vector(v, current_idx); + } + + case 0x1b: // Unsigned integer (eight-byte uint64_t follows) + { + idx += 8; // skip 8 content bytes + return get_from_vector(v, current_idx); + } + + // Negative integer -1-0x00..-1-0x17 (-1..-24) + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2a: + case 0x2b: + case 0x2c: + case 0x2d: + case 0x2e: + case 0x2f: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + { + return static_cast(0x20 - 1 - v[current_idx]); + } + + case 0x38: // Negative integer (one-byte uint8_t follows) + { + idx += 1; // skip content byte + // must be uint8_t ! + return static_cast(-1) - get_from_vector(v, current_idx); + } + + case 0x39: // Negative integer -1-n (two-byte uint16_t follows) + { + idx += 2; // skip 2 content bytes + return static_cast(-1) - get_from_vector(v, current_idx); + } + + case 0x3a: // Negative integer -1-n (four-byte uint32_t follows) + { + idx += 4; // skip 4 content bytes + return static_cast(-1) - get_from_vector(v, current_idx); + } + + case 0x3b: // Negative integer -1-n (eight-byte uint64_t follows) + { + idx += 8; // skip 8 content bytes + return static_cast(-1) - static_cast(get_from_vector(v, current_idx)); + } + + // UTF-8 string (0x00..0x17 bytes follow) + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6a: + case 0x6b: + case 0x6c: + case 0x6d: + case 0x6e: + case 0x6f: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + { + const auto len = static_cast(v[current_idx] - 0x60); + const size_t offset = current_idx + 1; + idx += len; // skip content bytes + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0x78: // UTF-8 string (one-byte uint8_t for n follows) + { + const auto len = static_cast(get_from_vector(v, current_idx)); + const size_t offset = current_idx + 2; + idx += len + 1; // skip size byte + content bytes + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0x79: // UTF-8 string (two-byte uint16_t for n follow) + { + const auto len = static_cast(get_from_vector(v, current_idx)); + const size_t offset = current_idx + 3; + idx += len + 2; // skip 2 size bytes + content bytes + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0x7a: // UTF-8 string (four-byte uint32_t for n follow) + { + const auto len = static_cast(get_from_vector(v, current_idx)); + const size_t offset = current_idx + 5; + idx += len + 4; // skip 4 size bytes + content bytes + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0x7b: // UTF-8 string (eight-byte uint64_t for n follow) + { + const auto len = static_cast(get_from_vector(v, current_idx)); + const size_t offset = current_idx + 9; + idx += len + 8; // skip 8 size bytes + content bytes + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0x7f: // UTF-8 string (indefinite length) + { + std::string result; + while (v[idx] != 0xff) + { + string_t s = from_cbor_internal(v, idx); + result += s; + } + // skip break byte (0xFF) + idx += 1; + return result; + } + + // array (0x00..0x17 data items follow) + case 0x80: + case 0x81: + case 0x82: + case 0x83: + case 0x84: + case 0x85: + case 0x86: + case 0x87: + case 0x88: + case 0x89: + case 0x8a: + case 0x8b: + case 0x8c: + case 0x8d: + case 0x8e: + case 0x8f: + case 0x90: + case 0x91: + case 0x92: + case 0x93: + case 0x94: + case 0x95: + case 0x96: + case 0x97: + { + basic_json result = value_t::array; + const auto len = static_cast(v[current_idx] - 0x80); + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_cbor_internal(v, idx)); + } + return result; + } + + case 0x98: // array (one-byte uint8_t for n follows) + { + basic_json result = value_t::array; + const auto len = static_cast(get_from_vector(v, current_idx)); + idx += 1; // skip 1 size byte + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_cbor_internal(v, idx)); + } + return result; + } + + case 0x99: // array (two-byte uint16_t for n follow) + { + basic_json result = value_t::array; + const auto len = static_cast(get_from_vector(v, current_idx)); + idx += 2; // skip 4 size bytes + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_cbor_internal(v, idx)); + } + return result; + } + + case 0x9a: // array (four-byte uint32_t for n follow) + { + basic_json result = value_t::array; + const auto len = static_cast(get_from_vector(v, current_idx)); + idx += 4; // skip 4 size bytes + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_cbor_internal(v, idx)); + } + return result; + } + + case 0x9b: // array (eight-byte uint64_t for n follow) + { + basic_json result = value_t::array; + const auto len = static_cast(get_from_vector(v, current_idx)); + idx += 8; // skip 8 size bytes + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_cbor_internal(v, idx)); + } + return result; + } + + case 0x9f: // array (indefinite length) + { + basic_json result = value_t::array; + while (v[idx] != 0xff) + { + result.push_back(from_cbor_internal(v, idx)); + } + // skip break byte (0xFF) + idx += 1; + return result; + } + + // map (0x00..0x17 pairs of data items follow) + case 0xa0: + case 0xa1: + case 0xa2: + case 0xa3: + case 0xa4: + case 0xa5: + case 0xa6: + case 0xa7: + case 0xa8: + case 0xa9: + case 0xaa: + case 0xab: + case 0xac: + case 0xad: + case 0xae: + case 0xaf: + case 0xb0: + case 0xb1: + case 0xb2: + case 0xb3: + case 0xb4: + case 0xb5: + case 0xb6: + case 0xb7: + { + basic_json result = value_t::object; + const auto len = static_cast(v[current_idx] - 0xa0); + for (size_t i = 0; i < len; ++i) + { + std::string key = from_cbor_internal(v, idx); + result[key] = from_cbor_internal(v, idx); + } + return result; + } + + case 0xb8: // map (one-byte uint8_t for n follows) + { + basic_json result = value_t::object; + const auto len = static_cast(get_from_vector(v, current_idx)); + idx += 1; // skip 1 size byte + for (size_t i = 0; i < len; ++i) + { + std::string key = from_cbor_internal(v, idx); + result[key] = from_cbor_internal(v, idx); + } + return result; + } + + case 0xb9: // map (two-byte uint16_t for n follow) + { + basic_json result = value_t::object; + const auto len = static_cast(get_from_vector(v, current_idx)); + idx += 2; // skip 2 size bytes + for (size_t i = 0; i < len; ++i) + { + std::string key = from_cbor_internal(v, idx); + result[key] = from_cbor_internal(v, idx); + } + return result; + } + + case 0xba: // map (four-byte uint32_t for n follow) + { + basic_json result = value_t::object; + const auto len = static_cast(get_from_vector(v, current_idx)); + idx += 4; // skip 4 size bytes + for (size_t i = 0; i < len; ++i) + { + std::string key = from_cbor_internal(v, idx); + result[key] = from_cbor_internal(v, idx); + } + return result; + } + + case 0xbb: // map (eight-byte uint64_t for n follow) + { + basic_json result = value_t::object; + const auto len = static_cast(get_from_vector(v, current_idx)); + idx += 8; // skip 8 size bytes + for (size_t i = 0; i < len; ++i) + { + std::string key = from_cbor_internal(v, idx); + result[key] = from_cbor_internal(v, idx); + } + return result; + } + + case 0xbf: // map (indefinite length) + { + basic_json result = value_t::object; + while (v[idx] != 0xff) + { + std::string key = from_cbor_internal(v, idx); + result[key] = from_cbor_internal(v, idx); + } + // skip break byte (0xFF) + idx += 1; + return result; + } + + case 0xf4: // false + { + return false; + } + + case 0xf5: // true + { + return true; + } + + case 0xf6: // null + { + return value_t::null; + } + + case 0xf9: // Half-Precision Float (two-byte IEEE 754) + { + idx += 2; // skip two content bytes + + // code from RFC 7049, Appendix D, Figure 3: + // As half-precision floating-point numbers were only added to + // IEEE 754 in 2008, today's programming platforms often still + // only have limited support for them. It is very easy to + // include at least decoding support for them even without such + // support. An example of a small decoder for half-precision + // floating-point numbers in the C language is shown in Fig. 3. + const int half = (v[current_idx + 1] << 8) + v[current_idx + 2]; + const int exp = (half >> 10) & 0x1f; + const int mant = half & 0x3ff; + double val; + if (exp == 0) + { + val = std::ldexp(mant, -24); + } + else if (exp != 31) + { + val = std::ldexp(mant + 1024, exp - 25); + } + else + { + val = mant == 0 ? INFINITY : NAN; + } + return half & 0x8000 ? -val : val; + } + + case 0xfa: // Single-Precision Float (four-byte IEEE 754) + { + // copy bytes in reverse order into the float variable + float res; + for (size_t byte = 0; byte < sizeof(float); ++byte) + { + reinterpret_cast(&res)[sizeof(float) - byte - 1] = v[current_idx + 1 + byte]; + } + idx += sizeof(float); // skip content bytes + return res; + } + + case 0xfb: // Double-Precision Float (eight-byte IEEE 754) + { + // copy bytes in reverse order into the double variable + double res; + for (size_t byte = 0; byte < sizeof(double); ++byte) + { + reinterpret_cast(&res)[sizeof(double) - byte - 1] = v[current_idx + 1 + byte]; + } + idx += sizeof(double); // skip content bytes + return res; + } + + default: // anything else (0xFF is handled inside the other types) + { + throw std::invalid_argument("error parsing a CBOR @ " + std::to_string(current_idx) + ": " + std::to_string(static_cast(v[current_idx]))); + } + } + } + + public: + /*! + @brief create a MessagePack serialization of a given JSON value + + Serializes a given JSON value @a j to a byte vector using the MessagePack + serialization format. MessagePack is a binary serialization format which + aims to be more compact than JSON itself, yet more efficient to parse. + + @param[in] j JSON value to serialize + @return MessagePack serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in MessagePack format.,to_msgpack} + + @sa http://msgpack.org + @sa @ref from_msgpack(const std::vector&) for the analogous + deserialization + @sa @ref to_cbor(const basic_json& for the related CBOR format + */ + static std::vector to_msgpack(const basic_json& j) + { + std::vector result; + to_msgpack_internal(j, result); + return result; + } + + /*! + @brief create a JSON value from a byte vector in MessagePack format + + Deserializes a given byte vector @a v to a JSON value using the MessagePack + serialization format. + + @param[in] v a byte vector in MessagePack format + @return deserialized JSON value + + @throw std::invalid_argument if unsupported features from MessagePack were + used in the given vector @a v or if the input is not valid MessagePack + @throw std::out_of_range if the given vector ends prematurely + + @complexity Linear in the size of the byte vector @a v. + + @liveexample{The example shows the deserialization of a byte vector in + MessagePack format to a JSON value.,from_msgpack} + + @sa http://msgpack.org + @sa @ref to_msgpack(const basic_json&) for the analogous serialization + @sa @ref from_cbor(const std::vector&) for the related CBOR format + */ + static basic_json from_msgpack(const std::vector& v) + { + size_t i = 0; + return from_msgpack_internal(v, i); + } + + /*! + @brief create a MessagePack serialization of a given JSON value + + Serializes a given JSON value @a j to a byte vector using the CBOR (Concise + Binary Object Representation) serialization format. CBOR is a binary + serialization format which aims to be more compact than JSON itself, yet + more efficient to parse. + + @param[in] j JSON value to serialize + @return MessagePack serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in CBOR format.,to_cbor} + + @sa http://cbor.io + @sa @ref from_cbor(const std::vector&) for the analogous + deserialization + @sa @ref to_msgpack(const basic_json& for the related MessagePack format + */ + static std::vector to_cbor(const basic_json& j) + { + std::vector result; + to_cbor_internal(j, result); + return result; + } + + /*! + @brief create a JSON value from a byte vector in CBOR format + + Deserializes a given byte vector @a v to a JSON value using the CBOR + (Concise Binary Object Representation) serialization format. + + @param[in] v a byte vector in CBOR format + @return deserialized JSON value + + @throw std::invalid_argument if unsupported features from CBOR were used in + the given vector @a v or if the input is not valid MessagePack + @throw std::out_of_range if the given vector ends prematurely + + @complexity Linear in the size of the byte vector @a v. + + @liveexample{The example shows the deserialization of a byte vector in CBOR + format to a JSON value.,from_cbor} + + @sa http://cbor.io + @sa @ref to_cbor(const basic_json&) for the analogous serialization + @sa @ref from_msgpack(const std::vector&) for the related + MessagePack format + */ + static basic_json from_cbor(const std::vector& v) + { + size_t i = 0; + return from_cbor_internal(v, i); + } + + /// @} + + private: + /////////////////////////// + // convenience functions // + /////////////////////////// + + /*! + @brief return the type as string + + Returns the type name as string to be used in error messages - usually to + indicate that a function was called on a wrong JSON type. + + @return basically a string representation of a the @a m_type member + + @complexity Constant. + + @since version 1.0.0 + */ + std::string type_name() const + { + switch (m_type) + { + case value_t::null: + return "null"; + case value_t::object: + return "object"; + case value_t::array: + return "array"; + case value_t::string: + return "string"; + case value_t::boolean: + return "boolean"; + case value_t::discarded: + return "discarded"; + default: + return "number"; + } + } + + /*! + @brief calculates the extra space to escape a JSON string + + @param[in] s the string to escape + @return the number of characters required to escape string @a s + + @complexity Linear in the length of string @a s. + */ + static std::size_t extra_space(const string_t& s) noexcept + { + return std::accumulate(s.begin(), s.end(), size_t{}, + [](size_t res, typename string_t::value_type c) + { + switch (c) + { + case '"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + { + // from c (1 byte) to \x (2 bytes) + return res + 1; + } + + default: + { + if (c >= 0x00 and c <= 0x1f) + { + // from c (1 byte) to \uxxxx (6 bytes) + return res + 5; + } + else + { + return res; + } + } + } + }); + } + + /*! + @brief escape a string + + Escape a string by replacing certain special characters by a sequence of + an escape character (backslash) and another character and other control + characters by a sequence of "\u" followed by a four-digit hex + representation. + + @param[in] s the string to escape + @return the escaped string + + @complexity Linear in the length of string @a s. + */ + static string_t escape_string(const string_t& s) + { + const auto space = extra_space(s); + if (space == 0) + { + return s; + } + + // create a result string of necessary size + string_t result(s.size() + space, '\\'); + std::size_t pos = 0; + + for (const auto& c : s) + { + switch (c) + { + // quotation mark (0x22) + case '"': + { + result[pos + 1] = '"'; + pos += 2; + break; + } + + // reverse solidus (0x5c) + case '\\': + { + // nothing to change + pos += 2; + break; + } + + // backspace (0x08) + case '\b': + { + result[pos + 1] = 'b'; + pos += 2; + break; + } + + // formfeed (0x0c) + case '\f': + { + result[pos + 1] = 'f'; + pos += 2; + break; + } + + // newline (0x0a) + case '\n': + { + result[pos + 1] = 'n'; + pos += 2; + break; + } + + // carriage return (0x0d) + case '\r': + { + result[pos + 1] = 'r'; + pos += 2; + break; + } + + // horizontal tab (0x09) + case '\t': { result[pos + 1] = 't'; pos += 2; @@ -6694,10 +8205,10 @@ class basic_json public: /*! - @brief a const random access iterator for the @ref basic_json class + @brief a template for a random access iterator for the @ref basic_json class - This class implements a const iterator for the @ref basic_json class. From - this class, the @ref iterator class is derived. + This class implements a both iterators (iterator and const_iterator) for the + @ref basic_json class. @note An iterator is called *initialized* when a pointer to a JSON value has been set (e.g., by a constructor or a copy assignment). If the @@ -6710,27 +8221,37 @@ class basic_json The iterator that can be moved to point (forward and backward) to any element in constant time. - @since version 1.0.0 + @since version 1.0.0, simplified in version 2.0.9 */ - class const_iterator : public std::iterator + template + class iter_impl : public std::iterator { /// allow basic_json to access private members friend class basic_json; + // make sure U is basic_json or const basic_json + static_assert(std::is_same::value + or std::is_same::value, + "iter_impl only accepts (const) basic_json"); + public: /// the type of the values when the iterator is dereferenced using value_type = typename basic_json::value_type; /// a type to represent differences between iterators using difference_type = typename basic_json::difference_type; /// defines a pointer to the type iterated over (value_type) - using pointer = typename basic_json::const_pointer; + using pointer = typename std::conditional::value, + typename basic_json::const_pointer, + typename basic_json::pointer>::type; /// defines a reference to the type iterated over (value_type) - using reference = typename basic_json::const_reference; + using reference = typename std::conditional::value, + typename basic_json::const_reference, + typename basic_json::reference>::type; /// the category of the iterator using iterator_category = std::bidirectional_iterator_tag; /// default constructor - const_iterator() = default; + iter_impl() = default; /*! @brief constructor for a given JSON instance @@ -6738,7 +8259,7 @@ class basic_json @pre object != nullptr @post The iterator is initialized; i.e. `m_object != nullptr`. */ - explicit const_iterator(pointer object) noexcept + explicit iter_impl(pointer object) noexcept : m_object(object) { assert(m_object != nullptr); @@ -6765,37 +8286,25 @@ class basic_json } } - /*! - @brief copy constructor given a non-const iterator - @param[in] other iterator to copy from - @note It is not checked whether @a other is initialized. + /* + Use operator `const_iterator` instead of `const_iterator(const iterator& + other) noexcept` to avoid two class definitions for @ref iterator and + @ref const_iterator. + + This function is only called if this class is an @ref iterator. If this + class is a @ref const_iterator this function is not called. */ - explicit const_iterator(const iterator& other) noexcept - : m_object(other.m_object) + operator const_iterator() const { - if (m_object != nullptr) - { - switch (m_object->m_type) - { - case basic_json::value_t::object: - { - m_it.object_iterator = other.m_it.object_iterator; - break; - } - - case basic_json::value_t::array: - { - m_it.array_iterator = other.m_it.array_iterator; - break; - } + const_iterator ret; - default: - { - m_it.primitive_iterator = other.m_it.primitive_iterator; - break; - } - } + if (m_object) + { + ret.m_object = m_object; + ret.m_it = m_it; } + + return ret; } /*! @@ -6803,7 +8312,7 @@ class basic_json @param[in] other iterator to copy from @note It is not checked whether @a other is initialized. */ - const_iterator(const const_iterator& other) noexcept + iter_impl(const iter_impl& other) noexcept : m_object(other.m_object), m_it(other.m_it) {} @@ -6812,7 +8321,7 @@ class basic_json @param[in,out] other iterator to copy from @note It is not checked whether @a other is initialized. */ - const_iterator& operator=(const_iterator other) noexcept( + iter_impl& operator=(iter_impl other) noexcept( std::is_nothrow_move_constructible::value and std::is_nothrow_move_assignable::value and std::is_nothrow_move_constructible::value and @@ -6974,7 +8483,7 @@ class basic_json @brief post-increment (it++) @pre The iterator is initialized; i.e. `m_object != nullptr`. */ - const_iterator operator++(int) + iter_impl operator++(int) { auto result = *this; ++(*this); @@ -6985,7 +8494,7 @@ class basic_json @brief pre-increment (++it) @pre The iterator is initialized; i.e. `m_object != nullptr`. */ - const_iterator& operator++() + iter_impl& operator++() { assert(m_object != nullptr); @@ -7017,7 +8526,7 @@ class basic_json @brief post-decrement (it--) @pre The iterator is initialized; i.e. `m_object != nullptr`. */ - const_iterator operator--(int) + iter_impl operator--(int) { auto result = *this; --(*this); @@ -7028,7 +8537,7 @@ class basic_json @brief pre-decrement (--it) @pre The iterator is initialized; i.e. `m_object != nullptr`. */ - const_iterator& operator--() + iter_impl& operator--() { assert(m_object != nullptr); @@ -7060,7 +8569,7 @@ class basic_json @brief comparison: equal @pre The iterator is initialized; i.e. `m_object != nullptr`. */ - bool operator==(const const_iterator& other) const + bool operator==(const iter_impl& other) const { // if objects are not the same, the comparison is undefined if (m_object != other.m_object) @@ -7093,7 +8602,7 @@ class basic_json @brief comparison: not equal @pre The iterator is initialized; i.e. `m_object != nullptr`. */ - bool operator!=(const const_iterator& other) const + bool operator!=(const iter_impl& other) const { return not operator==(other); } @@ -7102,7 +8611,7 @@ class basic_json @brief comparison: smaller @pre The iterator is initialized; i.e. `m_object != nullptr`. */ - bool operator<(const const_iterator& other) const + bool operator<(const iter_impl& other) const { // if objects are not the same, the comparison is undefined if (m_object != other.m_object) @@ -7135,7 +8644,7 @@ class basic_json @brief comparison: less than or equal @pre The iterator is initialized; i.e. `m_object != nullptr`. */ - bool operator<=(const const_iterator& other) const + bool operator<=(const iter_impl& other) const { return not other.operator < (*this); } @@ -7144,7 +8653,7 @@ class basic_json @brief comparison: greater than @pre The iterator is initialized; i.e. `m_object != nullptr`. */ - bool operator>(const const_iterator& other) const + bool operator>(const iter_impl& other) const { return not operator<=(other); } @@ -7153,7 +8662,7 @@ class basic_json @brief comparison: greater than or equal @pre The iterator is initialized; i.e. `m_object != nullptr`. */ - bool operator>=(const const_iterator& other) const + bool operator>=(const iter_impl& other) const { return not operator<(other); } @@ -7162,7 +8671,7 @@ class basic_json @brief add to iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ - const_iterator& operator+=(difference_type i) + iter_impl& operator+=(difference_type i) { assert(m_object != nullptr); @@ -7193,7 +8702,7 @@ class basic_json @brief subtract from iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ - const_iterator& operator-=(difference_type i) + iter_impl& operator-=(difference_type i) { return operator+=(-i); } @@ -7202,7 +8711,7 @@ class basic_json @brief add to iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ - const_iterator operator+(difference_type i) + iter_impl operator+(difference_type i) { auto result = *this; result += i; @@ -7213,7 +8722,7 @@ class basic_json @brief subtract from iterator @pre The iterator is initialized; i.e. `m_object != nullptr`. */ - const_iterator operator-(difference_type i) + iter_impl operator-(difference_type i) { auto result = *this; result -= i; @@ -7224,7 +8733,7 @@ class basic_json @brief return difference @pre The iterator is initialized; i.e. `m_object != nullptr`. */ - difference_type operator-(const const_iterator& other) const + difference_type operator-(const iter_impl& other) const { assert(m_object != nullptr); @@ -7320,141 +8829,6 @@ class basic_json internal_iterator m_it = internal_iterator(); }; - /*! - @brief a mutable random access iterator for the @ref basic_json class - - @requirement The class satisfies the following concept requirements: - - [RandomAccessIterator](http://en.cppreference.com/w/cpp/concept/RandomAccessIterator): - The iterator that can be moved to point (forward and backward) to any - element in constant time. - - [OutputIterator](http://en.cppreference.com/w/cpp/concept/OutputIterator): - It is possible to write to the pointed-to element. - - @since version 1.0.0 - */ - class iterator : public const_iterator - { - public: - using base_iterator = const_iterator; - using pointer = typename basic_json::pointer; - using reference = typename basic_json::reference; - - /// default constructor - iterator() = default; - - /// constructor for a given JSON instance - explicit iterator(pointer object) noexcept - : base_iterator(object) - {} - - /// copy constructor - iterator(const iterator& other) noexcept - : base_iterator(other) - {} - - /// copy assignment - iterator& operator=(iterator other) noexcept( - std::is_nothrow_move_constructible::value and - std::is_nothrow_move_assignable::value and - std::is_nothrow_move_constructible::value and - std::is_nothrow_move_assignable::value - ) - { - base_iterator::operator=(other); - return *this; - } - - /// return a reference to the value pointed to by the iterator - reference operator*() const - { - return const_cast(base_iterator::operator*()); - } - - /// dereference the iterator - pointer operator->() const - { - return const_cast(base_iterator::operator->()); - } - - /// post-increment (it++) - iterator operator++(int) - { - iterator result = *this; - base_iterator::operator++(); - return result; - } - - /// pre-increment (++it) - iterator& operator++() - { - base_iterator::operator++(); - return *this; - } - - /// post-decrement (it--) - iterator operator--(int) - { - iterator result = *this; - base_iterator::operator--(); - return result; - } - - /// pre-decrement (--it) - iterator& operator--() - { - base_iterator::operator--(); - return *this; - } - - /// add to iterator - iterator& operator+=(difference_type i) - { - base_iterator::operator+=(i); - return *this; - } - - /// subtract from iterator - iterator& operator-=(difference_type i) - { - base_iterator::operator-=(i); - return *this; - } - - /// add to iterator - iterator operator+(difference_type i) - { - auto result = *this; - result += i; - return result; - } - - /// subtract from iterator - iterator operator-(difference_type i) - { - auto result = *this; - result -= i; - return result; - } - - /// return difference - difference_type operator-(const iterator& other) const - { - return base_iterator::operator-(other); - } - - /// access to successor - reference operator[](difference_type n) const - { - return const_cast(base_iterator::operator[](n)); - } - - /// return the value of an iterator - reference value() const - { - return const_cast(base_iterator::value()); - } - }; - /*! @brief a template for a reverse iterator class @@ -7618,6 +8992,12 @@ class basic_json explicit lexer(std::istream& s) : m_stream(&s), m_line_buffer() { + // immediately abort if stream is erroneous + if (s.fail()) + { + throw std::invalid_argument("stream error: " + std::string(strerror(errno))); + } + // fill buffer fill_line_buffer(); @@ -8740,8 +10120,22 @@ basic_json_parser_66: */ void fill_line_buffer(size_t n = 0) { + // if line buffer is used, m_content points to its data + assert(m_line_buffer.empty() + or m_content == reinterpret_cast(m_line_buffer.data())); + + // if line buffer is used, m_limit is set past the end of its data + assert(m_line_buffer.empty() + or m_limit == m_content + m_line_buffer.size()); + + // pointer relationships + assert(m_content <= m_start); + assert(m_start <= m_cursor); + assert(m_cursor <= m_limit); + assert(m_marker == nullptr or m_marker <= m_limit); + // number of processed characters (p) - const auto offset_start = m_start - m_content; + const size_t num_processed_chars = static_cast(m_start - m_content); // offset for m_marker wrt. to m_start const auto offset_marker = (m_marker == nullptr) ? 0 : m_marker - m_start; // number of unprocessed characters (u) @@ -8750,35 +10144,34 @@ basic_json_parser_66: // no stream is used or end of file is reached if (m_stream == nullptr or m_stream->eof()) { - // skip this part if we are already using the line buffer - if (m_start != reinterpret_cast(m_line_buffer.data())) - { - // copy unprocessed characters to line buffer - m_line_buffer.clear(); - for (m_cursor = m_start; m_cursor != m_limit; ++m_cursor) - { - m_line_buffer.append(1, static_cast(*m_cursor)); - } - } + // m_start may or may not be pointing into m_line_buffer at + // this point. We trust the standand library to do the right + // thing. See http://stackoverflow.com/q/28142011/266378 + m_line_buffer.assign(m_start, m_limit); // append n characters to make sure that there is sufficient // space between m_cursor and m_limit m_line_buffer.append(1, '\x00'); - m_line_buffer.append(n - 1, '\x01'); + if (n > 0) + { + m_line_buffer.append(n - 1, '\x01'); + } } else { // delete processed characters from line buffer - m_line_buffer.erase(0, static_cast(offset_start)); + m_line_buffer.erase(0, num_processed_chars); // read next line from input stream - std::string line; - std::getline(*m_stream, line, '\n'); + m_line_buffer_tmp.clear(); + std::getline(*m_stream, m_line_buffer_tmp, '\n'); + // add line with newline symbol to the line buffer - m_line_buffer += line + "\n"; + m_line_buffer += m_line_buffer_tmp; + m_line_buffer.push_back('\n'); } // set pointers - m_content = reinterpret_cast(m_line_buffer.c_str()); + m_content = reinterpret_cast(m_line_buffer.data()); assert(m_content != nullptr); m_start = m_content; m_marker = m_start + offset_marker; @@ -8861,9 +10254,20 @@ basic_json_parser_66: // iterate the result between the quotes for (const lexer_char_t* i = m_start + 1; i < m_cursor - 1; ++i) { - // process escaped characters - if (*i == '\\') + // find next escape character + auto e = std::find(i, m_cursor - 1, '\\'); + if (e != i) + { + // see https://github.com/nlohmann/json/issues/365#issuecomment-262874705 + for (auto k = i; k < e; k++) + { + result.push_back(static_cast(*k)); + } + i = e - 1; // -1 because of ++i + } + else { + // processing escaped character // read next character ++i; @@ -8950,12 +10354,6 @@ basic_json_parser_66: } } } - else - { - // all other characters are just copied to the end of the - // string - result.append(1, static_cast(*i)); - } } return result; @@ -8969,8 +10367,6 @@ basic_json_parser_66: supplied via the first parameter. Set this to @a static_cast(nullptr). - @param[in] type the @ref number_float_t in use - @param[in,out] endptr recieves a pointer to the first character after the number @@ -8989,8 +10385,6 @@ basic_json_parser_66: supplied via the first parameter. Set this to @a static_cast(nullptr). - @param[in] type the @ref number_float_t in use - @param[in,out] endptr recieves a pointer to the first character after the number @@ -9009,8 +10403,6 @@ basic_json_parser_66: supplied via the first parameter. Set this to @a static_cast(nullptr). - @param[in] type the @ref number_float_t in use - @param[in,out] endptr recieves a pointer to the first character after the number @@ -9091,19 +10483,19 @@ basic_json_parser_66: // skip if definitely not an integer if (type != value_t::number_float) { - // multiply last value by ten and add the new digit - auto temp = value * 10 + *curptr - '0'; + auto digit = static_cast(*curptr - '0'); - // test for overflow - if (temp < value || temp > max) + // overflow if value * 10 + digit > max, move terms around + // to avoid overflow in intermediate values + if (value > (max - digit) / 10) { // overflow type = value_t::number_float; } else { - // no overflow - save it - value = temp; + // no overflow + value = value * 10 + digit; } } } @@ -9115,7 +10507,22 @@ basic_json_parser_66: } else if (type == value_t::number_integer) { - result.m_value.number_integer = -static_cast(value); + // invariant: if we parsed a '-', the absolute value is between + // 0 (we allow -0) and max == -INT64_MIN + assert(value >= 0); + assert(value <= max); + + if (value == max) + { + // we cannot simply negate value (== max == -INT64_MIN), + // see https://github.com/nlohmann/json/issues/389 + result.m_value.number_integer = static_cast(INT64_MIN); + } + else + { + // all other values can be negated safely + result.m_value.number_integer = -static_cast(value); + } } else { @@ -9139,6 +10546,8 @@ basic_json_parser_66: std::istream* m_stream = nullptr; /// line buffer buffer for m_stream string_t m_line_buffer {}; + /// used for filling m_line_buffer + string_t m_line_buffer_tmp {}; /// the buffer pointer const lexer_char_t* m_content = nullptr; /// pointer to the beginning of the current symbol @@ -9164,7 +10573,7 @@ basic_json_parser_66: /// a parser reading from a string literal parser(const char* buff, const parser_callback_t cb = nullptr) : callback(cb), - m_lexer(reinterpret_cast(buff), strlen(buff)) + m_lexer(reinterpret_cast(buff), std::strlen(buff)) {} /// a parser reading from an input stream @@ -9896,13 +11305,11 @@ basic_json_parser_66: /*! @brief replace all occurrences of a substring by another string - @param[in,out] s the string to manipulate + @param[in,out] s the string to manipulate; changed so that all + occurrences of @a f are replaced with @a t @param[in] f the substring to replace with @a t @param[in] t the string to replace @a f - @return The string @a s where all occurrences of @a f are replaced - with @a t. - @pre The search string @a f must not be empty. @since version 2.0.0 -- cgit 1.4.1 From 1d4b88e0bfe162419bf9466341ccec5f1e5656da Mon Sep 17 00:00:00 2001 From: Kelly Rauchenberger Date: Sun, 17 Dec 2017 14:44:38 -0500 Subject: Updated curlcpp (important bug fixes) This update fixes the underlying issue behind an infinite-stalling bug that has been around since differencebot was written. The four bots that download images from ImageNet (difference, advice, grunge, capital) had the same small logic error whereby the catch block, written to handle the situation when an image had failed to download, incorrectly took the exception by value rather than by const reference. While inefficient, this should not have caused any problems, but curlcpp, which the four bots all use through libtwitter++'s dependence on it, had a bug in the copy constructor of the curl_exception object which could cause a deadlock situation. This went unnoticed for a while, despite the four bots having serious downtime issues. When this was discovered, the bots were patched so that the relevant catch block took the exception by const reference. While this fixed things, it is also prudent to update curlcpp to the newest version in which this bug has been fixed. refs JosephP91/curlcpp#100 --- vendor/curlcpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/curlcpp b/vendor/curlcpp index 687be1c..11c4979 160000 --- a/vendor/curlcpp +++ b/vendor/curlcpp @@ -1 +1 @@ -Subproject commit 687be1c252dce098f488ca8a46d847dac4972310 +Subproject commit 11c4979d323f2e12a466bb768b66569e5f9a7ea3 -- cgit 1.4.1 From 4df9da5873749c85f50d49a94ae8f3ace4af57a6 Mon Sep 17 00:00:00 2001 From: Kelly Rauchenberger Date: Wed, 20 Dec 2017 11:05:07 -0500 Subject: Fixed unpredictable media upload failure While uploading media worked in development, as well as on production when compiled in debug mode, it failed with memory corruption issues on production in release mode. This turned out to be because curlcpp was taking lvalue references to temporary std::string objects converted from string constants, instead of just copying the string pointer. --- .gitmodules | 2 +- vendor/curlcpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 4f1abfd..9ad89e7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,4 +3,4 @@ url = https://github.com/hatkirby/liboauthcpp [submodule "vendor/curlcpp"] path = vendor/curlcpp - url = https://github.com/JosephP91/curlcpp + url = https://github.com/hatkirby/curlcpp diff --git a/vendor/curlcpp b/vendor/curlcpp index 687be1c..98df597 160000 --- a/vendor/curlcpp +++ b/vendor/curlcpp @@ -1 +1 @@ -Subproject commit 687be1c252dce098f488ca8a46d847dac4972310 +Subproject commit 98df5971b87295fad7c9a164c27b2bd34653a225 -- cgit 1.4.1 From d1d20515281e32b23657762e72bf37dcff14f0ab Mon Sep 17 00:00:00 2001 From: Kelly Rauchenberger Date: Wed, 20 Dec 2017 13:07:24 -0500 Subject: Switched back to upstream curlcpp because my pull request was merged --- .gitmodules | 2 +- vendor/curlcpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 9ad89e7..4f1abfd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,4 +3,4 @@ url = https://github.com/hatkirby/liboauthcpp [submodule "vendor/curlcpp"] path = vendor/curlcpp - url = https://github.com/hatkirby/curlcpp + url = https://github.com/JosephP91/curlcpp diff --git a/vendor/curlcpp b/vendor/curlcpp index 98df597..7ff0b1b 160000 --- a/vendor/curlcpp +++ b/vendor/curlcpp @@ -1 +1 @@ -Subproject commit 98df5971b87295fad7c9a164c27b2bd34653a225 +Subproject commit 7ff0b1bf7d96169f268b73f5fa510ef3af0a1dc4 -- cgit 1.4.1 From 5014ad408ace1dda89e0e9852db4a001b605c0a2 Mon Sep 17 00:00:00 2001 From: Kelly Rauchenberger Date: Fri, 23 Feb 2018 11:20:55 -0500 Subject: Added ability to get blocklist --- src/client.cpp | 34 +++++++++++++++++++++++++++++++++- src/client.h | 4 +++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/client.cpp b/src/client.cpp index ccfd9a3..c2ef06f 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -591,7 +591,39 @@ namespace twitter { { return getFollowers(getUser().getID()); } - + + std::set client::getBlocks() const + { + long long cursor = -1; + std::set result; + + while (cursor != 0) + { + std::stringstream urlstream; + urlstream << "https://api.twitter.com/1.1/blocks/ids.json?cursor="; + urlstream << cursor; + + std::string url = urlstream.str(); + std::string response_data = get(*_oauth_client, url).perform(); + + try + { + nlohmann::json rjs = nlohmann::json::parse(response_data); + + cursor = rjs["next_cursor"].get(); + result.insert(std::begin(rjs["ids"]), std::end(rjs["ids"])); + } catch (const std::invalid_argument& error) + { + std::throw_with_nested(invalid_response(response_data)); + } catch (const std::domain_error& error) + { + std::throw_with_nested(invalid_response(response_data)); + } + } + + return result; + } + void client::follow(user_id toFollow) const { std::stringstream datastrstream; diff --git a/src/client.h b/src/client.h index c920f82..9282321 100644 --- a/src/client.h +++ b/src/client.h @@ -38,7 +38,9 @@ namespace twitter { std::set getFollowers(user_id id) const; std::set getFollowers(const user& u) const; std::set getFollowers() const; - + + std::set getBlocks() const; + void follow(user_id toFollow) const; void follow(const user& toFollow) const; -- cgit 1.4.1 From 3e292e37a1313e22f13ef4b7f342c6002ea6a947 Mon Sep 17 00:00:00 2001 From: Kelly Rauchenberger Date: Fri, 23 Feb 2018 11:21:10 -0500 Subject: Whitespace changes --- src/client.cpp | 220 ++++++++++++++++++++++++++++----------------------------- src/client.h | 30 ++++---- 2 files changed, 125 insertions(+), 125 deletions(-) diff --git a/src/client.cpp b/src/client.cpp index c2ef06f..6fea80a 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -17,13 +17,13 @@ void dump(const char *text, size_t i; size_t c; unsigned int width=80; - + fprintf(stream, "%s, %10.10ld bytes (0x%8.8lx)\n", text, (long)size, (long)size); - + for(i=0; i= 0x20 && ptr[i+c] < 0x80) ? ptr[i+c] : '.'; fputc(x, stream); } - + fputc('\n', stream); /* newline */ } } - + static int my_trace(CURL *handle, curl_infotype type, char *data, size_t size, @@ -49,13 +49,13 @@ int my_trace(CURL *handle, curl_infotype type, { const char *text; (void)handle; /* prevent compiler warning */ - + switch (type) { case CURLINFO_TEXT: fprintf(stderr, "== Info: %s", data); default: /* in case a new one is introduced to shock us */ return 0; - + case CURLINFO_HEADER_OUT: text = "=> Send header"; break; @@ -75,17 +75,17 @@ int my_trace(CURL *handle, curl_infotype type, text = "<= Recv SSL data"; break; } - + dump(text, stderr, (unsigned char *)data, size); return 0; } namespace twitter { - + class request { public: - + explicit request(std::string url) try : _ios(_output), _conn(_ios) { @@ -93,10 +93,10 @@ namespace twitter { } catch (const curl::curl_easy_exception& error) { error.print_traceback(); - + assert(false); } - + std::string perform() { try @@ -106,14 +106,14 @@ namespace twitter { { std::throw_with_nested(connection_error()); } - + int response_code = _conn.get_info().get(); std::string result = _output.str(); - + if (response_code / 100 != 2) { nlohmann::json response_json; - + try { response_json = nlohmann::json::parse(result); @@ -121,12 +121,12 @@ namespace twitter { { std::throw_with_nested(invalid_response(result)); } - + for (nlohmann::json& error : response_json["errors"]) { int error_code; std::string error_message; - + try { error_code = error["code"].get(); @@ -135,44 +135,44 @@ namespace twitter { { std::throw_with_nested(invalid_response(result)); } - + switch (error_code) { case 32: case 135: case 215: throw bad_auth(error_message); - + case 44: throw invalid_media(error_message); - + case 64: throw account_suspended(error_message); - + case 88: throw rate_limit_exceeded(error_message); - + case 89: throw bad_token(error_message); - + case 130: throw server_overloaded(error_message); - + case 131: throw server_error(error_message); - + case 185: throw update_limit_exceeded(error_message); - + case 186: throw bad_length(error_message); - + case 187: throw duplicate_status(error_message); - + case 226: throw suspected_spam(error_message); - + case 261: throw write_restricted(error_message); } @@ -194,27 +194,27 @@ namespace twitter { { throw server_timeout("HTTP 504 Gateway Timeout"); } - + throw unknown_error(response_code, result); } - + return result; } - + private: - + std::ostringstream _output; curl::curl_ios _ios; - + protected: - + curl::curl_easy _conn; }; - + class get : public request { public: - + get(const OAuth::Client& oauth_client, std::string url) try : request(url) { @@ -223,31 +223,31 @@ namespace twitter { { _headers.add(std::move(oauth_header)); } - + _conn.add(_headers.get()); } catch (const OAuth::ParseError& error) { std::cout << "Error generating OAuth header:" << std::endl; std::cout << error.what() << std::endl; std::cout << "This is likely due to a malformed URL." << std::endl; - + assert(false); } catch (const curl::curl_easy_exception& error) { error.print_traceback(); - + assert(false); } - + private: - + curl::curl_header _headers; }; - + class post : public request { public: - + post(const OAuth::Client& oauth_client, std::string url, std::string datastr) try : request(url) { @@ -256,7 +256,7 @@ namespace twitter { { _headers.add(std::move(oauth_header)); } - + _conn.add(_headers.get()); _conn.add(datastr.c_str()); } catch (const OAuth::ParseError& error) @@ -264,24 +264,24 @@ namespace twitter { std::cout << "Error generating OAuth header:" << std::endl; std::cout << error.what() << std::endl; std::cout << "This is likely due to a malformed URL." << std::endl; - + assert(false); } catch (const curl::curl_easy_exception& error) { error.print_traceback(); - + assert(false); } - + private: - + curl::curl_header _headers; }; - + class multipost : public request { public: - + multipost(const OAuth::Client& oauth_client, std::string url, const curl_httppost* fields) try : request(url) { @@ -290,7 +290,7 @@ namespace twitter { { _headers.add(std::move(oauth_header)); } - + _conn.add(_headers.get()); _conn.add(fields); } catch (const OAuth::ParseError& error) @@ -298,94 +298,94 @@ namespace twitter { std::cout << "Error generating OAuth header:" << std::endl; std::cout << error.what() << std::endl; std::cout << "This is likely due to a malformed URL." << std::endl; - + assert(false); } catch (const curl::curl_easy_exception& error) { error.print_traceback(); - + assert(false); } - + private: - + curl::curl_header _headers; }; - + client::client(const auth& _arg) { _oauth_consumer = make_unique( _arg.getConsumerKey(), _arg.getConsumerSecret()); - + _oauth_token = make_unique( _arg.getAccessKey(), _arg.getAccessSecret()); - + _oauth_client = make_unique( _oauth_consumer.get(), _oauth_token.get()); - + _current_user = make_unique( get(*_oauth_client, "https://api.twitter.com/1.1/account/verify_credentials.json") .perform()); } - + client::~client() = default; - + tweet client::updateStatus(std::string msg, std::list media_ids) const { std::stringstream datastrstream; datastrstream << "status=" << OAuth::PercentEncode(msg); - + if (!media_ids.empty()) { datastrstream << "&media_ids="; datastrstream << twitter::implode(std::begin(media_ids), std::end(media_ids), ","); } - + return tweet( post(*_oauth_client, "https://api.twitter.com/1.1/statuses/update.json", datastrstream.str()) .perform()); } - + tweet client::replyToTweet(std::string msg, tweet_id in_response_to, std::list media_ids) const { std::stringstream datastrstream; datastrstream << "status=" << OAuth::PercentEncode(msg); datastrstream << "&in_reply_to_status_id="; datastrstream << in_response_to; - + if (!media_ids.empty()) { datastrstream << "&media_ids="; datastrstream << twitter::implode(std::begin(media_ids), std::end(media_ids), ","); } - + return tweet( post(*_oauth_client, "https://api.twitter.com/1.1/statuses/update.json", datastrstream.str()) .perform()); } - + tweet client::replyToTweet(std::string msg, const tweet& in_response_to, std::list media_ids) const { return replyToTweet(msg, in_response_to.getID(), media_ids); } - + long client::uploadMedia(std::string media_type, const char* data, long data_length) const try { curl::curl_form form; std::string str_data_length = std::to_string(data_length); - + curl::curl_pair command_name(CURLFORM_COPYNAME, "command"); curl::curl_pair command_cont(CURLFORM_COPYCONTENTS, "INIT"); curl::curl_pair bytes_name(CURLFORM_COPYNAME, "total_bytes"); @@ -402,7 +402,7 @@ namespace twitter { curl::curl_pair category_cont(CURLFORM_COPYCONTENTS, "tweet_gif"); form.add(category_name, category_cont); } - + std::string init_response = multipost(*_oauth_client, "https://upload.twitter.com/1.1/media/upload.json", @@ -410,7 +410,7 @@ namespace twitter { .perform(); long media_id; - + try { nlohmann::json response_json = nlohmann::json::parse(init_response); @@ -434,21 +434,21 @@ namespace twitter { { assert(false); } - + multipost(*_oauth_client, "https://upload.twitter.com/1.1/media/upload.json", append_form_post).perform(); - + curl_formfree(append_form_post); - + curl::curl_form finalize_form; std::string str_media_id = std::to_string(media_id); - + curl::curl_pair command3_name(CURLFORM_COPYNAME, "command"); curl::curl_pair command3_cont(CURLFORM_COPYCONTENTS, "FINALIZE"); curl::curl_pair media_id_name(CURLFORM_COPYNAME, "media_id"); curl::curl_pair media_id_cont(CURLFORM_COPYCONTENTS, str_media_id); finalize_form.add(command3_name, command3_cont); finalize_form.add(media_id_name, media_id_cont); - + std::string finalize_response = multipost(*_oauth_client, "https://upload.twitter.com/1.1/media/upload.json", @@ -456,7 +456,7 @@ namespace twitter { .perform(); nlohmann::json finalize_json; - + try { finalize_json = nlohmann::json::parse(finalize_response); @@ -464,26 +464,26 @@ namespace twitter { { std::throw_with_nested(invalid_response(finalize_response)); } - + if (finalize_json.find("processing_info") != finalize_json.end()) { std::stringstream datastr; datastr << "https://upload.twitter.com/1.1/media/upload.json?command=STATUS&media_id=" << media_id; - + for (;;) { std::string status_response = get(*_oauth_client, datastr.str()).perform(); - + try { nlohmann::json status_json = nlohmann::json::parse(status_response); std::string state = status_json["processing_info"]["state"].get(); - + if (state == "succeeded") { break; } - + int ttw = status_json["processing_info"]["check_after_secs"].get(); std::this_thread::sleep_for(std::chrono::seconds(ttw)); } catch (const std::invalid_argument& error) @@ -495,20 +495,20 @@ namespace twitter { } } } - + return media_id; } catch (const curl::curl_exception& error) { error.print_traceback(); - + assert(false); } - + std::set client::getFriends(user_id id) const { long long cursor = -1; std::set result; - + while (cursor != 0) { std::stringstream urlstream; @@ -516,14 +516,14 @@ namespace twitter { urlstream << id; urlstream << "&cursor="; urlstream << cursor; - + std::string url = urlstream.str(); std::string response_data = get(*_oauth_client, url).perform(); - + try { nlohmann::json rjs = nlohmann::json::parse(response_data); - + cursor = rjs["next_cursor"].get(); result.insert(std::begin(rjs["ids"]), std::end(rjs["ids"])); } catch (const std::invalid_argument& error) @@ -534,25 +534,25 @@ namespace twitter { std::throw_with_nested(invalid_response(response_data)); } } - + return result; } - + std::set client::getFriends(const user& id) const { return getFriends(id.getID()); } - + std::set client::getFriends() const { return getFriends(getUser().getID()); } - + std::set client::getFollowers(user_id id) const { long long cursor = -1; std::set result; - + while (cursor != 0) { std::stringstream urlstream; @@ -560,14 +560,14 @@ namespace twitter { urlstream << id; urlstream << "&cursor="; urlstream << cursor; - + std::string url = urlstream.str(); std::string response_data = get(*_oauth_client, url).perform(); - + try { nlohmann::json rjs = nlohmann::json::parse(response_data); - + cursor = rjs["next_cursor"].get(); result.insert(std::begin(rjs["ids"]), std::end(rjs["ids"])); } catch (const std::invalid_argument& error) @@ -578,15 +578,15 @@ namespace twitter { std::throw_with_nested(invalid_response(response_data)); } } - + return result; } - + std::set client::getFollowers(const user& id) const { return getFollowers(id.getID()); } - + std::set client::getFollowers() const { return getFollowers(getUser().getID()); @@ -629,24 +629,24 @@ namespace twitter { std::stringstream datastrstream; datastrstream << "follow=true&user_id="; datastrstream << toFollow; - + post(*_oauth_client, "https://api.twitter.com/1.1/friendships/create.json", datastrstream.str()).perform(); } - + void client::follow(const user& toFollow) const { return follow(toFollow.getID()); } - + void client::unfollow(user_id toUnfollow) const { std::stringstream datastrstream; datastrstream << "user_id="; datastrstream << toUnfollow; - + post(*_oauth_client, "https://api.twitter.com/1.1/friendships/destroy.json", datastrstream.str()).perform(); } - + void client::unfollow(const user& toUnfollow) const { return unfollow(toUnfollow.getID()); @@ -656,7 +656,7 @@ namespace twitter { { return *_current_user; } - + const configuration& client::getConfiguration() const { if (!_configuration || (difftime(time(NULL), _last_configuration_update) > 60*60*24)) @@ -669,8 +669,8 @@ namespace twitter { _last_configuration_update = time(NULL); } - + return *_configuration; } - + }; diff --git a/src/client.h b/src/client.h index 9282321..1e1b1ab 100644 --- a/src/client.h +++ b/src/client.h @@ -18,23 +18,23 @@ namespace OAuth { }; namespace twitter { - + class client { public: - + client(const auth& _auth); ~client(); - + tweet updateStatus(std::string msg, std::list media_ids = {}) const; long uploadMedia(std::string media_type, const char* data, long data_length) const; - + tweet replyToTweet(std::string msg, tweet_id in_response_to, std::list media_ids = {}) const; tweet replyToTweet(std::string msg, const tweet& in_response_to, std::list media_ids = {}) const; - + std::set getFriends(user_id id) const; std::set getFriends(const user& u) const; std::set getFriends() const; - + std::set getFollowers(user_id id) const; std::set getFollowers(const user& u) const; std::set getFollowers() const; @@ -43,28 +43,28 @@ namespace twitter { void follow(user_id toFollow) const; void follow(const user& toFollow) const; - + void unfollow(user_id toUnfollow) const; void unfollow(const user& toUnfollow) const; - + const user& getUser() const; - + const configuration& getConfiguration() const; - + private: - + friend class stream; - + std::unique_ptr _oauth_consumer; std::unique_ptr _oauth_token; std::unique_ptr _oauth_client; - + std::unique_ptr _current_user; - + mutable std::unique_ptr _configuration; mutable time_t _last_configuration_update; }; - + }; #endif /* end of include guard: TWITTER_H_ABFF6A12 */ -- cgit 1.4.1