File indexing completed on 2024-06-23 05:14:12
0001 /* -*- mode: c++; c-basic-offset:4 -*- 0002 uiserver/assuancommand.h 0003 0004 This file is part of Kleopatra, the KDE keymanager 0005 SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB 0006 0007 SPDX-License-Identifier: GPL-2.0-or-later 0008 */ 0009 0010 #pragma once 0011 0012 #include <utils/pimpl_ptr.h> 0013 #include <utils/types.h> 0014 0015 #include <gpgme++/error.h> 0016 #include <gpgme++/global.h> 0017 0018 #include <gpg-error.h> 0019 0020 #include <KMime/Types> 0021 0022 #include <qwindowdefs.h> // for WId 0023 0024 #include <map> 0025 #include <memory> 0026 #include <string> 0027 #include <vector> 0028 0029 class QVariant; 0030 class QObject; 0031 #include <QStringList> 0032 0033 struct assuan_context_s; 0034 0035 namespace Kleo 0036 { 0037 0038 class Input; 0039 class Output; 0040 0041 class AssuanCommandFactory; 0042 0043 /*! 0044 \brief Base class for GnuPG UI Server commands 0045 0046 \note large parts of this are outdated by now! 0047 0048 <h3>Implementing a new AssuanCommand</h3> 0049 0050 You do not directly inherit AssuanCommand, unless you want to 0051 deal with implementing low-level, repetitive things like name() 0052 in terms of staticName(). Assuming you don't, then you inherit 0053 your command class from AssuanCommandMixin, passing your class 0054 as the template argument to AssuanCommandMixin, like this: 0055 0056 \code 0057 class MyFooCommand : public AssuanCommandMixin<MyFooCommand> { 0058 \endcode 0059 (http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) 0060 0061 You then choose a command name, and return that from the static 0062 method staticName(), which is by convention queried by both 0063 AssuanCommandMixin<> and GenericAssuanCommandFactory<>: 0064 0065 \code 0066 static const char * staticName() { return "MYFOO"; } 0067 \endcode 0068 0069 The string should be all-uppercase by convention, but the 0070 UiServer implementation doesn't enforce this. 0071 0072 The next step is to implement start(), the starting point of 0073 command execution: 0074 0075 <h3>Executing the command</h3> 0076 0077 \code 0078 int start( const std::string & line ) { 0079 \endcode 0080 0081 This should set everything up and check the parameters in \a 0082 line and any options this command understands. If there's an 0083 error, choose one of the gpg-error codes and create a 0084 gpg_error_t from it using the protected makeError() function: 0085 0086 \code 0087 return makeError( GPG_ERR_NOT_IMPLEMENTED ); 0088 \endcode 0089 0090 But usually, you will want to create a dialog, or call some 0091 GpgME function from here. In case of errors from GpgME, you 0092 shouldn't pipe them through makeError(), but return them 0093 as-is. This will preserve the error source. Error created using 0094 makeError() will have Kleopatra as their error source, so watch 0095 out what you're doing :) 0096 0097 In addition to options and the command line, your command might 0098 require <em>bulk data</em> input or output. That's what the bulk 0099 input and output channels are for. You can check whether the 0100 client handed you an input channel by checking that 0101 bulkInputDevice() isn't NULL, likewise for bulkOutputDevice(). 0102 0103 If everything is ok, you return 0. This indicates to the client 0104 that the command has been accepted and is now in progress. 0105 0106 In this mode (start() returned 0), there are a bunch of options 0107 for your command to do. Some commands may require additional 0108 information from the client. The options passed to start() are 0109 designed to be persistent across commands, and rather limited in 0110 length (there's a strict line length limit in the assuan 0111 protocol with no line continuation mechanism). The same is true 0112 for command line arguments, which, in addition, you have to 0113 parse yourself. Those usually apply only to this command, and 0114 not to following ones. 0115 0116 If you need data that might be larger than the line length 0117 limit, you can either expect it on the bulkInputDevice(), or, if 0118 you have the need for more than one such data channel, or the 0119 data is optional or conditional on some condition that can only 0120 be determined during command execution, you can \em inquire the 0121 missing information from the client. 0122 0123 As an example, a VERIFY command would expect the signed data on 0124 the bulkInputDevice(). But if the input stream doesn't contain 0125 an embedded (opaque) signature, indicating a \em detached 0126 signature, it would go and inquire that data from the 0127 client. Here's how it works: 0128 0129 \code 0130 const int err = inquire( "DETACHED_SIGNATURE", 0131 this, SLOT(slotDetachedSignature(int,QByteArray,QByteArray)) ); 0132 if ( err ) 0133 done( err ); 0134 \endcode 0135 0136 This should be self-explanatory: You give a slot to call when 0137 the data has arrived. The slot's first argument is an error 0138 code. The second the data (if any), and the third is just 0139 repeating what you gave as inquire()'s first argument. As usual, 0140 you can leave argument off of the end, if you are not interested 0141 in them. 0142 0143 You can do as many inquiries as you want, but only one at a 0144 time. 0145 0146 You should periodically send status updates to the client. You do 0147 that by calling sendStatus(). 0148 0149 Once your command has finished executing, call done(). If it's 0150 with an error code, call done(err) like above. <b>Do not 0151 forget to call done() when done!</b>. It will close 0152 bulkInputDevice(), bulkOutputDevice(), and send an OK or ERR 0153 message back to the client. 0154 0155 At that point, your command has finished executing, and a new 0156 one can be accepted, or the connection closed. 0157 0158 Apropos connection closed. The only way for the client to cancel 0159 an operation is to shut down the connection. In this case, the 0160 canceled() function will be called. At that point, the 0161 connection to the client will have been broken already, and all 0162 you can do is pack your things and go down gracefully. 0163 0164 If _you_ detect that the user has canceled (your dialog contains 0165 a cancel button, doesn't it?), then you should instead call 0166 done( GPG_ERR_CANCELED ), like for normal operation. 0167 0168 <h3>Registering the command with UiServer</h3> 0169 0170 To register a command, you implement a AssuanCommandFactory for 0171 your AssuanCommand subclass, and register it with the 0172 UiServer. This can be made considerably easier using 0173 GenericAssuanCommandFactory: 0174 0175 \code 0176 UiServer server; 0177 server.registerCommandFactory( shared_ptr<AssuanCommandFactory>( new GenericAssuanCommandFactory<MyFooCommand> ) ); 0178 // more registerCommandFactory calls... 0179 server.start(); 0180 \endcode 0181 0182 */ 0183 class AssuanCommand : public ExecutionContext, public std::enable_shared_from_this<AssuanCommand> 0184 { 0185 // defined in assuanserverconnection.cpp! 0186 public: 0187 AssuanCommand(); 0188 ~AssuanCommand() override; 0189 0190 int start(); 0191 void canceled(); 0192 0193 virtual const char *name() const = 0; 0194 0195 class Memento 0196 { 0197 public: 0198 virtual ~Memento() 0199 { 0200 } 0201 }; 0202 0203 template<typename T> 0204 class TypedMemento : public Memento 0205 { 0206 T m_t; 0207 0208 public: 0209 explicit TypedMemento(const T &t) 0210 : m_t(t) 0211 { 0212 } 0213 0214 const T &get() const 0215 { 0216 return m_t; 0217 } 0218 T &get() 0219 { 0220 return m_t; 0221 } 0222 }; 0223 0224 template<typename T> 0225 static std::shared_ptr<TypedMemento<T>> make_typed_memento(const T &t) 0226 { 0227 return std::shared_ptr<TypedMemento<T>>(new TypedMemento<T>(t)); 0228 } 0229 0230 static int makeError(int code); 0231 0232 // convenience methods: 0233 enum Mode { NoMode, EMail, FileManager }; 0234 Mode checkMode() const; 0235 0236 enum CheckProtocolOption { 0237 AllowProtocolMissing = 0x01, 0238 }; 0239 0240 GpgME::Protocol checkProtocol(Mode mode, int options = 0) const; 0241 0242 void applyWindowID(QWidget *w) const override 0243 { 0244 doApplyWindowID(w); 0245 } 0246 WId parentWId() const; 0247 0248 void setNohup(bool on); 0249 bool isNohup() const; 0250 bool isDone() const; 0251 0252 QString sessionTitle() const; 0253 unsigned int sessionId() const; 0254 0255 bool informativeRecipients() const; 0256 bool informativeSenders() const; 0257 0258 const std::vector<KMime::Types::Mailbox> &recipients() const; 0259 const std::vector<KMime::Types::Mailbox> &senders() const; 0260 0261 bool hasMemento(const QByteArray &tag) const; 0262 std::shared_ptr<Memento> memento(const QByteArray &tag) const; 0263 template<typename T> 0264 std::shared_ptr<T> mementoAs(const QByteArray &tag) const 0265 { 0266 return std::dynamic_pointer_cast<T>(this->memento(tag)); 0267 } 0268 QByteArray registerMemento(const std::shared_ptr<Memento> &mem); 0269 QByteArray registerMemento(const QByteArray &tag, const std::shared_ptr<Memento> &mem); 0270 void removeMemento(const QByteArray &tag); 0271 template<typename T> 0272 T mementoContent(const QByteArray &tag) const 0273 { 0274 if (std::shared_ptr<TypedMemento<T>> m = mementoAs<TypedMemento<T>>(tag)) { 0275 return m->get(); 0276 } else { 0277 return T(); 0278 } 0279 } 0280 0281 bool hasOption(const char *opt) const; 0282 QVariant option(const char *opt) const; 0283 const std::map<std::string, QVariant> &options() const; 0284 0285 const std::vector<std::shared_ptr<Input>> &inputs() const; 0286 const std::vector<std::shared_ptr<Input>> &messages() const; 0287 const std::vector<std::shared_ptr<Output>> &outputs() const; 0288 0289 QStringList fileNames() const; 0290 unsigned int numFiles() const; 0291 0292 void sendStatus(const char *keyword, const QString &text); 0293 void sendStatusEncoded(const char *keyword, const std::string &text); 0294 void sendData(const QByteArray &data, bool moreToCome = false); 0295 0296 int inquire(const char *keyword, QObject *receiver, const char *slot, unsigned int maxSize = 0); 0297 0298 void done(const GpgME::Error &err = GpgME::Error()); 0299 void done(const GpgME::Error &err, const QString &details); 0300 void done(int err) 0301 { 0302 done(GpgME::Error(err)); 0303 } 0304 void done(int err, const QString &details) 0305 { 0306 done(GpgME::Error(err), details); 0307 } 0308 0309 private: 0310 virtual void doCanceled() = 0; 0311 virtual int doStart() = 0; 0312 0313 private: 0314 void doApplyWindowID(QWidget *w) const; 0315 0316 private: 0317 const std::map<QByteArray, std::shared_ptr<Memento>> &mementos() const; 0318 0319 private: 0320 friend class ::Kleo::AssuanCommandFactory; 0321 class Private; 0322 kdtools::pimpl_ptr<Private> d; 0323 }; 0324 0325 class AssuanCommandFactory 0326 { 0327 public: 0328 virtual ~AssuanCommandFactory() 0329 { 0330 } 0331 0332 virtual std::shared_ptr<AssuanCommand> create() const = 0; 0333 virtual const char *name() const = 0; 0334 0335 using _Handler = gpg_error_t (*)(assuan_context_s *, char *); 0336 virtual _Handler _handler() const = 0; 0337 0338 static gpg_error_t _handle(assuan_context_s *, char *, const char *); 0339 }; 0340 0341 template<typename Command> 0342 class GenericAssuanCommandFactory : public AssuanCommandFactory 0343 { 0344 AssuanCommandFactory::_Handler _handler() const override 0345 { 0346 return &GenericAssuanCommandFactory::_handle; 0347 } 0348 static gpg_error_t _handle(assuan_context_s *_ctx, char *_line) 0349 { 0350 return AssuanCommandFactory::_handle(_ctx, _line, Command::staticName()); 0351 } 0352 std::shared_ptr<AssuanCommand> create() const override 0353 { 0354 return make(); 0355 } 0356 const char *name() const override 0357 { 0358 return Command::staticName(); 0359 } 0360 0361 public: 0362 static std::shared_ptr<Command> make() 0363 { 0364 return std::shared_ptr<Command>(new Command); 0365 } 0366 }; 0367 0368 template<typename Derived, typename Base = AssuanCommand> 0369 class AssuanCommandMixin : public Base 0370 { 0371 protected: 0372 /* reimp */ const char *name() const override 0373 { 0374 return Derived::staticName(); 0375 } 0376 }; 0377 0378 }