libzypp  17.35.14
PluginFrame.cc
Go to the documentation of this file.
1 /*---------------------------------------------------------------------\
2 | ____ _ __ __ ___ |
3 | |__ / \ / / . \ . \ |
4 | / / \ V /| _/ _/ |
5 | / /__ | | | | | | |
6 | /_____||_| |_| |_| |
7 | |
8 \---------------------------------------------------------------------*/
12 #include <iostream>
13 #include <utility>
16 #include <zypp-core/zyppng/core/String>
17 
18 using std::endl;
19 
20 #undef ZYPP_BASE_LOGGER_LOGGROUP
21 #define ZYPP_BASE_LOGGER_LOGGROUP "zypp::plugin"
22 
24 namespace zypp
25 {
26 
28  //
29  // CLASS NAME : PluginFrame::Impl
30  //
33  {
34  public:
35  Impl()
36  {}
37 
38  Impl( const std::string & command_r )
39  { setCommand( command_r ); }
40 
41  Impl( const std::string & command_r, ByteArray &&body_r )
42  : _body(std::move( body_r ))
43  { setCommand( command_r ); }
44 
45  Impl( const std::string & command_r, HeaderInitializerList contents_r )
46  { setCommand( command_r ); addHeader( contents_r ); }
47 
48  Impl( const std::string & command_r, ByteArray &&body_r, HeaderInitializerList contents_r )
49  : _body(std::move( body_r ))
50  { setCommand( command_r ); addHeader( contents_r ); }
51 
52  Impl( std::istream & stream_r );
53 
54  public:
55  bool empty() const
56  { return _command.empty() && _body.empty(); }
57 
58  const std::string & command() const
59  { return _command; }
60 
61  void setCommand( const std::string & command_r )
62  {
63  if ( command_r.find( '\n' ) != std::string::npos )
64  ZYPP_THROW( PluginFrameException( "Multiline command", command_r ) );
65  _command = command_r;
66  }
67 
68  const ByteArray & body() const
69  { return _body; }
70 
72  { return _body; }
73 
74  void setBody( ByteArray && body_r )
75  { _body = std::move(body_r); }
76 
77  static std::string escapeHeader( std::string_view val, bool escapeColon=true ) {
78 
79  std::string escaped;
80  /*
81  Escape rules from the STOMP spec:
82  \r (octet 92 and 114) translates to carriage return (octet 13)
83  \n (octet 92 and 110) translates to line feed (octet 10)
84  \c (octet 92 and 99) translates to : (octet 58)
85  \\ (octet 92 and 92) translates to \ (octet 92)
86  Undefined escape sequences such as \t (octet 92 and 116) MUST be treated as a fatal protocol error.
87 
88  bsc#1231043: We do allow a literal ":" in the header value(!) in order to stay
89  compatible with with plugin implementations (like zypp-plugin) which are not
90  prepared to unescape a ":" there. And in fact it should not be necessary because
91  the 1st colon separates header and value.
92  */
93  for ( auto c = val.begin (); c!= val.end(); c++ ) {
94  switch( *c ) {
95  case '\n': {
96  escaped.push_back('\\');
97  escaped.push_back('n');
98  break;
99  }
100  case '\r': {
101  escaped.push_back('\\');
102  escaped.push_back('r');
103  break;
104  }
105  case '\\': {
106  escaped.push_back('\\');
107  escaped.push_back('\\');
108  break;
109  }
110  case ':': {
111  if ( escapeColon ) {
112  escaped.push_back('\\');
113  escaped.push_back('c');
114  break;
115  } else
116  [[fallthrough]];
117  }
118  default:
119  escaped.push_back (*c);
120  break;
121  }
122  }
123  return escaped;
124  }
125 
126  static std::string unescapeHeader( std::string_view val ) {
127  std::string unescaped;
128  for ( auto c = val.begin (); c!= val.end(); ) {
129  if ( *c != '\\' ) {
130  unescaped.push_back (*c);
131  c++;
132  continue;
133  }
134 
135  c++;
136  if ( c == val.end() )
137  ZYPP_THROW( PluginFrameException( "Invalid start of escape sequence" ) );
138 
139  switch ( *c ) {
140  case 'n': {
141  unescaped.push_back('\n');
142  c++;
143  break;
144  }
145  case 'r': {
146  unescaped.push_back('\r');
147  c++;
148  break;
149  }
150  case '\\': {
151  unescaped.push_back('\\');
152  c++;
153  break;
154  }
155  case 'c': {
156  unescaped.push_back(':');
157  c++;
158  break;
159  }
160  default:
161  ZYPP_THROW( PluginFrameException( "Unknown escape sequence" ) );
162  break;
163  }
164  }
165  return unescaped;
166  }
167 
168  public:
169  using constKeyRange = std::pair<HeaderListIterator, HeaderListIterator>;
170  using KeyRange = std::pair<HeaderList::iterator, HeaderList::iterator>;
171 
173  { return _header; }
174 
175  const HeaderList & headerList() const
176  { return _header; }
177 
178  const std::string & getHeader( const std::string & key_r ) const
179  {
180  constKeyRange r( _header.equal_range( key_r ) );
181  if ( r.first == r.second )
182  ZYPP_THROW( PluginFrameException( "No value for key", key_r ) );
183  const std::string & ret( r.first->second );
184  if ( ++r.first != r.second )
185  ZYPP_THROW( PluginFrameException( "Multiple values for key", key_r ) );
186  return ret;
187  }
188 
189  const std::string & getHeader( const std::string & key_r, const std::string & default_r ) const
190  {
191  constKeyRange r( _header.equal_range( key_r ) );
192  if ( r.first == r.second )
193  return default_r;
194  const std::string & ret( r.first->second );
195  if ( ++r.first != r.second )
196  ZYPP_THROW( PluginFrameException( "Multiple values for key", key_r ) );
197  return ret;
198  }
199 
200  const std::string & getHeaderNT( const std::string & key_r, const std::string & default_r ) const
201  {
202  HeaderListIterator iter( _header.find( key_r ) );
203  return iter != _header.end() ? iter->second : default_r;
204  }
205 
206  HeaderList::value_type mkHeaderPair( const std::string & key_r, const std::string & value_r )
207  {
208  return HeaderList::value_type( key_r, value_r );
209  }
210 
211  void setHeader( const std::string & key_r, const std::string & value_r )
212  {
213  clearHeader( key_r );
214  addHeader( key_r, value_r );
215  }
216 
217  void addHeader( const std::string & key_r, const std::string & value_r )
218  {
219  _header.insert( mkHeaderPair( key_r, value_r ) );
220  }
221 
222  void addHeader( HeaderInitializerList contents_r )
223  {
224  for ( const auto & el : contents_r )
225  addHeader( el.first, el.second );
226  }
227 
228  void addRawHeader ( const std::string_view data )
229  {
230  std::string::size_type sep( data.find( ':') );
231  if ( sep == std::string::npos )
232  ZYPP_THROW( PluginFrameException( "Missing colon in header" ) );
233 
234  _header.insert( HeaderList::value_type( unescapeHeader(data.substr(0,sep)), unescapeHeader(data.substr(sep+1)) ) );
235  }
236 
237  void clearHeader( const std::string & key_r )
238  {
239  _header.erase( key_r );
240  }
241 
242  public:
243  std::ostream & writeTo( std::ostream & stream_r ) const;
244 
245  private:
246  std::string _command;
249 
250  public:
252  static shared_ptr<Impl> nullimpl()
253  {
254  static shared_ptr<Impl> _nullimpl( new Impl );
255  return _nullimpl;
256  }
257  private:
258  friend Impl * rwcowClone<Impl>( const Impl * rhs );
260  Impl * clone() const
261  { return new Impl( *this ); }
262  };
264 
266  inline std::ostream & operator<<( std::ostream & str, const PluginFrame::Impl & obj )
267  {
268  return str << "PluginFrame[" << obj.command() << "](" << obj.headerList().size() << "){" << obj.body().size() << "}";
269  }
270 
271  PluginFrame::Impl::Impl( std::istream & stream_r )
272  {
273  // ATTENTION: Remember to also update the parser logic in zypp-core/zyppng/rpc/stompframestream.cc
274  // if code here is changed or features are added.
275 
276  //DBG << "Parse from " << stream_r << endl;
277  if ( ! stream_r )
278  ZYPP_THROW( PluginFrameException( "Bad Stream" ) );
279 
280  // JFYI: stream status after getline():
281  // Bool | Bits
282  // ------|---------------
283  // true | [g___] >FOO< : FOO line was \n-terminated
284  // true | [_e__] >BAA< : BAA before EOF, but not \n-terminated
285  // false | [_eF_] >< : No valid data to consume
286 
287  //command
288  _command = str::getline( stream_r );
289  if ( ! stream_r.good() )
290  ZYPP_THROW( PluginFrameException( "Missing NL after command" ) );
291 
292  // header
293  do {
294  std::string data = str::getline( stream_r );
295  if ( ! stream_r.good() )
296  ZYPP_THROW( PluginFrameException( "Missing NL after header" ) );
297 
298  if ( data.empty() )
299  break; // --> empty line sep. header and body
300 
301  addRawHeader( data );
302 
303  } while ( true );
304 
305 
306  // check for content-length header
307  std::optional<uint64_t> cLen;
308  {
309  const auto &contentLen = getHeaderNT( zypp::PluginFrame::contentLengthHeader(), std::string() );
310  if ( !contentLen.empty() ) {
311  cLen = zyppng::str::safe_strtonum<uint64_t>(contentLen);
312  if ( !cLen ) {
313  ERR << "Received malformed message from peer: Invalid value for " << zypp::PluginFrame::contentLengthHeader() << ":" << contentLen << std::endl;
314  ZYPP_THROW( PluginFrameException( "Invalid value for content-length." ) );
315  }
316 
317  // do not keep the header, we regenerate it again when writing the frame anyway
319  }
320 
321  }
322 
323  // data
324  if ( cLen ) {
325  _body.resize ( (*cLen)+1, '\0' );
326  stream_r.read ( _body.data(), (*cLen)+1 );
327 
328  if ( ! stream_r.good() )
329  ZYPP_THROW( PluginFrameException( "Missing data in stream" ) );
330  if ( _body.back() != '\0' )
331  ZYPP_THROW( PluginFrameException( "Missing NUL after body" ) );
332 
333  _body.pop_back (); // get rid of \0
334 
335  } else {
336  const auto &data = str::receiveUpTo( stream_r, '\0' );
337  _body = ByteArray( data.c_str(), data.size() );
338  if ( ! stream_r.good() )
339  ZYPP_THROW( PluginFrameException( "Missing NUL after body" ) );
340  }
341  }
342 
343  std::ostream & PluginFrame::Impl::writeTo( std::ostream & stream_r ) const
344  {
345  //DBG << "Write " << *this << " to " << stream_r << endl;
346  if ( ! stream_r )
347  ZYPP_THROW( PluginFrameException( "Bad Stream" ) );
348 
349  // command
350  stream_r << _command << "\n";
351 
352  // STOMP recommends sending a content-length header
353  stream_r << contentLengthHeader() << ':' << str::numstring( _body.size() ) << "\n";
354 
355  // header
356  // bsc#1231043: We do allow a literal ":" in the header value(!) in order to stay
357  // compatible with with plugin implementations (like zypp-plugin) which are not
358  // prepared to unescape a ":" there. And in fact it should not be necessary because
359  // the 1st colon separates header and value.
360  for_( it, _header.begin(), _header.end() )
361  stream_r << escapeHeader(it->first) << ':' << escapeHeader(it->second,/*escapeColon=*/false) << "\n";
362 
363  // header end
364  stream_r << "\n";
365 
366  // body
367  stream_r.write( _body.data(), _body.size() );
368 
369  // body end
370  stream_r << '\0';
371  stream_r.flush();
372 
373  if ( ! stream_r )
374  ZYPP_THROW( PluginFrameException( "Write error" ) );
375  return stream_r;
376  }
377 
379  //
380  // CLASS NAME : PluginFrame
381  //
383 
384  const std::string & PluginFrame::ackCommand()
385  {
386  static std::string _val( "ACK" );
387  return _val;
388  }
389 
390  const std::string & PluginFrame::errorCommand()
391  {
392  static std::string _val( "ERROR" );
393  return _val;
394  }
395 
396  const std::string & PluginFrame::enomethodCommand()
397  {
398  static std::string _val( "_ENOMETHOD" );
399  return _val;
400  }
401 
402  const std::string &PluginFrame::contentLengthHeader()
403  {
404  static std::string _val("content-length");
405  return _val;
406  }
407 
409  : _pimpl( Impl::nullimpl() )
410  {}
411 
412  PluginFrame::PluginFrame( const std::string & command_r )
413  : _pimpl( new Impl( command_r ) )
414  {}
415 
416  PluginFrame::PluginFrame(const std::string & command_r, std::string body_r )
417  : _pimpl( new Impl( command_r, ByteArray(body_r) ) )
418  {}
419 
420  PluginFrame::PluginFrame(const std::string & command_r, ByteArray body_r )
421  : _pimpl( new Impl( command_r, std::move(body_r) ) )
422  {}
423 
424  PluginFrame::PluginFrame( const std::string & command_r, HeaderInitializerList contents_r )
425  : _pimpl( new Impl( command_r, contents_r ) )
426  {}
427 
428  PluginFrame::PluginFrame(const std::string & command_r, ByteArray body_r, HeaderInitializerList contents_r )
429  : _pimpl( new Impl( command_r, std::move(body_r), contents_r ) )
430  {}
431 
432  PluginFrame::PluginFrame( std::istream & stream_r )
433  : _pimpl( new Impl( stream_r ) )
434  {}
435 
436  bool PluginFrame::empty() const
437  { return _pimpl->empty(); }
438 
439  const std::string & PluginFrame::command() const
440  { return _pimpl->command(); }
441 
442  void PluginFrame::setCommand( const std::string & command_r )
443  { _pimpl->setCommand( command_r ); }
444 
446  { return _pimpl->body(); }
447 
449  { return _pimpl->bodyRef(); }
450 
451  void PluginFrame::setBody( const std::string & body_r )
452  { _pimpl->setBody( ByteArray(body_r.data(), body_r.size()) ); }
453 
454  void PluginFrame::setBody( const ByteArray & body_r )
455  { _pimpl->setBody( ByteArray(body_r) ); }
456 
457  void PluginFrame::setBody( ByteArray && body_r )
458  { _pimpl->setBody( std::move(body_r) ); }
459 
460  std::ostream & PluginFrame::writeTo( std::ostream & stream_r ) const
461  { return _pimpl->writeTo( stream_r ); }
462 
464  { return _pimpl->headerList(); }
465 
467  { return _pimpl->headerList(); }
468 
469  const std::string & PluginFrame::getHeader( const std::string & key_r ) const
470  { return _pimpl->getHeader( key_r ); }
471 
472  const std::string & PluginFrame::getHeader( const std::string & key_r, const std::string & default_r ) const
473  { return _pimpl->getHeader( key_r, default_r ); }
474 
475  const std::string & PluginFrame::getHeaderNT( const std::string & key_r, const std::string & default_r ) const
476  { return _pimpl->getHeaderNT( key_r, default_r ); }
477 
478  void PluginFrame::setHeader( const std::string & key_r, const std::string & value_r )
479  { _pimpl->setHeader( key_r, value_r ); }
480 
481  void PluginFrame::addHeader( const std::string & key_r, const std::string & value_r )
482  { _pimpl->addHeader( key_r, value_r ); }
483 
485  { _pimpl->addHeader( contents_r ); }
486 
487  void PluginFrame::addRawHeader( const ByteArray &header )
488  {
489  _pimpl->addRawHeader( header.asStringView() );
490  }
491 
492  void PluginFrame::clearHeader( const std::string & key_r )
493  { _pimpl->clearHeader( key_r ); }
494 
496 
497  std::ostream & operator<<( std::ostream & str, const PluginFrame & obj )
498  { return str << *obj._pimpl; }
499 
500  bool operator==( const PluginFrame & lhs, const PluginFrame & rhs )
501  {
502  return ( lhs._pimpl == rhs._pimpl )
503  || (( lhs.command() == rhs.command() ) && ( lhs.headerList() == rhs.headerList() ) && ( lhs.body() == rhs.body() ));
504  }
505 
507 } // namespace zypp
Impl(const std::string &command_r, ByteArray &&body_r)
Definition: PluginFrame.cc:41
std::ostream & writeTo(std::ostream &stream_r) const
Write frame to stream.
Definition: PluginFrame.cc:460
PluginFrame()
Default ctor (empty frame)
Definition: PluginFrame.cc:408
ByteArray & bodyRef()
Definition: PluginFrame.cc:71
void addRawHeader(const ByteArray &header)
Definition: PluginFrame.cc:487
static shared_ptr< Impl > nullimpl()
Offer default Impl.
Definition: PluginFrame.cc:252
std::string getline(std::istream &str, const Trim trim_r)
Return stream content up to (but not returning) the next newline.
Definition: String.cc:479
#define ZYPP_THROW(EXCPT)
Drops a logline and throws the Exception.
Definition: Exception.h:424
std::pair< HeaderListIterator, HeaderListIterator > constKeyRange
Definition: PluginFrame.cc:169
Command frame for communication with PluginScript.
Definition: PluginFrame.h:41
void setCommand(const std::string &command_r)
Set the frame command.
Definition: PluginFrame.cc:442
const std::string & command() const
Return the frame command.
Definition: PluginFrame.cc:439
const ByteArray & body() const
Return the frame body.
Definition: PluginFrame.cc:445
std::multimap< std::string, std::string > HeaderList
The header list.
Definition: PluginFrame.h:149
void clearHeader(const std::string &key_r)
Definition: PluginFrame.cc:237
#define for_(IT, BEG, END)
Convenient for-loops using iterator.
Definition: Easy.h:28
const std::string & getHeaderNT(const std::string &key_r, const std::string &default_r=std::string()) const
Not throwing version returing one of the matching header values or default_r string.
Definition: PluginFrame.cc:475
String related utilities and Regular expression matching.
std::ostream & operator<<(std::ostream &str, const SerialNumber &obj)
Definition: SerialNumber.cc:52
bool empty() const
Whether this is an empty frame.
Definition: PluginFrame.cc:436
Definition: Arch.h:363
Impl(const std::string &command_r)
Definition: PluginFrame.cc:38
ByteArray & bodyRef()
Return a reference to the frame body.
Definition: PluginFrame.cc:448
Impl(const std::string &command_r, ByteArray &&body_r, HeaderInitializerList contents_r)
Definition: PluginFrame.cc:48
void addHeader(const std::string &key_r, const std::string &value_r=std::string())
Add header for key_r leaving already existing headers for key_r unchanged.
Definition: PluginFrame.cc:481
bool operator==(const SetRelation::Enum &lhs, const SetCompare &rhs)
static const std::string & errorCommand()
"ERROR" command.
Definition: PluginFrame.cc:390
void setCommand(const std::string &command_r)
Definition: PluginFrame.cc:61
void addRawHeader(const std::string_view data)
Definition: PluginFrame.cc:228
const std::string & getHeader(const std::string &key_r, const std::string &default_r) const
Definition: PluginFrame.cc:189
#define ERR
Definition: Logger.h:102
std::ostream & writeTo(std::ostream &stream_r) const
Definition: PluginFrame.cc:343
std::pair< HeaderList::iterator, HeaderList::iterator > KeyRange
Definition: PluginFrame.cc:170
void setBody(ByteArray &&body_r)
Definition: PluginFrame.cc:74
static const std::string & enomethodCommand()
"_ENOMETHOD" command.
Definition: PluginFrame.cc:396
static std::string unescapeHeader(std::string_view val)
Definition: PluginFrame.cc:126
void addHeader(HeaderInitializerList contents_r)
Definition: PluginFrame.cc:222
const ByteArray & body() const
Definition: PluginFrame.cc:68
Base class for PluginFrame Exception.
Impl(const std::string &command_r, HeaderInitializerList contents_r)
Definition: PluginFrame.cc:45
static const std::string & ackCommand()
"ACK" command.
Definition: PluginFrame.cc:384
RWCOW_pointer< Impl > _pimpl
Pointer to implementation.
Definition: PluginFrame.h:270
std::string numstring(char n, int w=0)
Definition: String.h:289
void setHeader(const std::string &key_r, const std::string &value_r)
Definition: PluginFrame.cc:211
HeaderList & headerList()
Definition: PluginFrame.cc:172
void setBody(const std::string &body_r)
Set the frame body.
Definition: PluginFrame.cc:451
Impl * clone() const
clone for RWCOW_pointer
Definition: PluginFrame.cc:260
HeaderList::value_type mkHeaderPair(const std::string &key_r, const std::string &value_r)
Definition: PluginFrame.cc:206
const std::string & getHeaderNT(const std::string &key_r, const std::string &default_r) const
Definition: PluginFrame.cc:200
void addHeader(const std::string &key_r, const std::string &value_r)
Definition: PluginFrame.cc:217
const std::initializer_list< std::pair< std::string, std::string > > & HeaderInitializerList
Definition: PluginFrame.h:46
const std::string & getHeader(const std::string &key_r) const
Definition: PluginFrame.cc:178
std::string receiveUpTo(std::istream &str, const char delim_r, bool returnDelim_r)
Return stream content up to the next ocurrence of delim_r or EOF delim_r, if found, is always read from the stream.
Definition: String.cc:489
HeaderList::const_iterator HeaderListIterator
Header list iterator.
Definition: PluginFrame.h:152
void setHeader(const std::string &key_r, const std::string &value_r=std::string())
Set header for key_r removing all other occurrences of key_r.
Definition: PluginFrame.cc:478
HeaderList & headerList()
Modifyalble header list for internal use only.
Definition: PluginFrame.cc:463
const std::string & getHeader(const std::string &key_r) const
Return header value for key_r.
Definition: PluginFrame.cc:469
const HeaderList & headerList() const
Definition: PluginFrame.cc:175
std::ostream & operator<<(std::ostream &str, const PluginFrame::Impl &obj)
Definition: PluginFrame.cc:266
PluginFrame implementation.
Definition: PluginFrame.cc:32
Easy-to use interface to the ZYPP dependency resolver.
Definition: Application.cc:19
SolvableIdType size_type
Definition: PoolMember.h:126
void clearHeader(const std::string &key_r)
Remove all headers for key_r.
Definition: PluginFrame.cc:492
static std::string escapeHeader(std::string_view val, bool escapeColon=true)
Definition: PluginFrame.cc:77
const std::string & command() const
Definition: PluginFrame.cc:58
static const std::string & contentLengthHeader()
"content-lenght" header name
Definition: PluginFrame.cc:402
zypp::ByteArray ByteArray
Definition: bytearray.h:21