Skip to content

Commit

Permalink
Merge pull request #919 from aschnell/master
Browse files Browse the repository at this point in the history
- handle content-length of stomp in zypper plugin
  • Loading branch information
aschnell authored Jul 5, 2024
2 parents 82b4986 + e596a1f commit 20aef71
Show file tree
Hide file tree
Showing 15 changed files with 478 additions and 96 deletions.
2 changes: 1 addition & 1 deletion Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#

SUBDIRS = snapper dbus server client scripts pam data doc po examples \
testsuite testsuite-real testsuite-cmp zypp-plugin
testsuite testsuite-real testsuite-cmp stomp zypp-plugin

AUTOMAKE_OPTIONS = foreign dist-xz no-dist-gzip

Expand Down
2 changes: 2 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,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
Expand Down
6 changes: 6 additions & 0 deletions package/snapper.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
-------------------------------------------------------------------
Fri Jul 05 16:03:42 CEST 2024 - aschnell@suse.com

- handle content-length of stomp in zypper plugin
(gh#openSUSE/snapper#918)

-------------------------------------------------------------------
Wed May 22 17:21:18 CEST 2024 - aschnell@suse.com

Expand Down
2 changes: 2 additions & 0 deletions stomp/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.lo
*.la
10 changes: 10 additions & 0 deletions stomp/Makefile.am
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#
# Makefile.am for snapper/stomp
#

SUBDIRS = . testsuite

noinst_LTLIBRARIES = libstomp.la

libstomp_la_SOURCES = \
Stomp.h Stomp.cc
216 changes: 216 additions & 0 deletions stomp/Stomp.cc
Original file line number Diff line number Diff line change
@@ -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;
}

}
60 changes: 60 additions & 0 deletions stomp/Stomp.h
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions stomp/testsuite/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
*.log
*.o
*.test
*.trs
test-suite.log
17 changes: 17 additions & 0 deletions stomp/testsuite/Makefile.am
Original file line number Diff line number Diff line change
@@ -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)

29 changes: 29 additions & 0 deletions stomp/testsuite/escape.cc
Original file line number Diff line number Diff line change
@@ -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");
}
Loading

0 comments on commit 20aef71

Please sign in to comment.