1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
3 #include "base/netstring.hpp"
4 #include "base/debug.hpp"
5 #include "base/tlsstream.hpp"
10 #include <boost/asio/buffer.hpp>
11 #include <boost/asio/read.hpp>
12 #include <boost/asio/spawn.hpp>
13 #include <boost/asio/write.hpp>
15 using namespace icinga;
18 * Reads data from a stream in netstring format.
20 * @param stream The stream to read from.
21 * @param[out] str The String that has been read from the IOQueue.
22 * @returns true if a complete String was read from the IOQueue, false otherwise.
23 * @exception invalid_argument The input stream is invalid.
24 * @see https://github.com/PeterScott/netstring-c/blob/master/netstring.c
26 StreamReadStatus NetString::ReadStringFromStream(const Stream::Ptr& stream, String *str, StreamReadContext& context,
27 bool may_wait, ssize_t maxMessageLength)
32 if (context.MustRead) {
33 if (!context.FillFromStream(stream, may_wait)) {
38 context.MustRead = false;
41 size_t header_length = 0;
43 for (size_t i = 0; i < context.Size; i++) {
44 if (context.Buffer[i] == ':') {
47 /* make sure there's a header */
48 if (header_length == 0)
49 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid NetString (no length specifier)"));
53 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid NetString (missing :)"));
56 if (header_length == 0) {
57 context.MustRead = true;
58 return StatusNeedData;
61 /* no leading zeros allowed */
62 if (context.Buffer[0] == '0' && isdigit(context.Buffer[1]))
63 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid NetString (leading zero)"));
68 for (i = 0; i < header_length && isdigit(context.Buffer[i]); i++) {
69 /* length specifier must have at most 9 characters */
71 BOOST_THROW_EXCEPTION(std::invalid_argument("Length specifier must not exceed 9 characters"));
73 len = len * 10 + (context.Buffer[i] - '0');
76 /* read the whole message */
77 size_t data_length = len + 1;
79 if (maxMessageLength >= 0 && data_length > (size_t)maxMessageLength) {
80 std::stringstream errorMessage;
81 errorMessage << "Max data length exceeded: " << (maxMessageLength / 1024) << " KB";
83 BOOST_THROW_EXCEPTION(std::invalid_argument(errorMessage.str()));
86 char *data = context.Buffer + header_length + 1;
88 if (context.Size < header_length + 1 + data_length) {
89 context.MustRead = true;
90 return StatusNeedData;
94 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid NetString (missing ,)"));
96 *str = String(&data[0], &data[len]);
98 context.DropData(header_length + 1 + len + 1);
100 return StatusNewItem;
104 * Writes data into a stream using the netstring format and returns bytes written.
106 * @param stream The stream.
107 * @param str The String that is to be written.
109 * @return The amount of bytes written.
111 size_t NetString::WriteStringToStream(const Stream::Ptr& stream, const String& str)
113 std::ostringstream msgbuf;
114 WriteStringToStream(msgbuf, str);
116 String msg = msgbuf.str();
117 stream->Write(msg.CStr(), msg.GetLength());
118 return msg.GetLength();
122 * Reads data from a stream in netstring format.
124 * @param stream The stream to read from.
125 * @returns The String that has been read from the IOQueue.
126 * @exception invalid_argument The input stream is invalid.
127 * @see https://github.com/PeterScott/netstring-c/blob/master/netstring.c
129 String NetString::ReadStringFromStream(const std::shared_ptr<AsioTlsStream>& stream,
130 ssize_t maxMessageLength)
132 namespace asio = boost::asio;
135 bool leadingZero = false;
137 for (uint_fast8_t readBytes = 0;; ++readBytes) {
141 asio::mutable_buffer byteBuf (&byte, 1);
142 asio::read(*stream, byteBuf);
146 if (readBytes == 9) {
147 BOOST_THROW_EXCEPTION(std::invalid_argument("Length specifier must not exceed 9 characters"));
151 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid NetString (leading zero)"));
154 len = len * 10u + size_t(byte - '0');
156 if (!readBytes && byte == '0') {
159 } else if (byte == ':') {
161 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid NetString (no length specifier)"));
166 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid NetString (missing :)"));
170 if (maxMessageLength >= 0 && len > maxMessageLength) {
171 std::stringstream errorMessage;
172 errorMessage << "Max data length exceeded: " << (maxMessageLength / 1024) << " KB";
174 BOOST_THROW_EXCEPTION(std::invalid_argument(errorMessage.str()));
180 payload.Append(len, 0);
182 asio::mutable_buffer payloadBuf (&*payload.Begin(), payload.GetLength());
183 asio::read(*stream, payloadBuf);
189 asio::mutable_buffer trailerBuf (&trailer, 1);
190 asio::read(*stream, trailerBuf);
193 if (trailer != ',') {
194 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid NetString (missing ,)"));
197 return std::move(payload);
201 * Reads data from a stream in netstring format.
203 * @param stream The stream to read from.
204 * @returns The String that has been read from the IOQueue.
205 * @exception invalid_argument The input stream is invalid.
206 * @see https://github.com/PeterScott/netstring-c/blob/master/netstring.c
208 String NetString::ReadStringFromStream(const std::shared_ptr<AsioTlsStream>& stream,
209 boost::asio::yield_context yc, ssize_t maxMessageLength)
211 namespace asio = boost::asio;
214 bool leadingZero = false;
216 for (uint_fast8_t readBytes = 0;; ++readBytes) {
220 asio::mutable_buffer byteBuf (&byte, 1);
221 asio::async_read(*stream, byteBuf, yc);
225 if (readBytes == 9) {
226 BOOST_THROW_EXCEPTION(std::invalid_argument("Length specifier must not exceed 9 characters"));
230 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid NetString (leading zero)"));
233 len = len * 10u + size_t(byte - '0');
235 if (!readBytes && byte == '0') {
238 } else if (byte == ':') {
240 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid NetString (no length specifier)"));
245 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid NetString (missing :)"));
249 if (maxMessageLength >= 0 && len > maxMessageLength) {
250 std::stringstream errorMessage;
251 errorMessage << "Max data length exceeded: " << (maxMessageLength / 1024) << " KB";
253 BOOST_THROW_EXCEPTION(std::invalid_argument(errorMessage.str()));
259 payload.Append(len, 0);
261 asio::mutable_buffer payloadBuf (&*payload.Begin(), payload.GetLength());
262 asio::async_read(*stream, payloadBuf, yc);
268 asio::mutable_buffer trailerBuf (&trailer, 1);
269 asio::async_read(*stream, trailerBuf, yc);
272 if (trailer != ',') {
273 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid NetString (missing ,)"));
276 return std::move(payload);
280 * Writes data into a stream using the netstring format and returns bytes written.
282 * @param stream The stream.
283 * @param str The String that is to be written.
285 * @return The amount of bytes written.
287 size_t NetString::WriteStringToStream(const std::shared_ptr<AsioTlsStream>& stream, const String& str)
289 namespace asio = boost::asio;
291 std::ostringstream msgbuf;
292 WriteStringToStream(msgbuf, str);
294 String msg = msgbuf.str();
295 asio::const_buffer msgBuf (msg.CStr(), msg.GetLength());
297 asio::write(*stream, msgBuf);
299 return msg.GetLength();
303 * Writes data into a stream using the netstring format and returns bytes written.
305 * @param stream The stream.
306 * @param str The String that is to be written.
308 * @return The amount of bytes written.
310 size_t NetString::WriteStringToStream(const std::shared_ptr<AsioTlsStream>& stream, const String& str, boost::asio::yield_context yc)
312 namespace asio = boost::asio;
314 std::ostringstream msgbuf;
315 WriteStringToStream(msgbuf, str);
317 String msg = msgbuf.str();
318 asio::const_buffer msgBuf (msg.CStr(), msg.GetLength());
320 asio::async_write(*stream, msgBuf, yc);
322 return msg.GetLength();
326 * Writes data into a stream using the netstring format.
328 * @param stream The stream.
329 * @param str The String that is to be written.
331 void NetString::WriteStringToStream(std::ostream& stream, const String& str)
333 stream << str.GetLength() << ":" << str << ",";