From 570c6765b07f39dd744fc101f44db4f5862e76f4 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sun, 21 Jan 2018 12:17:18 +0100 Subject: input/tidal: parse and report userMessage from error responses --- src/input/plugins/TidalErrorParser.cxx | 101 ++++++++++++++++++++++++++++++++ src/input/plugins/TidalErrorParser.hxx | 74 +++++++++++++++++++++++ src/input/plugins/TidalLoginRequest.cxx | 17 +++++- src/input/plugins/TidalLoginRequest.hxx | 4 ++ src/input/plugins/TidalTrackRequest.cxx | 17 +++++- src/input/plugins/TidalTrackRequest.hxx | 4 ++ 6 files changed, 213 insertions(+), 4 deletions(-) create mode 100644 src/input/plugins/TidalErrorParser.cxx create mode 100644 src/input/plugins/TidalErrorParser.hxx (limited to 'src') diff --git a/src/input/plugins/TidalErrorParser.cxx b/src/input/plugins/TidalErrorParser.cxx new file mode 100644 index 000000000..71d523660 --- /dev/null +++ b/src/input/plugins/TidalErrorParser.cxx @@ -0,0 +1,101 @@ +/* + * Copyright 2003-2018 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "TidalErrorParser.hxx" +#include "lib/yajl/Callbacks.hxx" +#include "util/ConstBuffer.hxx" +#include "util/RuntimeError.hxx" + +using Wrapper = Yajl::CallbacksWrapper; +static constexpr yajl_callbacks tidal_error_parser_callbacks = { + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + Wrapper::String, + nullptr, + Wrapper::MapKey, + Wrapper::EndMap, + nullptr, + nullptr, +}; + +TidalErrorParser::TidalErrorParser(unsigned _status, + const std::multimap &headers) + :status(_status), + parser(&tidal_error_parser_callbacks, nullptr, this) +{ + auto i = headers.find("content-type"); + if (i == headers.end() || i->second.find("/json") == i->second.npos) + throw FormatRuntimeError("Status %u from Tidal", status); +} + +void +TidalErrorParser::OnData(ConstBuffer data) +{ + parser.Parse((const unsigned char *)data.data, data.size); +} + +void +TidalErrorParser::OnEnd() +{ + parser.CompleteParse(); + + if (!message.empty()) + throw FormatRuntimeError("Error from Tidal: %s", + message.c_str()); + else + throw FormatRuntimeError("Status %u from Tidal", status); +} + +inline bool +TidalErrorParser::String(StringView value) noexcept +{ + switch (state) { + case State::NONE: + break; + + case State::USER_MESSAGE: + message.assign(value.data, value.size); + break; + } + + return true; +} + +inline bool +TidalErrorParser::MapKey(StringView value) noexcept +{ + if (value.Equals("userMessage")) + state = State::USER_MESSAGE; + else + state = State::NONE; + + return true; +} + +inline bool +TidalErrorParser::EndMap() noexcept +{ + state = State::NONE; + + return true; +} diff --git a/src/input/plugins/TidalErrorParser.hxx b/src/input/plugins/TidalErrorParser.hxx new file mode 100644 index 000000000..0ccf2fe8a --- /dev/null +++ b/src/input/plugins/TidalErrorParser.hxx @@ -0,0 +1,74 @@ +/* + * Copyright 2003-2018 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef TIDAL_ERROR_PARSER_HXX +#define TIDAL_ERROR_PARSER_HXX + +#include "check.h" +#include "lib/yajl/Handle.hxx" + +#include +#include +#include + +template struct ConstBuffer; +struct StringView; + +/** + * Parse an error JSON response. + */ +class TidalErrorParser { + const unsigned status; + + Yajl::Handle parser; + + enum class State { + NONE, + USER_MESSAGE, + } state = State::NONE; + + std::string message; + +public: + /** + * May throw if there is a formal error in the response + * headers. + */ + TidalErrorParser(unsigned status, + const std::multimap &headers); + + /** + * Feed response body data into the JSON parser. + */ + void OnData(ConstBuffer data); + + /** + * Throw an exception describing the error condition. Call + * this at the end of the response body. + */ + void OnEnd(); + +public: + /* yajl callbacks */ + bool String(StringView value) noexcept; + bool MapKey(StringView value) noexcept; + bool EndMap() noexcept; +}; + +#endif diff --git a/src/input/plugins/TidalLoginRequest.cxx b/src/input/plugins/TidalLoginRequest.cxx index 71e4ce7e6..e378ef9ad 100644 --- a/src/input/plugins/TidalLoginRequest.cxx +++ b/src/input/plugins/TidalLoginRequest.cxx @@ -19,6 +19,7 @@ #include "config.h" #include "TidalLoginRequest.hxx" +#include "TidalErrorParser.hxx" #include "lib/curl/Form.hxx" #include "lib/yajl/Callbacks.hxx" #include "util/RuntimeError.hxx" @@ -69,8 +70,10 @@ void TidalLoginRequest::OnHeaders(unsigned status, std::multimap &&headers) { - if (status != 200) - throw FormatRuntimeError("Status %u from Tidal", status); + if (status != 200) { + error_parser = std::make_unique(status, headers); + return; + } auto i = headers.find("content-type"); if (i == headers.end() || i->second.find("/json") == i->second.npos) @@ -82,12 +85,22 @@ TidalLoginRequest::OnHeaders(unsigned status, void TidalLoginRequest::OnData(ConstBuffer data) { + if (error_parser) { + error_parser->OnData(data); + return; + } + parser.Parse((const unsigned char *)data.data, data.size); } void TidalLoginRequest::OnEnd() { + if (error_parser) { + error_parser->OnEnd(); + return; + } + parser.CompleteParse(); if (session.empty()) diff --git a/src/input/plugins/TidalLoginRequest.hxx b/src/input/plugins/TidalLoginRequest.hxx index d4b4820e5..e5d17d6df 100644 --- a/src/input/plugins/TidalLoginRequest.hxx +++ b/src/input/plugins/TidalLoginRequest.hxx @@ -27,9 +27,11 @@ #include "lib/yajl/Handle.hxx" #include +#include #include class CurlRequest; +class TidalErrorParser; /** * Callback class for #TidalLoginRequest. @@ -52,6 +54,8 @@ class TidalLoginRequest final : CurlResponseHandler { CurlRequest request; + std::unique_ptr error_parser; + Yajl::Handle parser; enum class State { diff --git a/src/input/plugins/TidalTrackRequest.cxx b/src/input/plugins/TidalTrackRequest.cxx index c0b8d540d..e8f27ae7a 100644 --- a/src/input/plugins/TidalTrackRequest.cxx +++ b/src/input/plugins/TidalTrackRequest.cxx @@ -19,6 +19,7 @@ #include "config.h" #include "TidalTrackRequest.hxx" +#include "TidalErrorParser.hxx" #include "lib/yajl/Callbacks.hxx" #include "util/RuntimeError.hxx" @@ -71,8 +72,10 @@ void TidalTrackRequest::OnHeaders(unsigned status, std::multimap &&headers) { - if (status != 200) - throw FormatRuntimeError("Status %u from Tidal", status); + if (status != 200) { + error_parser = std::make_unique(status, headers); + return; + } auto i = headers.find("content-type"); if (i == headers.end() || i->second.find("/json") == i->second.npos) @@ -84,12 +87,22 @@ TidalTrackRequest::OnHeaders(unsigned status, void TidalTrackRequest::OnData(ConstBuffer data) { + if (error_parser) { + error_parser->OnData(data); + return; + } + parser.Parse((const unsigned char *)data.data, data.size); } void TidalTrackRequest::OnEnd() { + if (error_parser) { + error_parser->OnEnd(); + return; + } + parser.CompleteParse(); if (url.empty()) diff --git a/src/input/plugins/TidalTrackRequest.hxx b/src/input/plugins/TidalTrackRequest.hxx index eb580c624..829cd053e 100644 --- a/src/input/plugins/TidalTrackRequest.hxx +++ b/src/input/plugins/TidalTrackRequest.hxx @@ -27,9 +27,11 @@ #include "lib/yajl/Handle.hxx" #include +#include #include class CurlRequest; +class TidalErrorParser; /** * Callback class for #TidalTrackRequest. @@ -54,6 +56,8 @@ class TidalTrackRequest final : CurlResponseHandler { CurlRequest request; + std::unique_ptr error_parser; + Yajl::Handle parser; enum class State { -- cgit v1.2.3