Merge branch 'develop'
All checks were successful
continuous-integration/drone/push Build is passing

Switched from curlpp to POCO, modernized cmake recipes.
This commit is contained in:
tastytea 2019-08-30 08:18:04 +02:00
commit 2b265a5831
Signed by: tastytea
GPG Key ID: CFC39497F1B26E07
27 changed files with 667 additions and 465 deletions

View File

@ -27,12 +27,9 @@ steps:
commands:
- rm /etc/apt/apt.conf.d/docker-clean
- rm /var/cache/apt/archives/lock
- echo "APT::Default-Release \"stretch\";" >> /etc/apt/apt.conf.d/00default_release
- echo "deb http://deb.debian.org/debian sid main" >> /etc/apt/sources.list.d/sid.list
- apt-get update -q
- apt-get install -qy build-essential cmake pkg-config
- apt-get install -qy libcurl4-openssl-dev libjsoncpp-dev doxygen catch
- apt-get install -qy -t sid libcurlpp-dev
- apt-get install -qy build-essential cmake
- apt-get install -qy libpoco-dev libjsoncpp-dev doxygen catch
- rm -rf build && mkdir -p build && cd build
- cmake -DWITH_EXAMPLES=YES -DWITH_TESTS=YES -DEXTRA_TEST_ARGS="~[api]" ..
- make VERBOSE=1
@ -55,16 +52,14 @@ steps:
- rm /var/cache/apt/archives/lock
- apt-get update -q
- echo "APT::Default-Release \"stretch\";" >> /etc/apt/apt.conf.d/00default_release
- echo "deb http://deb.debian.org/debian sid main" >> /etc/apt/sources.list.d/sid.list
- echo "deb http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu xenial main" >> /etc/apt/sources.list.d/ubuntu-toolchain-r.list
- apt-get install -qy gnupg
- gpg --keyserver hkp://keyserver.ubuntu.com --recv-keys 0x60c317803a41ba51845e371a1e9377a2ba9ef27f
- gpg --armor --export 0x60c317803a41ba51845e371a1e9377a2ba9ef27f | apt-key add -
- apt-get update -q
- apt-get install -qy build-essential cmake pkg-config
- apt-get install -qy build-essential cmake
- apt-get install -qy -t xenial g++-9
- apt-get install -qy libcurl4-openssl-dev libjsoncpp-dev doxygen catch
- apt-get install -qy -t sid libcurlpp-dev
- apt-get install -qy libpoco-dev libjsoncpp-dev doxygen catch
- rm -rf build && mkdir -p build && cd build
- cmake -DWITH_EXAMPLES=YES ..
- make VERBOSE=1
@ -87,12 +82,10 @@ steps:
- apt-get update -q
- echo "APT::Default-Release \"stretch\";" >> /etc/apt/apt.conf.d/00default_release
- echo "deb http://deb.debian.org/debian stretch-backports main" >> /etc/apt/sources.list.d/stretch.list
- echo "deb http://deb.debian.org/debian sid main" >> /etc/apt/sources.list.d/sid.list
- apt-get update -q
- apt-get install -qy build-essential cmake pkg-config
- apt-get install -qy build-essential cmake
- apt-get install -qy -t stretch-backports clang-5.0
- apt-get install -qy libcurl4-openssl-dev libjsoncpp-dev doxygen catch
- apt-get install -qy -t sid libcurlpp-dev
- apt-get install -qy libpoco-dev libjsoncpp-dev doxygen catch
- rm -rf build && mkdir -p build && cd build
- cmake -DWITH_EXAMPLES=YES ..
- make VERBOSE=1
@ -101,12 +94,12 @@ steps:
- name: debian-package-cache
path: /var/cache/apt/archives
- name: clang6
image: debian:stretch-slim
- name: clang7
image: debian:buster-slim
pull: true
environment:
LANG: C.utf8
CXX: clang++-6.0
CXX: clang++
CXXFLAGS: -pipe -O2
MASTODON_CPP_ACCESS_TOKEN:
from_secret: mastodon_cpp_access_token
@ -114,14 +107,9 @@ steps:
- rm /etc/apt/apt.conf.d/docker-clean
- rm /var/cache/apt/archives/lock
- apt-get update -q
- echo "APT::Default-Release \"stretch\";" >> /etc/apt/apt.conf.d/00default_release
- echo "deb http://deb.debian.org/debian stretch-backports main" >> /etc/apt/sources.list.d/stretch.list
- echo "deb http://deb.debian.org/debian sid main" >> /etc/apt/sources.list.d/sid.list
- apt-get update -q
- apt-get install -qy build-essential cmake pkg-config
- apt-get install -qy -t stretch-backports clang-6.0
- apt-get install -qy libcurl4-openssl-dev libjsoncpp-dev doxygen catch
- apt-get install -qy -t sid libcurlpp-dev
- apt-get install -qy build-essential cmake clang
- apt-get install -qy libpoco-dev libjsoncpp-dev doxygen catch
- rm -rf build && mkdir -p build && cd build
- cmake -DWITH_EXAMPLES=YES ..
- make VERBOSE=1
@ -173,13 +161,9 @@ steps:
commands:
- rm /etc/apt/apt.conf.d/docker-clean
- rm /var/cache/apt/archives/lock
- echo "APT::Default-Release \"stretch\";" >> /etc/apt/apt.conf.d/00default_release
- echo "deb http://deb.debian.org/debian sid main" >> /etc/apt/sources.list.d/sid.list
- apt-get update -q
- apt-get install -qy build-essential cmake pkg-config
- apt-get install -qy libcurl4-openssl-dev libjsoncpp-dev doxygen
- apt-get install -qy -t sid libcurlpp-dev
- apt-get install -qy file
- apt-get install -qy build-essential cmake
- apt-get install -qy libpoco-dev libjsoncpp-dev doxygen file
- rm -rf build && mkdir -p build && cd build
- cmake -DWITH_EXAMPLES=NO -DWITH_TESTS=NO ..
- make package
@ -204,11 +188,8 @@ steps:
- yum install -qy centos-release-scl
- yum install -qy devtoolset-6
- scl enable devtoolset-6 bash
- yum install -qy libcurl-devel doxygen rpm-build
- yum install -qy libcurl-devel poco-devel doxygen rpm-build
- yum --enablerepo=epel install -qy cmake3 jsoncpp-devel
- curl -s -o /var/cache/yum/curlpp-devel-0.7.3-5.el6.x86_64.rpm https://download.fedoraproject.org/pub/epel/6/x86_64/Packages/c/curlpp-devel-0.7.3-5.el6.x86_64.rpm
- curl -s -o /var/cache/yum/curlpp-0.7.3-5.el6.x86_64.rpm https://download.fedoraproject.org/pub/epel/6/x86_64/Packages/c/curlpp-0.7.3-5.el6.x86_64.rpm
- yum localinstall -qy /var/cache/yum/curlpp-*
- rm -rf build && mkdir -p build && cd build
- cmake3 -DWITH_EXAMPLES=NO -DWITH_TESTS=NO ..
- make package

View File

@ -1,124 +1,86 @@
cmake_minimum_required (VERSION 3.6)
project (mastodon-cpp
VERSION 0.106.0
LANGUAGES CXX
)
set(WITH_EASY "YES" CACHE STRING "WITH_EASY defaults to \"YES\"")
set(WITH_EXAMPLES "NO" CACHE STRING "WITH_EXAMPLES defaults to \"NO\"")
set(WITH_TESTS "NO" CACHE STRING "WITH_TESTS defaults to \"NO\"")
set(WITH_DOC "YES" CACHE STRING "WITH_DOC defaults to \"YES\"")
set(WITH_DEB "NO" CACHE STRING "WITH_DEB defaults to \"NO\"")
set(WITH_RPM "NO" CACHE STRING "WITH_RPM defaults to \"NO\"")
# Support version 3.6 and above, but use policy settings up to 3.14.
# 3.6 is needed because of IMPORTED_TARGET in pkg_check_modules().
cmake_minimum_required(VERSION 3.6...3.14)
# Ranges are supported from 3.12, set policy to current for < 3.12.
if(${CMAKE_VERSION} VERSION_LESS 3.12)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
endif()
include(GNUInstallDirs)
find_package(PkgConfig REQUIRED)
find_package(PkgConfig REQUIRED)
pkg_check_modules(CURLPP REQUIRED curlpp)
if(WITH_EASY)
pkg_check_modules(JSONCPP REQUIRED jsoncpp)
project (mastodon-cpp
VERSION 0.110.0
LANGUAGES CXX)
# DESCRIPTION was introduced in version 3.9.
if(NOT (${CMAKE_VERSION} VERSION_LESS 3.9))
set(PROJECT_DESCRIPTION
"C++ wrapper for the Mastodon API.")
endif()
option(WITH_EASY "Compile Easy interface." YES)
option(WITH_EXAMPLES "Compile examples." NO)
option(WITH_TESTS "Compile tests." NO)
option(WITH_DOC "Generate HTML documentation." YES)
option(WITH_DEB "Prepare for the building of .deb packages." NO)
option(WITH_RPM "Prepare for the building of .rpm packages." NO)
option(BUILD_SHARED_LIBS "Build shared libraries." YES)
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type, Release or Debug.")
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_FLAGS_DEBUG
"${CMAKE_CXX_FLAGS_DEBUG} -Wall -Wextra -Wpedantic -ftrapv -fsanitize=undefined -g -Og -fno-omit-frame-pointer")
# Do not complain about compatibility-wrapper
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-declarations")
set(DEBUG_CXXFLAGS
"-Wall"
"-Wextra"
"-Wpedantic"
"-ftrapv"
"-fsanitize=undefined"
"-g"
"-Og"
"-fno-omit-frame-pointer"
"-Wno-deprecated-declarations")
set(DEBUG_LDFLAGS
"-fsanitize=undefined")
add_compile_options("$<$<CONFIG:Debug>:${DEBUG_CXXFLAGS}>")
# add_link_options was introduced in version 3.13.
if(${CMAKE_VERSION} VERSION_LESS 3.13)
set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${DEBUG_LDFLAGS}")
else()
add_link_options("$<$<CONFIG:Debug>:${DEBUG_LDFLAGS}>")
endif()
include_directories(${PROJECT_SOURCE_DIR}/src)
include_directories(${PROJECT_BINARY_DIR})
include_directories(${CURL_INCLUDE_DIRS})
include_directories(${CURLPP_INCLUDE_DIRS})
include_directories(${JSONCPP_INCLUDE_DIRS})
link_directories(${CURL_LIBRARY_DIRS})
link_directories(${CURLPP_LIBRARY_DIRS})
link_directories(${JSONCPP_LIBRARY_DIRS})
# Write version in header
configure_file (
"${PROJECT_SOURCE_DIR}/src/version.hpp.in"
"${PROJECT_BINARY_DIR}/version.hpp"
)
# Turn on debug output.
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
add_definitions(-DDEBUG=1)
add_definitions("-DDEBUG=1")
endif()
if(NOT WITH_EASY)
add_definitions(-DWITHOUT_EASY=1)
add_definitions("-DWITHOUT_EASY=1")
endif()
# Compile library
if(WITH_EASY)
file(GLOB sources src/*.cpp src/api/*.cpp
src/easy/*.cpp src/easy/entities/*.cpp)
else()
file(GLOB sources src/*.cpp src/api/*.cpp)
endif()
add_library(${PROJECT_NAME} SHARED ${sources})
set_target_properties(${PROJECT_NAME} PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${${PROJECT_NAME}_VERSION_MAJOR}
)
add_subdirectory("src")
if(WITH_EASY)
target_link_libraries(${PROJECT_NAME}
${CURLPP_LIBRARIES} pthread ${JSONCPP_LIBRARIES})
else()
target_link_libraries(${PROJECT_NAME}
${CURLPP_LIBRARIES} pthread)
endif()
# Compile examples
if(WITH_EXAMPLES)
file(GLOB sources_examples examples/*.cpp)
foreach(src ${sources_examples})
get_filename_component(bin ${src} NAME_WE)
add_executable(${bin} ${src})
target_link_libraries(${bin} pthread ${JSONCPP_LIBRARIES} ${PROJECT_NAME})
endforeach()
add_subdirectory("examples")
endif()
# Compile tests
if(WITH_TESTS)
configure_file("tests/test.gif" "${CMAKE_CURRENT_BINARY_DIR}" COPYONLY)
add_subdirectory(tests)
add_subdirectory("tests")
endif()
# Install library and header files
install(TARGETS ${PROJECT_NAME} LIBRARY
DESTINATION ${CMAKE_INSTALL_LIBDIR})
install(FILES src/mastodon-cpp.hpp src/return_types.hpp src/types.hpp
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME})
if(WITH_EASY)
file(GLOB easy_header src/easy/*.hpp)
install(FILES ${easy_header}
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/easy)
file(GLOB easy_entities_header src/easy/entities/*.hpp)
install(FILES ${easy_entities_header}
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/easy/entities)
endif()
# Compile & install documentation
if(WITH_DOC)
add_custom_command(OUTPUT ${PROJECT_SOURCE_DIR}/doc/html
COMMAND ./build_doc.sh WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
COMMAND "./build_doc.sh" WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
add_custom_target(doc DEPENDS doc/html)
add_dependencies(${PROJECT_NAME} doc)
install(DIRECTORY ${PROJECT_SOURCE_DIR}/doc/html
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/${PROJECT_NAME}-${PROJECT_VERSION})
file(GLOB examples examples/example*.cpp)
install(FILES ${examples}
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/${PROJECT_NAME}-${PROJECT_VERSION}/examples)
DESTINATION "${CMAKE_INSTALL_DOCDIR}")
endif()
# Packages
include(packages.CMakeLists.txt)
add_subdirectory("cmake")
add_subdirectory("pkg-config")
include("cmake/packages.cmake")

View File

@ -105,14 +105,13 @@ Not included in this list are entities.
|===================================================
| Code | Explanation
| 0 | No error
| 22 | Invalid argument
| 78 | URL changed (HTTP 301 or 308)
| 110 | Connection timed out
| 111 | Connection refused (check http_error_code)
| 113 | No route to host / Could not resolve host
| 192 | curlpp runtime error
| 193 | curlpp logic error
| 255 | Unknown error
| 1 | Invalid argument
| 10 | URL changed (HTTP 301 or 308)
| 11 | Connection timed out
| 12 | Connection refused (check http_error_code)
| 13 | No route to host / Could not resolve host
| 14 | Encryption error
| 127 | Unknown error
|===================================================
If you use a debug build, you get more verbose error messages.
@ -157,23 +156,15 @@ Prebuilt DEB and RPM packages for x86_64(amd64) are provided with each release.
`.deb` packages are built on Debian stretch and `.rpm` packages are built on
CentOS 7. These packages are automatically built and not tested.
To use the `.deb` package on Debian stretch, you will need
https://packages.debian.org/libcurlpp0[libcurlpp0] from sid.
To use the `.rpm` package on CentOS 7, you will need
https://download.fedoraproject.org/pub/epel/6/x86_64/Packages/c/[curlpp]
from EPEL 6.
=== From source
==== Dependencies
* Tested OS: Linux
* C++ compiler (tested: https://gcc.gnu.org/[gcc] 6/8/9,
https://llvm.org/[clang] 5/6)
https://llvm.org/[clang] 5/7)
* https://cmake.org/[cmake] (at least: 3.6)
* https://pkgconfig.freedesktop.org/wiki/[pkgconfig] (tested: 0.29 / 0.27)
* http://www.curlpp.org/[curlpp] (tested: 0.8)
* https://pocoproject.org/[POCO] (tested: 1.9 / 1.7)
* Optional
** Easy interface & Examples:
https://github.com/open-source-parsers/jsoncpp[jsoncpp] (tested: 1.8 / 1.7)
@ -186,11 +177,7 @@ from EPEL 6.
====
[source,shell]
----
echo "APT::Default-Release \"stretch\";" >> /etc/apt/apt.conf.d/00default_release
echo "deb http://deb.debian.org/debian sid main" >> /etc/apt/sources.list.d/sid.list
apt-get update
apt-get install build-essential cmake pkg-config libcurl4-openssl-dev libjsoncpp-dev doxygen file
apt-get install -t sid libcurlpp-dev
apt-get install build-essential cmake libpoco-dev libjsoncpp-dev doxygen
----
====

19
cmake/CMakeLists.txt Normal file
View File

@ -0,0 +1,19 @@
include(CMakePackageConfigHelpers)
include(GNUInstallDirs)
write_basic_package_version_file("${PROJECT_NAME}ConfigVersion.cmake"
VERSION ${PACKAGE_VERSION}
COMPATIBILITY ExactVersion) # TODO: Set to SameMajorVersion when stable.
install(EXPORT ${PROJECT_NAME}Targets
FILE "${PROJECT_NAME}Targets.cmake"
NAMESPACE "${PROJECT_NAME}::"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}")
configure_file("${PROJECT_NAME}Config.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" @ONLY)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}")

View File

@ -0,0 +1,7 @@
include(CMakeFindDependencyMacro)
include(GNUInstallDirs)
find_depencency(jsoncpp CONFIG REQUIRED)
find_package(Poco COMPONENTS Foundation Net NetSSL CONFIG REQUIRED)
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")

40
cmake/packages.cmake Normal file
View File

@ -0,0 +1,40 @@
set(CPACK_PACKAGE_NAME ${PROJECT_NAME})
set(CPACK_PACKAGE_VERSION_MAJOR ${${PROJECT_NAME}_VERSION_MAJOR})
set(CPACK_PACKAGE_VERSION_MINOR ${${PROJECT_NAME}_VERSION_MINOR})
set(CPACK_PACKAGE_VERSION_PATCH ${${PROJECT_NAME}_VERSION_PATCH})
set(CPACK_PACKAGE_VERSION ${mastodon-cpp_VERSION})
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${PROJECT_DESCRIPTION}")
set(CPACK_PACKAGE_CONTACT "tastytea <tastytea@tastytea.de>")
set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE")
set(CPACK_RESOURCE_FILE_README "${PROJECT_SOURCE_DIR}/README.adoc")
execute_process(COMMAND uname -m
OUTPUT_VARIABLE CPACK_PACKAGE_ARCHITECTURE OUTPUT_STRIP_TRAILING_WHITESPACE)
set(CPACK_PACKAGE_FILE_NAME
"${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}_${CPACK_PACKAGE_ARCHITECTURE}")
set(CPACK_GENERATOR "TGZ")
if (WITH_DEB)
set(CPACK_PACKAGE_NAME "lib${PROJECT_NAME}")
set(CPACK_GENERATOR "DEB")
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE
"https://schlomp.space/tastytea/${PROJECT_NAME}")
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
execute_process(COMMAND dpkg --print-architecture
OUTPUT_VARIABLE CPACK_DEBIAN_PACKAGE_ARCHITECTURE
OUTPUT_STRIP_TRAILING_WHITESPACE)
set(CPACK_PACKAGE_FILE_NAME
"${CPACK_PACKAGE_NAME}_${CPACK_PACKAGE_VERSION}-0_${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}")
endif()
if (WITH_RPM)
set(CPACK_PACKAGE_NAME "lib${PROJECT_NAME}")
set(CPACK_GENERATOR "RPM")
set(CPACK_RPM_PACKAGE_LICENSE "AGPL-3")
set(CPACK_RPM_PACKAGE_URL "https://schlomp.space/tastytea/${PROJECT_NAME}")
set(CPACK_RPM_PACKAGE_REQUIRES "poco-netssl >= 1.6, jsoncpp >= 1.7.4")
set(CPACK_PACKAGE_FILE_NAME
"${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-0.${CPACK_PACKAGE_ARCHITECTURE}")
endif()
include(CPack)

11
examples/CMakeLists.txt Normal file
View File

@ -0,0 +1,11 @@
file(GLOB sources_examples *.cpp)
foreach(src ${sources_examples})
get_filename_component(bin ${src} NAME_WE)
add_executable(${bin} ${src})
target_link_libraries(${bin} PRIVATE ${PROJECT_NAME})
endforeach()
if(WITH_DOC)
install(FILES ${sources_examples}
DESTINATION "${CMAKE_INSTALL_DOCDIR}/examples")
endif()

View File

@ -1,54 +0,0 @@
set(CPACK_PACKAGE_NAME ${PROJECT_NAME})
set(CPACK_PACKAGE_VERSION_MAJOR ${${PROJECT_NAME}_VERSION_MAJOR})
set(CPACK_PACKAGE_VERSION_MINOR ${${PROJECT_NAME}_VERSION_MINOR})
set(CPACK_PACKAGE_VERSION_PATCH ${${PROJECT_NAME}_VERSION_PATCH})
set(CPACK_PACKAGE_VERSION ${mastodon-cpp_VERSION})
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "C++ wrapper for the Mastodon API")
set(CPACK_PACKAGE_CONTACT "tastytea <tastytea@tastytea.de>")
set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE")
set(CPACK_RESOURCE_FILE_README "${PROJECT_SOURCE_DIR}/README.adoc")
list(APPEND CPACK_SOURCE_IGNORE_FILES "/\\\\.git"
"/.gitignore"
"/build/"
"/doc/"
"\\\\.sublime-"
"/update_doc.sh"
"/.drone.yml"
"/ISSUE_TEMPLATE.md$")
execute_process(COMMAND uname -m
OUTPUT_VARIABLE CPACK_PACKAGE_ARCHITECTURE
OUTPUT_STRIP_TRAILING_WHITESPACE)
set(CPACK_PACKAGE_FILE_NAME
"${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}_${CPACK_PACKAGE_ARCHITECTURE}")
set(CPACK_GENERATOR "TGZ")
set(CPACK_SOURCE_GENERATOR "TGZ")
if (WITH_DEB)
set(CPACK_PACKAGE_NAME "lib${PROJECT_NAME}")
set(CPACK_GENERATOR "DEB")
set(CPACK_SOURCE_GENERATOR "DEB")
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://schlomp.space/tastytea/mastodon-cpp")
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
execute_process(COMMAND dpkg --print-architecture
OUTPUT_VARIABLE CPACK_DEBIAN_PACKAGE_ARCHITECTURE
OUTPUT_STRIP_TRAILING_WHITESPACE)
set(CPACK_PACKAGE_FILE_NAME
"${CPACK_PACKAGE_NAME}_${CPACK_PACKAGE_VERSION}-0_${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}")
set(CPACK_SOURCE_PACKAGE_FILE_NAME
"${CPACK_PACKAGE_NAME}_${CPACK_PACKAGE_VERSION}-0_src")
endif()
if (WITH_RPM)
set(CPACK_PACKAGE_NAME "lib${PROJECT_NAME}")
set(CPACK_GENERATOR "RPM")
set(CPACK_SOURCE_GENERATOR "RPM")
set(CPACK_RPM_PACKAGE_LICENSE "GPL-3")
set(CPACK_RPM_PACKAGE_URL "https://schlomp.space/tastytea/mastodon-cpp")
set(CPACK_RPM_PACKAGE_REQUIRES "curlpp >= 0.8.1 jsoncpp, >= 1.7.4")
set(CPACK_PACKAGE_FILE_NAME
"${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-0.${CPACK_PACKAGE_ARCHITECTURE}")
set(CPACK_SOURCE_PACKAGE_FILE_NAME
"${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-0.src")
endif()
include(CPack)

View File

@ -0,0 +1,7 @@
include(GNUInstallDirs)
configure_file("${PROJECT_NAME}.pc.in"
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc" @ONLY)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")

View File

@ -0,0 +1,12 @@
name=@PROJECT_NAME@
prefix=@CMAKE_INSTALL_PREFIX@
libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@
includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
Name: ${name}
Description: @PROJECT_DESCRIPTION@
Version: @PROJECT_VERSION@
Cflags: -I${includedir}
Libs: -L${libdir} -l${name} -lpthread -lPocoNet
Requires: jsoncpp
Libs.private: -lPocoFoundation -lPocoNetSSL

71
src/CMakeLists.txt Normal file
View File

@ -0,0 +1,71 @@
include(GNUInstallDirs)
if(WITH_EASY)
find_package(jsoncpp CONFIG REQUIRED)
endif()
# Some distributions do not contain Poco*Config.cmake recipes.
find_package(Poco COMPONENTS Foundation Net NetSSL CONFIG)
if(WITH_EASY)
file(GLOB_RECURSE sources *.cpp *.hpp)
else()
file(GLOB sources *.cpp api/*.cpp *.hpp api/*.hpp)
endif()
# Write version in header
configure_file ("version.hpp.in"
"${CMAKE_CURRENT_BINARY_DIR}/version.hpp")
add_library(${PROJECT_NAME} ${sources})
set_target_properties(${PROJECT_NAME} PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${${PROJECT_NAME}_VERSION_MAJOR})
target_include_directories(${PROJECT_NAME}
PRIVATE
"$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>"
PUBLIC
"$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/src>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>")
if(WITH_EASY)
target_link_libraries(${PROJECT_NAME}
PUBLIC pthread jsoncpp_lib)
endif()
# If no Poco*Config.cmake recipes are found, look for headers in standard dirs.
if(PocoNetSSL_FOUND)
target_link_libraries(${PROJECT_NAME}
PRIVATE Poco::Foundation Poco::Net Poco::NetSSL)
else()
find_file(Poco_h NAMES "Poco/Poco.h"
PATHS "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}")
if("${Poco_h}" STREQUAL "Poco_h-NOTFOUND")
message(FATAL_ERROR "Could not find POCO.")
else()
message(WARNING
"Your distribution of POCO doesn't contain the *Config.cmake recipes, "
"but the files seem to be in the standard directories. "
"Let's hope this works.")
target_link_libraries(${PROJECT_NAME}
PRIVATE PocoFoundation PocoNet PocoNetSSL)
endif()
endif()
install(TARGETS ${PROJECT_NAME}
EXPORT "${PROJECT_NAME}Targets"
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}")
install(FILES mastodon-cpp.hpp return_types.hpp types.hpp
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME})
if(WITH_EASY)
file(GLOB easy_header easy/*.hpp)
install(FILES ${easy_header}
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/easy)
file(GLOB easy_entities_header easy/entities/*.hpp)
install(FILES ${easy_entities_header}
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/easy/entities)
endif()

View File

@ -78,7 +78,7 @@ return_call API::del(const Mastodon::API::v1 &call,
default:
{
ttdebug << "ERROR: Invalid argument.\n";
return { 22, "Invalid argument", 0, "" };
return { error::INVALID_ARGUMENT, "Invalid argument", 0, "" };
}
}
@ -88,5 +88,5 @@ return_call API::del(const Mastodon::API::v1 &call,
return_call API::del(const std::string &call, const parameters &params)
{
return _http.request(http_method::DELETE, call, maptoformdata(params));
return _http.request(http_method::DELETE, call, *maptoformdata(params));
}

View File

@ -192,7 +192,7 @@ const return_call API::get(const Mastodon::API::v1 &call,
else
{
ttdebug << "ERROR: Invalid argument.\n";
return { 22, "Invalid argument", 0, "" };
return { error::INVALID_ARGUMENT, "Invalid argument", 0, "" };
}
break;
}
@ -239,7 +239,7 @@ const return_call API::get(const Mastodon::API::v1 &call,
default:
{
ttdebug << "ERROR: Invalid argument.\n";
return { 22, "Invalid argument", 0, "" };
return { error::INVALID_ARGUMENT, "Invalid argument", 0, "" };
}
}
@ -276,7 +276,7 @@ const return_call API::get(const Mastodon::API::v2 &call,
default:
{
ttdebug << "ERROR: Invalid argument.\n";
return { 22, "Invalid argument", 0, "" };
return { error::INVALID_ARGUMENT, "Invalid argument", 0, "" };
}
}

View File

@ -20,6 +20,7 @@
using namespace Mastodon;
using std::cerr;
using std::to_string;
void API::get_stream(const Mastodon::API::v1 &call,
const parameters &params,
@ -57,8 +58,10 @@ void API::get_stream(const Mastodon::API::v1 &call,
}
default:
{
const uint8_t err = static_cast<uint8_t>(error::INVALID_ARGUMENT);
ttdebug << "ERROR: Invalid call.\n";
stream = "event: ERROR\ndata: {\"error_code\":22}\n";
stream = "event: ERROR\ndata: "
"{\"error_code\":" + to_string(err) + "}\n";
return;
}
}

View File

@ -32,10 +32,9 @@ return_call API::patch(const Mastodon::API::v1 &call,
break;
default:
ttdebug << "ERROR: Invalid argument.\n";
return { 22, "Invalid argument", 0, "" };
return { error::INVALID_ARGUMENT, "Invalid argument", 0, "" };
break;
}
return _http.request(http_method::PATCH,
strcall, maptoformdata(params));
return _http.request(http_method::PATCH, strcall, *maptoformdata(params));
}

View File

@ -198,7 +198,7 @@ return_call API::post(const Mastodon::API::v1 &call,
default:
{
ttdebug << "ERROR: Invalid argument.\n";
return { 22, "Invalid argument", 0, ""};
return { error::INVALID_ARGUMENT, "Invalid argument", 0, ""};
}
}
@ -214,5 +214,5 @@ return_call API::post(const Mastodon::API::v1 &call)
return_call API::post(const string &call, const parameters &params)
{
return _http.request(http_method::POST, call, maptoformdata(params));
return _http.request(http_method::POST, call, *maptoformdata(params));
}

View File

@ -58,7 +58,7 @@ return_call API::put(const Mastodon::API::v1 &call,
default:
{
ttdebug << "ERROR: Invalid argument.\n";
return { 22, "Invalid argument", 0, "" };
return { error::INVALID_ARGUMENT, "Invalid argument", 0, "" };
}
}
@ -68,5 +68,5 @@ return_call API::put(const Mastodon::API::v1 &call,
return_call API::put(const string &call, const parameters &params)
{
return _http.request(http_method::PUT, call, maptoformdata(params));
return _http.request(http_method::PUT, call, *maptoformdata(params));
}

View File

@ -51,6 +51,16 @@ Easy::return_entity<T>::return_entity(const uint8_t ec, const string &em,
http_error_code = hec;
}
template<typename T>
Easy::return_entity<T>::return_entity(const error ec, const string &em,
const uint16_t hec, const T &ent)
: entity(ent)
{
error_code = static_cast<uint8_t>(ec);
error_message = em;
http_error_code = hec;
}
template<typename T>
Easy::return_entity<T>::return_entity::operator const T() const
{

View File

@ -61,6 +61,19 @@ namespace Easy
return_entity(const uint8_t ec, const string &em,
const uint16_t hec, const T &ent);
/*!
* @brief Return type for easy Mastodon::Easy::API.
*
* @param ec Error code
* @param em Error message
* @param hec HTTP error code
* @param ent Answer
*
* @since 0.110.0
*/
return_entity(const error ec, const string &em,
const uint16_t hec, const T &ent);
/*!
* @brief Same as return_entity::entity.
*

View File

@ -39,7 +39,8 @@ const return_entity<Status> API::send_post(const Status &status)
else
{
ttdebug << "ERROR: Easy::Status::content can not be empty.\n";
return { 22, "Easy::Status::content can not be empty", 0, Status() };
return { error::INVALID_ARGUMENT,
"Easy::Status::content can not be empty", 0, Status() };
}
if (!status.in_reply_to_id().empty())
@ -94,8 +95,9 @@ const return_entity<Status> API::send_post(const Status &status)
else
{
ttdebug << "ERROR: Easy::Attachment::file can not be empty.\n";
return { 22, "Easy::Attachment::file can not be empty",
0, Status() };
return { error::INVALID_ARGUMENT,
"Easy::Attachment::file can not be empty", 0,
Status() };
}
if (!att.description().empty())
{

View File

@ -16,19 +16,35 @@
#include <iostream>
#include <functional> // std::bind
#include <list>
#include <cstring> // std::strncmp
#include <exception>
#include <thread>
#include <curlpp/Options.hpp>
#include <curlpp/Exception.hpp>
#include <curlpp/Infos.hpp>
#include <regex>
#include <Poco/Net/HTTPSClientSession.h>
#include <Poco/Net/HTTPRequest.h>
#include <Poco/Net/HTTPResponse.h>
#include <Poco/StreamCopier.h>
#include <Poco/URI.h>
#include <Poco/Environment.h>
#include <Poco/Exception.h>
#include <Poco/Net/NetException.h>
#include <Poco/Net/SSLException.h>
#include "debug.hpp"
#include "mastodon-cpp.hpp"
using namespace Mastodon;
namespace curlopts = curlpp::options;
using std::cerr;
using std::istream;
using std::make_unique;
using std::move;
using std::regex;
using std::regex_search;
using std::smatch;
using Poco::Net::HTTPSClientSession;
using Poco::Net::HTTPRequest;
using Poco::Net::HTTPResponse;
using Poco::Net::HTTPMessage;
using Poco::StreamCopier;
using Poco::Environment;
API::http::http(const API &api, const string &instance,
const string &access_token)
@ -37,21 +53,95 @@ API::http::http(const API &api, const string &instance,
, _access_token(access_token)
, _cancel_stream(false)
{
curlpp::initialize();
Poco::Net::initializeSSL();
try
{
string env_proxy = Environment::get("http_proxy");
size_t pos;
// Only keep text between // and /.
if ((pos = env_proxy.find("//")) != string::npos)
{
env_proxy = env_proxy.substr(pos + 2);
}
if ((pos = env_proxy.find('/')) != string::npos)
{
env_proxy = env_proxy.substr(0, pos);
}
if ((pos = env_proxy.find('@')) != string::npos)
{
string hostport = env_proxy.substr(pos + 1);
string userpw = env_proxy.substr(0, pos);
set_proxy(hostport, userpw);
}
else
{
set_proxy(env_proxy);
}
}
catch (const std::exception &)
{
// No proxy found, no problem.
}
}
API::http::~http()
{
curlpp::terminate();
Poco::Net::uninitializeSSL();
}
void API::http::set_proxy(const string &hostport, const string &userpw)
{
try
{
HTTPSClientSession::ProxyConfig proxyconfig;
size_t pos = hostport.find(':');
proxyconfig.host = hostport.substr(0, pos);
if (pos != string::npos)
{
proxyconfig.port = std::stoi(hostport.substr(pos + 1));
}
if (!userpw.empty())
{
string username;
pos = userpw.find(':');
Poco::URI::decode(userpw.substr(0, pos), username);
proxyconfig.username = username;
if (pos != string::npos)
{
string password;
Poco::URI::decode(userpw.substr(pos + 1), password);
proxyconfig.password = password;
}
}
HTTPSClientSession::setGlobalProxyConfig(proxyconfig);
ttdebug << "Set proxy to " << hostport << ".\n";
}
catch (const std::exception &e)
{
if (parent.exceptions())
{
std::rethrow_exception(std::current_exception());
}
}
}
return_call API::http::request(const http_method &meth, const string &path)
{
return request(meth, path, curlpp::Forms());
HTMLForm form;
return request(meth, path, form);
}
return_call API::http::request(const http_method &meth, const string &path,
const curlpp::Forms &formdata)
HTMLForm &formdata)
{
string answer;
return request_common(meth, path, formdata, answer);
@ -63,8 +153,9 @@ void API::http::request_stream(const string &path, string &stream)
_streamthread = std::thread(
[&, path] // path is captured by value because it may be
{ // deleted before we access it.
HTMLForm form;
ret = request_common(http_method::GET_STREAM, path,
curlpp::Forms(), stream);
form, stream);
ttdebug << "Remaining content of the stream: " << stream << '\n';
if (!ret)
{
@ -78,177 +169,185 @@ void API::http::request_stream(const string &path, string &stream)
return_call API::http::request_common(const http_method &meth,
const string &path,
const curlpp::Forms &formdata,
HTMLForm &formdata,
string &answer)
{
using namespace std::placeholders; // _1, _2, _3
ttdebug << "Path is: " << path << '\n';
try
{
curlpp::Easy request;
std::list<string> headers;
request.setOpt<curlopts::Url>("https://" + _instance + path);
ttdebug << "User-Agent: " << parent.get_useragent() << "\n";
request.setOpt<curlopts::UserAgent>(parent.get_useragent());
{
string proxy;
string userpw;
parent.get_proxy(proxy, userpw);
if (!proxy.empty())
{
request.setOpt<curlopts::Proxy>(proxy);
if (!userpw.empty())
{
request.setOpt<curlopts::ProxyUserPwd>(userpw);
}
}
}
if (!_access_token.empty())
{
headers.push_back("Authorization: Bearer " + _access_token);
}
if (meth != http_method::GET_STREAM)
{
headers.push_back("Connection: close");
// Get headers from server
request.setOpt<curlpp::options::Header>(true);
}
request.setOpt<curlopts::HttpHeader>(headers);
request.setOpt<curlopts::FollowLocation>(true);
request.setOpt<curlopts::WriteFunction>
(std::bind(&http::callback_write, this, _1, _2, _3, &answer));
request.setOpt<curlopts::ProgressFunction>
(std::bind(&http::callback_progress, this, _1, _2, _3, _4));
request.setOpt<curlopts::NoProgress>(0);
if (!formdata.empty())
{
request.setOpt<curlopts::HttpPost>(formdata);
}
string method;
switch (meth)
{
case http_method::GET:
case http_method::GET_STREAM:
break;
case http_method::PATCH:
request.setOpt<curlopts::CustomRequest>("PATCH");
break;
case http_method::POST:
request.setOpt<curlopts::CustomRequest>("POST");
break;
case http_method::PUT:
request.setOpt<curlopts::CustomRequest>("PUT");
break;
case http_method::DELETE:
request.setOpt<curlopts::CustomRequest>("DELETE");
{
method = HTTPRequest::HTTP_GET;
break;
}
case http_method::PUT:
{
method = HTTPRequest::HTTP_PUT;
break;
}
case http_method::POST:
{
method = HTTPRequest::HTTP_POST;
break;
}
case http_method::PATCH:
{
method = HTTPRequest::HTTP_PATCH;
break;
}
case http_method::DELETE:
{
method = HTTPRequest::HTTP_DELETE;
break;
}
default:
{
break;
}
}
//request.setOpt<curlopts::Verbose>(true);
HTTPSClientSession session(_instance);
HTTPRequest request(method, path, HTTPMessage::HTTP_1_1);
request.set("User-Agent", parent.get_useragent());
if (!_access_token.empty())
{
request.set("Authorization", " Bearer " + _access_token);
}
if (!formdata.empty())
{
ttdebug << "Size of HTMLForm is " << formdata.size() << '\n';
formdata.prepareSubmit(request);
formdata.write(session.sendRequest(request));
}
else
{
session.sendRequest(request);
}
HTTPResponse response;
istream &rs = session.receiveResponse(response);
const uint16_t http_code = response.getStatus();
ttdebug << "Response code: " << http_code << '\n';
answer.clear();
request.perform();
uint16_t http_code = curlpp::infos::ResponseCode::get(request);
ttdebug << "Response code: " << http_code << '\n';
// Work around "HTTP/1.1 100 Continue\r\n\r\nHTTP/1.1 200 OK"
size_t pos = answer.find("\r\n\r\n", 25);
_headers = answer.substr(0, pos);
// Only return body
answer = answer.substr(pos + 4);
StreamCopier::copyToString(rs, answer);
if (http_code == 200 || http_code == 302 || http_code == 307)
{ // OK or Found or Temporary Redirect
return { 0, "", http_code, answer };
}
else if (http_code == 301 || http_code == 308)
{ // Moved Permanently or Permanent Redirect
// return new URL
answer = curlpp::infos::EffectiveUrl::get(request);
return { 78, "Remote address changed", http_code, answer };
}
else if (http_code == 0)
switch (http_code)
{
return { 255, "Unknown error", http_code, answer };
}
else
case HTTPResponse::HTTP_OK:
{
return { 111, "Connection refused", http_code, answer };
return { error::OK, "", http_code, answer };
}
// Not using the constants because some are too new for Debian stretch.
case 301: // HTTPResponse::HTTP_MOVED_PERMANENTLY
case 308: // HTTPResponse::HTTP_PERMANENT_REDIRECT
case 302: // HTTPResponse::HTTP_FOUND
case 303: // HTTPResponse::HTTP_SEE_OTHER
case 307: // HTTPResponse::HTTP_TEMPORARY_REDIRECT
{
ttdebug << "HTTP redirect.\n";
string location = response.get("Location");
if (location.substr(0, 4) == "http")
{ // Remove protocol and instance from path.
size_t pos1 = location.find("//") + 2;
size_t pos2 = location.find('/', pos1);
if (location.substr(pos1, pos2 - pos1) != _instance)
{ // Return new location if the domain changed.
ttdebug << "New location is on another domain.\n";
return { error::URL_CHANGED, "Remote address changed",
http_code, location };
}
location = location.substr(pos2);
}
if (http_code == 301 || http_code == 308)
{ // Return new location for permanent redirects.
return { error::URL_CHANGED, "Remote address changed",
http_code, location };
}
else
{
ttdebug << "Following temporary redirect: " << location << '\n';
return request_common(meth, location, formdata, answer);
}
}
default:
{
return { error::CONNECTION_REFUSED, "Connection refused",
http_code, answer };
}
}
}
catch (curlpp::RuntimeError &e)
catch (const Poco::Net::DNSException &e)
{
const string what = e.what();
// This error is thrown if http.cancel_stream() is used.
if ((what.compare(0, 16, "Callback aborted") == 0) ||
(what.compare(0, 19, "Failed writing body") == 0))
{
ttdebug << "Request was cancelled by user\n";
return { 0, "Request was cancelled by user", 0, "" };
}
else if (what.compare(what.size() - 20, 20, "Connection timed out") == 0)
{
ttdebug << what << "\n";
return { 110, "Connection timed out", 0, "" };
}
else if (what.compare(0, 23, "Could not resolve host:") == 0)
{
ttdebug << what << "\n";
return { 113, "Could not resolve host", 0, "" };
}
if (parent.exceptions())
{
std::rethrow_exception(std::current_exception());
}
else
{
ttdebug << "curlpp::RuntimeError: " << e.what() << std::endl;
return { 192, e.what(), 0, "" };
e.rethrow();
}
ttdebug << e.displayText() << "\n";
return { error::DNS, e.displayText(), 0, "" };
}
catch (curlpp::LogicError &e)
catch (const Poco::Net::ConnectionRefusedException &e)
{
if (parent.exceptions())
{
e.rethrow();
}
ttdebug << e.displayText() << "\n";
return { error::CONNECTION_REFUSED, e.displayText(), 0, "" };
}
catch (const Poco::Net::SSLException &e)
{
if (parent.exceptions())
{
e.rethrow();
}
ttdebug << e.displayText() << "\n";
return { error::ENCRYPTION, e.displayText(), 0, "" };
}
catch (const Poco::Net::NetException &e)
{
if (parent.exceptions())
{
e.rethrow();
}
ttdebug << "Unknown network error: " << e.displayText() << std::endl;
return { error::UNKNOWN, e.displayText(), 0, "" };
}
catch (const std::exception &e)
{
if (parent.exceptions())
{
std::rethrow_exception(std::current_exception());
}
ttdebug << "curlpp::LogicError: " << e.what() << std::endl;
return { 193, e.what(), 0, "" };
ttdebug << "Unknown error: " << e.what() << std::endl;
return { error::UNKNOWN, e.what(), 0, "" };
}
}
// FIXME: get_headers() doesn't work anymore.
void API::http::get_headers(string &headers) const
{
headers = _headers;
}
size_t API::http::callback_write(char* data, size_t size, size_t nmemb,
string *str)
{
std::lock_guard<std::mutex> lock(_mutex);
str->append(data, size * nmemb);
// ttdebug << "Received " << size * nmemb << " Bytes\n";
return size * nmemb;
}
double API::http::callback_progress(double /* dltotal */, double /* dlnow */,
double /* ultotal */, double /* ulnow */)
{
if (_cancel_stream)
{
// This throws the runtime error: Callback aborted
return 1;
}
return 0;
}
void API::http::cancel_stream()
{
_cancel_stream = true;

View File

@ -14,20 +14,21 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <sstream>
#include <regex>
#include <locale>
#include <codecvt>
#include <utility>
#include <iostream>
#include <algorithm>
#include <fstream>
#include <exception>
#include <Poco/Net/FilePartSource.h>
#include <Poco/URI.h>
#include "version.hpp"
#include "debug.hpp"
#include "mastodon-cpp.hpp"
using namespace Mastodon;
using std::make_unique;
using Poco::Net::FilePartSource;
API::API(const string &instance, const string &access_token)
: _instance(instance)
@ -35,8 +36,6 @@ API::API(const string &instance, const string &access_token)
, _useragent(string("mastodon-cpp/") + global::version)
, _http(*this, instance, access_token)
, _exceptions(false)
, _proxy("")
, _proxy_userpw("")
{
bool fash = false;
const std::regex re_gab("(?:\\.|^)gab\\.[^\\.]+$");
@ -111,9 +110,12 @@ const string API::maptostr(const parameters &map, const bool &firstparam)
return result;
}
const curlpp::Forms API::maptoformdata(const parameters &map)
unique_ptr<HTMLForm> API::maptoformdata(const parameters &map)
{
curlpp::Forms formdata;
ttdebug << "Transforming Mastodon::parameters to Poco::Net::HTMLForm.\n";
unique_ptr<HTMLForm> formdata =
make_unique<HTMLForm>(HTMLForm::ENCODING_MULTIPART);
if (map.size() == 0)
{
@ -122,51 +124,58 @@ const curlpp::Forms API::maptoformdata(const parameters &map)
for (const auto &it : map)
{
string key = it.key;
ttdebug << "Processing \"" + key + "\".\n";
// TODO: Test nested parameters.
if (const size_t pos = key.find('.') != string::npos)
{ // Nested parameters.
key.replace(pos, 1, "[");
key += ']';
}
if (it.values.size() == 1)
{ // If the file is not base64-encoded, treat as filename.
if ((it.key == "avatar" ||
it.key == "header" ||
it.key == "file") &&
if ((key == "avatar" ||
key == "header" ||
key == "file") &&
it.values.front().substr(0, 5) != "data:")
{
ttdebug << it.key << ": Filename detected.\n";
std::ifstream testfile(it.values.front());
if (testfile.good())
{
ttdebug << key << ": Filename detected.\n";
try
{
testfile.close();
formdata.push_back(
new curlpp::FormParts::File(it.key, it.values.front()));
formdata->addPart(key,
new FilePartSource(it.values.front()));
}
else
catch (const std::exception &e)
{
std::cerr << "Error: File not found: " << it.values.front()
<< std::endl;
if (exceptions())
{
std::rethrow_exception(std::current_exception());
}
// TODO: Proper error handling without exceptions.
std::cerr << "Error: Could not open file: "
<< it.values.front() << std::endl;
}
}
else
{
string key = it.key;
// Append [] to array keys.
if (key == "account_ids"
}
else if (key == "account_ids"
|| key == "exclude_types"
|| key == "media_ids"
|| key == "context")
{
key += "[]";
}
formdata.push_back(
new curlpp::FormParts::Content(key, it.values.front()));
{
key += "[]";
}
formdata->add(key, it.values.front());
}
else
{
std::transform(it.values.begin(), it.values.end(),
std::back_inserter(formdata),
[&it](const string &s)
{
return new curlpp::FormParts::Content
(it.key + "[]", s);
});
for (const string &value : it.values)
{
formdata->add(key + "[]", value);
}
}
}
@ -282,22 +291,9 @@ bool API::exceptions() const
return _exceptions;
}
void API::set_proxy(const string &proxy, const string &userpw)
void API::set_proxy(const string &hostport, const string &userpw)
{
_proxy = proxy;
_proxy_userpw = userpw;
}
void API::get_proxy(string &proxy, string &userpw) const
{
if (!_proxy.empty())
{
proxy = _proxy;
if (!_proxy_userpw.empty())
{
userpw = _proxy_userpw;
}
}
_http.set_proxy(hostport, userpw);
}
const parameters API::delete_params(const parameters &params,
@ -323,11 +319,16 @@ const parameters API::delete_params(const parameters &params,
const string Mastodon::urlencode(const std::string &str)
{
return curlpp::escape(str);
string out;
Poco::URI::encode(str, "", out);
return out;
}
const string Mastodon::urldecode(const std::string &str)
{
return curlpp::unescape(str);
string out;
Poco::URI::decode(str, out);
return out;
}
const string Mastodon::unescape_html(const string &html)

View File

@ -25,14 +25,15 @@
#include <ostream>
#include <thread>
#include <cstdint>
#include <curlpp/cURLpp.hpp>
#include <curlpp/Easy.hpp>
#include <Poco/Net/HTMLForm.h>
#include "return_types.hpp"
#include "types.hpp"
using std::string;
using std::uint8_t;
using std::unique_ptr;
using Poco::Net::HTMLForm;
/*!
* @example example01_get_public_timeline.cpp
@ -55,14 +56,13 @@ namespace Mastodon
* | Code | Explanation |
* | --------: |:-------------------------------------------|
* | 0 | No error |
* | 22 | Invalid argument |
* | 78 | URL changed (HTTP 301 or 308) |
* | 110 | Connection timed out |
* | 111 | Connection refused (check http_error_code) |
* | 113 | No route to host / Could not resolve host |
* | 192 | curlpp runtime error |
* | 193 | curlpp logic error |
* | 255 | Unknown error |
* | 1 | Invalid argument |
* | 10 | URL changed (HTTP 301 or 308) |
* | 11 | Connection timed out |
* | 12 | Connection refused (check http_error_code) |
* | 13 | No route to host / Could not resolve host |
* | 14 | Encryption error |
* | 127 | Unknown error |
*
* @since before 0.11.0
*/
@ -102,7 +102,7 @@ namespace Mastodon
*/
return_call request(const http_method &meth,
const string &path,
const curlpp::Forms &formdata);
HTMLForm &formdata);
/*!
* @brief HTTP Request for streams.
@ -142,6 +142,16 @@ namespace Mastodon
*/
std::mutex &get_mutex();
/*!
* @brief Set proxy. Do not call this directly.
*
* @param hostport host[:port]
* @param userpw user[:password] (optional)
*
* @since 0.110.0
*/
void set_proxy(const string &hostport, const string &userpw = "");
private:
const API &parent;
const string _instance;
@ -153,7 +163,7 @@ namespace Mastodon
return_call request_common(const http_method &meth,
const string &path,
const curlpp::Forms &formdata,
HTMLForm &formdata,
string &answer);
size_t callback_write(char* data, size_t size, size_t nmemb,
string *oss);
@ -412,8 +422,7 @@ namespace Mastodon
/*!
* @brief Turn exceptions on or off. Defaults to off.
*
* This applies to exceptions from curlpp. curlpp::RuntimeError
* and curlpp::LogicError.
* Most exceptions will be thrown at you to handle if on.
*
* @param value true for on, false for off
*
@ -439,25 +448,15 @@ namespace Mastodon
/*!
* @brief Sets the proxy.
*
* Since mastodon-cpp is built on libcurl, it respects the same
* proxy environment variables. See `man curl`.
* Both the username and the password will be URL decoded
* before use.
*
* @param proxy See `man 3 CURLOPT_PROXY`
* @param userpw See `man 3 CURLOPT_PROXYUSERPWD` (optional)
* @param hostport host[:port]
* @param userpw username[:password] (optional)
*
* @since 0.15.0
*/
void set_proxy(const string &proxy, const string &userpw = "");
/*!
* @brief For internal use
*
* @param proxy URL
* @param userpw username:password
*
* @since 0.15.1
*/
void get_proxy(string &proxy, string &userpw) const;
void set_proxy(const string &hostport, const string &userpw = "");
/*!
* @brief Make a GET request that doesn't require parameters.
@ -514,7 +513,7 @@ namespace Mastodon
*/
void get_stream(const Mastodon::API::v1 &call,
const parameters &parameters,
std::unique_ptr<Mastodon::API::http> &ptr,
unique_ptr<Mastodon::API::http> &ptr,
string &stream);
/*!
@ -527,7 +526,7 @@ namespace Mastodon
* @since 0.100.0
*/
void get_stream(const Mastodon::API::v1 &call,
std::unique_ptr<Mastodon::API::http> &ptr,
unique_ptr<Mastodon::API::http> &ptr,
string &stream);
/*!
@ -540,7 +539,7 @@ namespace Mastodon
* @since 0.100.0
*/
void get_stream(const string &call,
std::unique_ptr<Mastodon::API::http> &ptr,
unique_ptr<Mastodon::API::http> &ptr,
string &stream);
/*!
@ -646,8 +645,6 @@ namespace Mastodon
string _useragent;
http _http;
bool _exceptions;
string _proxy;
string _proxy_userpw;
/*!
* @brief Converts map of parameters into a string.
@ -665,9 +662,9 @@ namespace Mastodon
*
* @param map Map of parameters
*
* @return Form data as curlpp::Forms
* @return Form data as Poco::Net::HTMLForm.
*/
const curlpp::Forms maptoformdata(const parameters &map);
unique_ptr<HTMLForm> maptoformdata(const parameters &map);
/*!
* @brief Delete Mastodon::param from Mastodon::parameters.
@ -684,13 +681,12 @@ namespace Mastodon
};
/*!
* @brief Percent-encodes a string. This is done automatically, unless
* you make a custom request.
* @brief Percent-encodes a string.
*
* Calls curlpp::escape(str).
*
* The only time you should use this, is if you use
* get(const string &call, string &answer).
* This is done automatically where necessary. The only time you
* should use this, is if you use get(const string &call, string
* &answer).
*
* See RFC 3986 section 2.1 for more info.
*
@ -705,8 +701,6 @@ namespace Mastodon
/*!
* @brief Decodes a percent-encoded string.
*
* Calls curlpp::unescape(str).
*
* See RFC 3986 section 2.1 for more info.
*
* @param str The string to decode.

View File

@ -57,4 +57,13 @@ namespace Mastodon
error_message = em;
http_error_code = hec;
}
return_call::return_call(const error ec, const string &em,
const uint16_t hec, const string &a)
: answer(a)
{
error_code = static_cast<uint8_t>(ec);
error_message = em;
http_error_code = hec;
}
}

View File

@ -19,6 +19,7 @@
#include <cstdint>
#include <string>
#include "types.hpp"
using std::uint8_t;
using std::uint16_t;
@ -113,6 +114,19 @@ namespace Mastodon
return_call(const uint8_t ec, const string &em,
const uint16_t hec, const string &a);
/*!
* @brief Return type for Mastodon::API.
*
* @param ec Error code
* @param em Error message
* @param hec HTTP error code
* @param a Answer
*
* @since 0.110.0
*/
return_call(const error ec, const string &em,
const uint16_t hec, const string &a);
/*!
* @brief Same es return_call::answer.
*

View File

@ -93,6 +93,18 @@ namespace Mastodon
DELETE,
GET_STREAM
};
enum class error
{
OK = 0,
INVALID_ARGUMENT = 1,
URL_CHANGED = 10,
CONNECTION_TIMEOUT = 11,
CONNECTION_REFUSED = 12,
DNS = 13,
ENCRYPTION = 14,
UNKNOWN = 127
};
}
#endif // MASTODON_CPP_TYPES_HPP

View File

@ -1,20 +1,23 @@
include(CTest)
file(GLOB sources_tests test_*.cpp */test_*.cpp)
file(GLOB_RECURSE sources_tests test_*.cpp)
configure_file("test.gif" "${CMAKE_CURRENT_BINARY_DIR}" COPYONLY)
find_package(Catch2)
if(Catch2_FOUND) # Catch 2.x
if(Catch2_FOUND) # Catch 2.x
include(Catch)
add_executable(all_tests main.cpp ${sources_tests})
target_link_libraries(all_tests ${PROJECT_NAME} Catch2::Catch2)
target_link_libraries(all_tests PRIVATE ${PROJECT_NAME} Catch2::Catch2)
target_include_directories(all_tests PRIVATE "/usr/include/catch2")
catch_discover_tests(all_tests EXTRA_ARGS "${EXTRA_TEST_ARGS}")
else() # Catch 1.x
else() # Catch 1.x
if(EXISTS "/usr/include/catch.hpp")
message(STATUS "Catch 1.x found.")
foreach(src ${sources_tests})
get_filename_component(bin ${src} NAME_WE)
add_executable(${bin} main.cpp ${src})
target_link_libraries(${bin} ${PROJECT_NAME})
target_link_libraries(${bin} PRIVATE ${PROJECT_NAME})
add_test(${bin} ${bin} "${EXTRA_TEST_ARGS}")
endforeach()
else()