Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
SUSE:SLE-15-SP3:Update
snapper
pr919.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File pr919.patch of Package snapper
diff --git a/Makefile.am b/Makefile.am index 69b66787..fe6a1b9e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3,7 +3,7 @@ # SUBDIRS = snapper examples dbus server client scripts pam data doc po \ - testsuite testsuite-real testsuite-cmp zypp-plugin + testsuite testsuite-real testsuite-cmp stomp zypp-plugin AUTOMAKE_OPTIONS = foreign dist-bzip2 no-dist-gzip diff --git a/configure.ac b/configure.ac index b5fe5f4d..53a095f8 100644 --- a/configure.ac +++ b/configure.ac @@ -232,6 +232,8 @@ AC_CONFIG_FILES([ testsuite/Makefile testsuite-real/Makefile testsuite-cmp/Makefile + stomp/Makefile + stomp/testsuite/Makefile zypp-plugin/Makefile zypp-plugin/testsuite/Makefile package/snapper.spec:snapper.spec.in diff --git a/stomp/.gitignore b/stomp/.gitignore new file mode 100644 index 00000000..66a3f3fe --- /dev/null +++ b/stomp/.gitignore @@ -0,0 +1,2 @@ +*.lo +*.la diff --git a/stomp/Makefile.am b/stomp/Makefile.am new file mode 100644 index 00000000..ef425daf --- /dev/null +++ b/stomp/Makefile.am @@ -0,0 +1,10 @@ +# +# Makefile.am for snapper/stomp +# + +SUBDIRS = . testsuite + +noinst_LTLIBRARIES = libstomp.la + +libstomp_la_SOURCES = \ + Stomp.h Stomp.cc diff --git a/stomp/Stomp.cc b/stomp/Stomp.cc new file mode 100644 index 00000000..ebdd2af9 --- /dev/null +++ b/stomp/Stomp.cc @@ -0,0 +1,216 @@ +/* + * Copyright (c) [2019-2024] SUSE LLC + * + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as published + * by the Free Software Foundation. + * + * 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, contact SUSE LLC. + * + * To contact SUSE about this file by physical or electronic mail, you may + * find current contact information at www.suse.com. + */ + + +#include <iostream> +#include <regex> + + +using namespace std; + + +#include "Stomp.h" + + +namespace Stomp +{ + + Message + read_message(istream& is) + { + static const regex rx_command("[A-Za-z0-9_]+", regex::extended); + + enum class State { Start, Headers, Body } state = State::Start; + bool has_content_length = false; + ssize_t content_length = 0; + + Message msg; + + while (!is.eof()) + { + string line; + getline(is, line); + + if (state == State::Start) + { + if (is.eof()) + return msg; // empty + + if (line.empty()) + continue; + + if (regex_match(line, rx_command)) + { + msg = Message(); + msg.command = line; + state = State::Headers; + } + else + { + throw runtime_error("stomp error: expected a command, got '" + line + "'"); + } + } + else if (state == State::Headers) + { + if (line.empty()) + { + state = State::Body; + + if (has_content_length) + { + if (content_length > 0) + { + vector<char> buf(content_length); + is.read(buf.data(), content_length); + msg.body.assign(buf.data(), content_length); + } + + // still read the \0 that terminates the frame + char buf2 = '-'; + is.read(&buf2, 1); + if (buf2 != '\0') + throw runtime_error("stomp error: missing \\0 at frame end"); + } + else + { + getline(is, msg.body, '\0'); + } + + return msg; + } + else + { + string::size_type pos = line.find(':'); + if (pos == string::npos) + throw runtime_error("stomp error: expected a header or new line, got '" + line + "'"); + + string key = unescape_header(line.substr(0, pos)); + string value = unescape_header(line.substr(pos + 1)); + + if (key == "content-length") + { + has_content_length = true; + content_length = std::stol(value.c_str()); + } + + msg.headers[key] = value; + } + } + } + + throw runtime_error("stomp error: expected a message, got a part of it"); + } + + + void + write_message(ostream& os, const Message& msg) + { + os << msg.command << '\n'; + for (auto it : msg.headers) + os << escape_header(it.first) << ':' << escape_header(it.second) << '\n'; + os << '\n'; + os << msg.body << '\0'; + os.flush(); + } + + + Message + ack() + { + Message msg; + msg.command = "ACK"; + return msg; + } + + + Message + nack() + { + Message msg; + msg.command = "NACK"; + return msg; + } + + + std::string + escape_header(const std::string& in) + { + string out; + + for (const char c : in) + { + switch (c) + { + case '\r': + out += "\\r"; break; + case '\n': + out += "\\n"; break; + case ':': + out += "\\c"; break; + case '\\': + out += "\\\\"; break; + + default: + out += c; + } + } + + return out; + } + + + std::string + unescape_header(const std::string& in) + { + string out; + + for (string::const_iterator it = in.begin(); it != in.end(); ++it) + { + if (*it == '\\') + { + if (++it == in.end()) + throw runtime_error("stomp error: invalid start of escape sequence"); + + switch (*it) + { + case 'r': + out += '\r'; break; + case 'n': + out += '\n'; break; + case 'c': + out += ':'; break; + case '\\': + out += '\\'; break; + + default: + throw runtime_error("stomp error: unknown escape sequence"); + } + } + else + { + out += *it; + } + } + + return out; + } + +} diff --git a/stomp/Stomp.h b/stomp/Stomp.h new file mode 100644 index 00000000..2220d8dc --- /dev/null +++ b/stomp/Stomp.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) [2019-2024] SUSE LLC + * + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as published + * by the Free Software Foundation. + * + * 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, contact SUSE LLC. + * + * To contact SUSE about this file by physical or electronic mail, you may + * find current contact information at www.suse.com. + */ + + +#ifndef SNAPPER_STOMP_H +#define SNAPPER_STOMP_H + + +#include <istream> +#include <ostream> +#include <string> +#include <map> + + +/** + * A tiny STOMP (https://stomp.github.io/) implementation. + */ + +namespace Stomp +{ + + struct Message + { + std::string command; + std::map<std::string, std::string> headers; + std::string body; + }; + + + Message read_message(std::istream& is); + void write_message(std::ostream& os, const Message& msg); + + Message ack(); + Message nack(); + + std::string escape_header(const std::string& in); + std::string unescape_header(const std::string& in); + +} + + +#endif diff --git a/stomp/testsuite/.gitignore b/stomp/testsuite/.gitignore new file mode 100644 index 00000000..85f0d0bb --- /dev/null +++ b/stomp/testsuite/.gitignore @@ -0,0 +1,5 @@ +*.log +*.o +*.test +*.trs +test-suite.log diff --git a/stomp/testsuite/Makefile.am b/stomp/testsuite/Makefile.am new file mode 100644 index 00000000..424ce182 --- /dev/null +++ b/stomp/testsuite/Makefile.am @@ -0,0 +1,17 @@ +# +# Makefile.am for snapper/stomp/testsuite +# + +SUBDIRS = . + +LDADD = \ + ../libstomp.la \ + -lboost_unit_test_framework + +check_PROGRAMS = \ + read1.test write1.test escape.test + +AM_DEFAULT_SOURCE_EXT = .cc + +TESTS = $(check_PROGRAMS) + diff --git a/stomp/testsuite/escape.cc b/stomp/testsuite/escape.cc new file mode 100644 index 00000000..cad9aa22 --- /dev/null +++ b/stomp/testsuite/escape.cc @@ -0,0 +1,29 @@ + +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE snapper + +#include <boost/test/unit_test.hpp> + +#include "../Stomp.h" + + +using namespace std; +using namespace Stomp; + + +BOOST_AUTO_TEST_CASE(escape) +{ + BOOST_CHECK_EQUAL(Stomp::escape_header("hello"), "hello"); + + BOOST_CHECK_EQUAL(Stomp::escape_header("hello\nworld"), "hello\\nworld"); + BOOST_CHECK_EQUAL(Stomp::escape_header("hello:world"), "hello\\cworld"); +} + + +BOOST_AUTO_TEST_CASE(unescape) +{ + BOOST_CHECK_EQUAL(Stomp::unescape_header("hello"), "hello"); + + BOOST_CHECK_EQUAL(Stomp::unescape_header("hello\\nworld"), "hello\nworld"); + BOOST_CHECK_EQUAL(Stomp::unescape_header("hello\\cworld"), "hello:world"); +} diff --git a/stomp/testsuite/read1.cc b/stomp/testsuite/read1.cc new file mode 100644 index 00000000..2d89dd70 --- /dev/null +++ b/stomp/testsuite/read1.cc @@ -0,0 +1,73 @@ + +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE snapper + +#include <boost/test/unit_test.hpp> + +#include "../Stomp.h" + + +using namespace std; +using namespace Stomp; + + +const string null("\0", 1); + + +BOOST_AUTO_TEST_CASE(test1) +{ + // no optional content-lenght + + istringstream s1("HELLO\nkey:value\n\nWORLD" + null); + istream s2(s1.rdbuf()); + + Message msg = read_message(s2); + + BOOST_CHECK_EQUAL(s2.peek(), -1); + + BOOST_CHECK_EQUAL(msg.command, "HELLO"); + + BOOST_CHECK_EQUAL(msg.headers.size(), 1); + BOOST_CHECK_EQUAL(msg.headers["key"], "value"); + + BOOST_CHECK_EQUAL(msg.body, "WORLD"); +} + + +BOOST_AUTO_TEST_CASE(test2) +{ + // optional content-lenght + + istringstream s1("HELLO\nkey:value\ncontent-length:5\n\nWORLD" + null); + istream s2(s1.rdbuf()); + + Message msg = read_message(s2); + + BOOST_CHECK_EQUAL(s2.peek(), -1); + + BOOST_CHECK_EQUAL(msg.command, "HELLO"); + + BOOST_CHECK_EQUAL(msg.headers.size(), 2); + BOOST_CHECK_EQUAL(msg.headers["key"], "value"); + BOOST_CHECK_EQUAL(msg.headers["content-length"], "5"); + + BOOST_CHECK_EQUAL(msg.body, "WORLD"); +} + + +BOOST_AUTO_TEST_CASE(escape1) +{ + // special characters in header + + istringstream s1("HELLO\nGermany\\cSpain:2\\c1\n\nWORLD" + null); + istream s2(s1.rdbuf()); + + Message msg = read_message(s2); + + BOOST_CHECK_EQUAL(msg.command, "HELLO"); + + BOOST_CHECK_EQUAL(msg.headers.size(), 1); + BOOST_CHECK_EQUAL(msg.headers["Germany:Spain"], "2:1"); + + BOOST_CHECK_EQUAL(msg.body, "WORLD"); +} diff --git a/stomp/testsuite/write1.cc b/stomp/testsuite/write1.cc new file mode 100644 index 00000000..a231605d --- /dev/null +++ b/stomp/testsuite/write1.cc @@ -0,0 +1,46 @@ + +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE snapper + +#include <boost/test/unit_test.hpp> + +#include "../Stomp.h" + + +using namespace std; +using namespace Stomp; + + +const string null("\0", 1); + + +BOOST_AUTO_TEST_CASE(test1) +{ + Message msg; + msg.command = "HELLO"; + msg.headers["key"] = "value"; + msg.body = "WORLD"; + + ostringstream s1; + write_message(s1, msg); + string s2 = s1.str(); + + BOOST_CHECK_EQUAL(s2, "HELLO\nkey:value\n\nWORLD" + null); +} + + +BOOST_AUTO_TEST_CASE(escape1) +{ + // special characters in header + + Message msg; + msg.command = "HELLO"; + msg.headers["Germany:Spain"] = "2:1"; + msg.body = "WORLD"; + + ostringstream s1; + write_message(s1, msg); + string s2 = s1.str(); + + BOOST_CHECK_EQUAL(s2, "HELLO\nGermany\\cSpain:2\\c1\n\nWORLD" + null); +} diff --git a/zypp-plugin/Makefile.am b/zypp-plugin/Makefile.am index 3e4ddf6c..2fd99b8c 100644 --- a/zypp-plugin/Makefile.am +++ b/zypp-plugin/Makefile.am @@ -21,6 +21,7 @@ snapper_zypp_plugin_LDADD = \ ../client/libclient.la \ ../snapper/libsnapper.la \ ../dbus/libdbus.la \ + ../stomp/libstomp.la \ $(JSONC_LIBS) check_PROGRAMS = solvable_matcher.test forwarding-zypp-plugin @@ -31,6 +32,7 @@ forwarding_zypp_plugin_SOURCES = \ forwarding_zypp_plugin_LDADD = \ ../snapper/libsnapper.la \ + ../stomp/libstomp.la \ -lboost_system \ -lpthread @@ -41,6 +43,7 @@ solvable_matcher_test_SOURCES = \ solvable_matcher_test_LDADD = \ ../snapper/libsnapper.la \ + ../stomp/libstomp.la \ $(XML2_LIBS) \ -lboost_unit_test_framework diff --git a/zypp-plugin/zypp_plugin.cc b/zypp-plugin/zypp_plugin.cc index 26403376..943963f4 100644 --- a/zypp-plugin/zypp_plugin.cc +++ b/zypp-plugin/zypp_plugin.cc @@ -38,79 +38,6 @@ return 0; } -void ZyppPlugin::write_message(ostream& os, const Message& msg) { - os << msg.command << endl; - for(auto it: msg.headers) { - os << it.first << ':' << it.second << endl; - } - os << endl; - os << msg.body << '\0'; - os.flush(); -} - -ZyppPlugin::Message ZyppPlugin::read_message(istream& is) { - enum class State { - Start, - Headers, - Body - } state = State::Start; - - Message msg; - - while(!is.eof()) { - string line; - - getline(is, line); - boost::trim_right(line); - - if (state == State::Start) { - if (is.eof()) - return msg; //empty - - if (line.empty()) - continue; - - static const regex rx_word("[A-Za-z0-9_]+", regex::extended); - if (regex_match(line, rx_word)) - { - msg = Message(); - msg.command = line; - state = State::Headers; - } - else - { - throw runtime_error("Plugin protocol error: expected a command. Got '" + line + "'"); - } - } - else if (state == State::Headers) { - if (line.empty()) { - state = State::Body; - getline(is, msg.body, '\0'); - - return msg; - } - else - { - static const regex rx_header("([A-Za-z0-9_]+):[ \t]*(.+)", regex::extended); - smatch match; - - if (regex_match(line, match, rx_header)) - { - string key = match[1]; - string value = match[2]; - msg.headers[key] = value; - } - else - { - throw runtime_error("Plugin protocol error: expected a header or new line. Got '" + line + "'"); - } - } - } - } - - throw runtime_error("Plugin protocol error: expected a message, got a part of it"); -} - ZyppPlugin::Message ZyppPlugin::dispatch(const Message& msg) { if (msg.command == "_DISCONNECT") { return ack(); diff --git a/zypp-plugin/zypp_plugin.h b/zypp-plugin/zypp_plugin.h index cf51d05a..ed356178 100644 --- a/zypp-plugin/zypp_plugin.h +++ b/zypp-plugin/zypp_plugin.h @@ -23,18 +23,15 @@ #define ZYPP_PLUGIN_H #include <iostream> -#include <map> -#include <string> -class ZyppPlugin { +#include "../stomp/Stomp.h" + +class ZyppPlugin +{ public: // Plugin message aka frame // https://doc.opensuse.org/projects/libzypp/SLE12SP2/zypp-plugins.html - struct Message { - std::string command; - std::map<std::string, std::string> headers; - std::string body; - }; + using Message = Stomp::Message; /// Where the protocol reads from std::istream& pin; @@ -49,24 +46,22 @@ , pout(out) , plog(log) {} - virtual ~ZyppPlugin() {} + virtual ~ZyppPlugin() = default; virtual int main(); protected: + + Message read_message(std::istream& is) const { return Stomp::read_message(is); } + void write_message(std::ostream& os, const Message& msg) const { Stomp::write_message(os, msg); } + /// Handle a message and return a reply. // Derived classes should override it. // The base acks a _DISCONNECT and replies _ENOMETHOD to everything else. virtual Message dispatch(const Message&); - Message read_message(std::istream& is); - void write_message(std::ostream& os, const Message& msg); + Message ack() const { return Stomp::ack(); } - Message ack() { - Message a; - a.command = "ACK"; - return a; - } }; -#endif //ZYPP_PLUGIN_H +#endif
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor