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 }